1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar; 17 18 import static android.view.MotionEvent.ACTION_HOVER_ENTER; 19 import static android.view.MotionEvent.ACTION_HOVER_EXIT; 20 import static android.view.View.ALPHA; 21 import static android.view.View.SCALE_Y; 22 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT; 23 24 import static com.android.app.animation.Interpolators.LINEAR; 25 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_EXCEPT_ON_BOARD_POPUP; 26 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS; 27 import static com.android.launcher3.views.ArrowTipView.TEXT_ALPHA; 28 29 import android.animation.AnimatorSet; 30 import android.animation.ObjectAnimator; 31 import android.graphics.Rect; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.view.ContextThemeWrapper; 35 import android.view.MotionEvent; 36 import android.view.View; 37 38 import com.android.app.animation.Interpolators; 39 import com.android.launcher3.AbstractFloatingView; 40 import com.android.launcher3.BubbleTextView; 41 import com.android.launcher3.R; 42 import com.android.launcher3.Utilities; 43 import com.android.launcher3.compat.AccessibilityManagerCompat; 44 import com.android.launcher3.folder.FolderIcon; 45 import com.android.launcher3.views.ArrowTipView; 46 47 /** 48 * Controls showing a tooltip in the taskbar above each icon when it is hovered. 49 */ 50 public class TaskbarHoverToolTipController implements View.OnHoverListener { 51 52 private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 250; 53 private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150; 54 55 private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper()); 56 private final Runnable mRevealHoverToolTipRunnable = this::revealHoverToolTip; 57 private final Runnable mHideHoverToolTipRunnable = this::hideHoverToolTip; 58 59 private final TaskbarActivityContext mActivity; 60 private final TaskbarView mTaskbarView; 61 private final View mHoverView; 62 private final ArrowTipView mHoverToolTipView; 63 private final String mToolTipText; 64 TaskbarHoverToolTipController(TaskbarActivityContext activity, TaskbarView taskbarView, View hoverView)65 public TaskbarHoverToolTipController(TaskbarActivityContext activity, TaskbarView taskbarView, 66 View hoverView) { 67 mActivity = activity; 68 mTaskbarView = taskbarView; 69 mHoverView = hoverView; 70 71 if (mHoverView instanceof BubbleTextView) { 72 mToolTipText = ((BubbleTextView) mHoverView).getText().toString(); 73 } else if (mHoverView instanceof FolderIcon 74 && ((FolderIcon) mHoverView).mInfo.title != null) { 75 mToolTipText = ((FolderIcon) mHoverView).mInfo.title.toString(); 76 } else { 77 mToolTipText = null; 78 } 79 80 ContextThemeWrapper arrowContextWrapper = new ContextThemeWrapper(mActivity, 81 R.style.ArrowTipTaskbarStyle); 82 mHoverToolTipView = new ArrowTipView(arrowContextWrapper, /* isPointingUp = */ false, 83 R.layout.arrow_toast); 84 int verticalPadding = arrowContextWrapper.getResources().getDimensionPixelSize( 85 R.dimen.taskbar_tooltip_vertical_padding); 86 int horizontalPadding = arrowContextWrapper.getResources().getDimensionPixelSize( 87 R.dimen.taskbar_tooltip_horizontal_padding); 88 mHoverToolTipView.findViewById(R.id.text).setPadding(horizontalPadding, verticalPadding, 89 horizontalPadding, verticalPadding); 90 91 AnimatorSet hoverCloseAnimator = new AnimatorSet(); 92 ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0); 93 textCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0, 0.33f)); 94 ObjectAnimator alphaCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0); 95 alphaCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 0.66f)); 96 ObjectAnimator scaleCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0); 97 scaleCloseAnimator.setInterpolator(Interpolators.STANDARD); 98 hoverCloseAnimator.playTogether( 99 textCloseAnimator, 100 alphaCloseAnimator, 101 scaleCloseAnimator); 102 hoverCloseAnimator.setStartDelay(0); 103 hoverCloseAnimator.setDuration(HOVER_TOOL_TIP_EXIT_DURATION); 104 mHoverToolTipView.setCustomCloseAnimation(hoverCloseAnimator); 105 106 AnimatorSet hoverOpenAnimator = new AnimatorSet(); 107 ObjectAnimator textOpenAnimator = 108 ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0, 255); 109 textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.15f, 0.75f)); 110 ObjectAnimator scaleOpenAnimator = 111 ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0f, 1f); 112 scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED); 113 ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0f, 1f); 114 alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f, 0.33f)); 115 hoverOpenAnimator.playTogether( 116 scaleOpenAnimator, 117 textOpenAnimator, 118 alphaOpenAnimator); 119 hoverOpenAnimator.setDuration(HOVER_TOOL_TIP_REVEAL_DURATION); 120 mHoverToolTipView.setCustomOpenAnimation(hoverOpenAnimator); 121 122 mHoverToolTipView.addOnLayoutChangeListener( 123 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 124 mHoverToolTipView.setPivotY(bottom); 125 mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top)); 126 }); 127 } 128 129 @Override onHover(View v, MotionEvent event)130 public boolean onHover(View v, MotionEvent event) { 131 boolean isAnyOtherFloatingViewOpen = 132 AbstractFloatingView.hasOpenView(mActivity, TYPE_ALL_EXCEPT_ON_BOARD_POPUP); 133 if (isAnyOtherFloatingViewOpen) { 134 mHoverToolTipHandler.removeCallbacksAndMessages(null); 135 } 136 // If hover leaves a taskbar icon animate the tooltip closed. 137 if (event.getAction() == ACTION_HOVER_EXIT) { 138 startHideHoverToolTip(); 139 mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, false); 140 return true; 141 } else if (!isAnyOtherFloatingViewOpen && event.getAction() == ACTION_HOVER_ENTER) { 142 // If hovering above a taskbar icon starts, animate the tooltip open. Do not 143 // reveal if any floating views such as folders or edu pop-ups are open. 144 startRevealHoverToolTip(); 145 mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true); 146 return true; 147 } 148 return false; 149 } 150 startRevealHoverToolTip()151 private void startRevealHoverToolTip() { 152 mHoverToolTipHandler.post(mRevealHoverToolTipRunnable); 153 } 154 revealHoverToolTip()155 private void revealHoverToolTip() { 156 if (mHoverView == null || mToolTipText == null) { 157 return; 158 } 159 if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) { 160 return; 161 } 162 Rect iconViewBounds = Utilities.getViewBounds(mHoverView); 163 mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(), 164 mTaskbarView.getTop(), /* shouldAutoClose= */ false); 165 } 166 startHideHoverToolTip()167 private void startHideHoverToolTip() { 168 int accessibilityHideTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis( 169 mActivity, /* originalTimeout= */ 0, FLAG_CONTENT_TEXT); 170 mHoverToolTipHandler.postDelayed(mHideHoverToolTipRunnable, accessibilityHideTimeout); 171 } 172 hideHoverToolTip()173 private void hideHoverToolTip() { 174 mHoverToolTipView.close(/* animate = */ true); 175 } 176 } 177