• 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.anim.AnimatorListeners.forEndCallback;
27 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
28 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
29 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
30 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
31 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
32 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
33 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
34 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_SPACE;
35 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD;
36 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_SMALL_SCREEN;
37 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
38 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
39 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
40 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
41 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
42 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
43 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
44 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
45 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
46 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
47 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
48 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
49 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
50 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
51 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
52 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
53 import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
54 
55 import android.animation.Animator;
56 import android.animation.ArgbEvaluator;
57 import android.animation.ObjectAnimator;
58 import android.annotation.DrawableRes;
59 import android.annotation.IdRes;
60 import android.annotation.LayoutRes;
61 import android.annotation.SuppressLint;
62 import android.content.Context;
63 import android.content.pm.ActivityInfo.Config;
64 import android.content.res.ColorStateList;
65 import android.content.res.Resources;
66 import android.graphics.Color;
67 import android.graphics.Point;
68 import android.graphics.Rect;
69 import android.graphics.RectF;
70 import android.graphics.Region;
71 import android.graphics.Region.Op;
72 import android.graphics.drawable.Drawable;
73 import android.graphics.drawable.PaintDrawable;
74 import android.graphics.drawable.RotateDrawable;
75 import android.inputmethodservice.InputMethodService;
76 import android.os.Bundle;
77 import android.os.Handler;
78 import android.os.SystemProperties;
79 import android.util.Property;
80 import android.view.Gravity;
81 import android.view.HapticFeedbackConstants;
82 import android.view.KeyEvent;
83 import android.view.MotionEvent;
84 import android.view.View;
85 import android.view.View.OnAttachStateChangeListener;
86 import android.view.ViewGroup;
87 import android.view.ViewTreeObserver;
88 import android.view.WindowManager;
89 import android.view.accessibility.AccessibilityNodeInfo;
90 import android.view.inputmethod.Flags;
91 import android.widget.FrameLayout;
92 import android.widget.ImageView;
93 import android.widget.LinearLayout;
94 import android.widget.Space;
95 
96 import androidx.annotation.Nullable;
97 
98 import com.android.launcher3.DeviceProfile;
99 import com.android.launcher3.LauncherAnimUtils;
100 import com.android.launcher3.R;
101 import com.android.launcher3.Utilities;
102 import com.android.launcher3.anim.AlphaUpdateListener;
103 import com.android.launcher3.anim.AnimatedFloat;
104 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
105 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
106 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory;
107 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter;
108 import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
109 import com.android.launcher3.util.DimensionUtils;
110 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
111 import com.android.launcher3.util.MultiValueAlpha;
112 import com.android.launcher3.util.TouchController;
113 import com.android.launcher3.util.window.WindowManagerProxy;
114 import com.android.launcher3.views.BaseDragLayer;
115 import com.android.systemui.shared.navigationbar.KeyButtonRipple;
116 import com.android.systemui.shared.rotation.FloatingRotationButton;
117 import com.android.systemui.shared.rotation.RotationButton;
118 import com.android.systemui.shared.statusbar.phone.BarTransitions;
119 import com.android.systemui.shared.system.QuickStepContract;
120 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
121 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
122 
123 import java.io.PrintWriter;
124 import java.util.ArrayList;
125 import java.util.StringJoiner;
126 import java.util.concurrent.atomic.AtomicBoolean;
127 import java.util.function.IntPredicate;
128 
129 /**
130  * Controller for managing nav bar buttons in taskbar
131  */
132 public class NavbarButtonsViewController implements TaskbarControllers.LoggableTaskbarController,
133         BubbleBarController.BubbleBarLocationListener {
134 
135     private final Rect mTempRect = new Rect();
136 
137     /** Whether the IME Switcher button is visible. */
138     private static final int FLAG_IME_SWITCHER_BUTTON_VISIBLE = 1 << 0;
139     /** Whether the IME is visible. */
140     private static final int FLAG_IME_VISIBLE = 1 << 1;
141     /**
142      * The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
143      * This only takes effect while the IME is visible. By default, it is set while the IME is
144      * visible, but may be overridden by the
145      * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode}
146      * set by the IME.
147      */
148     private static final int FLAG_BACK_DISMISS_IME = 1 << 2;
149     private static final int FLAG_A11Y_VISIBLE = 1 << 3;
150     private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
151     private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
152     private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6;
153     private static final int FLAG_DISABLE_HOME = 1 << 7;
154     private static final int FLAG_DISABLE_RECENTS = 1 << 8;
155     private static final int FLAG_DISABLE_BACK = 1 << 9;
156     private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
157     private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11;
158     private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
159     private static final int FLAG_SMALL_SCREEN = 1 << 13;
160     private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
161     private static final int FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING = 1 << 15;
162 
163     /**
164      * Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
165      */
166     private static final int FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED =
167             FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
168 
169     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
170 
171     private static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
172     private static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
173 
174     public static final int ALPHA_INDEX_IMMERSIVE_MODE = 0;
175     public static final int ALPHA_INDEX_KEYGUARD_OR_DISABLE = 1;
176     public static final int ALPHA_INDEX_SUW = 2;
177     private static final int NUM_ALPHA_CHANNELS = 3;
178 
179     private static final long AUTODIM_TIMEOUT_MS = 2250;
180     private static final long PREDICTIVE_BACK_TIMEOUT_MS = 200;
181 
182     private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
183     private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
184     private int mState;
185 
186     private final TaskbarActivityContext mContext;
187     private final @Nullable Context mNavigationBarPanelContext;
188     private final WindowManagerProxy mWindowManagerProxy;
189     private final NearestTouchFrame mNavButtonsView;
190     private final Handler mHandler;
191     private final LinearLayout mNavButtonContainer;
192     // Used for IME+A11Y buttons
193     private final ViewGroup mEndContextualContainer;
194     private final ViewGroup mStartContextualContainer;
195     private final int mLightIconColorOnWorkspace;
196     private final int mDarkIconColorOnWorkspace;
197     /** Color to use for navbar buttons, if they are on on a Taskbar surface background. */
198     private final int mOnBackgroundIconColor;
199     private final boolean mIsExpressiveThemeEnabled;
200 
201     private @Nullable Animator mNavBarLocationAnimator;
202     private @Nullable BubbleBarLocation mBubbleBarTargetLocation;
203 
204     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
205             this::updateNavButtonTranslationY);
206     private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
207             this::updateNavButtonTranslationY);
208     private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
209             this::updateNavButtonTranslationY);
210     private float mLastSetNavButtonTranslationY;
211     // Used for System UI state updates that should translate the nav button for in-app display.
212     private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
213             this::updateNavButtonInAppDisplayProgressForSysui);
214     /**
215      * Expected nav button dark intensity piped down from {@code LightBarController} in framework
216      * via {@code TaskbarDelegate}.
217      */
218     private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
219             this::onDarkIntensityChanged);
220     /** {@code 1} if the Taskbar background color is fully opaque. */
221     private final AnimatedFloat mOnTaskbarBackgroundNavButtonColorOverride = new AnimatedFloat(
222             this::updateNavButtonColor);
223     /** {@code 1} if a Taskbar slide in overlay is visible over Taskbar. */
224     private final AnimatedFloat mSlideInViewVisibleNavButtonColorOverride = new AnimatedFloat(
225             this::updateNavButtonColor);
226     /** Disables the {@link #mOnBackgroundIconColor} override if {@code 0}. */
227     private final AnimatedFloat mOnBackgroundNavButtonColorOverrideMultiplier = new AnimatedFloat(
228             this::updateNavButtonColor);
229     private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
230 
231     private final Rect mFloatingRotationButtonBounds = new Rect();
232 
233     // Initialized in init.
234     private TaskbarControllers mControllers;
235     private boolean mIsImeRenderingNavButtons;
236     private ImageView mA11yButton;
237     @SystemUiStateFlags
238     private long mSysuiStateFlags;
239     private ImageView mBackButton;
240     private ImageView mHomeButton;
241     private MultiValueAlpha mBackButtonAlpha;
242     private MultiValueAlpha mHomeButtonAlpha;
243     private FloatingRotationButton mFloatingRotationButton;
244     private ImageView mImeSwitcherButton;
245 
246     // Variables for moving nav buttons to a separate window above IME
247     private boolean mAreNavButtonsInSeparateWindow = false;
248     private BaseDragLayer<TaskbarActivityContext> mSeparateWindowParent; // Initialized in init.
249     private final ViewTreeObserver.OnComputeInternalInsetsListener mSeparateWindowInsetsComputer =
250             this::onComputeInsetsForSeparateWindow;
251     private final RecentsHitboxExtender mHitboxExtender = new RecentsHitboxExtender();
252     private ImageView mRecentsButton;
253     private Space mSpace;
254 
255     private TaskbarTransitions mTaskbarTransitions;
256     private @BarTransitions.TransitionMode int mTransitionMode;
257 
258     private final Runnable mAutoDim = () -> mTaskbarTransitions.setAutoDim(true);
259 
NavbarButtonsViewController(TaskbarActivityContext context, @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView, Handler handler)260     public NavbarButtonsViewController(TaskbarActivityContext context,
261             @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView,
262             Handler handler) {
263         mContext = context;
264         mNavigationBarPanelContext = navigationBarPanelContext;
265         mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
266         mNavButtonsView = navButtonsView;
267         mHandler = handler;
268         mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
269         mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
270         mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
271 
272         mLightIconColorOnWorkspace = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
273         mDarkIconColorOnWorkspace = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
274         mOnBackgroundIconColor = Utilities.isDarkTheme(context)
275                 ? context.getColor(R.color.taskbar_nav_icon_light_color)
276                 : context.getColor(R.color.taskbar_nav_icon_dark_color);
277 
278         if (mContext.isPhoneMode()) {
279             mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
280         }
281         String SUWTheme = SystemProperties.get("setupwizard.theme", "");
282         mIsExpressiveThemeEnabled = SUWTheme.equals("glif_expressive")
283                 || SUWTheme.equals("glif_expressive_light");
284     }
285 
286     /**
287      * Initializes the controller
288      */
init(TaskbarControllers controllers)289     public void init(TaskbarControllers controllers) {
290         mControllers = controllers;
291         setupController();
292     }
293 
setupController()294     protected void setupController() {
295         final boolean isThreeButtonNav = mContext.isThreeButtonNav();
296         final boolean isPhoneMode = mContext.isPhoneMode();
297         DeviceProfile deviceProfile = mContext.getDeviceProfile();
298         Resources resources = mContext.getResources();
299 
300         int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
301         Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources, isPhoneMode,
302                 mContext.isGestureNav());
303         ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
304         navButtonsViewLayoutParams.width = p.x;
305         if (!mContext.isUserSetupComplete()) {
306             // Setup mode in phone mode uses gesture nav.
307             navButtonsViewLayoutParams.height = setupSize;
308         } else {
309             navButtonsViewLayoutParams.height = p.y;
310         }
311         mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
312 
313         mIsImeRenderingNavButtons =
314                 InputMethodService.canImeRenderGesturalNavButtons() && mContext.imeDrawsImeNavBar();
315         if (!mIsImeRenderingNavButtons) {
316             // IME switcher
317             final int switcherResId = Flags.imeSwitcherRevamp()
318                     ? com.android.internal.R.drawable.ic_ime_switcher_new
319                     : R.drawable.ic_ime_switcher;
320             mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
321                     isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
322                     mControllers.navButtonController, R.id.ime_switcher);
323             // A11y and IME Switcher buttons overlap on phone mode, show only a11y if both visible.
324             mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
325                     flags -> (flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0
326                             && !(isPhoneMode && (flags & FLAG_A11Y_VISIBLE) != 0)));
327         }
328 
329         mPropertyHolders.add(new StatePropertyHolder(
330                 mControllers.taskbarViewController.getTaskbarIconAlpha()
331                         .get(ALPHA_INDEX_KEYGUARD),
332                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
333                         && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0));
334 
335         mPropertyHolders.add(new StatePropertyHolder(
336                 mControllers.taskbarViewController.getTaskbarIconAlpha()
337                         .get(ALPHA_INDEX_SMALL_SCREEN),
338                 flags -> (flags & FLAG_SMALL_SCREEN) == 0));
339 
340         if (!isPhoneMode) {
341             mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
342                     .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
343         }
344 
345         // Start at 1 because relevant flags are unset at init.
346         mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
347 
348         // Potentially force the back button to be visible during setup wizard.
349         boolean shouldShowInSetup = !mContext.isUserSetupComplete() && !mIsExpressiveThemeEnabled;
350         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
351         boolean alwaysShowButtons = isThreeButtonNav || shouldShowInSetup;
352 
353         // Make sure to remove nav bar buttons translation when any of the following occur:
354         // - Notification shade is expanded
355         // - IME is visible (add separate translation for IME)
356         // - VoiceInteractionWindow (assistant) is showing
357         // - Keyboard shortcuts helper is showing
358         if (!isPhoneMode) {
359             int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
360                     | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
361             mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
362                     flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
363                     1, 0));
364             // Center nav buttons in new height for IME.
365             float transForIme = (mContext.getDeviceProfile().taskbarHeight
366                     - mControllers.taskbarInsetsController.getTaskbarHeightForIme()) / 2f;
367             // For gesture nav, nav buttons only show for IME anyway so keep them translated down.
368             float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme;
369             mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme,
370                     flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
371                     transForIme, defaultButtonTransY));
372 
373             mPropertyHolders.add(new StatePropertyHolder(
374                     mOnBackgroundNavButtonColorOverrideMultiplier,
375                     flags -> (flags & FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED) == 0));
376 
377             mPropertyHolders.add(new StatePropertyHolder(
378                     mSlideInViewVisibleNavButtonColorOverride,
379                     flags -> (flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0));
380         }
381 
382         if (alwaysShowButtons) {
383             initButtons(mNavButtonContainer, mEndContextualContainer,
384                     mControllers.navButtonController);
385             updateButtonLayoutSpacing();
386             updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneMode);
387 
388             if (!isPhoneMode) {
389                 mPropertyHolders.add(new StatePropertyHolder(
390                         mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
391                         flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
392             }
393         } else if (!mIsImeRenderingNavButtons) {
394             View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
395                     mStartContextualContainer, mControllers.navButtonController, R.id.back);
396             imeDownButton.setRotation(Utilities.isRtl(resources) ? 90 : -90);
397             // Only show when IME is visible.
398             mPropertyHolders.add(new StatePropertyHolder(imeDownButton,
399                     flags -> (flags & FLAG_IME_VISIBLE) != 0));
400         }
401         mFloatingRotationButton = new FloatingRotationButton(
402                 ENABLE_TASKBAR_NAVBAR_UNIFICATION ? mNavigationBarPanelContext : mContext,
403                 R.string.accessibility_rotate_button,
404                 R.layout.rotate_suggestion,
405                 R.id.rotate_suggestion,
406                 R.dimen.floating_rotation_button_min_margin,
407                 R.dimen.rounded_corner_content_padding,
408                 R.dimen.floating_rotation_button_taskbar_left_margin,
409                 R.dimen.floating_rotation_button_taskbar_bottom_margin,
410                 R.dimen.floating_rotation_button_diameter,
411                 R.dimen.key_button_ripple_max_width,
412                 R.bool.floating_rotation_button_position_left);
413         mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
414                 mRotationButtonListener);
415         if (isPhoneMode) {
416             mTaskbarTransitions.init();
417         }
418 
419         applyState();
420         mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
421 
422         // Initialize things needed to move nav buttons to separate window.
423         mSeparateWindowParent = new BaseDragLayer<>(mContext, null, 0) {
424             @Override
425             public void recreateControllers() {
426                 mControllers = new TouchController[0];
427             }
428 
429             @Override
430             protected boolean canFindActiveController() {
431                 // We don't have any controllers, but we don't want any floating views such as
432                 // folder to intercept, either. This ensures nav buttons can always be pressed.
433                 return false;
434             }
435         };
436         mSeparateWindowParent.recreateControllers();
437         if (BubbleBarController.isBubbleBarEnabled()) {
438             mNavButtonsView.addOnLayoutChangeListener(
439                     (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
440                             onLayoutsUpdated()
441             );
442         }
443     }
444 
initButtons(ViewGroup navContainer, ViewGroup endContainer, TaskbarNavButtonController navButtonController)445     private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
446             TaskbarNavButtonController navButtonController) {
447 
448         mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
449                 mNavButtonContainer, mControllers.navButtonController, R.id.back);
450         mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);
451         mBackButtonAlpha.setUpdateVisibility(true);
452         mPropertyHolders.add(new StatePropertyHolder(
453                 mBackButtonAlpha.get(ALPHA_INDEX_KEYGUARD_OR_DISABLE),
454                 flags -> {
455                     // Show only if not disabled, and if not on the keyguard or otherwise only when
456                     // the bouncer or a lockscreen app is showing above the keyguard
457                     boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
458                             (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
459                             (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
460                     return (flags & FLAG_DISABLE_BACK) == 0
461                             && (!mContext.isGestureNav() || !mContext.isUserSetupComplete())
462                             && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
463                 }));
464         // Hide back button in SUW if keyboard is showing (IME draws its own back).
465         if (mIsImeRenderingNavButtons) {
466             mPropertyHolders.add(new StatePropertyHolder(
467                     mBackButtonAlpha.get(ALPHA_INDEX_SUW),
468                     flags -> (flags & FLAG_IME_VISIBLE) == 0));
469         }
470         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
471                 flags -> (flags & FLAG_BACK_DISMISS_IME) != 0,
472                 ROTATION_DRAWABLE_PERCENT, 1f, 0f));
473         // Translate back button to be at end/start of other buttons for keyguard (only after SUW
474         // since it is laid to align with SUW actions while in that state)
475         int navButtonSize = mContext.getResources().getDimensionPixelSize(
476                 R.dimen.taskbar_nav_buttons_size);
477         boolean isRtl = Utilities.isRtl(mContext.getResources());
478         if (!mContext.isPhoneMode()) {
479             mPropertyHolders.add(new StatePropertyHolder(
480                     mBackButton, flags -> mContext.isUserSetupComplete()
481                         && ((flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
482                             || (flags & FLAG_KEYGUARD_VISIBLE) != 0)
483                         && (!shouldShowHomeButtonInLockscreen(flags)),
484                     VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));
485         }
486 
487         // home button
488         mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
489                 navButtonController, R.id.home);
490         mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
491         mHomeButtonAlpha.setUpdateVisibility(true);
492         mPropertyHolders.add(
493                 new StatePropertyHolder(mHomeButtonAlpha.get(ALPHA_INDEX_KEYGUARD_OR_DISABLE),
494                         this::shouldShowHomeButtonInLockscreen));
495 
496         // Recents button
497         mRecentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
498                 navContainer, navButtonController, R.id.recent_apps);
499         mHitboxExtender.init(mRecentsButton, mNavButtonsView, mContext.getDeviceProfile(),
500                 () -> {
501                     float[] recentsCoords = new float[2];
502                     getDescendantCoordRelativeToAncestor(mRecentsButton, mNavButtonsView,
503                             recentsCoords, false);
504                     return recentsCoords;
505                 }, new Handler());
506         mRecentsButton.setOnClickListener(v -> {
507             navButtonController.onButtonClick(BUTTON_RECENTS, v);
508             mHitboxExtender.onRecentsButtonClicked();
509         });
510         mPropertyHolders.add(new StatePropertyHolder(mRecentsButton,
511                 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
512                         && !mContext.isNavBarKidsModeActive() && !mContext.isGestureNav()));
513 
514         // A11y button
515         mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
516                 endContainer, navButtonController, R.id.accessibility_button,
517                 R.layout.taskbar_contextual_button);
518         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
519                 flags -> (flags & FLAG_A11Y_VISIBLE) != 0));
520 
521         mSpace = new Space(mNavButtonsView.getContext());
522         mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
523         mSpace.setOnLongClickListener(view ->
524                 navButtonController.onButtonLongClick(BUTTON_SPACE, view));
525     }
526 
527     /**
528      * Method to determine whether to show the home button in lockscreen
529      *
530      * When the keyguard is visible hide home button. Anytime we are
531      * occluded we want to show the home button for apps over keyguard.
532      * however we don't want to show when not occluded/visible.
533      * (visible false || occluded true) && disable false && not gnav
534      */
shouldShowHomeButtonInLockscreen(int flags)535     private boolean shouldShowHomeButtonInLockscreen(int flags) {
536         return ((flags & FLAG_KEYGUARD_VISIBLE) == 0
537                 || (flags & FLAG_KEYGUARD_OCCLUDED) != 0)
538                 && (flags & FLAG_DISABLE_HOME) == 0
539                 && !mContext.isGestureNav();
540     }
541 
parseSystemUiFlags(@ystemUiStateFlags long sysUiStateFlags)542     private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
543         mSysuiStateFlags = sysUiStateFlags;
544         boolean isImeSwitcherButtonVisible =
545                 (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0;
546         boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0;
547         boolean isBackDismissIme = (sysUiStateFlags & SYSUI_STATE_BACK_DISMISS_IME) != 0;
548         boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
549         boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
550         boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
551         boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0;
552         long shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
553                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
554         boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
555         boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
556         boolean isVoiceInteractionWindowShowing =
557                 (sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
558         boolean isKeyboardShortcutHelperShowing =
559                 (sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
560 
561         updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
562         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
563         updateStateForFlag(FLAG_BACK_DISMISS_IME, isBackDismissIme);
564         updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
565         updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
566         updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
567         updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
568         updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
569         updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
570         updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
571         updateStateForFlag(FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING, isKeyboardShortcutHelperShowing);
572 
573         if (mA11yButton != null) {
574             // Only used in 3 button
575             boolean a11yLongClickable =
576                     (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
577             mA11yButton.setLongClickable(a11yLongClickable);
578             updateButtonLayoutSpacing();
579         }
580     }
581 
updateStateForSysuiFlags(@ystemUiStateFlags long systemUiStateFlags, boolean skipAnim)582     public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags,
583             boolean skipAnim) {
584         if (systemUiStateFlags == mSysuiStateFlags) {
585             return;
586         }
587         parseSystemUiFlags(systemUiStateFlags);
588         applyState();
589         if (skipAnim) {
590             mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
591         }
592     }
593 
594     /**
595      * @return {@code true} if A11y is showing in 3 button nav taskbar
596      */
isA11yButtonPersistent()597     private boolean isA11yButtonPersistent() {
598         return mContext.isThreeButtonNav() && (mState & FLAG_A11Y_VISIBLE) != 0;
599     }
600 
601     /**
602      * Should be called when we need to show back button for bouncer
603      */
setBackForBouncer(boolean isBouncerVisible)604     public void setBackForBouncer(boolean isBouncerVisible) {
605         updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible);
606         applyState();
607     }
608 
609     /**
610      * Slightly misnamed, but should be called when keyguard OR AOD is showing.
611      * We consider keyguardVisible when it's showing bouncer OR is occlucded by another app
612      */
setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded)613     public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) {
614         updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded);
615         updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded);
616         applyState();
617     }
618 
619     /** {@code true} if a slide in view is currently visible over taskbar. */
setSlideInViewVisible(boolean isSlideInViewVisible)620     public void setSlideInViewVisible(boolean isSlideInViewVisible) {
621         updateStateForFlag(FLAG_SLIDE_IN_VIEW_VISIBLE, isSlideInViewVisible);
622         applyState();
623     }
624 
625     /**
626      * Returns true if IME bar is visible
627      */
isImeVisible()628     public boolean isImeVisible() {
629         return (mState & FLAG_IME_VISIBLE) != 0;
630     }
631 
isImeRenderingNavButtons()632     public boolean isImeRenderingNavButtons() {
633         return mIsImeRenderingNavButtons;
634     }
635 
636     /**
637      * Returns true if the home button is disabled
638      */
isHomeDisabled()639     public boolean isHomeDisabled() {
640         return (mState & FLAG_DISABLE_HOME) != 0;
641     }
642 
643     /**
644      * Returns true if the recents (overview) button is disabled
645      */
isRecentsDisabled()646     public boolean isRecentsDisabled() {
647         return (mState & FLAG_DISABLE_RECENTS) != 0;
648     }
649 
650     /**
651      * Adds the bounds corresponding to all visible buttons to provided region
652      */
addVisibleButtonsRegion(BaseDragLayer<?> parent, Region outRegion)653     public void addVisibleButtonsRegion(BaseDragLayer<?> parent, Region outRegion) {
654         int count = mAllButtons.size();
655         for (int i = 0; i < count; i++) {
656             View button = mAllButtons.get(i);
657             if (button.getVisibility() == View.VISIBLE) {
658                 parent.getDescendantRectRelativeToSelf(button, mTempRect);
659                 if (mHitboxExtender.extendedHitboxEnabled()) {
660                     mTempRect.bottom += mContext.getDeviceProfile().getTaskbarOffsetY();
661                 }
662                 outRegion.op(mTempRect, Op.UNION);
663             }
664         }
665     }
666 
667     /**
668      * Returns multi-value alpha controller for back button.
669      */
getBackButtonAlpha()670     public MultiValueAlpha getBackButtonAlpha() {
671         return mBackButtonAlpha;
672     }
673 
674     /**
675      * Returns multi-value alpha controller for home button.
676      */
getHomeButtonAlpha()677     public MultiValueAlpha getHomeButtonAlpha() {
678         return mHomeButtonAlpha;
679     }
680 
681     /**
682      * Sets the AccessibilityDelegate for the home button.
683      */
setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate)684     public void setHomeButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
685         if (mHomeButton == null) {
686             return;
687         }
688         mHomeButton.setAccessibilityDelegate(accessibilityDelegate);
689     }
690 
691     /**
692      * Sets the AccessibilityDelegate for the back button.
693      *
694      * When setting a back button accessibility delegate, make sure to not dispatch any duplicate
695      * click events. Click events get injected in the internal accessibility delegate in
696      * {@link #setupBackButtonAccessibility(View, AccessibilityDelegate)}.
697      */
setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate)698     public void setBackButtonAccessibilityDelegate(AccessibilityDelegate accessibilityDelegate) {
699         if (mBackButton == null) {
700             return;
701         }
702         if (predictiveBackThreeButtonNav()) {
703             setupBackButtonAccessibility(mBackButton, accessibilityDelegate);
704         } else {
705             mBackButton.setAccessibilityDelegate(accessibilityDelegate);
706         }
707     }
708 
setWallpaperVisible(boolean isVisible)709     public void setWallpaperVisible(boolean isVisible) {
710         if (mContext.isPhoneMode()) {
711             mTaskbarTransitions.setWallpaperVisibility(isVisible);
712         }
713     }
714 
onTransitionModeUpdated(int barMode, boolean checkBarModes)715     public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
716         mTransitionMode = barMode;
717         if (checkBarModes) {
718             checkNavBarModes();
719         }
720     }
721 
checkNavBarModes()722     public void checkNavBarModes() {
723         if (mContext.isPhoneMode()) {
724             boolean isBarHidden = (mSysuiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
725             mTaskbarTransitions.transitionTo(mTransitionMode, !isBarHidden);
726         }
727     }
728 
finishBarAnimations()729     public void finishBarAnimations() {
730         if (mContext.isPhoneMode()) {
731             mTaskbarTransitions.finishAnimations();
732         }
733     }
734 
touchAutoDim(boolean reset)735     public void touchAutoDim(boolean reset) {
736         if (mContext.isPhoneMode()) {
737             mTaskbarTransitions.setAutoDim(false);
738             mHandler.removeCallbacks(mAutoDim);
739             if (reset) {
740                 mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
741             }
742         }
743     }
744 
transitionTo(@arTransitions.TransitionMode int barMode, boolean animate)745     public void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) {
746         if (mContext.isPhoneMode()) {
747             mTaskbarTransitions.transitionTo(barMode, animate);
748         }
749     }
750 
751     /** Use to set the translationY for the all nav+contextual buttons */
getTaskbarNavButtonTranslationY()752     public AnimatedFloat getTaskbarNavButtonTranslationY() {
753         return mTaskbarNavButtonTranslationY;
754     }
755 
756     /** Use to set the translationY for the all nav+contextual buttons when in Launcher */
getTaskbarNavButtonTranslationYForInAppDisplay()757     public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
758         return mTaskbarNavButtonTranslationYForInAppDisplay;
759     }
760 
761     /** Use to set the dark intensity for the all nav+contextual buttons */
getTaskbarNavButtonDarkIntensity()762     public AnimatedFloat getTaskbarNavButtonDarkIntensity() {
763         return mTaskbarNavButtonDarkIntensity;
764     }
765 
766     /** Use to override the nav button color with {@link #mOnBackgroundIconColor}. */
getOnTaskbarBackgroundNavButtonColorOverride()767     public AnimatedFloat getOnTaskbarBackgroundNavButtonColorOverride() {
768         return mOnTaskbarBackgroundNavButtonColorOverride;
769     }
770 
771     /**
772      * Does not call {@link #applyState()}. Don't forget to!
773      */
updateStateForFlag(int flag, boolean enabled)774     private void updateStateForFlag(int flag, boolean enabled) {
775         if (enabled) {
776             mState |= flag;
777         } else {
778             mState &= ~flag;
779         }
780     }
781 
applyState()782     private void applyState() {
783         int count = mPropertyHolders.size();
784         for (int i = 0; i < count; i++) {
785             mPropertyHolders.get(i).setState(mState, mContext.isGestureNav());
786         }
787     }
788 
updateNavButtonInAppDisplayProgressForSysui()789     private void updateNavButtonInAppDisplayProgressForSysui() {
790         TaskbarUIController uiController = mControllers.uiController;
791         if (uiController instanceof LauncherTaskbarUIController) {
792             ((LauncherTaskbarUIController) uiController).onTaskbarInAppDisplayProgressUpdate(
793                     mNavButtonInAppDisplayProgressForSysui.value, SYSUI_SURFACE_PROGRESS_INDEX);
794         }
795     }
796 
797     /**
798      * Sets the translationY of the nav buttons based on the current device state.
799      */
updateNavButtonTranslationY()800     public void updateNavButtonTranslationY() {
801         if (mContext.isPhoneButtonNavMode()) {
802             return;
803         }
804         final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
805         final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
806         TaskbarUIController uiController = mControllers.uiController;
807         final float inAppDisplayAdjustmentTranslationY =
808                 (uiController instanceof LauncherTaskbarUIController
809                         && ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
810                         ? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
811 
812         mLastSetNavButtonTranslationY = normalTranslationY
813                 + imeAdjustmentTranslationY
814                 + inAppDisplayAdjustmentTranslationY;
815         mNavButtonsView.setTranslationY(mLastSetNavButtonTranslationY);
816     }
817 
818     /**
819      * Sets Taskbar 3-button mode icon colors based on the
820      * {@link #mTaskbarNavButtonDarkIntensity} value piped in from Framework. For certain cases
821      * in large screen taskbar where there may be opaque surfaces, the selected SystemUI button
822      * colors are intentionally overridden.
823      * <p>
824      * This method is also called when any of the AnimatedFloat instances change.
825      */
updateNavButtonColor()826     private void updateNavButtonColor() {
827         final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
828         int taskbarNavButtonColor = getSysUiIconColorOnHome(argbEvaluator);
829         // Only phone mode foldable button colors should be identical to SysUI navbar colors.
830         if (!(ENABLE_TASKBAR_NAVBAR_UNIFICATION && mContext.isPhoneMode())) {
831             taskbarNavButtonColor = getTaskbarButtonColor(argbEvaluator, taskbarNavButtonColor);
832         }
833         applyButtonColors(taskbarNavButtonColor);
834     }
835 
836     /**
837      * Taskbar 3-button mode icon colors based on the
838      * {@link #mTaskbarNavButtonDarkIntensity} value piped in from Framework.
839      */
getSysUiIconColorOnHome(ArgbEvaluator argbEvaluator)840     private int getSysUiIconColorOnHome(ArgbEvaluator argbEvaluator) {
841         return (int) argbEvaluator.evaluate(getTaskbarNavButtonDarkIntensity().value,
842                 mLightIconColorOnWorkspace, mDarkIconColorOnWorkspace);
843     }
844 
845     /**
846      * If Taskbar background is opaque or slide in overlay is visible, the selected SystemUI button
847      * colors are intentionally overridden. The override can be disabled when
848      * {@link #mOnBackgroundNavButtonColorOverrideMultiplier} is {@code 0}.
849      */
getTaskbarButtonColor(ArgbEvaluator argbEvaluator, int sysUiIconColorOnHome)850     private int getTaskbarButtonColor(ArgbEvaluator argbEvaluator, int sysUiIconColorOnHome) {
851         final float sysUIColorOverride =
852                 mOnBackgroundNavButtonColorOverrideMultiplier.value * Math.max(
853                         mOnTaskbarBackgroundNavButtonColorOverride.value,
854                         mSlideInViewVisibleNavButtonColorOverride.value);
855         return (int) argbEvaluator.evaluate(sysUIColorOverride, sysUiIconColorOnHome,
856                 mOnBackgroundIconColor);
857     }
858 
859     /**
860      * Iteratively sets button colors for each button in {@link #mAllButtons}.
861      */
applyButtonColors(int iconColor)862     private void applyButtonColors(int iconColor) {
863         for (ImageView button : mAllButtons) {
864             button.setImageTintList(ColorStateList.valueOf(iconColor));
865             Drawable background = button.getBackground();
866             if (background instanceof KeyButtonRipple) {
867                 ((KeyButtonRipple) background).setDarkIntensity(
868                         getTaskbarNavButtonDarkIntensity().value);
869             }
870         }
871     }
872 
873     /**
874      * Updates Taskbar 3-Button icon colors as {@link #mTaskbarNavButtonDarkIntensity} changes.
875      */
onDarkIntensityChanged()876     private void onDarkIntensityChanged() {
877         updateNavButtonColor();
878         if (mContext.isPhoneMode()) {
879             mTaskbarTransitions.onDarkIntensityChanged(getTaskbarNavButtonDarkIntensity().value);
880         }
881     }
882 
addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id)883     protected ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
884             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
885         return addButton(drawableId, buttonType, parent, navButtonController, id,
886                 R.layout.taskbar_nav_button);
887     }
888 
889     @SuppressLint("ClickableViewAccessibility")
addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id, @LayoutRes int layoutId)890     private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
891             ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id,
892             @LayoutRes int layoutId) {
893         ImageView buttonView = addButton(parent, id, layoutId);
894         buttonView.setImageResource(drawableId);
895         buttonView.setContentDescription(parent.getContext().getString(
896                 navButtonController.getButtonContentDescription(buttonType)));
897         if (predictiveBackThreeButtonNav() && buttonType == BUTTON_BACK) {
898             // set up special touch listener for back button to support predictive back
899             setupBackButtonAccessibility(buttonView, null);
900             setBackButtonTouchListener(buttonView, navButtonController);
901             // Set this View clickable, so that NearestTouchFrame.java forwards closeby touches to
902             // this View
903             buttonView.setClickable(true);
904         } else {
905             buttonView.setOnClickListener(view ->
906                     navButtonController.onButtonClick(buttonType, view));
907             buttonView.setOnLongClickListener(view ->
908                     navButtonController.onButtonLongClick(buttonType, view));
909             buttonView.setOnTouchListener((v, event) -> {
910                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
911                     buttonView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
912                 }
913                 return false;
914             });
915         }
916         return buttonView;
917     }
918 
setupBackButtonAccessibility(View backButton, AccessibilityDelegate accessibilityDelegate)919     private void setupBackButtonAccessibility(View backButton,
920             AccessibilityDelegate accessibilityDelegate) {
921         View.AccessibilityDelegate backButtonAccessibilityDelegate =
922                 new View.AccessibilityDelegate() {
923                     @Override
924                     public boolean performAccessibilityAction(View host, int action, Bundle args) {
925                         if (accessibilityDelegate != null) {
926                             accessibilityDelegate.performAccessibilityAction(host, action, args);
927                         }
928                         if (action == AccessibilityNodeInfo.ACTION_CLICK) {
929                             mControllers.navButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN,
930                                     /*cancelled*/ false);
931                             mControllers.navButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP,
932                                     /*cancelled*/ false);
933                             return true;
934                         }
935                         return super.performAccessibilityAction(host, action, args);
936                     }
937                 };
938         backButton.setAccessibilityDelegate(backButtonAccessibilityDelegate);
939     }
940 
setBackButtonTouchListener(View buttonView, TaskbarNavButtonController navButtonController)941     private void setBackButtonTouchListener(View buttonView,
942             TaskbarNavButtonController navButtonController) {
943         final RectF rect = new RectF();
944         final AtomicBoolean hasSentDownEvent = new AtomicBoolean(false);
945         final Runnable longPressTimeout = () -> {
946             navButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, /*cancelled*/ false);
947             hasSentDownEvent.set(true);
948         };
949         buttonView.setOnTouchListener((v, event) -> {
950             int motionEventAction = event.getAction();
951             if (motionEventAction == MotionEvent.ACTION_DOWN) {
952                 hasSentDownEvent.set(false);
953                 mHandler.postDelayed(longPressTimeout, PREDICTIVE_BACK_TIMEOUT_MS);
954                 rect.set(0, 0, v.getWidth(), v.getHeight());
955                 buttonView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
956             }
957             boolean isCancelled = motionEventAction == MotionEvent.ACTION_CANCEL
958                     || (!rect.contains(event.getX(), event.getY())
959                     && (motionEventAction == MotionEvent.ACTION_MOVE
960                     || motionEventAction == MotionEvent.ACTION_UP));
961             if (motionEventAction != MotionEvent.ACTION_UP && !isCancelled) {
962                 // return early. we don't care about any other cases than UP or CANCEL from here on
963                 return false;
964             }
965             mHandler.removeCallbacks(longPressTimeout);
966             if (!hasSentDownEvent.get()) {
967                 if (isCancelled) {
968                     // if it is cancelled and ACTION_DOWN has not been sent yet, return early and
969                     // don't send anything to sysui.
970                     return false;
971                 }
972                 navButtonController.sendBackKeyEvent(KeyEvent.ACTION_DOWN, isCancelled);
973             }
974             navButtonController.sendBackKeyEvent(KeyEvent.ACTION_UP, isCancelled);
975             if (motionEventAction == MotionEvent.ACTION_UP && !isCancelled) {
976                 buttonView.performClick();
977             }
978             return false;
979         });
980         buttonView.setOnLongClickListener((view) ->  {
981             navButtonController.onButtonLongClick(BUTTON_BACK, view);
982             return false;
983         });
984     }
985 
addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId)986     private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) {
987         ImageView buttonView = (ImageView) mContext.getLayoutInflater()
988                 .inflate(layoutId, parent, false);
989         buttonView.setId(id);
990         parent.addView(buttonView);
991         mAllButtons.add(buttonView);
992         return buttonView;
993     }
994 
isEventOverAnyItem(MotionEvent ev)995     public boolean isEventOverAnyItem(MotionEvent ev) {
996         return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
997     }
998 
onConfigurationChanged(@onfig int configChanges)999     public void onConfigurationChanged(@Config int configChanges) {
1000         if (mFloatingRotationButton != null) {
1001             mFloatingRotationButton.onConfigurationChanged(configChanges);
1002         }
1003         if (!mContext.isUserSetupComplete()) {
1004             handleSetupUi();
1005         }
1006         updateButtonLayoutSpacing();
1007     }
1008 
handleSetupUi()1009     private void handleSetupUi() {
1010         // Setup wizard handles the UI when the expressive theme is enabled.
1011         if (mIsExpressiveThemeEnabled) {
1012             return;
1013         }
1014         // Since setup wizard only has back button enabled, it looks strange to be
1015         // end-aligned, so start-align instead.
1016         FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
1017                 mNavButtonContainer.getLayoutParams();
1018         FrameLayout.LayoutParams navButtonsViewLayoutParams = (FrameLayout.LayoutParams)
1019                 mNavButtonsView.getLayoutParams();
1020         Resources resources = mContext.getResources();
1021         DeviceProfile deviceProfile = mContext.getDeviceProfile();
1022 
1023         navButtonsLayoutParams.setMarginEnd(0);
1024         navButtonsLayoutParams.gravity = Gravity.START;
1025         mControllers.taskbarActivityContext.setTaskbarWindowSize(
1026                 mControllers.taskbarActivityContext.getSetupWindowSize());
1027 
1028         // If SUW is on a large screen device that is landscape (or has a square aspect
1029         // ratio) the back button has to be placed accordingly
1030         if ((deviceProfile.isTablet && deviceProfile.isLandscape)
1031                 || (deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
1032                 && deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND)) {
1033             navButtonsLayoutParams.setMarginStart(
1034                     resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin));
1035             navButtonsViewLayoutParams.bottomMargin = resources.getDimensionPixelSize(
1036                     R.dimen.taskbar_back_button_suw_bottom_margin);
1037             navButtonsLayoutParams.height = resources.getDimensionPixelSize(
1038                     R.dimen.taskbar_back_button_suw_height);
1039         } else {
1040             int phoneOrPortraitSetupMargin = resources.getDimensionPixelSize(
1041                     R.dimen.taskbar_contextual_button_suw_margin);
1042             navButtonsLayoutParams.setMarginStart(phoneOrPortraitSetupMargin);
1043             navButtonsLayoutParams.bottomMargin = !deviceProfile.isLandscape
1044                     ? 0
1045                     : phoneOrPortraitSetupMargin - (resources.getDimensionPixelSize(
1046                             R.dimen.taskbar_nav_buttons_size) / 2);
1047             navButtonsViewLayoutParams.height = resources.getDimensionPixelSize(
1048                     R.dimen.taskbar_contextual_button_suw_height);
1049         }
1050         mNavButtonsView.setLayoutParams(navButtonsViewLayoutParams);
1051         mNavButtonContainer.setLayoutParams(navButtonsLayoutParams);
1052     }
1053 
1054     /**
1055      * Adds the correct spacing to 3 button nav container depending on if device is in kids mode,
1056      * setup wizard, or normal 3 button nav.
1057      */
updateButtonLayoutSpacing()1058     private void updateButtonLayoutSpacing() {
1059         boolean isThreeButtonNav = mContext.isThreeButtonNav();
1060 
1061         DeviceProfile dp = mContext.getDeviceProfile();
1062         Resources res = mContext.getResources();
1063         boolean isInSetup = !mContext.isUserSetupComplete();
1064         // TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
1065         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
1066 
1067         if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
1068             NavButtonLayoutter navButtonLayoutter =
1069                     NavButtonLayoutFactory.Companion.getUiLayoutter(
1070                             dp, mNavButtonsView, mImeSwitcherButton,
1071                             mA11yButton, mSpace, res, isInKidsMode, isInSetup, isThreeButtonNav,
1072                             mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
1073             navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
1074             updateButtonsBackground();
1075             updateNavButtonColor();
1076             return;
1077         }
1078 
1079         if (isInSetup) {
1080             handleSetupUi();
1081         } else if (isInKidsMode) {
1082             int iconSize = res.getDimensionPixelSize(
1083                     R.dimen.taskbar_icon_size_kids);
1084             int buttonWidth = res.getDimensionPixelSize(
1085                     R.dimen.taskbar_nav_buttons_width_kids);
1086             int buttonHeight = res.getDimensionPixelSize(
1087                     R.dimen.taskbar_nav_buttons_height_kids);
1088             int buttonRadius = res.getDimensionPixelSize(
1089                     R.dimen.taskbar_nav_buttons_corner_radius_kids);
1090             int paddingleft = (buttonWidth - iconSize) / 2;
1091             int paddingRight = paddingleft;
1092             int paddingTop = (buttonHeight - iconSize) / 2;
1093             int paddingBottom = paddingTop;
1094 
1095             // Update icons
1096             final RotateDrawable rotateDrawable = new RotateDrawable();
1097             rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back_kids));
1098             rotateDrawable.setFromDegrees(0f);
1099             rotateDrawable.setToDegrees(-90f);
1100             mBackButton.setImageDrawable(rotateDrawable);
1101             mBackButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
1102             mBackButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
1103 
1104             mHomeButton.setImageDrawable(
1105                     mHomeButton.getContext().getDrawable(R.drawable.ic_sysbar_home_kids));
1106             mHomeButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
1107             mHomeButton.setPadding(paddingleft, paddingTop, paddingRight, paddingBottom);
1108 
1109             // Home button layout
1110             LinearLayout.LayoutParams homeLayoutparams = new LinearLayout.LayoutParams(
1111                     buttonWidth,
1112                     buttonHeight
1113             );
1114             int homeButtonLeftMargin = res.getDimensionPixelSize(
1115                     R.dimen.taskbar_home_button_left_margin_kids);
1116             homeLayoutparams.setMargins(homeButtonLeftMargin, 0, 0, 0);
1117             mHomeButton.setLayoutParams(homeLayoutparams);
1118 
1119             // Back button layout
1120             LinearLayout.LayoutParams backLayoutParams = new LinearLayout.LayoutParams(
1121                     buttonWidth,
1122                     buttonHeight
1123             );
1124             int backButtonLeftMargin = res.getDimensionPixelSize(
1125                     R.dimen.taskbar_back_button_left_margin_kids);
1126             backLayoutParams.setMargins(backButtonLeftMargin, 0, 0, 0);
1127             mBackButton.setLayoutParams(backLayoutParams);
1128 
1129             // Button backgrounds
1130             int whiteWith10PctAlpha = Color.argb(0.1f, 1, 1, 1);
1131             PaintDrawable buttonBackground = new PaintDrawable(whiteWith10PctAlpha);
1132             buttonBackground.setCornerRadius(buttonRadius);
1133             mHomeButton.setBackground(buttonBackground);
1134             mBackButton.setBackground(buttonBackground);
1135 
1136             // Update alignment within taskbar
1137             FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
1138                     mNavButtonContainer.getLayoutParams();
1139             navButtonsLayoutParams.setMarginStart(
1140                     navButtonsLayoutParams.getMarginEnd() / 2);
1141             navButtonsLayoutParams.setMarginEnd(navButtonsLayoutParams.getMarginStart());
1142             navButtonsLayoutParams.gravity = Gravity.CENTER;
1143             mNavButtonContainer.requestLayout();
1144 
1145             mHomeButton.setOnLongClickListener(null);
1146         } else if (isThreeButtonNav) {
1147             final RotateDrawable rotateDrawable = new RotateDrawable();
1148             rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back));
1149             rotateDrawable.setFromDegrees(0f);
1150             rotateDrawable.setToDegrees(Utilities.isRtl(mContext.getResources()) ? 90f : -90f);
1151             mBackButton.setImageDrawable(rotateDrawable);
1152 
1153             // Setup normal 3 button
1154             // Add spacing after the end of the last nav button
1155             FrameLayout.LayoutParams navButtonParams =
1156                     (FrameLayout.LayoutParams) mNavButtonContainer.getLayoutParams();
1157             navButtonParams.gravity = Gravity.END;
1158             navButtonParams.width = FrameLayout.LayoutParams.WRAP_CONTENT;
1159             navButtonParams.height = MATCH_PARENT;
1160 
1161             int navMarginEnd = (int) res.getDimension(dp.inv.inlineNavButtonsEndSpacing);
1162             int contextualWidth = mEndContextualContainer.getWidth();
1163             // If contextual buttons are showing, we check if the end margin is enough for the
1164             // contextual button to be showing - if not, move the nav buttons over a smidge
1165             if (isA11yButtonPersistent() && navMarginEnd < contextualWidth) {
1166                 // Additional spacing, eat up half of space between last icon and nav button
1167                 navMarginEnd += res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing) / 2;
1168             }
1169             navButtonParams.setMarginEnd(navMarginEnd);
1170             mNavButtonContainer.setLayoutParams(navButtonParams);
1171 
1172             // Add the spaces in between the nav buttons
1173             int spaceInBetween = res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween);
1174             for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
1175                 View navButton = mNavButtonContainer.getChildAt(i);
1176                 LinearLayout.LayoutParams buttonLayoutParams =
1177                         (LinearLayout.LayoutParams) navButton.getLayoutParams();
1178                 buttonLayoutParams.weight = 0;
1179                 if (i == 0) {
1180                     buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
1181                 } else if (i == mNavButtonContainer.getChildCount() - 1) {
1182                     buttonLayoutParams.setMarginStart(spaceInBetween / 2);
1183                 } else {
1184                     buttonLayoutParams.setMarginStart(spaceInBetween / 2);
1185                     buttonLayoutParams.setMarginEnd(spaceInBetween / 2);
1186                 }
1187             }
1188         }
1189     }
1190 
updateButtonsBackground()1191     private void updateButtonsBackground() {
1192         boolean clipped = !mContext.isPhoneButtonNavMode();
1193         mNavButtonContainer.setClipToPadding(clipped);
1194         mNavButtonContainer.setClipChildren(clipped);
1195         mNavButtonsView.setClipToPadding(clipped);
1196         mNavButtonsView.setClipChildren(clipped);
1197 
1198         for (ImageView button : mAllButtons) {
1199             updateButtonBackground(button, mContext.isPhoneButtonNavMode());
1200         }
1201     }
1202 
updateButtonBackground(View view, boolean isPhoneButtonNavMode)1203     private static void updateButtonBackground(View view, boolean isPhoneButtonNavMode) {
1204         if (isPhoneButtonNavMode) {
1205             view.setBackground(new KeyButtonRipple(view.getContext(), view,
1206                     R.dimen.key_button_ripple_max_width));
1207         } else {
1208             view.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
1209         }
1210     }
1211 
onDestroy()1212     public void onDestroy() {
1213         mPropertyHolders.clear();
1214         mControllers.rotationButtonController.unregisterListeners();
1215         if (mFloatingRotationButton != null) {
1216             mFloatingRotationButton.hide();
1217         }
1218 
1219         moveNavButtonsBackToTaskbarWindow();
1220         mNavButtonContainer.removeAllViews();
1221         mEndContextualContainer.removeAllViews();
1222         mStartContextualContainer.removeAllViews();
1223         mAllButtons.clear();
1224     }
1225 
1226     /**
1227      * Moves mNavButtonsView from TaskbarDragLayer to a placeholder BaseDragLayer on a new window.
1228      */
moveNavButtonsToNewWindow()1229     public void moveNavButtonsToNewWindow() {
1230         if (mAreNavButtonsInSeparateWindow) {
1231             return;
1232         }
1233 
1234         if (mIsImeRenderingNavButtons) {
1235             // IME is rendering the nav buttons, so we don't need to create a new layer for them.
1236             return;
1237         }
1238 
1239         mSeparateWindowParent.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
1240             @Override
1241             public void onViewAttachedToWindow(View view) {
1242                 mSeparateWindowParent.getViewTreeObserver().addOnComputeInternalInsetsListener(
1243                         mSeparateWindowInsetsComputer);
1244             }
1245 
1246             @Override
1247             public void onViewDetachedFromWindow(View view) {
1248                 mSeparateWindowParent.removeOnAttachStateChangeListener(this);
1249                 mSeparateWindowParent.getViewTreeObserver().removeOnComputeInternalInsetsListener(
1250                         mSeparateWindowInsetsComputer);
1251             }
1252         });
1253 
1254         mAreNavButtonsInSeparateWindow = true;
1255         mContext.getDragLayer().removeView(mNavButtonsView);
1256         mSeparateWindowParent.addView(mNavButtonsView);
1257         WindowManager.LayoutParams windowLayoutParams = mContext.createDefaultWindowLayoutParams(
1258                 TYPE_NAVIGATION_BAR_PANEL, NAV_BUTTONS_SEPARATE_WINDOW_TITLE);
1259         mContext.addWindowView(mSeparateWindowParent, windowLayoutParams);
1260 
1261     }
1262 
1263     /**
1264      * Moves mNavButtonsView from its temporary window and reattaches it to TaskbarDragLayer.
1265      */
moveNavButtonsBackToTaskbarWindow()1266     public void moveNavButtonsBackToTaskbarWindow() {
1267         if (!mAreNavButtonsInSeparateWindow) {
1268             return;
1269         }
1270 
1271         mAreNavButtonsInSeparateWindow = false;
1272         mContext.removeWindowView(mSeparateWindowParent);
1273         mSeparateWindowParent.removeView(mNavButtonsView);
1274         mContext.getDragLayer().addView(mNavButtonsView);
1275     }
1276 
onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo)1277     private void onComputeInsetsForSeparateWindow(ViewTreeObserver.InternalInsetsInfo insetsInfo) {
1278         addVisibleButtonsRegion(mSeparateWindowParent, insetsInfo.touchableRegion);
1279         insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
1280     }
1281 
1282     /**
1283      * Called whenever a new ui controller is set, and should update anything that depends on the
1284      * ui controller.
1285      */
onUiControllerChanged()1286     public void onUiControllerChanged() {
1287         updateNavButtonInAppDisplayProgressForSysui();
1288         updateNavButtonTranslationY();
1289     }
1290 
1291     @Override
dumpLogs(String prefix, PrintWriter pw)1292     public void dumpLogs(String prefix, PrintWriter pw) {
1293         pw.println(prefix + "NavbarButtonsViewController:");
1294 
1295         pw.println(prefix + "\tmState=" + getStateString(mState));
1296         pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
1297         pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
1298                 mSysuiStateFlags));
1299         pw.println(prefix + "\tLast set nav button translationY=" + mLastSetNavButtonTranslationY);
1300         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationY="
1301                 + mTaskbarNavButtonTranslationY.value);
1302         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForInAppDisplay="
1303                 + mTaskbarNavButtonTranslationYForInAppDisplay.value);
1304         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
1305                 + mTaskbarNavButtonTranslationYForIme.value);
1306         pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
1307                 + mTaskbarNavButtonDarkIntensity.value);
1308         pw.println(prefix + "\t\tmSlideInViewVisibleNavButtonColorOverride="
1309                 + mSlideInViewVisibleNavButtonColorOverride.value);
1310         pw.println(prefix + "\t\tmOnTaskbarBackgroundNavButtonColorOverride="
1311                 + mOnTaskbarBackgroundNavButtonColorOverride.value);
1312         pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
1313                 + mOnBackgroundNavButtonColorOverrideMultiplier.value);
1314 
1315         mNavButtonsView.dumpLogs(prefix + "\t", pw);
1316         if (mContext.isPhoneMode()) {
1317             mTaskbarTransitions.dumpLogs(prefix + "\t", pw);
1318         }
1319     }
1320 
getStateString(int flags)1321     private static String getStateString(int flags) {
1322         StringJoiner str = new StringJoiner("|");
1323         appendFlag(str, flags, FLAG_IME_SWITCHER_BUTTON_VISIBLE,
1324                 "FLAG_IME_SWITCHER_BUTTON_VISIBLE");
1325         appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
1326         appendFlag(str, flags, FLAG_BACK_DISMISS_IME, "FLAG_BACK_DISMISS_IME");
1327         appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
1328         appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
1329                 "FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
1330         appendFlag(str, flags, FLAG_KEYGUARD_VISIBLE, "FLAG_KEYGUARD_VISIBLE");
1331         appendFlag(str, flags, FLAG_KEYGUARD_OCCLUDED, "FLAG_KEYGUARD_OCCLUDED");
1332         appendFlag(str, flags, FLAG_DISABLE_HOME, "FLAG_DISABLE_HOME");
1333         appendFlag(str, flags, FLAG_DISABLE_RECENTS, "FLAG_DISABLE_RECENTS");
1334         appendFlag(str, flags, FLAG_DISABLE_BACK, "FLAG_DISABLE_BACK");
1335         appendFlag(str, flags, FLAG_NOTIFICATION_SHADE_EXPANDED,
1336                 "FLAG_NOTIFICATION_SHADE_EXPANDED");
1337         appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
1338         appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
1339                 "FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
1340         appendFlag(str, flags, FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING,
1341                 "FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING");
1342         return str.toString();
1343     }
1344 
getTouchController()1345     public TouchController getTouchController() {
1346         return mHitboxExtender;
1347     }
1348 
1349     /**
1350      * @param alignment 0 -> Taskbar, 1 -> Workspace
1351      */
updateTaskbarAlignment(float alignment)1352     public void updateTaskbarAlignment(float alignment) {
1353         mHitboxExtender.onAnimationProgressToOverview(alignment);
1354     }
1355 
1356     /** Adjusts navigation buttons layout accordingly to the bubble bar position. */
1357     @Override
onBubbleBarLocationUpdated(BubbleBarLocation location)1358     public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
1359         boolean locationUpdated = location != mBubbleBarTargetLocation;
1360         if (locationUpdated) {
1361             cancelExistingNavBarAnimation();
1362         } else {
1363             endExistingAnimation();
1364         }
1365         mNavButtonContainer.setTranslationX(getNavBarTranslationX(location));
1366         mBubbleBarTargetLocation = location;
1367     }
1368 
1369     /** Animates navigation buttons accordingly to the bubble bar position. */
1370     @Override
onBubbleBarLocationAnimated(BubbleBarLocation location)1371     public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
1372         if (location == mBubbleBarTargetLocation) return;
1373         cancelExistingNavBarAnimation();
1374         mBubbleBarTargetLocation = location;
1375         int finalX = getNavBarTranslationX(location);
1376         Animator teleportAnimator = BarsLocationAnimatorHelper
1377                 .getTeleportAnimatorForNavButtons(location, mNavButtonContainer, finalX);
1378         teleportAnimator.addListener(forEndCallback(() -> mNavBarLocationAnimator = null));
1379         mNavBarLocationAnimator = teleportAnimator;
1380         mNavBarLocationAnimator.start();
1381     }
1382 
endExistingAnimation()1383     private void endExistingAnimation() {
1384         if (mNavBarLocationAnimator != null) {
1385             mNavBarLocationAnimator.end();
1386             mNavBarLocationAnimator = null;
1387         }
1388     }
1389 
cancelExistingNavBarAnimation()1390     private void cancelExistingNavBarAnimation() {
1391         if (mNavBarLocationAnimator != null) {
1392             mNavBarLocationAnimator.cancel();
1393             mNavBarLocationAnimator = null;
1394         }
1395     }
1396 
getNavBarTranslationX(BubbleBarLocation location)1397     private int getNavBarTranslationX(BubbleBarLocation location) {
1398         boolean isNavbarOnRight = location.isOnLeft(mNavButtonsView.isLayoutRtl());
1399         DeviceProfile dp = mContext.getDeviceProfile();
1400         float navBarTargetStartX;
1401         if (!mContext.isUserSetupComplete()) {
1402             // Skip additional translations on the nav bar container while in SUW layout
1403             return 0;
1404         } else if (mContext.shouldStartAlignTaskbar()) {
1405             int navBarSpacing = dp.inlineNavButtonsEndSpacingPx;
1406             // If the taskbar is start aligned the navigation bar is aligned to the start or end of
1407             // the container, depending on the bubble bar location
1408             if (isNavbarOnRight) {
1409                 navBarTargetStartX = dp.widthPx - navBarSpacing - mNavButtonContainer.getWidth();
1410             } else {
1411                 navBarTargetStartX = navBarSpacing;
1412             }
1413         } else {
1414             // If the task bar is not start aligned, the navigation bar is located in the center
1415             // between the taskbar and screen edges, depending on the bubble bar location.
1416             float navbarWidth = mNavButtonContainer.getWidth();
1417             Rect taskbarBounds = mControllers.taskbarViewController
1418                     .getTransientTaskbarIconLayoutBoundsInParent();
1419             if (isNavbarOnRight) {
1420                 if (mNavButtonsView.isLayoutRtl()) {
1421                     float taskBarEnd = taskbarBounds.right;
1422                     navBarTargetStartX = (dp.widthPx + taskBarEnd - navbarWidth) / 2;
1423                 } else {
1424                     navBarTargetStartX = mNavButtonContainer.getLeft();
1425                 }
1426             } else {
1427                 float taskBarStart = taskbarBounds.left;
1428                 navBarTargetStartX = (taskBarStart - navbarWidth) / 2;
1429             }
1430         }
1431         return (int) navBarTargetStartX - mNavButtonContainer.getLeft();
1432     }
1433 
1434     /** Adjusts the navigation buttons layout position according to the bubble bar location. */
onLayoutsUpdated()1435     public void onLayoutsUpdated() {
1436         // no need to do anything if on phone, or if taskbar or navbar views were not placed on
1437         // screen.
1438         Rect transientTaskbarIconLayoutBoundsInParent = mControllers.taskbarViewController
1439                 .getTransientTaskbarIconLayoutBoundsInParent();
1440         if (mContext.getDeviceProfile().isPhone
1441                 || transientTaskbarIconLayoutBoundsInParent.isEmpty()
1442                 || mNavButtonsView.getWidth() == 0) {
1443             return;
1444         }
1445         if (mControllers.bubbleControllers.isPresent()) {
1446             if (mBubbleBarTargetLocation == null) {
1447                 // only set bubble bar location if it was not set before
1448                 mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
1449                         .bubbleBarViewController.getBubbleBarLocation();
1450             }
1451             onBubbleBarLocationUpdated(mBubbleBarTargetLocation);
1452         }
1453     }
1454 
1455     private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback {
1456         @Override
onVisibilityChanged(boolean isVisible)1457         public void onVisibilityChanged(boolean isVisible) {
1458             if (isVisible) {
1459                 mFloatingRotationButton.getCurrentView()
1460                         .getBoundsOnScreen(mFloatingRotationButtonBounds);
1461             } else {
1462                 mFloatingRotationButtonBounds.setEmpty();
1463             }
1464         }
1465     }
1466 
1467     private static class StatePropertyHolder {
1468 
1469         private final float mEnabledValue, mDisabledValue;
1470         private final ObjectAnimator mAnimator;
1471         private final IntPredicate mEnableCondition;
1472 
1473         private boolean mIsEnabled = true;
1474 
StatePropertyHolder(View view, IntPredicate enableCondition)1475         StatePropertyHolder(View view, IntPredicate enableCondition) {
1476             this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0);
1477             mAnimator.addListener(new AlphaUpdateListener(view));
1478         }
1479 
StatePropertyHolder(MultiProperty alphaProperty, IntPredicate enableCondition)1480         StatePropertyHolder(MultiProperty alphaProperty,
1481                 IntPredicate enableCondition) {
1482             this(alphaProperty, enableCondition, MULTI_PROPERTY_VALUE, 1, 0);
1483         }
1484 
StatePropertyHolder(AnimatedFloat animatedFloat, IntPredicate enableCondition)1485         StatePropertyHolder(AnimatedFloat animatedFloat, IntPredicate enableCondition) {
1486             this(animatedFloat, enableCondition, AnimatedFloat.VALUE, 1, 0);
1487         }
1488 
StatePropertyHolder(T target, IntPredicate enabledCondition, Property<T, Float> property, float enabledValue, float disabledValue)1489         <T> StatePropertyHolder(T target, IntPredicate enabledCondition,
1490                 Property<T, Float> property, float enabledValue, float disabledValue) {
1491             mEnableCondition = enabledCondition;
1492             mEnabledValue = enabledValue;
1493             mDisabledValue = disabledValue;
1494             mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue);
1495         }
1496 
setState(int flags, boolean skipAnimation)1497         public void setState(int flags, boolean skipAnimation) {
1498             boolean isEnabled = mEnableCondition.test(flags);
1499             if (mIsEnabled != isEnabled) {
1500                 mIsEnabled = isEnabled;
1501                 mAnimator.cancel();
1502                 mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue);
1503                 mAnimator.start();
1504                 if (skipAnimation) {
1505                     mAnimator.end();
1506                 }
1507             }
1508         }
1509 
endAnimation()1510         public void endAnimation() {
1511             if (mAnimator.isRunning()) {
1512                 mAnimator.end();
1513             }
1514         }
1515     }
1516 }
1517