1 /* 2 * Copyright 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 17 package com.android.systemui.shared.rotation; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 21 import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ObjectAnimator; 26 import android.annotation.ColorInt; 27 import android.annotation.DrawableRes; 28 import android.annotation.SuppressLint; 29 import android.app.StatusBarManager; 30 import android.content.ContentResolver; 31 import android.content.Context; 32 import android.graphics.drawable.AnimatedVectorDrawable; 33 import android.graphics.drawable.Drawable; 34 import android.os.Handler; 35 import android.os.Looper; 36 import android.os.RemoteException; 37 import android.provider.Settings; 38 import android.util.Log; 39 import android.view.HapticFeedbackConstants; 40 import android.view.IRotationWatcher; 41 import android.view.MotionEvent; 42 import android.view.Surface; 43 import android.view.View; 44 import android.view.WindowInsetsController; 45 import android.view.WindowManagerGlobal; 46 import android.view.accessibility.AccessibilityManager; 47 import android.view.animation.Interpolator; 48 import android.view.animation.LinearInterpolator; 49 50 import com.android.internal.logging.UiEvent; 51 import com.android.internal.logging.UiEventLogger; 52 import com.android.internal.logging.UiEventLoggerImpl; 53 import com.android.internal.view.RotationPolicy; 54 import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback; 55 import com.android.systemui.shared.recents.utilities.Utilities; 56 import com.android.systemui.shared.recents.utilities.ViewRippler; 57 import com.android.systemui.shared.system.ActivityManagerWrapper; 58 import com.android.systemui.shared.system.TaskStackChangeListener; 59 import com.android.systemui.shared.system.TaskStackChangeListeners; 60 61 import java.util.Optional; 62 import java.util.function.Consumer; 63 import java.util.function.Supplier; 64 65 /** 66 * Contains logic that deals with showing a rotate suggestion button with animation. 67 */ 68 public class RotationButtonController { 69 70 private static final String TAG = "StatusBar/RotationButtonController"; 71 private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; 72 private static final int NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS = 20000; 73 private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 74 75 private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3; 76 77 private final Context mContext; 78 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); 79 private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); 80 private final ViewRippler mViewRippler = new ViewRippler(); 81 private final Supplier<Integer> mWindowRotationProvider; 82 private RotationButton mRotationButton; 83 84 private boolean mIsRecentsAnimationRunning; 85 private boolean mHomeRotationEnabled; 86 private int mLastRotationSuggestion; 87 private boolean mPendingRotationSuggestion; 88 private boolean mHoveringRotationSuggestion; 89 private final AccessibilityManager mAccessibilityManager; 90 private final TaskStackListenerImpl mTaskStackListener; 91 private Consumer<Integer> mRotWatcherListener; 92 93 private boolean mListenersRegistered = false; 94 private boolean mIsNavigationBarShowing; 95 @SuppressLint("InlinedApi") 96 private @WindowInsetsController.Behavior 97 int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT; 98 private boolean mSkipOverrideUserLockPrefsOnce; 99 private final int mLightIconColor; 100 private final int mDarkIconColor; 101 102 @DrawableRes 103 private final int mIconCcwStart0ResId; 104 @DrawableRes 105 private final int mIconCcwStart90ResId; 106 @DrawableRes 107 private final int mIconCwStart0ResId; 108 @DrawableRes 109 private final int mIconCwStart90ResId; 110 111 @DrawableRes 112 private int mIconResId; 113 114 private final Runnable mRemoveRotationProposal = 115 () -> setRotateSuggestionButtonState(false /* visible */); 116 private final Runnable mCancelPendingRotationProposal = 117 () -> mPendingRotationSuggestion = false; 118 private Animator mRotateHideAnimator; 119 120 121 private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() { 122 @Override 123 public void onRotationChanged(final int rotation) { 124 // We need this to be scheduled as early as possible to beat the redrawing of 125 // window in response to the orientation change. 126 mMainThreadHandler.postAtFrontOfQueue(() -> { 127 // If the screen rotation changes while locked, potentially update lock to flow with 128 // new screen rotation and hide any showing suggestions. 129 if (isRotationLocked()) { 130 if (shouldOverrideUserLockPrefs(rotation)) { 131 setRotationLockedAtAngle(rotation); 132 } 133 setRotateSuggestionButtonState(false /* visible */, true /* forced */); 134 } 135 136 if (mRotWatcherListener != null) { 137 mRotWatcherListener.accept(rotation); 138 } 139 }); 140 } 141 }; 142 143 /** 144 * Determines if rotation suggestions disabled2 flag exists in flag 145 * 146 * @param disable2Flags see if rotation suggestion flag exists in this flag 147 * @return whether flag exists 148 */ hasDisable2RotateSuggestionFlag(int disable2Flags)149 public static boolean hasDisable2RotateSuggestionFlag(int disable2Flags) { 150 return (disable2Flags & StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS) != 0; 151 } 152 RotationButtonController(Context context, @ColorInt int lightIconColor, @ColorInt int darkIconColor, @DrawableRes int iconCcwStart0ResId, @DrawableRes int iconCcwStart90ResId, @DrawableRes int iconCwStart0ResId, @DrawableRes int iconCwStart90ResId, Supplier<Integer> windowRotationProvider)153 public RotationButtonController(Context context, 154 @ColorInt int lightIconColor, @ColorInt int darkIconColor, 155 @DrawableRes int iconCcwStart0ResId, 156 @DrawableRes int iconCcwStart90ResId, 157 @DrawableRes int iconCwStart0ResId, 158 @DrawableRes int iconCwStart90ResId, 159 Supplier<Integer> windowRotationProvider) { 160 161 mContext = context; 162 mLightIconColor = lightIconColor; 163 mDarkIconColor = darkIconColor; 164 165 mIconCcwStart0ResId = iconCcwStart0ResId; 166 mIconCcwStart90ResId = iconCcwStart90ResId; 167 mIconCwStart0ResId = iconCwStart0ResId; 168 mIconCwStart90ResId = iconCwStart90ResId; 169 mIconResId = mIconCcwStart90ResId; 170 171 mAccessibilityManager = AccessibilityManager.getInstance(context); 172 mTaskStackListener = new TaskStackListenerImpl(); 173 mWindowRotationProvider = windowRotationProvider; 174 } 175 setRotationButton(RotationButton rotationButton, RotationButtonUpdatesCallback updatesCallback)176 public void setRotationButton(RotationButton rotationButton, 177 RotationButtonUpdatesCallback updatesCallback) { 178 mRotationButton = rotationButton; 179 mRotationButton.setRotationButtonController(this); 180 mRotationButton.setOnClickListener(this::onRotateSuggestionClick); 181 mRotationButton.setOnHoverListener(this::onRotateSuggestionHover); 182 mRotationButton.setUpdatesCallback(updatesCallback); 183 } 184 getContext()185 public Context getContext() { 186 return mContext; 187 } 188 init()189 public void init() { 190 registerListeners(); 191 if (mContext.getDisplay().getDisplayId() != DEFAULT_DISPLAY) { 192 // Currently there is no accelerometer sensor on non-default display, disable fixed 193 // rotation for non-default display 194 onDisable2FlagChanged(StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS); 195 } 196 } 197 onDestroy()198 public void onDestroy() { 199 unregisterListeners(); 200 } 201 registerListeners()202 public void registerListeners() { 203 if (mListenersRegistered) { 204 return; 205 } 206 207 mListenersRegistered = true; 208 try { 209 WindowManagerGlobal.getWindowManagerService() 210 .watchRotation(mRotationWatcher, DEFAULT_DISPLAY); 211 } catch (IllegalArgumentException e) { 212 mListenersRegistered = false; 213 Log.w(TAG, "RegisterListeners for the display failed"); 214 } catch (RemoteException e) { 215 Log.e(TAG, "RegisterListeners caught a RemoteException", e); 216 return; 217 } 218 219 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 220 } 221 unregisterListeners()222 public void unregisterListeners() { 223 if (!mListenersRegistered) { 224 return; 225 } 226 227 mListenersRegistered = false; 228 try { 229 WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher); 230 } catch (RemoteException e) { 231 Log.e(TAG, "UnregisterListeners caught a RemoteException", e); 232 return; 233 } 234 235 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 236 } 237 setRotationCallback(Consumer<Integer> watcher)238 public void setRotationCallback(Consumer<Integer> watcher) { 239 mRotWatcherListener = watcher; 240 } 241 setRotationLockedAtAngle(int rotationSuggestion)242 public void setRotationLockedAtAngle(int rotationSuggestion) { 243 RotationPolicy.setRotationLockAtAngle(mContext, true, rotationSuggestion); 244 } 245 isRotationLocked()246 public boolean isRotationLocked() { 247 return RotationPolicy.isRotationLocked(mContext); 248 } 249 setRotateSuggestionButtonState(boolean visible)250 public void setRotateSuggestionButtonState(boolean visible) { 251 setRotateSuggestionButtonState(visible, false /* force */); 252 } 253 setRotateSuggestionButtonState(final boolean visible, final boolean force)254 void setRotateSuggestionButtonState(final boolean visible, final boolean force) { 255 // At any point the button can become invisible because an a11y service became active. 256 // Similarly, a call to make the button visible may be rejected because an a11y service is 257 // active. Must account for this. 258 // Rerun a show animation to indicate change but don't rerun a hide animation 259 if (!visible && !mRotationButton.isVisible()) return; 260 261 final View view = mRotationButton.getCurrentView(); 262 if (view == null) return; 263 264 final Drawable currentDrawable = mRotationButton.getImageDrawable(); 265 if (currentDrawable == null) return; 266 267 // Clear any pending suggestion flag as it has either been nullified or is being shown 268 mPendingRotationSuggestion = false; 269 mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal); 270 271 // Handle the visibility change and animation 272 if (visible) { // Appear and change (cannot force) 273 // Stop and clear any currently running hide animations 274 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { 275 mRotateHideAnimator.cancel(); 276 } 277 mRotateHideAnimator = null; 278 279 // Reset the alpha if any has changed due to hide animation 280 view.setAlpha(1f); 281 282 // Run the rotate icon's animation if it has one 283 if (currentDrawable instanceof AnimatedVectorDrawable) { 284 ((AnimatedVectorDrawable) currentDrawable).reset(); 285 ((AnimatedVectorDrawable) currentDrawable).start(); 286 } 287 288 // TODO(b/187754252): No idea why this doesn't work. If we remove the "false" 289 // we see the animation show the pressed state... but it only shows the first time. 290 if (!isRotateSuggestionIntroduced()) mViewRippler.start(view); 291 292 // Set visibility unless a11y service is active. 293 mRotationButton.show(); 294 } else { // Hide 295 mViewRippler.stop(); // Prevent any pending ripples, force hide or not 296 297 if (force) { 298 // If a hide animator is running stop it and make invisible 299 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) { 300 mRotateHideAnimator.pause(); 301 } 302 mRotationButton.hide(); 303 return; 304 } 305 306 // Don't start any new hide animations if one is running 307 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; 308 309 ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", 0f); 310 fadeOut.setDuration(BUTTON_FADE_IN_OUT_DURATION_MS); 311 fadeOut.setInterpolator(LINEAR_INTERPOLATOR); 312 fadeOut.addListener(new AnimatorListenerAdapter() { 313 @Override 314 public void onAnimationEnd(Animator animation) { 315 mRotationButton.hide(); 316 } 317 }); 318 319 mRotateHideAnimator = fadeOut; 320 fadeOut.start(); 321 } 322 } 323 setDarkIntensity(float darkIntensity)324 public void setDarkIntensity(float darkIntensity) { 325 mRotationButton.setDarkIntensity(darkIntensity); 326 } 327 setRecentsAnimationRunning(boolean running)328 public void setRecentsAnimationRunning(boolean running) { 329 mIsRecentsAnimationRunning = running; 330 updateRotationButtonStateInOverview(); 331 } 332 setHomeRotationEnabled(boolean enabled)333 public void setHomeRotationEnabled(boolean enabled) { 334 mHomeRotationEnabled = enabled; 335 updateRotationButtonStateInOverview(); 336 } 337 updateRotationButtonStateInOverview()338 private void updateRotationButtonStateInOverview() { 339 if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) { 340 setRotateSuggestionButtonState(false, true /* hideImmediately */); 341 } 342 } 343 onRotationProposal(int rotation, boolean isValid)344 public void onRotationProposal(int rotation, boolean isValid) { 345 int windowRotation = mWindowRotationProvider.get(); 346 347 if (!mRotationButton.acceptRotationProposal()) { 348 return; 349 } 350 351 if (!mHomeRotationEnabled && mIsRecentsAnimationRunning) { 352 return; 353 } 354 355 // This method will be called on rotation suggestion changes even if the proposed rotation 356 // is not valid for the top app. Use invalid rotation choices as a signal to remove the 357 // rotate button if shown. 358 if (!isValid) { 359 setRotateSuggestionButtonState(false /* visible */); 360 return; 361 } 362 363 // If window rotation matches suggested rotation, remove any current suggestions 364 if (rotation == windowRotation) { 365 mMainThreadHandler.removeCallbacks(mRemoveRotationProposal); 366 setRotateSuggestionButtonState(false /* visible */); 367 return; 368 } 369 370 // Prepare to show the navbar icon by updating the icon style to change anim params 371 mLastRotationSuggestion = rotation; // Remember rotation for click 372 final boolean rotationCCW = Utilities.isRotationAnimationCCW(windowRotation, rotation); 373 if (windowRotation == Surface.ROTATION_0 || windowRotation == Surface.ROTATION_180) { 374 mIconResId = rotationCCW ? mIconCcwStart0ResId : mIconCwStart0ResId; 375 } else { // 90 or 270 376 mIconResId = rotationCCW ? mIconCcwStart90ResId : mIconCwStart90ResId; 377 } 378 mRotationButton.updateIcon(mLightIconColor, mDarkIconColor); 379 380 if (canShowRotationButton()) { 381 // The navbar is visible / it's in visual immersive mode, so show the icon right away 382 showAndLogRotationSuggestion(); 383 } else { 384 // If the navbar isn't shown, flag the rotate icon to be shown should the navbar become 385 // visible given some time limit. 386 mPendingRotationSuggestion = true; 387 mMainThreadHandler.removeCallbacks(mCancelPendingRotationProposal); 388 mMainThreadHandler.postDelayed(mCancelPendingRotationProposal, 389 NAVBAR_HIDDEN_PENDING_ICON_TIMEOUT_MS); 390 } 391 } 392 onDisable2FlagChanged(int state2)393 public void onDisable2FlagChanged(int state2) { 394 final boolean rotateSuggestionsDisabled = hasDisable2RotateSuggestionFlag(state2); 395 if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled(); 396 } 397 onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior)398 public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) { 399 if (DEFAULT_DISPLAY != displayId) { 400 return; 401 } 402 403 if (mBehavior != behavior) { 404 mBehavior = behavior; 405 showPendingRotationButtonIfNeeded(); 406 } 407 } 408 onNavigationBarWindowVisibilityChange(boolean showing)409 public void onNavigationBarWindowVisibilityChange(boolean showing) { 410 if (mIsNavigationBarShowing != showing) { 411 mIsNavigationBarShowing = showing; 412 showPendingRotationButtonIfNeeded(); 413 } 414 } 415 onTaskbarStateChange(boolean visible, boolean stashed)416 public void onTaskbarStateChange(boolean visible, boolean stashed) { 417 getRotationButton().onTaskbarStateChanged(visible, stashed); 418 } 419 showPendingRotationButtonIfNeeded()420 private void showPendingRotationButtonIfNeeded() { 421 if (canShowRotationButton() && mPendingRotationSuggestion) { 422 showAndLogRotationSuggestion(); 423 } 424 } 425 426 /** 427 * Return true when either the task bar is visible or it's in visual immersive mode. 428 */ 429 @SuppressLint("InlinedApi") canShowRotationButton()430 private boolean canShowRotationButton() { 431 return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT; 432 } 433 434 @DrawableRes getIconResId()435 public int getIconResId() { 436 return mIconResId; 437 } 438 439 @ColorInt getLightIconColor()440 public int getLightIconColor() { 441 return mLightIconColor; 442 } 443 444 @ColorInt getDarkIconColor()445 public int getDarkIconColor() { 446 return mDarkIconColor; 447 } 448 getRotationButton()449 public RotationButton getRotationButton() { 450 return mRotationButton; 451 } 452 onRotateSuggestionClick(View v)453 private void onRotateSuggestionClick(View v) { 454 mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED); 455 incrementNumAcceptedRotationSuggestionsIfNeeded(); 456 setRotationLockedAtAngle(mLastRotationSuggestion); 457 v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 458 } 459 onRotateSuggestionHover(View v, MotionEvent event)460 private boolean onRotateSuggestionHover(View v, MotionEvent event) { 461 final int action = event.getActionMasked(); 462 mHoveringRotationSuggestion = (action == MotionEvent.ACTION_HOVER_ENTER) 463 || (action == MotionEvent.ACTION_HOVER_MOVE); 464 rescheduleRotationTimeout(true /* reasonHover */); 465 return false; // Must return false so a11y hover events are dispatched correctly. 466 } 467 onRotationSuggestionsDisabled()468 private void onRotationSuggestionsDisabled() { 469 // Immediately hide the rotate button and clear any planned removal 470 setRotateSuggestionButtonState(false /* visible */, true /* force */); 471 mMainThreadHandler.removeCallbacks(mRemoveRotationProposal); 472 } 473 showAndLogRotationSuggestion()474 private void showAndLogRotationSuggestion() { 475 setRotateSuggestionButtonState(true /* visible */); 476 rescheduleRotationTimeout(false /* reasonHover */); 477 mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_SHOWN); 478 } 479 480 /** 481 * Makes {@link #shouldOverrideUserLockPrefs} always return {@code false} once. It is used to 482 * avoid losing original user rotation when display rotation is changed by entering the fixed 483 * orientation overview. 484 */ setSkipOverrideUserLockPrefsOnce()485 public void setSkipOverrideUserLockPrefsOnce() { 486 // If live-tile is enabled (recents animation keeps running in overview), there is no 487 // activity switch so the display rotation is not changed, then it is no need to skip. 488 mSkipOverrideUserLockPrefsOnce = !mIsRecentsAnimationRunning; 489 } 490 shouldOverrideUserLockPrefs(final int rotation)491 private boolean shouldOverrideUserLockPrefs(final int rotation) { 492 if (mSkipOverrideUserLockPrefsOnce) { 493 mSkipOverrideUserLockPrefsOnce = false; 494 return false; 495 } 496 // Only override user prefs when returning to the natural rotation (normally portrait). 497 // Don't let apps that force landscape or 180 alter user lock. 498 return rotation == NATURAL_ROTATION; 499 } 500 rescheduleRotationTimeout(final boolean reasonHover)501 private void rescheduleRotationTimeout(final boolean reasonHover) { 502 // May be called due to a new rotation proposal or a change in hover state 503 if (reasonHover) { 504 // Don't reschedule if a hide animator is running 505 if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return; 506 // Don't reschedule if not visible 507 if (!mRotationButton.isVisible()) return; 508 } 509 510 // Stop any pending removal 511 mMainThreadHandler.removeCallbacks(mRemoveRotationProposal); 512 // Schedule timeout 513 mMainThreadHandler.postDelayed(mRemoveRotationProposal, 514 computeRotationProposalTimeout()); 515 } 516 computeRotationProposalTimeout()517 private int computeRotationProposalTimeout() { 518 return mAccessibilityManager.getRecommendedTimeoutMillis( 519 mHoveringRotationSuggestion ? 16000 : 5000, 520 AccessibilityManager.FLAG_CONTENT_CONTROLS); 521 } 522 isRotateSuggestionIntroduced()523 private boolean isRotateSuggestionIntroduced() { 524 ContentResolver cr = mContext.getContentResolver(); 525 return Settings.Secure.getInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0) 526 >= NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION; 527 } 528 incrementNumAcceptedRotationSuggestionsIfNeeded()529 private void incrementNumAcceptedRotationSuggestionsIfNeeded() { 530 // Get the number of accepted suggestions 531 ContentResolver cr = mContext.getContentResolver(); 532 final int numSuggestions = Settings.Secure.getInt(cr, 533 Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 0); 534 535 // Increment the number of accepted suggestions only if it would change intro mode 536 if (numSuggestions < NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION) { 537 Settings.Secure.putInt(cr, Settings.Secure.NUM_ROTATION_SUGGESTIONS_ACCEPTED, 538 numSuggestions + 1); 539 } 540 } 541 542 private class TaskStackListenerImpl extends TaskStackChangeListener { 543 // Invalidate any rotation suggestion on task change or activity orientation change 544 // Note: all callbacks happen on main thread 545 546 @Override onTaskStackChanged()547 public void onTaskStackChanged() { 548 setRotateSuggestionButtonState(false /* visible */); 549 } 550 551 @Override onTaskRemoved(int taskId)552 public void onTaskRemoved(int taskId) { 553 setRotateSuggestionButtonState(false /* visible */); 554 } 555 556 @Override onTaskMovedToFront(int taskId)557 public void onTaskMovedToFront(int taskId) { 558 setRotateSuggestionButtonState(false /* visible */); 559 } 560 561 @Override onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)562 public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { 563 // Only hide the icon if the top task changes its requestedOrientation 564 // Launcher can alter its requestedOrientation while it's not on top, don't hide on this 565 Optional.ofNullable(ActivityManagerWrapper.getInstance()) 566 .map(ActivityManagerWrapper::getRunningTask) 567 .ifPresent(a -> { 568 if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */); 569 }); 570 } 571 } 572 573 enum RotationButtonEvent implements UiEventLogger.UiEventEnum { 574 @UiEvent(doc = "The rotation button was shown") 575 ROTATION_SUGGESTION_SHOWN(206), 576 @UiEvent(doc = "The rotation button was clicked") 577 ROTATION_SUGGESTION_ACCEPTED(207); 578 579 private final int mId; 580 RotationButtonEvent(int id)581 RotationButtonEvent(int id) { 582 mId = id; 583 } 584 585 @Override getId()586 public int getId() { 587 return mId; 588 } 589 } 590 } 591 592