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