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