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