1 /*
2  * Copyright (C) 2021 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 com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
19 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
20 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
21 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
22 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
23 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
24 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
35 
36 import android.animation.ArgbEvaluator;
37 import android.animation.ObjectAnimator;
38 import android.annotation.DrawableRes;
39 import android.annotation.IdRes;
40 import android.annotation.LayoutRes;
41 import android.content.pm.ActivityInfo.Config;
42 import android.content.res.ColorStateList;
43 import android.content.res.Configuration;
44 import android.graphics.Rect;
45 import android.graphics.Region;
46 import android.graphics.Region.Op;
47 import android.graphics.drawable.AnimatedVectorDrawable;
48 import android.util.Property;
49 import android.view.Gravity;
50 import android.view.MotionEvent;
51 import android.view.View;
52 import android.view.View.OnClickListener;
53 import android.view.View.OnHoverListener;
54 import android.view.ViewGroup;
55 import android.widget.FrameLayout;
56 import android.widget.ImageView;
57 
58 import com.android.launcher3.LauncherAnimUtils;
59 import com.android.launcher3.R;
60 import com.android.launcher3.Utilities;
61 import com.android.launcher3.anim.AlphaUpdateListener;
62 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
63 import com.android.launcher3.util.MultiValueAlpha;
64 import com.android.quickstep.AnimatedFloat;
65 import com.android.systemui.shared.rotation.FloatingRotationButton;
66 import com.android.systemui.shared.rotation.RotationButton;
67 import com.android.systemui.shared.rotation.RotationButtonController;
68 
69 import java.util.ArrayList;
70 import java.util.function.IntPredicate;
71 
72 /**
73  * Controller for managing nav bar buttons in taskbar
74  */
75 public class NavbarButtonsViewController {
76 
77     private final Rect mTempRect = new Rect();
78 
79     private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0;
80     private static final int FLAG_IME_VISIBLE = 1 << 1;
81     private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
82     private static final int FLAG_A11Y_VISIBLE = 1 << 3;
83     private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
84     private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
85     private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6;
86     private static final int FLAG_DISABLE_HOME = 1 << 7;
87     private static final int FLAG_DISABLE_RECENTS = 1 << 8;
88     private static final int FLAG_DISABLE_BACK = 1 << 9;
89     private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
90     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
91 
92     private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
93 
94     private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
95     private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
96     private int mState;
97 
98     private final TaskbarActivityContext mContext;
99     private final FrameLayout mNavButtonsView;
100     private final ViewGroup mNavButtonContainer;
101     // Used for IME+A11Y buttons
102     private final ViewGroup mEndContextualContainer;
103     private final ViewGroup mStartContextualContainer;
104     private final int mLightIconColor;
105     private final int mDarkIconColor;
106 
107     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
108             this::updateNavButtonTranslationY);
109     private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
110             this::updateNavButtonTranslationY);
111     // Only applies to mTaskbarNavButtonTranslationY
112     private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat(
113             this::updateNavButtonTranslationY);
114     private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
115             this::updateNavButtonDarkIntensity);
116     private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
117             this::updateNavButtonDarkIntensity);
118     private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
119 
120     private final Rect mFloatingRotationButtonBounds = new Rect();
121 
122     // Initialized in init.
123     private TaskbarControllers mControllers;
124     private View mA11yButton;
125     private int mSysuiStateFlags;
126     private View mBackButton;
127     private FloatingRotationButton mFloatingRotationButton;
128 
NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView)129     public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
130         mContext = context;
131         mNavButtonsView = navButtonsView;
132         mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
133         mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
134         mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
135 
136         mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
137         mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
138     }
139 
140     /**
141      * Initializes the controller
142      */
init(TaskbarControllers controllers)143     public void init(TaskbarControllers controllers) {
144         mControllers = controllers;
145         mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize;
146         mNavButtonTranslationYMultiplier.value = 1;
147 
148         boolean isThreeButtonNav = mContext.isThreeButtonNav();
149         // IME switcher
150         View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
151                 isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
152                 mControllers.navButtonController, R.id.ime_switcher);
153         mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
154                 flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE)
155                         && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
156 
157         mPropertyHolders.add(new StatePropertyHolder(
158                 mControllers.taskbarViewController.getTaskbarIconAlpha()
159                         .getProperty(ALPHA_INDEX_KEYGUARD),
160                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
161                         && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0,
162                 MultiValueAlpha.VALUE, 1, 0));
163 
164         mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
165                 .getKeyguardBgTaskbar(),
166                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0));
167 
168         // Force nav buttons (specifically back button) to be visible during setup wizard.
169         boolean isInSetup = !mContext.isUserSetupComplete();
170         boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
171 
172         // Make sure to remove nav bar buttons translation when notification shade is expanded or
173         // IME is showing (add separate translation for IME).
174         int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE;
175         mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier,
176                 flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
177                 0, 1));
178         // Center nav buttons in new height for IME.
179         float transForIme = (mContext.getDeviceProfile().taskbarSize
180                 - mContext.getTaskbarHeightForIme()) / 2f;
181         // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
182         float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
183         mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
184                 flags -> (flags & FLAG_IME_VISIBLE) != 0, AnimatedFloat.VALUE, transForIme,
185                 defaultButtonTransY));
186 
187         if (alwaysShowButtons) {
188             initButtons(mNavButtonContainer, mEndContextualContainer,
189                     mControllers.navButtonController);
190 
191             if (isInSetup) {
192                 // Since setup wizard only has back button enabled, it looks strange to be
193                 // end-aligned, so start-align instead.
194                 FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
195                         mNavButtonContainer.getLayoutParams();
196                 navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd());
197                 navButtonsLayoutParams.setMarginEnd(0);
198                 navButtonsLayoutParams.gravity = Gravity.START;
199                 mNavButtonContainer.requestLayout();
200 
201                 // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
202                 //  it based on dark theme for now.
203                 int mode = mContext.getResources().getConfiguration().uiMode
204                         & Configuration.UI_MODE_NIGHT_MASK;
205                 boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
206                 mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
207             }
208 
209             // Animate taskbar background when any of these flags are enabled
210             int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE
211                     | FLAG_NOTIFICATION_SHADE_EXPANDED;
212             mPropertyHolders.add(new StatePropertyHolder(
213                     mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
214                     flags -> (flags & flagsToShowBg) != 0, AnimatedFloat.VALUE, 1, 0));
215 
216             // Rotation button
217             RotationButton rotationButton = new RotationButtonImpl(
218                     addButton(mEndContextualContainer, R.id.rotate_suggestion,
219                             R.layout.taskbar_contextual_button));
220             rotationButton.hide();
221             mControllers.rotationButtonController.setRotationButton(rotationButton, null);
222         } else {
223             mFloatingRotationButton = new FloatingRotationButton(mContext,
224                     R.string.accessibility_rotate_button,
225                     R.layout.rotate_suggestion,
226                     R.id.rotate_suggestion,
227                     R.dimen.floating_rotation_button_min_margin,
228                     R.dimen.rounded_corner_content_padding,
229                     R.dimen.floating_rotation_button_taskbar_left_margin,
230                     R.dimen.floating_rotation_button_taskbar_bottom_margin,
231                     R.dimen.floating_rotation_button_diameter,
232                     R.dimen.key_button_ripple_max_width);
233             mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
234                     mRotationButtonListener);
235 
236             View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
237                     mStartContextualContainer, mControllers.navButtonController, R.id.back);
238             imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90);
239             // Rotate when Ime visible
240             mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
241                     flags -> (flags & FLAG_IME_VISIBLE) != 0));
242         }
243 
244         applyState();
245         mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
246     }
247 
initButtons(ViewGroup navContainer, ViewGroup endContainer, TaskbarNavButtonController navButtonController)248     private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
249             TaskbarNavButtonController navButtonController) {
250 
251         mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
252                 mNavButtonContainer, mControllers.navButtonController, R.id.back);
253         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
254                 flags -> {
255                     // Show only if not disabled, and if not on the keyguard or otherwise only when
256                     // the bouncer or a lockscreen app is showing above the keyguard
257                     boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
258                             (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
259                             (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
260                     return (flags & FLAG_DISABLE_BACK) == 0
261                             && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
262                 }));
263         boolean isRtl = Utilities.isRtl(mContext.getResources());
264         mPropertyHolders.add(new StatePropertyHolder(
265                 mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION,
266                 isRtl ? 90 : -90, 0));
267         // Translate back button to be at end/start of other buttons for keyguard
268         int navButtonSize = mContext.getResources().getDimensionPixelSize(
269                 R.dimen.taskbar_nav_buttons_size);
270         mPropertyHolders.add(new StatePropertyHolder(
271                 mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
272                         || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
273                 VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
274 
275 
276         // home and recents buttons
277         View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
278                 navButtonController, R.id.home);
279         mPropertyHolders.add(new StatePropertyHolder(homeButton,
280                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
281                         (flags & FLAG_DISABLE_HOME) == 0));
282         View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
283                 navContainer, navButtonController, R.id.recent_apps);
284         mPropertyHolders.add(new StatePropertyHolder(recentsButton,
285                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
286                         (flags & FLAG_DISABLE_RECENTS) == 0));
287 
288         // A11y button
289         mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
290                 endContainer, navButtonController, R.id.accessibility_button,
291                 R.layout.taskbar_contextual_button);
292         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
293                 flags -> (flags & FLAG_A11Y_VISIBLE) != 0
294                         && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
295     }
296 
parseSystemUiFlags(int sysUiStateFlags)297     private void parseSystemUiFlags(int sysUiStateFlags) {
298         mSysuiStateFlags = sysUiStateFlags;
299         boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
300         boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
301         boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
302         boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
303         boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
304         boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
305         int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
306                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
307         boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
308         boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
309 
310         // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
311         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
312         updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing);
313         updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
314         updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
315         updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
316         updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
317         updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
318         updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
319 
320         if (mA11yButton != null) {
321             // Only used in 3 button
322             boolean a11yLongClickable =
323                     (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
324             mA11yButton.setLongClickable(a11yLongClickable);
325         }
326     }
327 
updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim)328     public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
329         if (systemUiStateFlags == mSysuiStateFlags) {
330             return;
331         }
332         parseSystemUiFlags(systemUiStateFlags);
333         applyState();
334         if (skipAnim) {
335             mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
336         }
337     }
338 
339     /**
340      * Should be called when we need to show back button for bouncer
341      */
setBackForBouncer(boolean isBouncerVisible)342     public void setBackForBouncer(boolean isBouncerVisible) {
343         updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
344         applyState();
345     }
346 
347     /**
348      * Slightly misnamed, but should be called when keyguard OR AOD is showing.
349      * We consider keyguardVisible when it's showing bouncer OR is occlucded by another app
350      */
setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded)351     public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) {
352         updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded);
353         updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded);
354         applyState();
355     }
356 
357     /**
358      * Returns true if IME bar is visible
359      */
isImeVisible()360     public boolean isImeVisible() {
361         return (mState & FLAG_IME_VISIBLE) != 0;
362     }
363 
364     /**
365      * Returns true if the home button is disabled
366      */
isHomeDisabled()367     public boolean isHomeDisabled() {
368         return (mState & FLAG_DISABLE_HOME) != 0;
369     }
370 
371     /**
372      * Returns true if the recents (overview) button is disabled
373      */
isRecentsDisabled()374     public boolean isRecentsDisabled() {
375         return (mState & FLAG_DISABLE_RECENTS) != 0;
376     }
377 
378     /**
379      * Adds the bounds corresponding to all visible buttons to provided region
380      */
addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion)381     public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) {
382         int count = mAllButtons.size();
383         for (int i = 0; i < count; i++) {
384             View button = mAllButtons.get(i);
385             if (button.getVisibility() == View.VISIBLE) {
386                 parent.getDescendantRectRelativeToSelf(button, mTempRect);
387                 outRegion.op(mTempRect, Op.UNION);
388             }
389         }
390     }
391 
392     /** Use to set the translationY for the all nav+contextual buttons */
getTaskbarNavButtonTranslationY()393     public AnimatedFloat getTaskbarNavButtonTranslationY() {
394         return mTaskbarNavButtonTranslationY;
395     }
396 
397     /** Use to set the dark intensity for the all nav+contextual buttons */
getTaskbarNavButtonDarkIntensity()398     public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
399         return mTaskbarNavButtonDarkIntensity;
400     }
401 
402     /** Use to determine whether to use the dark intensity requested by the underlying app */
getNavButtonDarkIntensityMultiplier()403     public AnimatedFloat getNavButtonDarkIntensityMultiplier() {
404         return mNavButtonDarkIntensityMultiplier;
405     }
406 
407     /**
408      * Does not call {@link #applyState()}. Don't forget to!
409      */
updateStateForFlag(int flag, boolean enabled)410     private void updateStateForFlag(int flag, boolean enabled) {
411         if (enabled) {
412             mState |= flag;
413         } else {
414             mState &= ~flag;
415         }
416     }
417 
applyState()418     private void applyState() {
419         int count = mPropertyHolders.size();
420         for (int i = 0; i < count; i++) {
421             mPropertyHolders.get(i).setState(mState);
422         }
423     }
424 
updateNavButtonTranslationY()425     private void updateNavButtonTranslationY() {
426         float normalTranslationY = mTaskbarNavButtonTranslationY.value
427                 * mNavButtonTranslationYMultiplier.value;
428         float otherTranslationY = mTaskbarNavButtonTranslationYForIme.value;
429         mNavButtonsView.setTranslationY(normalTranslationY + otherTranslationY);
430     }
431 
updateNavButtonDarkIntensity()432     private void updateNavButtonDarkIntensity() {
433         float darkIntensity = mTaskbarNavButtonDarkIntensity.value
434                 * mNavButtonDarkIntensityMultiplier.value;
435         int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor,
436                 mDarkIconColor);
437         for (ImageView button : mAllButtons) {
438             button.setImageTintList(ColorStateList.valueOf(iconColor));
439         }
440     }
441 
addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id)442     private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
443             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
444         return addButton(drawableId, buttonType, parent, navButtonController, id,
445                 R.layout.taskbar_nav_button);
446     }
447 
addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id, @LayoutRes int layoutId)448     private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
449             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
450             @LayoutRes int layoutId) {
451         ImageView buttonView = addButton(parent, id, layoutId);
452         buttonView.setImageResource(drawableId);
453         buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType));
454         buttonView.setOnLongClickListener(view ->
455                 navButtonController.onButtonLongClick(buttonType));
456         return buttonView;
457     }
458 
addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId)459     private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
460         ImageView buttonView = (ImageView) mContext.getLayoutInflater()
461                 .inflate(layoutId, parent, false);
462         buttonView.setId(id);
463         parent.addView(buttonView);
464         mAllButtons.add(buttonView);
465         return buttonView;
466     }
467 
isEventOverAnyItem(MotionEvent ev)468     public boolean isEventOverAnyItem(MotionEvent ev) {
469         return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
470     }
471 
onConfigurationChanged(@onfig int configChanges)472     public void onConfigurationChanged(@Config int configChanges) {
473         if (mFloatingRotationButton != null) {
474             mFloatingRotationButton.onConfigurationChanged(configChanges);
475         }
476     }
477 
onDestroy()478     public void onDestroy() {
479         mPropertyHolders.clear();
480         mControllers.rotationButtonController.unregisterListeners();
481         if (mFloatingRotationButton != null) {
482             mFloatingRotationButton.hide();
483         }
484     }
485 
486     private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
487         @Override
onVisibilityChanged(boolean isVisible)488         public void onVisibilityChanged(boolean isVisible) {
489             if (isVisible) {
490                 mFloatingRotationButton.getCurrentView()
491                         .getBoundsOnScreen(mFloatingRotationButtonBounds);
492             } else {
493                 mFloatingRotationButtonBounds.setEmpty();
494             }
495         }
496     }
497 
498     private class RotationButtonImpl implements RotationButton {
499 
500         private final ImageView mButton;
501         private AnimatedVectorDrawable mImageDrawable;
502 
RotationButtonImpl(ImageView button)503         RotationButtonImpl(ImageView button) {
504             mButton = button;
505         }
506 
507         @Override
setRotationButtonController(RotationButtonController rotationButtonController)508         public void setRotationButtonController(RotationButtonController rotationButtonController) {
509             // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
510             mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
511                     .getDrawable(rotationButtonController.getIconResId());
512             mButton.setImageDrawable(mImageDrawable);
513             mButton.setContentDescription(mButton.getResources()
514                     .getString(R.string.accessibility_rotate_button));
515             mImageDrawable.setCallback(mButton);
516         }
517 
518         @Override
getCurrentView()519         public View getCurrentView() {
520             return mButton;
521         }
522 
523         @Override
show()524         public boolean show() {
525             mButton.setVisibility(View.VISIBLE);
526             mState |= FLAG_ROTATION_BUTTON_VISIBLE;
527             applyState();
528             return true;
529         }
530 
531         @Override
hide()532         public boolean hide() {
533             mButton.setVisibility(View.GONE);
534             mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
535             applyState();
536             return true;
537         }
538 
539         @Override
isVisible()540         public boolean isVisible() {
541             return mButton.getVisibility() == View.VISIBLE;
542         }
543 
544         @Override
updateIcon(int lightIconColor, int darkIconColor)545         public void updateIcon(int lightIconColor, int darkIconColor) {
546             // TODO(b/187754252): UI Polish
547         }
548 
549         @Override
setOnClickListener(OnClickListener onClickListener)550         public void setOnClickListener(OnClickListener onClickListener) {
551             mButton.setOnClickListener(onClickListener);
552         }
553 
554         @Override
setOnHoverListener(OnHoverListener onHoverListener)555         public void setOnHoverListener(OnHoverListener onHoverListener) {
556             mButton.setOnHoverListener(onHoverListener);
557         }
558 
559         @Override
getImageDrawable()560         public AnimatedVectorDrawable getImageDrawable() {
561             return mImageDrawable;
562         }
563 
564         @Override
setDarkIntensity(float darkIntensity)565         public void setDarkIntensity(float darkIntensity) {
566             // TODO(b/187754252) UI polish
567         }
568 
569         @Override
acceptRotationProposal()570         public boolean acceptRotationProposal() {
571             return mButton.isAttachedToWindow();
572         }
573     }
574 
575     private static class StatePropertyHolder {
576 
577         private final float mEnabledValue, mDisabledValue;
578         private final ObjectAnimator mAnimator;
579         private final IntPredicate mEnableCondition;
580 
581         private boolean mIsEnabled = true;
582 
StatePropertyHolder(View view, IntPredicate enableCondition)583         StatePropertyHolder(View view, IntPredicate enableCondition) {
584             this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
585             mAnimator.addListener(new AlphaUpdateListener(view));
586         }
587 
StatePropertyHolder(T target, IntPredicate enabledCondition, Property<T, Float> property, float enabledValue, float disabledValue)588         <T> StatePropertyHolder(T target, IntPredicate enabledCondition,
589                 Property<T, Float> property, float enabledValue, float disabledValue) {
590             mEnableCondition = enabledCondition;
591             mEnabledValue = enabledValue;
592             mDisabledValue = disabledValue;
593             mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
594         }
595 
setState(int flags)596         public void setState(int flags) {
597             boolean isEnabled = mEnableCondition.test(flags);
598             if (mIsEnabled != isEnabled) {
599                 mIsEnabled = isEnabled;
600                 mAnimator.cancel();
601                 mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
602                 mAnimator.start();
603             }
604         }
605 
endAnimation()606         public void endAnimation() {
607             if (mAnimator.isRunning()) {
608                 mAnimator.end();
609             }
610         }
611     }
612 }
613