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 17 package com.android.wm.shell.back; 18 19 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; 20 import static com.android.window.flags.Flags.predictiveBackSystemAnims; 21 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; 22 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SuppressLint; 27 import android.app.ActivityTaskManager; 28 import android.app.IActivityTaskManager; 29 import android.content.ContentResolver; 30 import android.content.Context; 31 import android.content.res.Configuration; 32 import android.database.ContentObserver; 33 import android.graphics.Rect; 34 import android.hardware.input.InputManager; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.RemoteCallback; 39 import android.os.RemoteException; 40 import android.os.SystemClock; 41 import android.os.SystemProperties; 42 import android.os.UserHandle; 43 import android.provider.Settings.Global; 44 import android.util.Log; 45 import android.view.IRemoteAnimationRunner; 46 import android.view.InputDevice; 47 import android.view.KeyCharacterMap; 48 import android.view.KeyEvent; 49 import android.view.MotionEvent; 50 import android.view.RemoteAnimationTarget; 51 import android.view.WindowManager; 52 import android.window.BackAnimationAdapter; 53 import android.window.BackEvent; 54 import android.window.BackMotionEvent; 55 import android.window.BackNavigationInfo; 56 import android.window.BackTouchTracker; 57 import android.window.IBackAnimationFinishedCallback; 58 import android.window.IBackAnimationRunner; 59 import android.window.IOnBackInvokedCallback; 60 61 import com.android.internal.annotations.VisibleForTesting; 62 import com.android.internal.protolog.common.ProtoLog; 63 import com.android.internal.util.LatencyTracker; 64 import com.android.internal.view.AppearanceRegion; 65 import com.android.wm.shell.R; 66 import com.android.wm.shell.common.ExternalInterfaceBinder; 67 import com.android.wm.shell.common.RemoteCallable; 68 import com.android.wm.shell.common.ShellExecutor; 69 import com.android.wm.shell.shared.annotations.ShellBackgroundThread; 70 import com.android.wm.shell.shared.annotations.ShellMainThread; 71 import com.android.wm.shell.sysui.ConfigurationChangeListener; 72 import com.android.wm.shell.sysui.ShellCommandHandler; 73 import com.android.wm.shell.sysui.ShellController; 74 import com.android.wm.shell.sysui.ShellInit; 75 76 import java.io.PrintWriter; 77 import java.util.concurrent.atomic.AtomicBoolean; 78 79 /** 80 * Controls the window animation run when a user initiates a back gesture. 81 */ 82 public class BackAnimationController implements RemoteCallable<BackAnimationController>, 83 ConfigurationChangeListener { 84 private static final String TAG = "ShellBackPreview"; 85 private static final int SETTING_VALUE_OFF = 0; 86 private static final int SETTING_VALUE_ON = 1; 87 public static final boolean IS_ENABLED = 88 SystemProperties.getInt("persist.wm.debug.predictive_back", 89 SETTING_VALUE_ON) == SETTING_VALUE_ON; 90 91 /** Predictive back animation developer option */ 92 private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); 93 /** 94 * Max duration to wait for an animation to finish before triggering the real back. 95 */ 96 private static final long MAX_ANIMATION_DURATION = 2000; 97 private final LatencyTracker mLatencyTracker; 98 99 /** True when a back gesture is ongoing */ 100 private boolean mBackGestureStarted = false; 101 102 /** Tracks if an uninterruptible animation is in progress */ 103 private boolean mPostCommitAnimationInProgress = false; 104 105 /** Tracks if we should start the back gesture on the next motion move event */ 106 private boolean mShouldStartOnNextMoveEvent = false; 107 private boolean mOnBackStartDispatched = false; 108 private boolean mThresholdCrossed = false; 109 private boolean mPointersPilfered = false; 110 private final boolean mRequirePointerPilfer; 111 112 /** Registry for the back animations */ 113 private final ShellBackAnimationRegistry mShellBackAnimationRegistry; 114 115 @Nullable 116 private BackNavigationInfo mBackNavigationInfo; 117 private final IActivityTaskManager mActivityTaskManager; 118 private final Context mContext; 119 private final ContentResolver mContentResolver; 120 private final ShellController mShellController; 121 private final ShellCommandHandler mShellCommandHandler; 122 private final ShellExecutor mShellExecutor; 123 private final Handler mBgHandler; 124 private final WindowManager mWindowManager; 125 @VisibleForTesting 126 final Rect mTouchableArea = new Rect(); 127 128 /** 129 * Tracks the current user back gesture. 130 */ 131 private BackTouchTracker mCurrentTracker = new BackTouchTracker(); 132 133 /** 134 * Tracks the next back gesture in case a new user gesture has started while the back animation 135 * (and navigation) associated with {@link #mCurrentTracker} have not yet finished. 136 */ 137 private BackTouchTracker mQueuedTracker = new BackTouchTracker(); 138 139 private final Runnable mAnimationTimeoutRunnable = () -> { 140 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...", 141 MAX_ANIMATION_DURATION); 142 finishBackAnimation(); 143 }; 144 145 private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; 146 @VisibleForTesting 147 BackAnimationAdapter mBackAnimationAdapter; 148 149 @Nullable 150 private IOnBackInvokedCallback mActiveCallback; 151 @Nullable 152 private RemoteAnimationTarget[] mApps; 153 154 @VisibleForTesting 155 final RemoteCallback mNavigationObserver = new RemoteCallback( 156 new RemoteCallback.OnResultListener() { 157 @Override 158 public void onResult(@Nullable Bundle result) { 159 mShellExecutor.execute(() -> { 160 if (!mBackGestureStarted || mPostCommitAnimationInProgress) { 161 // If an uninterruptible animation is already in progress, we should 162 // ignore this due to it may cause focus lost. (alpha = 0) 163 return; 164 } 165 ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone."); 166 setTriggerBack(false); 167 resetTouchTracker(); 168 // Don't wait for animation start 169 mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); 170 }); 171 } 172 }); 173 174 private final BackAnimationBackground mAnimationBackground; 175 private StatusBarCustomizer mCustomizer; 176 private boolean mTrackingLatency; 177 178 // Keep previous navigation type before remove mBackNavigationInfo. 179 @BackNavigationInfo.BackTargetType 180 private int mPreviousNavigationType; 181 private Runnable mPilferPointerCallback; 182 BackAnimationController( @onNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context, @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler)183 public BackAnimationController( 184 @NonNull ShellInit shellInit, 185 @NonNull ShellController shellController, 186 @NonNull @ShellMainThread ShellExecutor shellExecutor, 187 @NonNull @ShellBackgroundThread Handler backgroundHandler, 188 Context context, 189 @NonNull BackAnimationBackground backAnimationBackground, 190 ShellBackAnimationRegistry shellBackAnimationRegistry, 191 ShellCommandHandler shellCommandHandler) { 192 this( 193 shellInit, 194 shellController, 195 shellExecutor, 196 backgroundHandler, 197 ActivityTaskManager.getService(), 198 context, 199 context.getContentResolver(), 200 backAnimationBackground, 201 shellBackAnimationRegistry, 202 shellCommandHandler); 203 } 204 205 @VisibleForTesting BackAnimationController( @onNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull IActivityTaskManager activityTaskManager, Context context, ContentResolver contentResolver, @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler)206 BackAnimationController( 207 @NonNull ShellInit shellInit, 208 @NonNull ShellController shellController, 209 @NonNull @ShellMainThread ShellExecutor shellExecutor, 210 @NonNull @ShellBackgroundThread Handler bgHandler, 211 @NonNull IActivityTaskManager activityTaskManager, 212 Context context, 213 ContentResolver contentResolver, 214 @NonNull BackAnimationBackground backAnimationBackground, 215 ShellBackAnimationRegistry shellBackAnimationRegistry, 216 ShellCommandHandler shellCommandHandler) { 217 mShellController = shellController; 218 mShellExecutor = shellExecutor; 219 mActivityTaskManager = activityTaskManager; 220 mContext = context; 221 mContentResolver = contentResolver; 222 mRequirePointerPilfer = 223 context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer); 224 mBgHandler = bgHandler; 225 shellInit.addInitCallback(this::onInit, this); 226 mAnimationBackground = backAnimationBackground; 227 mShellBackAnimationRegistry = shellBackAnimationRegistry; 228 mLatencyTracker = LatencyTracker.getInstance(mContext); 229 mShellCommandHandler = shellCommandHandler; 230 mWindowManager = context.getSystemService(WindowManager.class); 231 updateTouchableArea(); 232 } 233 onInit()234 private void onInit() { 235 setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); 236 updateEnableAnimationFromFlags(); 237 createAdapter(); 238 mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, 239 this::createExternalInterface, this); 240 mShellCommandHandler.addDumpCallback(this::dump, this); 241 mShellController.addConfigurationChangeListener(this); 242 } 243 setupAnimationDeveloperSettingsObserver( @onNull ContentResolver contentResolver, @NonNull @ShellBackgroundThread final Handler backgroundHandler)244 private void setupAnimationDeveloperSettingsObserver( 245 @NonNull ContentResolver contentResolver, 246 @NonNull @ShellBackgroundThread final Handler backgroundHandler) { 247 if (predictiveBackSystemAnims()) { 248 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore " 249 + "developer settings flag is ignored and no content observer registered"); 250 return; 251 } 252 ContentObserver settingsObserver = new ContentObserver(backgroundHandler) { 253 @Override 254 public void onChange(boolean selfChange, Uri uri) { 255 updateEnableAnimationFromFlags(); 256 } 257 }; 258 contentResolver.registerContentObserver( 259 Global.getUriFor(Global.ENABLE_BACK_ANIMATION), 260 false, settingsObserver, UserHandle.USER_SYSTEM 261 ); 262 } 263 264 /** 265 * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the 266 * aconfig flag and the developer settings flag 267 */ 268 @ShellBackgroundThread updateEnableAnimationFromFlags()269 private void updateEnableAnimationFromFlags() { 270 boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled(); 271 mEnableAnimations.set(isEnabled); 272 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); 273 } 274 isDeveloperSettingEnabled()275 private boolean isDeveloperSettingEnabled() { 276 return Global.getInt(mContext.getContentResolver(), 277 Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON; 278 } 279 getBackAnimationImpl()280 public BackAnimation getBackAnimationImpl() { 281 return mBackAnimation; 282 } 283 createExternalInterface()284 private ExternalInterfaceBinder createExternalInterface() { 285 return new IBackAnimationImpl(this); 286 } 287 288 private final BackAnimationImpl mBackAnimation = new BackAnimationImpl(); 289 290 @Override onConfigurationChanged(Configuration newConfig)291 public void onConfigurationChanged(Configuration newConfig) { 292 mShellBackAnimationRegistry.onConfigurationChanged(newConfig); 293 updateTouchableArea(); 294 } 295 updateTouchableArea()296 private void updateTouchableArea() { 297 mTouchableArea.set(mWindowManager.getCurrentWindowMetrics().getBounds()); 298 } 299 300 @Override getContext()301 public Context getContext() { 302 return mContext; 303 } 304 305 @Override getRemoteCallExecutor()306 public ShellExecutor getRemoteCallExecutor() { 307 return mShellExecutor; 308 } 309 310 private class BackAnimationImpl implements BackAnimation { 311 @Override onBackMotion( float touchX, float touchY, float velocityX, float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge )312 public void onBackMotion( 313 float touchX, 314 float touchY, 315 float velocityX, 316 float velocityY, 317 int keyAction, 318 @BackEvent.SwipeEdge int swipeEdge 319 ) { 320 mShellExecutor.execute(() -> onMotionEvent( 321 /* touchX = */ touchX, 322 /* touchY = */ touchY, 323 /* velocityX = */ velocityX, 324 /* velocityY = */ velocityY, 325 /* keyAction = */ keyAction, 326 /* swipeEdge = */ swipeEdge)); 327 } 328 329 @Override onThresholdCrossed()330 public void onThresholdCrossed() { 331 BackAnimationController.this.onThresholdCrossed(); 332 } 333 334 @Override setTriggerBack(boolean triggerBack)335 public void setTriggerBack(boolean triggerBack) { 336 mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack)); 337 } 338 339 @Override setSwipeThresholds( float linearDistance, float maxDistance, float nonLinearFactor)340 public void setSwipeThresholds( 341 float linearDistance, 342 float maxDistance, 343 float nonLinearFactor) { 344 mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds( 345 linearDistance, maxDistance, nonLinearFactor)); 346 } 347 348 @Override setStatusBarCustomizer(StatusBarCustomizer customizer)349 public void setStatusBarCustomizer(StatusBarCustomizer customizer) { 350 mCustomizer = customizer; 351 mAnimationBackground.setStatusBarCustomizer(customizer); 352 } 353 354 @Override setPilferPointerCallback(Runnable callback)355 public void setPilferPointerCallback(Runnable callback) { 356 mShellExecutor.execute(() -> { 357 mPilferPointerCallback = callback; 358 }); 359 } 360 } 361 362 private static class IBackAnimationImpl extends IBackAnimation.Stub 363 implements ExternalInterfaceBinder { 364 private BackAnimationController mController; 365 IBackAnimationImpl(BackAnimationController controller)366 IBackAnimationImpl(BackAnimationController controller) { 367 mController = controller; 368 } 369 370 @Override setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner)371 public void setBackToLauncherCallback(IOnBackInvokedCallback callback, 372 IRemoteAnimationRunner runner) { 373 executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback", 374 (controller) -> controller.registerAnimation( 375 BackNavigationInfo.TYPE_RETURN_TO_HOME, 376 new BackAnimationRunner( 377 callback, 378 runner, 379 controller.mContext, 380 CUJ_PREDICTIVE_BACK_HOME))); 381 } 382 383 @Override clearBackToLauncherCallback()384 public void clearBackToLauncherCallback() { 385 executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback", 386 (controller) -> controller.unregisterAnimation( 387 BackNavigationInfo.TYPE_RETURN_TO_HOME)); 388 } 389 customizeStatusBarAppearance(AppearanceRegion appearance)390 public void customizeStatusBarAppearance(AppearanceRegion appearance) { 391 executeRemoteCallWithTaskPermission(mController, "useLauncherSysBarFlags", 392 (controller) -> controller.customizeStatusBarAppearance(appearance)); 393 } 394 395 @Override invalidate()396 public void invalidate() { 397 mController = null; 398 } 399 } 400 customizeStatusBarAppearance(AppearanceRegion appearance)401 private void customizeStatusBarAppearance(AppearanceRegion appearance) { 402 if (mCustomizer != null) { 403 mCustomizer.customizeStatusBarAppearance(appearance); 404 } 405 } 406 registerAnimation(@ackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner)407 void registerAnimation(@BackNavigationInfo.BackTargetType int type, 408 @NonNull BackAnimationRunner runner) { 409 mShellBackAnimationRegistry.registerAnimation(type, runner); 410 } 411 unregisterAnimation(@ackNavigationInfo.BackTargetType int type)412 void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { 413 mShellBackAnimationRegistry.unregisterAnimation(type); 414 } 415 getActiveTracker()416 private BackTouchTracker getActiveTracker() { 417 if (mCurrentTracker.isActive()) return mCurrentTracker; 418 if (mQueuedTracker.isActive()) return mQueuedTracker; 419 return null; 420 } 421 422 @VisibleForTesting onThresholdCrossed()423 public void onThresholdCrossed() { 424 mThresholdCrossed = true; 425 // Dispatch onBackStarted, only to app callbacks. 426 // System callbacks will receive onBackStarted when the remote animation starts. 427 final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); 428 if (!shouldDispatchToAnimator && mActiveCallback != null) { 429 mCurrentTracker.updateStartLocation(); 430 tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null)); 431 if (mBackNavigationInfo != null && !isAppProgressGenerationAllowed()) { 432 tryPilferPointers(); 433 } 434 } else if (shouldDispatchToAnimator) { 435 tryPilferPointers(); 436 } 437 } 438 isAppProgressGenerationAllowed()439 private boolean isAppProgressGenerationAllowed() { 440 return mBackNavigationInfo.isAppProgressGenerationAllowed() 441 && mBackNavigationInfo.getTouchableRegion().equals(mTouchableArea); 442 } 443 444 /** 445 * Called when a new motion event needs to be transferred to this 446 * {@link BackAnimationController} 447 */ onMotionEvent( float touchX, float touchY, float velocityX, float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge)448 public void onMotionEvent( 449 float touchX, 450 float touchY, 451 float velocityX, 452 float velocityY, 453 int keyAction, 454 @BackEvent.SwipeEdge int swipeEdge) { 455 456 BackTouchTracker activeTouchTracker = getActiveTracker(); 457 if (activeTouchTracker != null) { 458 activeTouchTracker.update(touchX, touchY, velocityX, velocityY); 459 } 460 461 // two gestures are waiting to be processed at the moment, skip any further user touches 462 if (mCurrentTracker.isFinished() && mQueuedTracker.isFinished()) { 463 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 464 "Ignoring MotionEvent because two gestures are already being queued."); 465 return; 466 } 467 468 if (keyAction == MotionEvent.ACTION_DOWN) { 469 if (!mBackGestureStarted) { 470 mShouldStartOnNextMoveEvent = true; 471 } 472 } else if (keyAction == MotionEvent.ACTION_MOVE) { 473 if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) { 474 // Let the animation initialized here to make sure the onPointerDownOutsideFocus 475 // could be happened when ACTION_DOWN, it may change the current focus that we 476 // would access it when startBackNavigation. 477 onGestureStarted(touchX, touchY, swipeEdge); 478 mShouldStartOnNextMoveEvent = false; 479 } 480 onMove(); 481 } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) { 482 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 483 "Finishing gesture with event action: %d", keyAction); 484 if (keyAction == MotionEvent.ACTION_CANCEL) { 485 setTriggerBack(false); 486 } 487 onGestureFinished(); 488 } 489 } 490 onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge)491 private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) { 492 boolean interruptCancelPostCommitAnimation = mPostCommitAnimationInProgress 493 && mCurrentTracker.isFinished() && !mCurrentTracker.getTriggerBack() 494 && mQueuedTracker.isInInitialState(); 495 if (interruptCancelPostCommitAnimation) { 496 // If a system animation is currently in the post-commit phase animating an 497 // onBackCancelled event, let's interrupt it and start animating a new back gesture 498 resetTouchTracker(); 499 } 500 BackTouchTracker touchTracker; 501 if (mCurrentTracker.isInInitialState()) { 502 touchTracker = mCurrentTracker; 503 } else if (mQueuedTracker.isInInitialState()) { 504 touchTracker = mQueuedTracker; 505 } else { 506 ProtoLog.w(WM_SHELL_BACK_PREVIEW, 507 "Cannot start tracking new gesture with neither tracker in initial state."); 508 return; 509 } 510 touchTracker.setGestureStartLocation(touchX, touchY, swipeEdge); 511 touchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE); 512 mBackGestureStarted = true; 513 514 if (interruptCancelPostCommitAnimation) { 515 // post-commit cancel is currently running. let's interrupt it and dispatch a new 516 // onBackStarted event. 517 mPostCommitAnimationInProgress = false; 518 mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); 519 startSystemAnimation(); 520 } else if (touchTracker == mCurrentTracker) { 521 // Only start the back navigation if no other gesture is being processed. Otherwise, 522 // the back navigation will fall back to legacy back event injection. 523 startBackNavigation(mCurrentTracker); 524 } 525 } 526 startBackNavigation(@onNull BackTouchTracker touchTracker)527 private void startBackNavigation(@NonNull BackTouchTracker touchTracker) { 528 try { 529 startLatencyTracking(); 530 mBackNavigationInfo = mActivityTaskManager.startBackNavigation( 531 mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null); 532 onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker); 533 } catch (RemoteException remoteException) { 534 Log.e(TAG, "Failed to initAnimation", remoteException); 535 finishBackNavigation(touchTracker.getTriggerBack()); 536 } 537 } 538 onBackNavigationInfoReceived(@ullable BackNavigationInfo backNavigationInfo, @NonNull BackTouchTracker touchTracker)539 private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo, 540 @NonNull BackTouchTracker touchTracker) { 541 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo); 542 if (backNavigationInfo == null) { 543 ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null."); 544 cancelLatencyTracking(); 545 return; 546 } 547 final int backType = backNavigationInfo.getType(); 548 final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); 549 if (shouldDispatchToAnimator) { 550 if (!mShellBackAnimationRegistry.startGesture(backType)) { 551 mActiveCallback = null; 552 } 553 tryPilferPointers(); 554 } else { 555 mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); 556 // App is handling back animation. Cancel system animation latency tracking. 557 cancelLatencyTracking(); 558 tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null)); 559 if (!isAppProgressGenerationAllowed()) { 560 tryPilferPointers(); 561 } 562 } 563 } 564 onMove()565 private void onMove() { 566 if (!mBackGestureStarted 567 || mBackNavigationInfo == null 568 || mActiveCallback == null 569 || !mOnBackStartDispatched) { 570 return; 571 } 572 // Skip dispatching if the move corresponds to the queued instead of the current gesture 573 if (mQueuedTracker.isActive()) return; 574 final BackMotionEvent backEvent = mCurrentTracker.createProgressEvent(); 575 dispatchOnBackProgressed(mActiveCallback, backEvent); 576 } 577 injectBackKey()578 private void injectBackKey() { 579 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "injectBackKey"); 580 sendBackEvent(KeyEvent.ACTION_DOWN); 581 sendBackEvent(KeyEvent.ACTION_UP); 582 } 583 584 @SuppressLint("MissingPermission") sendBackEvent(int action)585 private void sendBackEvent(int action) { 586 final long when = SystemClock.uptimeMillis(); 587 final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 588 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 589 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 590 InputDevice.SOURCE_KEYBOARD); 591 592 ev.setDisplayId(mContext.getDisplay().getDisplayId()); 593 if (!mContext.getSystemService(InputManager.class) 594 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { 595 ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Inject input event fail"); 596 } 597 } 598 shouldDispatchToAnimator()599 private boolean shouldDispatchToAnimator() { 600 return mEnableAnimations.get() 601 && mBackNavigationInfo != null 602 && mBackNavigationInfo.isPrepareRemoteAnimation(); 603 } 604 tryPilferPointers()605 private void tryPilferPointers() { 606 if (mPointersPilfered || !mThresholdCrossed) { 607 return; 608 } 609 if (mPilferPointerCallback != null) { 610 mPilferPointerCallback.run(); 611 } 612 mPointersPilfered = true; 613 } 614 tryDispatchOnBackStarted( IOnBackInvokedCallback callback, BackMotionEvent backEvent)615 private void tryDispatchOnBackStarted( 616 IOnBackInvokedCallback callback, 617 BackMotionEvent backEvent) { 618 if (mOnBackStartDispatched 619 || callback == null 620 || (!mThresholdCrossed && mRequirePointerPilfer)) { 621 return; 622 } 623 dispatchOnBackStarted(callback, backEvent); 624 } 625 dispatchOnBackStarted( IOnBackInvokedCallback callback, BackMotionEvent backEvent)626 private void dispatchOnBackStarted( 627 IOnBackInvokedCallback callback, 628 BackMotionEvent backEvent) { 629 if (callback == null) { 630 return; 631 } 632 try { 633 callback.onBackStarted(backEvent); 634 mOnBackStartDispatched = true; 635 } catch (RemoteException e) { 636 Log.e(TAG, "dispatchOnBackStarted error: ", e); 637 } 638 } 639 dispatchOnBackInvoked(IOnBackInvokedCallback callback)640 private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { 641 if (callback == null) { 642 return; 643 } 644 try { 645 callback.onBackInvoked(); 646 } catch (RemoteException e) { 647 Log.e(TAG, "dispatchOnBackInvoked error: ", e); 648 } 649 } 650 tryDispatchOnBackCancelled(IOnBackInvokedCallback callback)651 private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) { 652 if (!mOnBackStartDispatched) { 653 Log.d(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched."); 654 return; 655 } 656 if (callback == null) { 657 return; 658 } 659 try { 660 callback.onBackCancelled(); 661 } catch (RemoteException e) { 662 Log.e(TAG, "dispatchOnBackCancelled error: ", e); 663 } 664 } 665 dispatchOnBackProgressed(IOnBackInvokedCallback callback, BackMotionEvent backEvent)666 private void dispatchOnBackProgressed(IOnBackInvokedCallback callback, 667 BackMotionEvent backEvent) { 668 if (callback == null || (!shouldDispatchToAnimator() && mBackNavigationInfo != null 669 && isAppProgressGenerationAllowed())) { 670 return; 671 } 672 try { 673 callback.onBackProgressed(backEvent); 674 } catch (RemoteException e) { 675 Log.e(TAG, "dispatchOnBackProgressed error: ", e); 676 } 677 } 678 679 /** 680 * Sets to true when the back gesture has passed the triggering threshold, false otherwise. 681 */ setTriggerBack(boolean triggerBack)682 public void setTriggerBack(boolean triggerBack) { 683 if (mActiveCallback != null) { 684 try { 685 mActiveCallback.setTriggerBack(triggerBack); 686 } catch (RemoteException e) { 687 Log.e(TAG, "remote setTriggerBack error: ", e); 688 } 689 } 690 BackTouchTracker activeBackGestureInfo = getActiveTracker(); 691 if (activeBackGestureInfo != null) { 692 activeBackGestureInfo.setTriggerBack(triggerBack); 693 } 694 } 695 setSwipeThresholds( float linearDistance, float maxDistance, float nonLinearFactor)696 private void setSwipeThresholds( 697 float linearDistance, 698 float maxDistance, 699 float nonLinearFactor) { 700 mCurrentTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor); 701 mQueuedTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor); 702 } 703 invokeOrCancelBack(@onNull BackTouchTracker touchTracker)704 private void invokeOrCancelBack(@NonNull BackTouchTracker touchTracker) { 705 // Make a synchronized call to core before dispatch back event to client side. 706 // If the close transition happens before the core receives onAnimationFinished, there will 707 // play a second close animation for that transition. 708 if (mBackAnimationFinishedCallback != null) { 709 try { 710 mBackAnimationFinishedCallback.onAnimationFinished(touchTracker.getTriggerBack()); 711 } catch (RemoteException e) { 712 Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e); 713 } 714 mBackAnimationFinishedCallback = null; 715 } 716 717 if (mBackNavigationInfo != null) { 718 final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); 719 if (touchTracker.getTriggerBack()) { 720 dispatchOnBackInvoked(callback); 721 } else { 722 tryDispatchOnBackCancelled(callback); 723 } 724 } 725 finishBackNavigation(touchTracker.getTriggerBack()); 726 } 727 728 /** 729 * Called when the gesture is released, then it could start the post commit animation. 730 */ onGestureFinished()731 private void onGestureFinished() { 732 BackTouchTracker activeTouchTracker = getActiveTracker(); 733 if (!mBackGestureStarted || activeTouchTracker == null) { 734 // This can happen when an unfinished gesture has been reset in resetTouchTracker 735 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 736 "onGestureFinished called while no gesture is started"); 737 return; 738 } 739 boolean triggerBack = activeTouchTracker.getTriggerBack(); 740 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", triggerBack); 741 742 // Reset gesture states. 743 mThresholdCrossed = false; 744 mPointersPilfered = false; 745 mBackGestureStarted = false; 746 activeTouchTracker.setState(BackTouchTracker.TouchTrackerState.FINISHED); 747 748 if (mPostCommitAnimationInProgress) { 749 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running"); 750 return; 751 } 752 753 if (mBackNavigationInfo == null) { 754 // No focus window found or core are running recents animation, inject back key as 755 // legacy behavior, or new back gesture was started while previous has not finished yet 756 if (!mQueuedTracker.isInInitialState()) { 757 ProtoLog.e(WM_SHELL_BACK_PREVIEW, "mBackNavigationInfo is null AND there is " 758 + "another back animation in progress"); 759 } 760 mCurrentTracker.reset(); 761 if (triggerBack) { 762 injectBackKey(); 763 } 764 finishBackNavigation(triggerBack); 765 return; 766 } 767 768 final int backType = mBackNavigationInfo.getType(); 769 // Simply trigger and finish back navigation when no animator defined. 770 if (!shouldDispatchToAnimator() 771 || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) { 772 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Trigger back without dispatching to animator."); 773 invokeOrCancelBack(mCurrentTracker); 774 mCurrentTracker.reset(); 775 return; 776 } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) { 777 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); 778 // Supposed it is in post commit animation state, and start the timeout to watch 779 // if the animation is ready. 780 mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); 781 return; 782 } 783 startPostCommitAnimation(); 784 } 785 786 /** 787 * Start the phase 2 animation when gesture is released. 788 * Callback to {@link #onBackAnimationFinished} when it is finished or timeout. 789 */ startPostCommitAnimation()790 private void startPostCommitAnimation() { 791 if (mPostCommitAnimationInProgress) { 792 return; 793 } 794 795 mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); 796 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()"); 797 mPostCommitAnimationInProgress = true; 798 mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); 799 800 // The next callback should be {@link #onBackAnimationFinished}. 801 if (mCurrentTracker.getTriggerBack()) { 802 // notify gesture finished 803 mBackNavigationInfo.onBackGestureFinished(true); 804 dispatchOnBackInvoked(mActiveCallback); 805 } else { 806 tryDispatchOnBackCancelled(mActiveCallback); 807 } 808 } 809 810 /** 811 * Called when the post commit animation is completed or timeout. 812 * This will trigger the real {@link IOnBackInvokedCallback} behavior. 813 */ 814 @VisibleForTesting onBackAnimationFinished()815 void onBackAnimationFinished() { 816 if (!mPostCommitAnimationInProgress) { 817 // This can happen when a post-commit cancel animation was interrupted by a new back 818 // gesture but the timing of interruption was bad such that the back-callback 819 // implementation finished in between the time of the new gesture having started and 820 // the time of the back-callback receiving the new onBackStarted event. Due to the 821 // asynchronous APIs this isn't an unlikely case. To handle this, let's return early. 822 // The back-callback implementation will call onBackAnimationFinished again when it is 823 // done with animating the second gesture. 824 return; 825 } 826 finishBackAnimation(); 827 } 828 finishBackAnimation()829 private void finishBackAnimation() { 830 // Stop timeout runner. 831 mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); 832 mPostCommitAnimationInProgress = false; 833 834 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()"); 835 836 if (mCurrentTracker.isActive() || mCurrentTracker.isFinished()) { 837 // Trigger the real back. 838 invokeOrCancelBack(mCurrentTracker); 839 } else { 840 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 841 "mCurrentBackGestureInfo was null when back animation finished"); 842 } 843 resetTouchTracker(); 844 } 845 846 /** 847 * Resets the BackTouchTracker and potentially starts a new back navigation in case one 848 * is queued. 849 */ resetTouchTracker()850 private void resetTouchTracker() { 851 BackTouchTracker temp = mCurrentTracker; 852 mCurrentTracker = mQueuedTracker; 853 temp.reset(); 854 mQueuedTracker = temp; 855 856 if (mCurrentTracker.isInInitialState()) { 857 if (mBackGestureStarted) { 858 mBackGestureStarted = false; 859 tryDispatchOnBackCancelled(mActiveCallback); 860 finishBackNavigation(false); 861 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 862 "resetTouchTracker -> reset an unfinished gesture"); 863 } else { 864 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> no queued gesture"); 865 } 866 return; 867 } 868 869 if (mCurrentTracker.isFinished() && mCurrentTracker.getTriggerBack()) { 870 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> start queued back navigation " 871 + "AND post commit animation"); 872 injectBackKey(); 873 finishBackNavigation(true); 874 mCurrentTracker.reset(); 875 } else if (!mCurrentTracker.isFinished()) { 876 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 877 "resetTouchTracker -> queued gesture not finished; do nothing"); 878 } else { 879 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> reset queued gesture"); 880 mCurrentTracker.reset(); 881 } 882 } 883 884 /** 885 * This should be called after the whole back navigation is completed. 886 */ 887 @VisibleForTesting finishBackNavigation(boolean triggerBack)888 void finishBackNavigation(boolean triggerBack) { 889 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); 890 mActiveCallback = null; 891 mApps = null; 892 mShouldStartOnNextMoveEvent = false; 893 mOnBackStartDispatched = false; 894 mThresholdCrossed = false; 895 mPointersPilfered = false; 896 mShellBackAnimationRegistry.resetDefaultCrossActivity(); 897 cancelLatencyTracking(); 898 if (mBackNavigationInfo != null) { 899 mPreviousNavigationType = mBackNavigationInfo.getType(); 900 mBackNavigationInfo.onBackNavigationFinished(triggerBack); 901 mBackNavigationInfo = null; 902 } 903 } 904 startLatencyTracking()905 private void startLatencyTracking() { 906 if (mTrackingLatency) { 907 cancelLatencyTracking(); 908 } 909 mLatencyTracker.onActionStart(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION); 910 mTrackingLatency = true; 911 } 912 cancelLatencyTracking()913 private void cancelLatencyTracking() { 914 if (!mTrackingLatency) { 915 return; 916 } 917 mLatencyTracker.onActionCancel(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION); 918 mTrackingLatency = false; 919 } 920 endLatencyTracking()921 private void endLatencyTracking() { 922 if (!mTrackingLatency) { 923 return; 924 } 925 mLatencyTracker.onActionEnd(LatencyTracker.ACTION_BACK_SYSTEM_ANIMATION); 926 mTrackingLatency = false; 927 } 928 startSystemAnimation()929 private void startSystemAnimation() { 930 if (mBackNavigationInfo == null) { 931 ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Lack of navigation info to start animation."); 932 return; 933 } 934 if (!validateAnimationTargets(mApps)) { 935 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Not starting animation due to mApps being null."); 936 return; 937 } 938 939 final BackAnimationRunner runner = 940 mShellBackAnimationRegistry.getAnimationRunnerAndInit(mBackNavigationInfo); 941 if (runner == null) { 942 if (mBackAnimationFinishedCallback != null) { 943 try { 944 mBackAnimationFinishedCallback.onAnimationFinished(false); 945 } catch (RemoteException e) { 946 Log.w(TAG, "Failed call IBackNaviAnimationController", e); 947 } 948 } 949 return; 950 } 951 mActiveCallback = runner.getCallback(); 952 953 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()"); 954 955 runner.startAnimation(mApps, /*wallpapers*/ null, /*nonApps*/ null, 956 () -> mShellExecutor.execute(this::onBackAnimationFinished)); 957 958 if (mApps.length >= 1) { 959 mCurrentTracker.updateStartLocation(); 960 BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); 961 dispatchOnBackStarted(mActiveCallback, startEvent); 962 } 963 } 964 965 /** 966 * Validate animation targets. 967 */ validateAnimationTargets(RemoteAnimationTarget[] apps)968 static boolean validateAnimationTargets(RemoteAnimationTarget[] apps) { 969 if (apps == null || apps.length == 0) { 970 return false; 971 } 972 for (int i = apps.length - 1; i >= 0; --i) { 973 if (!apps[i].leash.isValid()) { 974 return false; 975 } 976 } 977 return true; 978 } 979 createAdapter()980 private void createAdapter() { 981 IBackAnimationRunner runner = 982 new IBackAnimationRunner.Stub() { 983 @Override 984 public void onAnimationStart( 985 RemoteAnimationTarget[] apps, 986 RemoteAnimationTarget[] wallpapers, 987 RemoteAnimationTarget[] nonApps, 988 IBackAnimationFinishedCallback finishedCallback) { 989 mShellExecutor.execute( 990 () -> { 991 endLatencyTracking(); 992 if (!validateAnimationTargets(apps)) { 993 Log.e(TAG, "Invalid animation targets!"); 994 return; 995 } 996 mBackAnimationFinishedCallback = finishedCallback; 997 mApps = apps; 998 startSystemAnimation(); 999 1000 // Dispatch the first progress after animation start for 1001 // smoothing the initial animation, instead of waiting for next 1002 // onMove. 1003 final BackMotionEvent backFinish = mCurrentTracker 1004 .createProgressEvent(); 1005 dispatchOnBackProgressed(mActiveCallback, backFinish); 1006 if (!mBackGestureStarted) { 1007 // if the down -> up gesture happened before animation 1008 // start, we have to trigger the uninterruptible transition 1009 // to finish the back animation. 1010 startPostCommitAnimation(); 1011 } 1012 }); 1013 } 1014 1015 @Override 1016 public void onAnimationCancelled() { 1017 mShellExecutor.execute( 1018 () -> { 1019 if (!mShellBackAnimationRegistry.cancel( 1020 mBackNavigationInfo != null 1021 ? mBackNavigationInfo.getType() 1022 : mPreviousNavigationType)) { 1023 return; 1024 } 1025 if (!mBackGestureStarted) { 1026 invokeOrCancelBack(mCurrentTracker); 1027 } 1028 }); 1029 } 1030 }; 1031 mBackAnimationAdapter = new BackAnimationAdapter(runner); 1032 } 1033 1034 /** 1035 * Description of current BackAnimationController state. 1036 */ dump(PrintWriter pw, String prefix)1037 private void dump(PrintWriter pw, String prefix) { 1038 pw.println(prefix + "BackAnimationController state:"); 1039 pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get()); 1040 pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); 1041 pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); 1042 pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); 1043 pw.println(prefix + " mPointerPilfered=" + mThresholdCrossed); 1044 pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer); 1045 pw.println(prefix + " mCurrentTracker state:"); 1046 mCurrentTracker.dump(pw, prefix + " "); 1047 pw.println(prefix + " mQueuedTracker state:"); 1048 mQueuedTracker.dump(pw, prefix + " "); 1049 } 1050 1051 } 1052