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