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 com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 19 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y; 20 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; 21 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; 22 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; 23 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; 24 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_KEYGUARD; 25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; 26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; 27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED; 28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; 29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; 30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; 31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; 32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; 33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 35 36 import android.animation.ArgbEvaluator; 37 import android.animation.ObjectAnimator; 38 import android.annotation.DrawableRes; 39 import android.annotation.IdRes; 40 import android.annotation.LayoutRes; 41 import android.content.pm.ActivityInfo.Config; 42 import android.content.res.ColorStateList; 43 import android.content.res.Configuration; 44 import android.graphics.Rect; 45 import android.graphics.Region; 46 import android.graphics.Region.Op; 47 import android.graphics.drawable.AnimatedVectorDrawable; 48 import android.util.Property; 49 import android.view.Gravity; 50 import android.view.MotionEvent; 51 import android.view.View; 52 import android.view.View.OnClickListener; 53 import android.view.View.OnHoverListener; 54 import android.view.ViewGroup; 55 import android.widget.FrameLayout; 56 import android.widget.ImageView; 57 58 import com.android.launcher3.LauncherAnimUtils; 59 import com.android.launcher3.R; 60 import com.android.launcher3.Utilities; 61 import com.android.launcher3.anim.AlphaUpdateListener; 62 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; 63 import com.android.launcher3.util.MultiValueAlpha; 64 import com.android.quickstep.AnimatedFloat; 65 import com.android.systemui.shared.rotation.FloatingRotationButton; 66 import com.android.systemui.shared.rotation.RotationButton; 67 import com.android.systemui.shared.rotation.RotationButtonController; 68 69 import java.util.ArrayList; 70 import java.util.function.IntPredicate; 71 72 /** 73 * Controller for managing nav bar buttons in taskbar 74 */ 75 public class NavbarButtonsViewController { 76 77 private final Rect mTempRect = new Rect(); 78 79 private static final int FLAG_SWITCHER_SUPPORTED = 1 << 0; 80 private static final int FLAG_IME_VISIBLE = 1 << 1; 81 private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2; 82 private static final int FLAG_A11Y_VISIBLE = 1 << 3; 83 private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4; 84 private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5; 85 private static final int FLAG_KEYGUARD_OCCLUDED = 1 << 6; 86 private static final int FLAG_DISABLE_HOME = 1 << 7; 87 private static final int FLAG_DISABLE_RECENTS = 1 << 8; 88 private static final int FLAG_DISABLE_BACK = 1 << 9; 89 private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10; 90 private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 11; 91 92 private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE; 93 94 private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>(); 95 private final ArrayList<ImageView> mAllButtons = new ArrayList<>(); 96 private int mState; 97 98 private final TaskbarActivityContext mContext; 99 private final FrameLayout mNavButtonsView; 100 private final ViewGroup mNavButtonContainer; 101 // Used for IME+A11Y buttons 102 private final ViewGroup mEndContextualContainer; 103 private final ViewGroup mStartContextualContainer; 104 private final int mLightIconColor; 105 private final int mDarkIconColor; 106 107 private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat( 108 this::updateNavButtonTranslationY); 109 private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat( 110 this::updateNavButtonTranslationY); 111 // Only applies to mTaskbarNavButtonTranslationY 112 private final AnimatedFloat mNavButtonTranslationYMultiplier = new AnimatedFloat( 113 this::updateNavButtonTranslationY); 114 private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat( 115 this::updateNavButtonDarkIntensity); 116 private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat( 117 this::updateNavButtonDarkIntensity); 118 private final RotationButtonListener mRotationButtonListener = new RotationButtonListener(); 119 120 private final Rect mFloatingRotationButtonBounds = new Rect(); 121 122 // Initialized in init. 123 private TaskbarControllers mControllers; 124 private View mA11yButton; 125 private int mSysuiStateFlags; 126 private View mBackButton; 127 private FloatingRotationButton mFloatingRotationButton; 128 NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView)129 public NavbarButtonsViewController(TaskbarActivityContext context, FrameLayout navButtonsView) { 130 mContext = context; 131 mNavButtonsView = navButtonsView; 132 mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons); 133 mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons); 134 mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons); 135 136 mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color); 137 mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color); 138 } 139 140 /** 141 * Initializes the controller 142 */ init(TaskbarControllers controllers)143 public void init(TaskbarControllers controllers) { 144 mControllers = controllers; 145 mNavButtonsView.getLayoutParams().height = mContext.getDeviceProfile().taskbarSize; 146 mNavButtonTranslationYMultiplier.value = 1; 147 148 boolean isThreeButtonNav = mContext.isThreeButtonNav(); 149 // IME switcher 150 View imeSwitcherButton = addButton(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH, 151 isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer, 152 mControllers.navButtonController, R.id.ime_switcher); 153 mPropertyHolders.add(new StatePropertyHolder(imeSwitcherButton, 154 flags -> ((flags & MASK_IME_SWITCHER_VISIBLE) == MASK_IME_SWITCHER_VISIBLE) 155 && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0))); 156 157 mPropertyHolders.add(new StatePropertyHolder( 158 mControllers.taskbarViewController.getTaskbarIconAlpha() 159 .getProperty(ALPHA_INDEX_KEYGUARD), 160 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 161 && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0, 162 MultiValueAlpha.VALUE, 1, 0)); 163 164 mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController 165 .getKeyguardBgTaskbar(), 166 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, AnimatedFloat.VALUE, 1, 0)); 167 168 // Force nav buttons (specifically back button) to be visible during setup wizard. 169 boolean isInSetup = !mContext.isUserSetupComplete(); 170 boolean alwaysShowButtons = isThreeButtonNav || isInSetup; 171 172 // Make sure to remove nav bar buttons translation when notification shade is expanded or 173 // IME is showing (add separate translation for IME). 174 int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE; 175 mPropertyHolders.add(new StatePropertyHolder(mNavButtonTranslationYMultiplier, 176 flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE, 177 0, 1)); 178 // Center nav buttons in new height for IME. 179 float transForIme = (mContext.getDeviceProfile().taskbarSize 180 - mContext.getTaskbarHeightForIme()) / 2f; 181 // For gesture nav, nav buttons only show for IME anyway so keep them translated down. 182 float defaultButtonTransY = alwaysShowButtons ? 0 : transForIme; 183 mPropertyHolders.add(new StatePropertyHolder(mTaskbarNavButtonTranslationYForIme, 184 flags -> (flags & FLAG_IME_VISIBLE) != 0, AnimatedFloat.VALUE, transForIme, 185 defaultButtonTransY)); 186 187 if (alwaysShowButtons) { 188 initButtons(mNavButtonContainer, mEndContextualContainer, 189 mControllers.navButtonController); 190 191 if (isInSetup) { 192 // Since setup wizard only has back button enabled, it looks strange to be 193 // end-aligned, so start-align instead. 194 FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams) 195 mNavButtonContainer.getLayoutParams(); 196 navButtonsLayoutParams.setMarginStart(navButtonsLayoutParams.getMarginEnd()); 197 navButtonsLayoutParams.setMarginEnd(0); 198 navButtonsLayoutParams.gravity = Gravity.START; 199 mNavButtonContainer.requestLayout(); 200 201 // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set 202 // it based on dark theme for now. 203 int mode = mContext.getResources().getConfiguration().uiMode 204 & Configuration.UI_MODE_NIGHT_MASK; 205 boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES; 206 mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1); 207 } 208 209 // Animate taskbar background when any of these flags are enabled 210 int flagsToShowBg = FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE 211 | FLAG_NOTIFICATION_SHADE_EXPANDED; 212 mPropertyHolders.add(new StatePropertyHolder( 213 mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(), 214 flags -> (flags & flagsToShowBg) != 0, AnimatedFloat.VALUE, 1, 0)); 215 216 // Rotation button 217 RotationButton rotationButton = new RotationButtonImpl( 218 addButton(mEndContextualContainer, R.id.rotate_suggestion, 219 R.layout.taskbar_contextual_button)); 220 rotationButton.hide(); 221 mControllers.rotationButtonController.setRotationButton(rotationButton, null); 222 } else { 223 mFloatingRotationButton = new FloatingRotationButton(mContext, 224 R.string.accessibility_rotate_button, 225 R.layout.rotate_suggestion, 226 R.id.rotate_suggestion, 227 R.dimen.floating_rotation_button_min_margin, 228 R.dimen.rounded_corner_content_padding, 229 R.dimen.floating_rotation_button_taskbar_left_margin, 230 R.dimen.floating_rotation_button_taskbar_bottom_margin, 231 R.dimen.floating_rotation_button_diameter, 232 R.dimen.key_button_ripple_max_width); 233 mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton, 234 mRotationButtonListener); 235 236 View imeDownButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK, 237 mStartContextualContainer, mControllers.navButtonController, R.id.back); 238 imeDownButton.setRotation(Utilities.isRtl(mContext.getResources()) ? 90 : -90); 239 // Rotate when Ime visible 240 mPropertyHolders.add(new StatePropertyHolder(imeDownButton, 241 flags -> (flags & FLAG_IME_VISIBLE) != 0)); 242 } 243 244 applyState(); 245 mPropertyHolders.forEach(StatePropertyHolder::endAnimation); 246 } 247 initButtons(ViewGroup navContainer, ViewGroup endContainer, TaskbarNavButtonController navButtonController)248 private void initButtons(ViewGroup navContainer, ViewGroup endContainer, 249 TaskbarNavButtonController navButtonController) { 250 251 mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK, 252 mNavButtonContainer, mControllers.navButtonController, R.id.back); 253 mPropertyHolders.add(new StatePropertyHolder(mBackButton, 254 flags -> { 255 // Show only if not disabled, and if not on the keyguard or otherwise only when 256 // the bouncer or a lockscreen app is showing above the keyguard 257 boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 || 258 (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 || 259 (flags & FLAG_KEYGUARD_OCCLUDED) != 0; 260 return (flags & FLAG_DISABLE_BACK) == 0 261 && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard); 262 })); 263 boolean isRtl = Utilities.isRtl(mContext.getResources()); 264 mPropertyHolders.add(new StatePropertyHolder( 265 mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION, 266 isRtl ? 90 : -90, 0)); 267 // Translate back button to be at end/start of other buttons for keyguard 268 int navButtonSize = mContext.getResources().getDimensionPixelSize( 269 R.dimen.taskbar_nav_buttons_size); 270 mPropertyHolders.add(new StatePropertyHolder( 271 mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 272 || (flags & FLAG_KEYGUARD_VISIBLE) != 0, 273 VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0)); 274 275 276 // home and recents buttons 277 View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer, 278 navButtonController, R.id.home); 279 mPropertyHolders.add(new StatePropertyHolder(homeButton, 280 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && 281 (flags & FLAG_DISABLE_HOME) == 0)); 282 View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS, 283 navContainer, navButtonController, R.id.recent_apps); 284 mPropertyHolders.add(new StatePropertyHolder(recentsButton, 285 flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && 286 (flags & FLAG_DISABLE_RECENTS) == 0)); 287 288 // A11y button 289 mA11yButton = addButton(R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y, 290 endContainer, navButtonController, R.id.accessibility_button, 291 R.layout.taskbar_contextual_button); 292 mPropertyHolders.add(new StatePropertyHolder(mA11yButton, 293 flags -> (flags & FLAG_A11Y_VISIBLE) != 0 294 && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)); 295 } 296 parseSystemUiFlags(int sysUiStateFlags)297 private void parseSystemUiFlags(int sysUiStateFlags) { 298 mSysuiStateFlags = sysUiStateFlags; 299 boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0; 300 boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0; 301 boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; 302 boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0; 303 boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0; 304 boolean isBackDisabled = (sysUiStateFlags & SYSUI_STATE_BACK_DISABLED) != 0; 305 int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED 306 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 307 boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0; 308 boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0; 309 310 // TODO(b/202218289) we're getting IME as not visible on lockscreen from system 311 updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible); 312 updateStateForFlag(FLAG_SWITCHER_SUPPORTED, isImeSwitcherShowing); 313 updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible); 314 updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled); 315 updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled); 316 updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled); 317 updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded); 318 updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive); 319 320 if (mA11yButton != null) { 321 // Only used in 3 button 322 boolean a11yLongClickable = 323 (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; 324 mA11yButton.setLongClickable(a11yLongClickable); 325 } 326 } 327 updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim)328 public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) { 329 if (systemUiStateFlags == mSysuiStateFlags) { 330 return; 331 } 332 parseSystemUiFlags(systemUiStateFlags); 333 applyState(); 334 if (skipAnim) { 335 mPropertyHolders.forEach(StatePropertyHolder::endAnimation); 336 } 337 } 338 339 /** 340 * Should be called when we need to show back button for bouncer 341 */ setBackForBouncer(boolean isBouncerVisible)342 public void setBackForBouncer(boolean isBouncerVisible) { 343 updateStateForFlag(FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE, isBouncerVisible); 344 applyState(); 345 } 346 347 /** 348 * Slightly misnamed, but should be called when keyguard OR AOD is showing. 349 * We consider keyguardVisible when it's showing bouncer OR is occlucded by another app 350 */ setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded)351 public void setKeyguardVisible(boolean isKeyguardVisible, boolean isKeyguardOccluded) { 352 updateStateForFlag(FLAG_KEYGUARD_VISIBLE, isKeyguardVisible || isKeyguardOccluded); 353 updateStateForFlag(FLAG_KEYGUARD_OCCLUDED, isKeyguardOccluded); 354 applyState(); 355 } 356 357 /** 358 * Returns true if IME bar is visible 359 */ isImeVisible()360 public boolean isImeVisible() { 361 return (mState & FLAG_IME_VISIBLE) != 0; 362 } 363 364 /** 365 * Returns true if the home button is disabled 366 */ isHomeDisabled()367 public boolean isHomeDisabled() { 368 return (mState & FLAG_DISABLE_HOME) != 0; 369 } 370 371 /** 372 * Returns true if the recents (overview) button is disabled 373 */ isRecentsDisabled()374 public boolean isRecentsDisabled() { 375 return (mState & FLAG_DISABLE_RECENTS) != 0; 376 } 377 378 /** 379 * Adds the bounds corresponding to all visible buttons to provided region 380 */ addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion)381 public void addVisibleButtonsRegion(TaskbarDragLayer parent, Region outRegion) { 382 int count = mAllButtons.size(); 383 for (int i = 0; i < count; i++) { 384 View button = mAllButtons.get(i); 385 if (button.getVisibility() == View.VISIBLE) { 386 parent.getDescendantRectRelativeToSelf(button, mTempRect); 387 outRegion.op(mTempRect, Op.UNION); 388 } 389 } 390 } 391 392 /** Use to set the translationY for the all nav+contextual buttons */ getTaskbarNavButtonTranslationY()393 public AnimatedFloat getTaskbarNavButtonTranslationY() { 394 return mTaskbarNavButtonTranslationY; 395 } 396 397 /** Use to set the dark intensity for the all nav+contextual buttons */ getTaskbarNavButtonDarkIntensity()398 public AnimatedFloat getTaskbarNavButtonDarkIntensity() { 399 return mTaskbarNavButtonDarkIntensity; 400 } 401 402 /** Use to determine whether to use the dark intensity requested by the underlying app */ getNavButtonDarkIntensityMultiplier()403 public AnimatedFloat getNavButtonDarkIntensityMultiplier() { 404 return mNavButtonDarkIntensityMultiplier; 405 } 406 407 /** 408 * Does not call {@link #applyState()}. Don't forget to! 409 */ updateStateForFlag(int flag, boolean enabled)410 private void updateStateForFlag(int flag, boolean enabled) { 411 if (enabled) { 412 mState |= flag; 413 } else { 414 mState &= ~flag; 415 } 416 } 417 applyState()418 private void applyState() { 419 int count = mPropertyHolders.size(); 420 for (int i = 0; i < count; i++) { 421 mPropertyHolders.get(i).setState(mState); 422 } 423 } 424 updateNavButtonTranslationY()425 private void updateNavButtonTranslationY() { 426 float normalTranslationY = mTaskbarNavButtonTranslationY.value 427 * mNavButtonTranslationYMultiplier.value; 428 float otherTranslationY = mTaskbarNavButtonTranslationYForIme.value; 429 mNavButtonsView.setTranslationY(normalTranslationY + otherTranslationY); 430 } 431 updateNavButtonDarkIntensity()432 private void updateNavButtonDarkIntensity() { 433 float darkIntensity = mTaskbarNavButtonDarkIntensity.value 434 * mNavButtonDarkIntensityMultiplier.value; 435 int iconColor = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, mLightIconColor, 436 mDarkIconColor); 437 for (ImageView button : mAllButtons) { 438 button.setImageTintList(ColorStateList.valueOf(iconColor)); 439 } 440 } 441 addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id)442 private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType, 443 ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) { 444 return addButton(drawableId, buttonType, parent, navButtonController, id, 445 R.layout.taskbar_nav_button); 446 } 447 addButton(@rawableRes int drawableId, @TaskbarButton int buttonType, ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id, @LayoutRes int layoutId)448 private ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType, 449 ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id, 450 @LayoutRes int layoutId) { 451 ImageView buttonView = addButton(parent, id, layoutId); 452 buttonView.setImageResource(drawableId); 453 buttonView.setOnClickListener(view -> navButtonController.onButtonClick(buttonType)); 454 buttonView.setOnLongClickListener(view -> 455 navButtonController.onButtonLongClick(buttonType)); 456 return buttonView; 457 } 458 addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId)459 private ImageView addButton(ViewGroup parent, @IdRes int id, @LayoutRes int layoutId) { 460 ImageView buttonView = (ImageView) mContext.getLayoutInflater() 461 .inflate(layoutId, parent, false); 462 buttonView.setId(id); 463 parent.addView(buttonView); 464 mAllButtons.add(buttonView); 465 return buttonView; 466 } 467 isEventOverAnyItem(MotionEvent ev)468 public boolean isEventOverAnyItem(MotionEvent ev) { 469 return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY()); 470 } 471 onConfigurationChanged(@onfig int configChanges)472 public void onConfigurationChanged(@Config int configChanges) { 473 if (mFloatingRotationButton != null) { 474 mFloatingRotationButton.onConfigurationChanged(configChanges); 475 } 476 } 477 onDestroy()478 public void onDestroy() { 479 mPropertyHolders.clear(); 480 mControllers.rotationButtonController.unregisterListeners(); 481 if (mFloatingRotationButton != null) { 482 mFloatingRotationButton.hide(); 483 } 484 } 485 486 private class RotationButtonListener implements RotationButton.RotationButtonUpdatesCallback { 487 @Override onVisibilityChanged(boolean isVisible)488 public void onVisibilityChanged(boolean isVisible) { 489 if (isVisible) { 490 mFloatingRotationButton.getCurrentView() 491 .getBoundsOnScreen(mFloatingRotationButtonBounds); 492 } else { 493 mFloatingRotationButtonBounds.setEmpty(); 494 } 495 } 496 } 497 498 private class RotationButtonImpl implements RotationButton { 499 500 private final ImageView mButton; 501 private AnimatedVectorDrawable mImageDrawable; 502 RotationButtonImpl(ImageView button)503 RotationButtonImpl(ImageView button) { 504 mButton = button; 505 } 506 507 @Override setRotationButtonController(RotationButtonController rotationButtonController)508 public void setRotationButtonController(RotationButtonController rotationButtonController) { 509 // TODO(b/187754252) UI polish, different icons based on light/dark context, etc 510 mImageDrawable = (AnimatedVectorDrawable) mButton.getContext() 511 .getDrawable(rotationButtonController.getIconResId()); 512 mButton.setImageDrawable(mImageDrawable); 513 mButton.setContentDescription(mButton.getResources() 514 .getString(R.string.accessibility_rotate_button)); 515 mImageDrawable.setCallback(mButton); 516 } 517 518 @Override getCurrentView()519 public View getCurrentView() { 520 return mButton; 521 } 522 523 @Override show()524 public boolean show() { 525 mButton.setVisibility(View.VISIBLE); 526 mState |= FLAG_ROTATION_BUTTON_VISIBLE; 527 applyState(); 528 return true; 529 } 530 531 @Override hide()532 public boolean hide() { 533 mButton.setVisibility(View.GONE); 534 mState &= ~FLAG_ROTATION_BUTTON_VISIBLE; 535 applyState(); 536 return true; 537 } 538 539 @Override isVisible()540 public boolean isVisible() { 541 return mButton.getVisibility() == View.VISIBLE; 542 } 543 544 @Override updateIcon(int lightIconColor, int darkIconColor)545 public void updateIcon(int lightIconColor, int darkIconColor) { 546 // TODO(b/187754252): UI Polish 547 } 548 549 @Override setOnClickListener(OnClickListener onClickListener)550 public void setOnClickListener(OnClickListener onClickListener) { 551 mButton.setOnClickListener(onClickListener); 552 } 553 554 @Override setOnHoverListener(OnHoverListener onHoverListener)555 public void setOnHoverListener(OnHoverListener onHoverListener) { 556 mButton.setOnHoverListener(onHoverListener); 557 } 558 559 @Override getImageDrawable()560 public AnimatedVectorDrawable getImageDrawable() { 561 return mImageDrawable; 562 } 563 564 @Override setDarkIntensity(float darkIntensity)565 public void setDarkIntensity(float darkIntensity) { 566 // TODO(b/187754252) UI polish 567 } 568 569 @Override acceptRotationProposal()570 public boolean acceptRotationProposal() { 571 return mButton.isAttachedToWindow(); 572 } 573 } 574 575 private static class StatePropertyHolder { 576 577 private final float mEnabledValue, mDisabledValue; 578 private final ObjectAnimator mAnimator; 579 private final IntPredicate mEnableCondition; 580 581 private boolean mIsEnabled = true; 582 StatePropertyHolder(View view, IntPredicate enableCondition)583 StatePropertyHolder(View view, IntPredicate enableCondition) { 584 this(view, enableCondition, LauncherAnimUtils.VIEW_ALPHA, 1, 0); 585 mAnimator.addListener(new AlphaUpdateListener(view)); 586 } 587 StatePropertyHolder(T target, IntPredicate enabledCondition, Property<T, Float> property, float enabledValue, float disabledValue)588 <T> StatePropertyHolder(T target, IntPredicate enabledCondition, 589 Property<T, Float> property, float enabledValue, float disabledValue) { 590 mEnableCondition = enabledCondition; 591 mEnabledValue = enabledValue; 592 mDisabledValue = disabledValue; 593 mAnimator = ObjectAnimator.ofFloat(target, property, enabledValue, disabledValue); 594 } 595 setState(int flags)596 public void setState(int flags) { 597 boolean isEnabled = mEnableCondition.test(flags); 598 if (mIsEnabled != isEnabled) { 599 mIsEnabled = isEnabled; 600 mAnimator.cancel(); 601 mAnimator.setFloatValues(mIsEnabled ? mEnabledValue : mDisabledValue); 602 mAnimator.start(); 603 } 604 } 605 endAnimation()606 public void endAnimation() { 607 if (mAnimator.isRunning()) { 608 mAnimator.end(); 609 } 610 } 611 } 612 } 613