• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.view.View.AccessibilityDelegate;
19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
21 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
22 
23 import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT;
24 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
25 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
26 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
27 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
28 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
29 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
30 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
31 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
32 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
33 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
34 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_SMALL_SCREEN;
35 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
36 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
37 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
38 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
39 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
40 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
41 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
42 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
43 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
44 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
45 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
46 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
47 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
48 
49 import android.animation.ArgbEvaluator;
50 import android.animation.ObjectAnimator;
51 import android.annotation.DrawableRes;
52 import android.annotation.IdRes;
53 import android.annotation.LayoutRes;
54 import android.content.pm.ActivityInfo.Config;
55 import android.content.res.ColorStateList;
56 import android.content.res.Resources;
57 import android.graphics.Color;
58 import android.graphics.Point;
59 import android.graphics.Rect;
60 import android.graphics.Region;
61 import android.graphics.Region.Op;
62 import android.graphics.drawable.AnimatedVectorDrawable;
63 import android.graphics.drawable.PaintDrawable;
64 import android.graphics.drawable.RotateDrawable;
65 import android.inputmethodservice.InputMethodService;
66 import android.os.Handler;
67 import android.util.Property;
68 import android.view.Gravity;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.View.OnAttachStateChangeListener;
72 import android.view.View.OnClickListener;
73 import android.view.View.OnHoverListener;
74 import android.view.ViewGroup;
75 import android.view.ViewTreeObserver;
76 import android.view.WindowManager;
77 import android.widget.FrameLayout;
78 import android.widget.ImageView;
79 import android.widget.LinearLayout;
80 
81 import com.android.launcher3.DeviceProfile;
82 import com.android.launcher3.LauncherAnimUtils;
83 import com.android.launcher3.R;
84 import com.android.launcher3.Utilities;
85 import com.android.launcher3.anim.AlphaUpdateListener;
86 import com.android.launcher3.anim.AnimatedFloat;
87 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
88 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
89 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
90 import com.android.launcher3.util.DimensionUtils;
91 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
92 import com.android.launcher3.util.MultiValueAlpha;
93 import com.android.launcher3.util.TouchController;
94 import com.android.launcher3.views.BaseDragLayer;
95 import com.android.systemui.shared.rotation.FloatingRotationButton;
96 import com.android.systemui.shared.rotation.RotationButton;
97 import com.android.systemui.shared.rotation.RotationButtonController;
98 import com.android.systemui.shared.system.QuickStepContract;
99 
100 import java.io.PrintWriter;
101 import java.util.ArrayList;
102 import java.util.StringJoiner;
103 import java.util.function.IntPredicate;
104 
105 /**
106  * Controller for managing nav bar buttons in taskbar
107  */
108 public class NavbarButtonsViewController implements TaskbarControllers.LoggableTaskbarController {
109 
110     private final Rect mTempRect = new Rect();
111 
112     private static final int FLAG_SWITCHER_SHOWING = 1 << 0;
113     private static final int FLAG_IME_VISIBLE = 1 << 1;
114     private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
115     private static final int FLAG_A11Y_VISIBLE = 1 << 3;
116     private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
117     private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
118     private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6;
119     private static final int FLAG_DISABLE_HOME = 1 << 7;
120     private static final int FLAG_DISABLE_RECENTS = 1 << 8;
121     private static final int FLAG_DISABLE_BACK = 1 << 9;
122     private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
123     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
124     private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
125     private static final int FLAG_SMALL_SCREEN = 1 << 13;
126     private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
127 
128     /**
129      * Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
130      */
131     private static final int FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED =
132             FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
133 
134     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
135 
136     public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
137     public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
138     public static final int ALPHA_INDEX_SUW = 2;
139     private static final int NUM_ALPHA_CHANNELS = 3;
140 
141     private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
142     private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
143     private int mState;
144 
145     private final TaskbarActivityContext mContext;
146     private final FrameLayout mNavButtonsView;
147     private final LinearLayout mNavButtonContainer;
148     // Used for IME+A11Y buttons
149     private final ViewGroup mEndContextualContainer;
150     private final ViewGroup mStartContextualContainer;
151     private final int mLightIconColor;
152     private final int mDarkIconColor;
153     /** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
154     private final int mOnBackgroundIconColor;
155 
156     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
157             this::updateNavButtonTranslationY);
158     private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
159             this::updateNavButtonTranslationY);
160     private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
161             this::updateNavButtonTranslationY);
162     private float mLastSetNavButtonTranslationY;
163     // Used for System UI state updates that should translate the nav button for in-app display.
164     private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
165             this::updateNavButtonInAppDisplayProgressForSysui);
166     /** Expected nav button dark intensity communicated via the framework. */
167     private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
168             this::updateNavButtonColor);
169     /** {@code 1} if the Taskbar background color is fully opaque. */
170     private final AnimatedFloat mOnTaskbarBackgroundNavButtonColorOverride = new AnimatedFloat(
171             this::updateNavButtonColor);
172     /** {@code 1} if a Taskbar slide in overlay is visible over Taskbar. */
173     private final AnimatedFloat mSlideInViewVisibleNavButtonColorOverride = new AnimatedFloat(
174             this::updateNavButtonColor);
175     /** Disables the {@link #mOnBackgroundIconColor} override if {@code 0}. */
176     private final AnimatedFloat mOnBackgroundNavButtonColorOverrideMultiplier = new AnimatedFloat(
177             this::updateNavButtonColor);
178     private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
179 
180     private final Rect mFloatingRotationButtonBounds = new Rect();
181 
182     // Initialized in init.
183     private TaskbarControllers mControllers;
184     private boolean mIsImeRenderingNavButtons;
185     private ImageView mA11yButton;
186     private int mSysuiStateFlags;
187     private ImageView mBackButton;
188     private ImageView mHomeButton;
189     private MultiValueAlpha mBackButtonAlpha;
190     private MultiValueAlpha mHomeButtonAlpha;
191     private FloatingRotationButton mFloatingRotationButton;
192 
193     // Variables for moving nav buttons to a separate window above IME
194     private boolean mAreNavButtonsInSeparateWindow = false;
195     private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
196     private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
197             this::onComputeInsetsForSeparateWindow;
198     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
199     private ImageView mRecentsButton;
200 
NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView)201     public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) {
202         mContext = context;
203         mNavButtonsView = navButtonsView;
204         mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
205         mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
206         mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
207 
208         mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
209         mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
210         mOnBackgroundIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
211     }
212 
213     /**
214      * Initializes the controller
215      */
init(TaskbarControllers controllers)216     public void init(TaskbarControllers controllers) {
217         mControllers = controllers;
218         boolean isThreeButtonNav = mContext.isThreeButtonNav();
219         DeviceProfile deviceProfile = mContext.getDeviceProfile();
220         Resources resources = mContext.getResources();
221         Point p = !mContext.isUserSetupComplete()
222                 ? new Point(0, controllers.taskbarActivityContext.getSetupWindowHeight())
223                 : DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
224                         TaskbarManager.isPhoneMode(deviceProfile));
225         mNavButtonsView.getLayoutParams().height = p.y;
226 
227         mIsImeRenderingNavButtons =
228                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
229         if (!mIsImeRenderingNavButtons) {
230             // IME switcher
231             View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH,
232                     isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
233                     mControllers.navButtonController, R.id.ime_switcher);
234             mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton,
235                     flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
236                             && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
237         }
238 
239         mPropertyHolders.add(new StatePropertyHolder(
240                 mControllers.taskbarViewController.getTaskbarIconAlpha()
241                         .get(ALPHA_INDEX_KEYGUARD),
242                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
243                         && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0));
244 
245         mPropertyHolders.add(new StatePropertyHolder(
246                 mControllers.taskbarViewController.getTaskbarIconAlpha()
247                         .get(ALPHA_INDEX_SMALL_SCREEN),
248                 flags -> (flags & FLAG_SMALL_SCREEN) == 0));
249 
250         mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
251                 .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
252 
253         // Force nav buttons (specifically back button) to be visible during setup wizard.
254         boolean isInSetup = !mContext.isUserSetupComplete();
255         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
256         boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
257 
258         // Make sure to remove nav bar buttons translation when any of the following occur:
259         // - Notification shade is expanded
260         // - IME is showing (add separate translation for IME)
261         // - VoiceInteractionWindow (assistant) is showing
262         int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
263                 | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
264         mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
265                 flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
266                 1, 0));
267         // Center nav buttons in new height for IME.
268         float transForIme = (mContext.getDeviceProfile().taskbarHeight
269                 - mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
270         // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
271         float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
272         mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
273                 flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
274                 transForIme, defaultButtonTransY));
275 
276         // Start at 1 because relevant flags are unset at init.
277         mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
278         mPropertyHolders.add(new StatePropertyHolder(
279                 mOnBackgroundNavButtonColorOverrideMultiplier,
280                 flags -> (flags & FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED) == 0));
281 
282         mPropertyHolders.add(new StatePropertyHolder(
283                 mSlideInViewVisibleNavButtonColorOverride,
284                 flags -> (flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0));
285 
286         if (alwaysShowButtons) {
287             initButtons(mNavButtonContainer, mEndContextualContainer,
288                     mControllers.navButtonController);
289             updateButtonLayoutSpacing();
290             updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneButtonNavMode(mContext));
291 
292             mPropertyHolders.add(new StatePropertyHolder(
293                     mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
294                     flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
295 
296             // Rotation button
297             RotationButton rotationButton = new RotationButtonImpl(
298                     addButton(mEndContextualContainer, R.id.rotate_suggestion,
299                             R.layout.taskbar_contextual_button));
300             rotationButton.hide();
301             mControllers.rotationButtonController.setRotationButton(rotationButton, null);
302         } else {
303             mFloatingRotationButton = new FloatingRotationButton(mContext,
304                     R.string.accessibility_rotate_button,
305                     R.layout.rotate_suggestion,
306                     R.id.rotate_suggestion,
307                     R.dimen.floating_rotation_button_min_margin,
308                     R.dimen.rounded_corner_content_padding,
309                     R.dimen.floating_rotation_button_taskbar_left_margin,
310                     R.dimen.floating_rotation_button_taskbar_bottom_margin,
311                     R.dimen.floating_rotation_button_diameter,
312                     R.dimen.key_button_ripple_max_width,
313                     R.bool.floating_rotation_button_position_left);
314             mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
315                     mRotationButtonListener);
316 
317             if (!mIsImeRenderingNavButtons) {
318                 View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
319                         mStartContextualContainer, mControllers.navButtonController, R.id.back);
320                 imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);
321                 // Only show when IME is visible.
322                 mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
323                         flags -> (flags & FLAG_IME_VISIBLE) != 0));
324             }
325         }
326 
327         applyState();
328         mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
329 
330         // Initialize things needed to move nav buttons to separate window.
331         mSeparateWindowParent = new BaseDragLayer<TaskbarActivityContext>(mContext, null, 0) {
332             @Override
333             public void recreateControllers() {
334                 mControllers = new TouchController[0];
335             }
336 
337             @Override
338             protected boolean canFindActiveController() {
339                 // We don't have any controllers, but we don't want any floating views such as
340                 // folder to intercept, either. This ensures nav buttons can always be pressed.
341                 return false;
342             }
343         };
344         mSeparateWindowParent.recreateControllers();
345     }
346 
initButtons(ViewGroup navContainer, ViewGroup endContainer, TaskbarNavButtonController navButtonController)347     private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
348             TaskbarNavButtonController navButtonController) {
349 
350         mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
351                 mNavButtonContainer, mControllers.navButtonController, R.id.back);
352         mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);
353         mBackButtonAlpha.setUpdateVisibility(true);
354         mPropertyHolders.add(new StatePropertyHolder(
355                 mBackButtonAlpha.get(ALPHA_INDEX_KEYGUARD_OR_DISABLE),
356                 flags -> {
357                     // Show only if not disabled, and if not on the keyguard or otherwise only when
358                     // the bouncer or a lockscreen app is showing above the keyguard
359                     boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
360                             (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
361                             (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
362                     return (flags & FLAG_DISABLE_BACK) == 0
363                             && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
364                 }));
365         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
366                 flags -> (flags & FLAG_IME_VISIBLE) != 0,
367                 ROTATION_DRAWABLE_PERCENT, 1f, 0f));
368         // Translate back button to be at end/start of other buttons for keyguard
369         int navButtonSize = mContext.getResources().getDimensionPixelSize(
370                 R.dimen.taskbar_nav_buttons_size);
371         boolean isRtl = Utilities.isRtl(mContext.getResources());
372         mPropertyHolders.add(new StatePropertyHolder(
373                 mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
374                         || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
375                 VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
376 
377         // home button
378         mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
379                 navButtonController, R.id.home);
380         mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
381         mHomeButtonAlpha.setUpdateVisibility(true);
382         mPropertyHolders.add(
383                 new StatePropertyHolder(mHomeButtonAlpha.get(
384                         ALPHA_INDEX_KEYGUARD_OR_DISABLE),
385                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
386                         (flags & FLAG_DISABLE_HOME) == 0));
387 
388         // Recents button
389         mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
390                 navContainer, navButtonController, R.id.recent_apps);
391         mHitboxExtender.init(mRecentsButton, mNavButtonsView, mContext.getDeviceProfile(),
392                 () -> {
393                     float[] recentsCoords = new float[2];
394                     getDescendantCoordRelativeToAncestor(mRecentsButton, mNavButtonsView,
395                             recentsCoords, false);
396                     return recentsCoords;
397                 }, new Handler());
398         mRecentsButton.setOnClickListener(v -> {
399             navButtonController.onButtonClick(BUTTON_RECENTS, v);
400             mHitboxExtender.onRecentsButtonClicked();
401         });
402         mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
403                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
404                         && !mContext.isNavBarKidsModeActive()));
405 
406         // A11y button
407         mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
408                 endContainer, navButtonController, R.id.accessibility_button,
409                 R.layout.taskbar_contextual_button);
410         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
411                 flags -> (flags & FLAG_A11Y_VISIBLE) != 0
412                         && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
413     }
414 
parseSystemUiFlags(int sysUiStateFlags)415     private void parseSystemUiFlags(int sysUiStateFlags) {
416         mSysuiStateFlags = sysUiStateFlags;
417         boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
418         boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
419         boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
420         boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
421         boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
422         boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
423         int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
424                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
425         boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
426         boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
427         boolean isVoiceInteractionWindowShowing =
428                 (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
429 
430         // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
431         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
432         updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
433         updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
434         updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
435         updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
436         updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
437         updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
438         updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
439         updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
440 
441         if (mA11yButton != null) {
442             // Only used in 3 button
443             boolean a11yLongClickable =
444                     (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
445             mA11yButton.setLongClickable(a11yLongClickable);
446             updateButtonLayoutSpacing();
447         }
448     }
449 
updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim)450     public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
451         if (systemUiStateFlags == mSysuiStateFlags) {
452             return;
453         }
454         parseSystemUiFlags(systemUiStateFlags);
455         applyState();
456         if (skipAnim) {
457             mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
458         }
459     }
460 
461     /**
462      * @return {@code true} if A11y is showing in 3 button nav taskbar
463      */
isContextualButtonShowing()464     private boolean isContextualButtonShowing() {
465         return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
466     }
467 
468     /**
469      * Should be called when we need to show back button for bouncer
470      */
setBackForBouncer(boolean isBouncerVisible)471     public void setBackForBouncer(boolean isBouncerVisible) {
472         updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
473         applyState();
474     }
475 
476     /**
477      * Slightly misnamed, but should be called when keyguard OR AOD is showing.
478      * We consider keyguardVisible when it's showing bouncer OR is occlucded by another app
479      */
setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded)480     public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) {
481         updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded);
482         updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded);
483         applyState();
484     }
485 
486     /** {@code true} if a slide in view is currently visible over taskbar. */
setSlideInViewVisible(boolean isSlideInViewVisible)487     public void setSlideInViewVisible(boolean isSlideInViewVisible) {
488         updateStateForFlag(FLAG_SLIDE_IN_VIEW_VISIBLE, isSlideInViewVisible);
489         applyState();
490     }
491 
492     /**
493      * Returns true if IME bar is visible
494      */
isImeVisible()495     public boolean isImeVisible() {
496         return (mState & FLAG_IME_VISIBLE) != 0;
497     }
498 
499     /**
500      * Returns true if the home button is disabled
501      */
isHomeDisabled()502     public boolean isHomeDisabled() {
503         return (mState & FLAG_DISABLE_HOME) != 0;
504     }
505 
506     /**
507      * Returns true if the recents (overview) button is disabled
508      */
isRecentsDisabled()509     public boolean isRecentsDisabled() {
510         return (mState & FLAG_DISABLE_RECENTS) != 0;
511     }
512 
513     /**
514      * Adds the bounds corresponding to all visible buttons to provided region
515      */
addVisibleButtonsRegion(BaseDragLayer<?> parent, Region outRegion)516     public void addVisibleButtonsRegion(BaseDragLayer<?> parent, Region outRegion) {
517         int count = mAllButtons.size();
518         for (int i = 0; i < count; i++) {
519             View button = mAllButtons.get(i);
520             if (button.getVisibility() == View.VISIBLE) {
521                 parent.getDescendantRectRelativeToSelf(button, mTempRect);
522                 if (mHitboxExtender.extendedHitboxEnabled()) {
523                     mTempRect.bottom += mContext.getDeviceProfile().getTaskbarOffsetY();
524                 }
525                 outRegion.op(mTempRect, Op.UNION);
526             }
527         }
528     }
529 
530     /**
531      * Returns multi-value alpha controller for back button.
532      */
getBackButtonAlpha()533     public MultiValueAlpha getBackButtonAlpha() {
534         return mBackButtonAlpha;
535     }
536 
537     /**
538      * Returns multi-value alpha controller for home button.
539      */
getHomeButtonAlpha()540     public MultiValueAlpha getHomeButtonAlpha() {
541         return mHomeButtonAlpha;
542     }
543 
544     /**
545      * Sets the AccessibilityDelegate for the home button.
546      */
setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate)547     public void setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
548         if (mHomeButton == null) {
549             return;
550         }
551         mHomeButton.setAccessibilityDelegate(accessibilityDelegate);
552     }
553 
554     /**
555      * Sets the AccessibilityDelegate for the back button.
556      */
setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate)557     public void setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
558         if (mBackButton == null) {
559             return;
560         }
561         mBackButton.setAccessibilityDelegate(accessibilityDelegate);
562     }
563 
564     /** Use to set the translationY for the all nav+contextual buttons */
getTaskbarNavButtonTranslationY()565     public AnimatedFloat getTaskbarNavButtonTranslationY() {
566         return mTaskbarNavButtonTranslationY;
567     }
568 
569     /** Use to set the translationY for the all nav+contextual buttons when in Launcher */
getTaskbarNavButtonTranslationYForInAppDisplay()570     public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
571         return mTaskbarNavButtonTranslationYForInAppDisplay;
572     }
573 
574     /** Use to set the dark intensity for the all nav+contextual buttons */
getTaskbarNavButtonDarkIntensity()575     public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
576         return mTaskbarNavButtonDarkIntensity;
577     }
578 
579     /** Use to override the nav button color with {@link #mOnBackgroundIconColor}. */
getOnTaskbarBackgroundNavButtonColorOverride()580     public AnimatedFloat getOnTaskbarBackgroundNavButtonColorOverride() {
581         return mOnTaskbarBackgroundNavButtonColorOverride;
582     }
583 
584     /**
585      * Does not call {@link #applyState()}. Don't forget to!
586      */
updateStateForFlag(int flag, boolean enabled)587     private void updateStateForFlag(int flag, boolean enabled) {
588         if (enabled) {
589             mState |= flag;
590         } else {
591             mState &= ~flag;
592         }
593     }
594 
applyState()595     private void applyState() {
596         int count = mPropertyHolders.size();
597         for (int i = 0; i < count; i++) {
598             mPropertyHolders.get(i).setState(mState);
599         }
600     }
601 
updateNavButtonInAppDisplayProgressForSysui()602     private void updateNavButtonInAppDisplayProgressForSysui() {
603         TaskbarUIController uiController = mControllers.uiController;
604         if (uiController instanceof LauncherTaskbarUIController) {
605             ((LauncherTaskbarUIController) uiController).onTaskbarInAppDisplayProgressUpdate(
606                     mNavButtonInAppDisplayProgressForSysui.value, SYSUI_SURFACE_PROGRESS_INDEX);
607         }
608     }
609 
610     /**
611      * Sets the translationY of the nav buttons based on the current device state.
612      */
updateNavButtonTranslationY()613     public void updateNavButtonTranslationY() {
614         if (isPhoneButtonNavMode(mContext)) {
615             return;
616         }
617         final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
618         final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
619         TaskbarUIController uiController = mControllers.uiController;
620         final float inAppDisplayAdjustmentTranslationY =
621                 (uiController instanceof LauncherTaskbarUIController
622                         && ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
623                         ? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
624 
625         mLastSetNavButtonTranslationY = normalTranslationY
626                 + imeAdjustmentTranslationY
627                 + inAppDisplayAdjustmentTranslationY;
628         mNavButtonsView.setTranslationY(mLastSetNavButtonTranslationY);
629     }
630 
updateNavButtonColor()631     private void updateNavButtonColor() {
632         final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
633         final int sysUiNavButtonIconColor = (int) argbEvaluator.evaluate(
634                 mTaskbarNavButtonDarkIntensity.value,
635                 mLightIconColor,
636                 mDarkIconColor);
637         // Override the color from framework if nav buttons are over an opaque Taskbar surface.
638         final int iconColor = (int) argbEvaluator.evaluate(
639                 mOnBackgroundNavButtonColorOverrideMultiplier.value
640                         * Math.max(
641                                 mOnTaskbarBackgroundNavButtonColorOverride.value,
642                                 mSlideInViewVisibleNavButtonColorOverride.value),
643                 sysUiNavButtonIconColor,
644                 mOnBackgroundIconColor);
645         for (ImageView button : mAllButtons) {
646             button.setImageTintList(ColorStateList.valueOf(iconColor));
647         }
648     }
649 
addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id)650     protected ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
651             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
652         return addButton(drawableId, buttonType, parent, navButtonController, id,
653                 R.layout.taskbar_nav_button);
654     }
655 
addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id, @LayoutRes int layoutId)656     private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
657             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
658             @LayoutRes int layoutId) {
659         ImageView buttonView = addButton(parent, id, layoutId);
660         buttonView.setImageResource(drawableId);
661         buttonView.setContentDescription(parent.getContext().getString(
662                 navButtonController.getButtonContentDescription(buttonType)));
663         buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType, view));
664         buttonView.setOnLongClickListener(view ->
665                 navButtonController.onButtonLongClick(buttonType, view));
666         return buttonView;
667     }
668 
addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId)669     private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
670         ImageView buttonView = (ImageView) mContext.getLayoutInflater()
671                 .inflate(layoutId, parent, false);
672         buttonView.setId(id);
673         parent.addView(buttonView);
674         mAllButtons.add(buttonView);
675         return buttonView;
676     }
677 
isEventOverAnyItem(MotionEvent ev)678     public boolean isEventOverAnyItem(MotionEvent ev) {
679         return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
680     }
681 
onConfigurationChanged(@onfig int configChanges)682     public void onConfigurationChanged(@Config int configChanges) {
683         if (mFloatingRotationButton != null) {
684             mFloatingRotationButton.onConfigurationChanged(configChanges);
685         }
686         if (!mContext.isUserSetupComplete()) {
687             handleSetupUi();
688         }
689         updateButtonLayoutSpacing();
690     }
691 
handleSetupUi()692     private void handleSetupUi() {
693         // Since setup wizard only has back button enabled, it looks strange to be
694         // end-aligned, so start-align instead.
695         FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
696                 mNavButtonContainer.getLayoutParams();
697         Resources resources = mContext.getResources();
698         DeviceProfile deviceProfile = mContext.getDeviceProfile();
699         int setupMargin = resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_margin);
700         navButtonsLayoutParams.setMarginStart(setupMargin);
701         navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
702                 ? 0
703                 : setupMargin -
704                         (resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2);
705         navButtonsLayoutParams.setMarginEnd(0);
706         navButtonsLayoutParams.gravity = Gravity.START;
707         mNavButtonsView.getLayoutParams().height =
708                 mControllers.taskbarActivityContext.getSetupWindowHeight();
709         mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
710     }
711 
712     /**
713      * Adds the correct spacing to 3 button nav container depending on if device is in kids mode,
714      * setup wizard, or normal 3 button nav.
715      */
updateButtonLayoutSpacing()716     private void updateButtonLayoutSpacing() {
717         DeviceProfile dp = mContext.getDeviceProfile();
718         Resources res = mContext.getResources();
719         boolean isInSetup = !mContext.isUserSetupComplete();
720         // TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
721         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
722 
723         if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
724             boolean isThreeButtonNav = mContext.isThreeButtonNav();
725 
726             NavButtonLayoutter navButtonLayoutter =
727                     NavButtonLayoutFactory.Companion.getUiLayoutter(
728                             dp, mNavButtonsView, res, isInKidsMode, isInSetup, isThreeButtonNav,
729                             TaskbarManager.isPhoneMode(dp));
730             navButtonLayoutter.layoutButtons(dp, isContextualButtonShowing());
731             return;
732         }
733 
734         if (isInSetup) {
735             handleSetupUi();
736 
737             // Hide back button in SUW if keyboard is showing (IME draws its own back).
738             mPropertyHolders.add(new StatePropertyHolder(
739                     mBackButtonAlpha.get(ALPHA_INDEX_SUW),
740                     flags -> (flags & FLAG_IME_VISIBLE) == 0));
741         } else if (isInKidsMode) {
742             int iconSize = res.getDimensionPixelSize(
743                     R.dimen.taskbar_icon_size_kids);
744             int buttonWidth = res.getDimensionPixelSize(
745                     R.dimen.taskbar_nav_buttons_width_kids);
746             int buttonHeight = res.getDimensionPixelSize(
747                     R.dimen.taskbar_nav_buttons_height_kids);
748             int buttonRadius = res.getDimensionPixelSize(
749                     R.dimen.taskbar_nav_buttons_corner_radius_kids);
750             int paddingleft = (buttonWidth - iconSize) / 2;
751             int paddingRight = paddingleft;
752             int paddingTop = (buttonHeight - iconSize) / 2;
753             int paddingBottom = paddingTop;
754 
755             // Update icons
756             final RotateDrawable rotateDrawable = new RotateDrawable();
757             rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back_kids));
758             rotateDrawable.setFromDegrees(0f);
759             rotateDrawable.setToDegrees(-90f);
760             mBackButton.setImageDrawable(rotateDrawable);
761             mBackButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
762             mBackButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
763 
764             mHomeButton.setImageDrawable(
765                     mHomeButton.getContext().getDrawable(R.drawable.ic_sysbar_home_kids));
766             mHomeButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
767             mHomeButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
768 
769             // Home button layout
770             LinearLayout.LayoutParams homeLayoutparams = new LinearLayout.LayoutParams(
771                     buttonWidth,
772                     buttonHeight
773             );
774             int homeButtonLeftMargin = res.getDimensionPixelSize(
775                     R.dimen.taskbar_home_button_left_margin_kids);
776             homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
777             mHomeButton.setLayoutParams(homeLayoutparams);
778 
779             // Back button layout
780             LinearLayout.LayoutParams backLayoutParams = new LinearLayout.LayoutParams(
781                     buttonWidth,
782                     buttonHeight
783             );
784             int backButtonLeftMargin = res.getDimensionPixelSize(
785                     R.dimen.taskbar_back_button_left_margin_kids);
786             backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
787             mBackButton.setLayoutParams(backLayoutParams);
788 
789             // Button backgrounds
790             int whiteWith10PctAlpha = Color.argb(0.1f, 1, 1, 1);
791             PaintDrawable buttonBackground = new PaintDrawable(whiteWith10PctAlpha);
792             buttonBackground.setCornerRadius(buttonRadius);
793             mHomeButton.setBackground(buttonBackground);
794             mBackButton.setBackground(buttonBackground);
795 
796             // Update alignment within taskbar
797             FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
798                     mNavButtonContainer.getLayoutParams();
799             navButtonsLayoutParams.setMarginStart(
800                     navButtonsLayoutParams.getMarginEnd() / 2);
801             navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
802             navButtonsLayoutParams.gravity = Gravity.CENTER;
803             mNavButtonContainer.requestLayout();
804 
805             mHomeButton.setOnLongClickListener(null);
806         } else if (mContext.isThreeButtonNav()) {
807             final RotateDrawable rotateDrawable = new RotateDrawable();
808             rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back));
809             rotateDrawable.setFromDegrees(0f);
810             rotateDrawable.setToDegrees(Utilities.isRtl(mContext.getResources()) ? 90f : -90f);
811             mBackButton.setImageDrawable(rotateDrawable);
812 
813             // Setup normal 3 button
814             // Add spacing after the end of the last nav button
815             FrameLayout.LayoutParams navButtonParams =
816                     (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
817             navButtonParams.gravity = Gravity.END;
818             navButtonParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
819             navButtonParams.height = MATCH_PARENT;
820 
821             int navMarginEnd = (int) res.getDimension(dp.inv.inlineNavButtonsEndSpacing);
822             int contextualWidth = mEndContextualContainer.getWidth();
823             // If contextual buttons are showing, we check if the end margin is enough for the
824             // contextual button to be showing - if not, move the nav buttons over a smidge
825             if (isContextualButtonShowing() && navMarginEnd < contextualWidth) {
826                 // Additional spacing, eat up half of space between last icon and nav button
827                 navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
828             }
829             navButtonParams.setMarginEnd(navMarginEnd);
830             mNavButtonContainer.setLayoutParams(navButtonParams);
831 
832             // Add the spaces in between the nav buttons
833             int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween);
834             for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
835                 View navButton = mNavButtonContainer.getChildAt(i);
836                 LinearLayout.LayoutParams buttonLayoutParams =
837                         (LinearLayout.LayoutParams) navButton.getLayoutParams();
838                 buttonLayoutParams.weight = 0;
839                 if (i == 0) {
840                     buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
841                 } else if (i == mNavButtonContainer.getChildCount() - 1) {
842                     buttonLayoutParams.setMarginStart(spaceInBetween / 2);
843                 } else {
844                     buttonLayoutParams.setMarginStart(spaceInBetween / 2);
845                     buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
846                 }
847             }
848         }
849 
850     }
851 
onDestroy()852     public void onDestroy() {
853         mPropertyHolders.clear();
854         mControllers.rotationButtonController.unregisterListeners();
855         if (mFloatingRotationButton != null) {
856             mFloatingRotationButton.hide();
857         }
858 
859         moveNavButtonsBackToTaskbarWindow();
860         mNavButtonContainer.removeAllViews();
861         mEndContextualContainer.removeAllViews();
862         mStartContextualContainer.removeAllViews();
863         mAllButtons.clear();
864     }
865 
866     /**
867      * Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on a new window.
868      */
moveNavButtonsToNewWindow()869     public void moveNavButtonsToNewWindow() {
870         if (mAreNavButtonsInSeparateWindow) {
871             return;
872         }
873 
874         if (mIsImeRenderingNavButtons) {
875             // IME is rendering the nav buttons, so we don't need to create a new layer for them.
876             return;
877         }
878 
879         mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
880             @Override
881             public void onViewAttachedToWindow(View view) {
882                 mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener(
883                         mSeparateWindowInsetsComputer);
884             }
885 
886             @Override
887             public void onViewDetachedFromWindow(View view) {
888                 mSeparateWindowParent.removeOnAttachStateChangeListener(this);
889                 mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener(
890                         mSeparateWindowInsetsComputer);
891             }
892         });
893 
894         mAreNavButtonsInSeparateWindow = true;
895         mContext.getDragLayer().removeView(mNavButtonsView);
896         mSeparateWindowParent.addView(mNavButtonsView);
897         WindowManager.LayoutParams windowLayoutParams = mContext.createDefaultWindowLayoutParams(
898                 TYPE_NAVIGATION_BAR_PANEL, NAV_BUTTONS_SEPARATE_WINDOW_TITLE);
899         mContext.addWindowView(mSeparateWindowParent, windowLayoutParams);
900 
901     }
902 
903     /**
904      * Moves mNavButtonsView from its temporary window and reattaches it to TaskbarDragLayer.
905      */
moveNavButtonsBackToTaskbarWindow()906     public void moveNavButtonsBackToTaskbarWindow() {
907         if (!mAreNavButtonsInSeparateWindow) {
908             return;
909         }
910 
911         mAreNavButtonsInSeparateWindow = false;
912         mContext.removeWindowView(mSeparateWindowParent);
913         mSeparateWindowParent.removeView(mNavButtonsView);
914         mContext.getDragLayer().addView(mNavButtonsView);
915     }
916 
onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo)917     private void onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
918         addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion);
919         insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
920     }
921 
922     @Override
dumpLogs(String prefix, PrintWriter pw)923     public void dumpLogs(String prefix, PrintWriter pw) {
924         pw.println(prefix + "NavbarButtonsViewController:");
925 
926         pw.println(prefix + "\tmState=" + getStateString(mState));
927         pw.println(prefix + "\tmLightIconColor=" + Integer.toHexString(mLightIconColor));
928         pw.println(prefix + "\tmDarkIconColor=" + Integer.toHexString(mDarkIconColor));
929         pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
930         pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
931                 mSysuiStateFlags));
932         pw.println(prefix + "\tLast set nav button translationY=" + mLastSetNavButtonTranslationY);
933         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationY="
934                 + mTaskbarNavButtonTranslationY.value);
935         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForInAppDisplay="
936                 + mTaskbarNavButtonTranslationYForInAppDisplay.value);
937         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
938                 + mTaskbarNavButtonTranslationYForIme.value);
939     }
940 
getStateString(int flags)941     private static String getStateString(int flags) {
942         StringJoiner str = new StringJoiner("|");
943         appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING");
944         appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
945         appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
946         appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
947         appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
948                 "FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
949         appendFlag(str, flags, FLAG_KEYGUARD_VISIBLE, "FLAG_KEYGUARD_VISIBLE");
950         appendFlag(str, flags, FLAG_KEYGUARD_OCCLUDED, "FLAG_KEYGUARD_OCCLUDED");
951         appendFlag(str, flags, FLAG_DISABLE_HOME, "FLAG_DISABLE_HOME");
952         appendFlag(str, flags, FLAG_DISABLE_RECENTS, "FLAG_DISABLE_RECENTS");
953         appendFlag(str, flags, FLAG_DISABLE_BACK, "FLAG_DISABLE_BACK");
954         appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED,
955                 "FLAG_NOTIFICATION_SHADE_EXPANDED");
956         appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
957         appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
958                 "FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
959         return str.toString();
960     }
961 
getTouchController()962     public TouchController getTouchController() {
963         return mHitboxExtender;
964     }
965 
966     /**
967      * @param alignment 0 -> Taskbar, 1 -> Workspace
968      */
updateTaskbarAlignment(float alignment)969     public void updateTaskbarAlignment(float alignment) {
970         mHitboxExtender.onAnimationProgressToOverview(alignment);
971     }
972 
973     private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
974         @Override
onVisibilityChanged(boolean isVisible)975         public void onVisibilityChanged(boolean isVisible) {
976             if (isVisible) {
977                 mFloatingRotationButton.getCurrentView()
978                         .getBoundsOnScreen(mFloatingRotationButtonBounds);
979             } else {
980                 mFloatingRotationButtonBounds.setEmpty();
981             }
982         }
983     }
984 
985     private class RotationButtonImpl implements RotationButton {
986 
987         private final ImageView mButton;
988         private AnimatedVectorDrawable mImageDrawable;
989 
RotationButtonImpl(ImageView button)990         RotationButtonImpl(ImageView button) {
991             mButton = button;
992         }
993 
994         @Override
setRotationButtonController(RotationButtonController rotationButtonController)995         public void setRotationButtonController(RotationButtonController rotationButtonController) {
996             // TODO(b/187754252) UI polish, different icons based on light/dark context, etc
997             mImageDrawable = (AnimatedVectorDrawable) mButton.getContext()
998                     .getDrawable(rotationButtonController.getIconResId());
999             mButton.setImageDrawable(mImageDrawable);
1000             mButton.setContentDescription(mButton.getResources()
1001                     .getString(R.string.accessibility_rotate_button));
1002             mImageDrawable.setCallback(mButton);
1003         }
1004 
1005         @Override
getCurrentView()1006         public View getCurrentView() {
1007             return mButton;
1008         }
1009 
1010         @Override
show()1011         public boolean show() {
1012             mButton.setVisibility(View.VISIBLE);
1013             mState |= FLAG_ROTATION_BUTTON_VISIBLE;
1014             applyState();
1015             return true;
1016         }
1017 
1018         @Override
hide()1019         public boolean hide() {
1020             mButton.setVisibility(View.GONE);
1021             mState &= ~FLAG_ROTATION_BUTTON_VISIBLE;
1022             applyState();
1023             return true;
1024         }
1025 
1026         @Override
isVisible()1027         public boolean isVisible() {
1028             return mButton.getVisibility() == View.VISIBLE;
1029         }
1030 
1031         @Override
updateIcon(int lightIconColor, int darkIconColor)1032         public void updateIcon(int lightIconColor, int darkIconColor) {
1033             // TODO(b/187754252): UI Polish
1034         }
1035 
1036         @Override
setOnClickListener(OnClickListener onClickListener)1037         public void setOnClickListener(OnClickListener onClickListener) {
1038             mButton.setOnClickListener(onClickListener);
1039         }
1040 
1041         @Override
setOnHoverListener(OnHoverListener onHoverListener)1042         public void setOnHoverListener(OnHoverListener onHoverListener) {
1043             mButton.setOnHoverListener(onHoverListener);
1044         }
1045 
1046         @Override
getImageDrawable()1047         public AnimatedVectorDrawable getImageDrawable() {
1048             return mImageDrawable;
1049         }
1050 
1051         @Override
setDarkIntensity(float darkIntensity)1052         public void setDarkIntensity(float darkIntensity) {
1053             // TODO(b/187754252) UI polish
1054         }
1055 
1056         @Override
acceptRotationProposal()1057         public boolean acceptRotationProposal() {
1058             return mButton.isAttachedToWindow();
1059         }
1060     }
1061 
1062     private static class StatePropertyHolder {
1063 
1064         private final float mEnabledValue, mDisabledValue;
1065         private final ObjectAnimator mAnimator;
1066         private final IntPredicate mEnableCondition;
1067 
1068         private boolean mIsEnabled = true;
1069 
StatePropertyHolder(View view, IntPredicate enableCondition)1070         StatePropertyHolder(View view, IntPredicate enableCondition) {
1071             this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
1072             mAnimator.addListener(new AlphaUpdateListener(view));
1073         }
1074 
StatePropertyHolder(MultiProperty alphaProperty, IntPredicate enableCondition)1075         StatePropertyHolder(MultiProperty alphaProperty,
1076                 IntPredicate enableCondition) {
1077             this(alphaProperty, enableCondition, MULTI_PROPERTY_VALUE, 1, 0);
1078         }
1079 
StatePropertyHolder(AnimatedFloat animatedFloat, IntPredicate enableCondition)1080         StatePropertyHolder(AnimatedFloat animatedFloat, IntPredicate enableCondition) {
1081             this(animatedFloat, enableCondition, AnimatedFloat.VALUE, 1, 0);
1082         }
1083 
StatePropertyHolder(T target, IntPredicate enabledCondition, Property<T, Float> property, float enabledValue, float disabledValue)1084         <T> StatePropertyHolder(T target, IntPredicate enabledCondition,
1085                 Property<T, Float> property, float enabledValue, float disabledValue) {
1086             mEnableCondition = enabledCondition;
1087             mEnabledValue = enabledValue;
1088             mDisabledValue = disabledValue;
1089             mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
1090         }
1091 
setState(int flags)1092         public void setState(int flags) {
1093             boolean isEnabled = mEnableCondition.test(flags);
1094             if (mIsEnabled != isEnabled) {
1095                 mIsEnabled = isEnabled;
1096                 mAnimator.cancel();
1097                 mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
1098                 mAnimator.start();
1099             }
1100         }
1101 
endAnimation()1102         public void endAnimation() {
1103             if (mAnimator.isRunning()) {
1104                 mAnimator.end();
1105             }
1106         }
1107     }
1108 }
1109