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.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; 20 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; 21 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; 22 23 import android.animation.Animator; 24 import android.animation.AnimatorListenerAdapter; 25 import android.animation.ValueAnimator; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.app.ActivityTaskManager; 29 import android.app.IActivityTaskManager; 30 import android.content.ContentResolver; 31 import android.content.Context; 32 import android.database.ContentObserver; 33 import android.hardware.input.InputManager; 34 import android.net.Uri; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.RemoteCallback; 38 import android.os.RemoteException; 39 import android.os.SystemClock; 40 import android.os.SystemProperties; 41 import android.os.UserHandle; 42 import android.provider.Settings.Global; 43 import android.util.DisplayMetrics; 44 import android.util.Log; 45 import android.util.MathUtils; 46 import android.util.SparseArray; 47 import android.view.IRemoteAnimationRunner; 48 import android.view.InputDevice; 49 import android.view.KeyCharacterMap; 50 import android.view.KeyEvent; 51 import android.view.MotionEvent; 52 import android.view.RemoteAnimationTarget; 53 import android.window.BackAnimationAdapter; 54 import android.window.BackEvent; 55 import android.window.BackMotionEvent; 56 import android.window.BackNavigationInfo; 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.view.AppearanceRegion; 64 import com.android.wm.shell.animation.FlingAnimationUtils; 65 import com.android.wm.shell.common.ExternalInterfaceBinder; 66 import com.android.wm.shell.common.RemoteCallable; 67 import com.android.wm.shell.common.ShellExecutor; 68 import com.android.wm.shell.common.annotations.ShellBackgroundThread; 69 import com.android.wm.shell.common.annotations.ShellMainThread; 70 import com.android.wm.shell.sysui.ShellController; 71 import com.android.wm.shell.sysui.ShellInit; 72 73 import java.util.concurrent.atomic.AtomicBoolean; 74 75 /** 76 * Controls the window animation run when a user initiates a back gesture. 77 */ 78 public class BackAnimationController implements RemoteCallable<BackAnimationController> { 79 private static final String TAG = "ShellBackPreview"; 80 private static final int SETTING_VALUE_OFF = 0; 81 private static final int SETTING_VALUE_ON = 1; 82 public static final boolean IS_ENABLED = 83 SystemProperties.getInt("persist.wm.debug.predictive_back", 84 SETTING_VALUE_ON) == SETTING_VALUE_ON; 85 /** Flag for U animation features */ 86 public static boolean IS_U_ANIMATION_ENABLED = 87 SystemProperties.getInt("persist.wm.debug.predictive_back_anim", 88 SETTING_VALUE_ON) == SETTING_VALUE_ON; 89 90 public static final float FLING_MAX_LENGTH_SECONDS = 0.1f; // 100ms 91 public static final float FLING_SPEED_UP_FACTOR = 0.6f; 92 93 /** 94 * The maximum additional progress in case of fling gesture. 95 * The end animation starts after the user lifts the finger from the screen, we continue to 96 * fire {@link BackEvent}s until the velocity reaches 0. 97 */ 98 private static final float MAX_FLING_PROGRESS = 0.3f; /* 30% of the screen */ 99 100 /** Predictive back animation developer option */ 101 private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); 102 /** 103 * Max duration to wait for an animation to finish before triggering the real back. 104 */ 105 private static final long MAX_ANIMATION_DURATION = 2000; 106 107 /** True when a back gesture is ongoing */ 108 private boolean mBackGestureStarted = false; 109 110 /** Tracks if an uninterruptible animation is in progress */ 111 private boolean mPostCommitAnimationInProgress = false; 112 /** Tracks if we should start the back gesture on the next motion move event */ 113 private boolean mShouldStartOnNextMoveEvent = false; 114 /** @see #setTriggerBack(boolean) */ 115 private boolean mTriggerBack; 116 private FlingAnimationUtils mFlingAnimationUtils; 117 118 @Nullable 119 private BackNavigationInfo mBackNavigationInfo; 120 private final IActivityTaskManager mActivityTaskManager; 121 private final Context mContext; 122 private final ContentResolver mContentResolver; 123 private final ShellController mShellController; 124 private final ShellExecutor mShellExecutor; 125 private final Handler mBgHandler; 126 private final Runnable mAnimationTimeoutRunnable = () -> { 127 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...", 128 MAX_ANIMATION_DURATION); 129 onBackAnimationFinished(); 130 }; 131 132 private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; 133 @VisibleForTesting 134 BackAnimationAdapter mBackAnimationAdapter; 135 136 private final TouchTracker mTouchTracker = new TouchTracker(); 137 138 private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>(); 139 @Nullable 140 private IOnBackInvokedCallback mActiveCallback; 141 142 private CrossActivityAnimation mDefaultActivityAnimation; 143 private CustomizeActivityAnimation mCustomizeActivityAnimation; 144 145 @VisibleForTesting 146 final RemoteCallback mNavigationObserver = new RemoteCallback( 147 new RemoteCallback.OnResultListener() { 148 @Override 149 public void onResult(@Nullable Bundle result) { 150 mShellExecutor.execute(() -> { 151 if (!mBackGestureStarted || mPostCommitAnimationInProgress) { 152 // If an uninterruptible animation is already in progress, we should 153 // ignore this due to it may cause focus lost. (alpha = 0) 154 return; 155 } 156 ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone."); 157 setTriggerBack(false); 158 onGestureFinished(false); 159 }); 160 } 161 }); 162 163 private final BackAnimationBackground mAnimationBackground; 164 private StatusBarCustomizer mCustomizer; 165 BackAnimationController( @onNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context, @NonNull BackAnimationBackground backAnimationBackground)166 public BackAnimationController( 167 @NonNull ShellInit shellInit, 168 @NonNull ShellController shellController, 169 @NonNull @ShellMainThread ShellExecutor shellExecutor, 170 @NonNull @ShellBackgroundThread Handler backgroundHandler, 171 Context context, 172 @NonNull BackAnimationBackground backAnimationBackground) { 173 this(shellInit, shellController, shellExecutor, backgroundHandler, 174 ActivityTaskManager.getService(), context, context.getContentResolver(), 175 backAnimationBackground); 176 } 177 178 @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)179 BackAnimationController( 180 @NonNull ShellInit shellInit, 181 @NonNull ShellController shellController, 182 @NonNull @ShellMainThread ShellExecutor shellExecutor, 183 @NonNull @ShellBackgroundThread Handler bgHandler, 184 @NonNull IActivityTaskManager activityTaskManager, 185 Context context, ContentResolver contentResolver, 186 @NonNull BackAnimationBackground backAnimationBackground) { 187 mShellController = shellController; 188 mShellExecutor = shellExecutor; 189 mActivityTaskManager = activityTaskManager; 190 mContext = context; 191 mContentResolver = contentResolver; 192 mBgHandler = bgHandler; 193 shellInit.addInitCallback(this::onInit, this); 194 mAnimationBackground = backAnimationBackground; 195 DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); 196 mFlingAnimationUtils = new FlingAnimationUtils.Builder(displayMetrics) 197 .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) 198 .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) 199 .build(); 200 } 201 202 @VisibleForTesting setEnableUAnimation(boolean enable)203 void setEnableUAnimation(boolean enable) { 204 IS_U_ANIMATION_ENABLED = enable; 205 } 206 onInit()207 private void onInit() { 208 setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); 209 createAdapter(); 210 mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION, 211 this::createExternalInterface, this); 212 213 initBackAnimationRunners(); 214 } 215 initBackAnimationRunners()216 private void initBackAnimationRunners() { 217 if (!IS_U_ANIMATION_ENABLED) { 218 return; 219 } 220 221 final CrossTaskBackAnimation crossTaskAnimation = 222 new CrossTaskBackAnimation(mContext, mAnimationBackground); 223 mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK, 224 crossTaskAnimation.mBackAnimationRunner); 225 mDefaultActivityAnimation = 226 new CrossActivityAnimation(mContext, mAnimationBackground); 227 mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, 228 mDefaultActivityAnimation.mBackAnimationRunner); 229 mCustomizeActivityAnimation = 230 new CustomizeActivityAnimation(mContext, mAnimationBackground); 231 // TODO (236760237): register dialog close animation when it's completed. 232 } 233 setupAnimationDeveloperSettingsObserver( @onNull ContentResolver contentResolver, @NonNull @ShellBackgroundThread final Handler backgroundHandler)234 private void setupAnimationDeveloperSettingsObserver( 235 @NonNull ContentResolver contentResolver, 236 @NonNull @ShellBackgroundThread final Handler backgroundHandler) { 237 ContentObserver settingsObserver = new ContentObserver(backgroundHandler) { 238 @Override 239 public void onChange(boolean selfChange, Uri uri) { 240 updateEnableAnimationFromSetting(); 241 } 242 }; 243 contentResolver.registerContentObserver( 244 Global.getUriFor(Global.ENABLE_BACK_ANIMATION), 245 false, settingsObserver, UserHandle.USER_SYSTEM 246 ); 247 updateEnableAnimationFromSetting(); 248 } 249 250 @ShellBackgroundThread updateEnableAnimationFromSetting()251 private void updateEnableAnimationFromSetting() { 252 int settingValue = Global.getInt(mContext.getContentResolver(), 253 Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF); 254 boolean isEnabled = settingValue == SETTING_VALUE_ON; 255 mEnableAnimations.set(isEnabled); 256 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); 257 } 258 getBackAnimationImpl()259 public BackAnimation getBackAnimationImpl() { 260 return mBackAnimation; 261 } 262 createExternalInterface()263 private ExternalInterfaceBinder createExternalInterface() { 264 return new IBackAnimationImpl(this); 265 } 266 267 private final BackAnimationImpl mBackAnimation = new BackAnimationImpl(); 268 269 @Override getContext()270 public Context getContext() { 271 return mContext; 272 } 273 274 @Override getRemoteCallExecutor()275 public ShellExecutor getRemoteCallExecutor() { 276 return mShellExecutor; 277 } 278 279 private class BackAnimationImpl implements BackAnimation { 280 @Override onBackMotion( float touchX, float touchY, float velocityX, float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge )281 public void onBackMotion( 282 float touchX, 283 float touchY, 284 float velocityX, 285 float velocityY, 286 int keyAction, 287 @BackEvent.SwipeEdge int swipeEdge 288 ) { 289 mShellExecutor.execute(() -> onMotionEvent( 290 /* touchX = */ touchX, 291 /* touchY = */ touchY, 292 /* velocityX = */ velocityX, 293 /* velocityY = */ velocityY, 294 /* keyAction = */ keyAction, 295 /* swipeEdge = */ swipeEdge)); 296 } 297 298 @Override setTriggerBack(boolean triggerBack)299 public void setTriggerBack(boolean triggerBack) { 300 mShellExecutor.execute(() -> BackAnimationController.this.setTriggerBack(triggerBack)); 301 } 302 303 @Override setSwipeThresholds( float linearDistance, float maxDistance, float nonLinearFactor)304 public void setSwipeThresholds( 305 float linearDistance, 306 float maxDistance, 307 float nonLinearFactor) { 308 mShellExecutor.execute(() -> BackAnimationController.this.setSwipeThresholds( 309 linearDistance, maxDistance, nonLinearFactor)); 310 } 311 312 @Override setStatusBarCustomizer(StatusBarCustomizer customizer)313 public void setStatusBarCustomizer(StatusBarCustomizer customizer) { 314 mCustomizer = customizer; 315 mAnimationBackground.setStatusBarCustomizer(customizer); 316 } 317 } 318 319 private static class IBackAnimationImpl extends IBackAnimation.Stub 320 implements ExternalInterfaceBinder { 321 private BackAnimationController mController; 322 IBackAnimationImpl(BackAnimationController controller)323 IBackAnimationImpl(BackAnimationController controller) { 324 mController = controller; 325 } 326 327 @Override setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner)328 public void setBackToLauncherCallback(IOnBackInvokedCallback callback, 329 IRemoteAnimationRunner runner) { 330 executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback", 331 (controller) -> controller.registerAnimation( 332 BackNavigationInfo.TYPE_RETURN_TO_HOME, 333 new BackAnimationRunner(callback, runner))); 334 } 335 336 @Override clearBackToLauncherCallback()337 public void clearBackToLauncherCallback() { 338 executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback", 339 (controller) -> controller.unregisterAnimation( 340 BackNavigationInfo.TYPE_RETURN_TO_HOME)); 341 } 342 customizeStatusBarAppearance(AppearanceRegion appearance)343 public void customizeStatusBarAppearance(AppearanceRegion appearance) { 344 executeRemoteCallWithTaskPermission(mController, "useLauncherSysBarFlags", 345 (controller) -> controller.customizeStatusBarAppearance(appearance)); 346 } 347 348 @Override invalidate()349 public void invalidate() { 350 mController = null; 351 } 352 } 353 customizeStatusBarAppearance(AppearanceRegion appearance)354 private void customizeStatusBarAppearance(AppearanceRegion appearance) { 355 if (mCustomizer != null) { 356 mCustomizer.customizeStatusBarAppearance(appearance); 357 } 358 } 359 registerAnimation(@ackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner)360 void registerAnimation(@BackNavigationInfo.BackTargetType int type, 361 @NonNull BackAnimationRunner runner) { 362 mAnimationDefinition.set(type, runner); 363 } 364 unregisterAnimation(@ackNavigationInfo.BackTargetType int type)365 void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { 366 mAnimationDefinition.remove(type); 367 } 368 369 /** 370 * Called when a new motion event needs to be transferred to this 371 * {@link BackAnimationController} 372 */ onMotionEvent( float touchX, float touchY, float velocityX, float velocityY, int keyAction, @BackEvent.SwipeEdge int swipeEdge)373 public void onMotionEvent( 374 float touchX, 375 float touchY, 376 float velocityX, 377 float velocityY, 378 int keyAction, 379 @BackEvent.SwipeEdge int swipeEdge) { 380 if (mPostCommitAnimationInProgress) { 381 return; 382 } 383 384 mTouchTracker.update(touchX, touchY, velocityX, velocityY); 385 if (keyAction == MotionEvent.ACTION_DOWN) { 386 if (!mBackGestureStarted) { 387 mShouldStartOnNextMoveEvent = true; 388 } 389 } else if (keyAction == MotionEvent.ACTION_MOVE) { 390 if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) { 391 // Let the animation initialized here to make sure the onPointerDownOutsideFocus 392 // could be happened when ACTION_DOWN, it may change the current focus that we 393 // would access it when startBackNavigation. 394 onGestureStarted(touchX, touchY, swipeEdge); 395 mShouldStartOnNextMoveEvent = false; 396 } 397 onMove(); 398 } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) { 399 ProtoLog.d(WM_SHELL_BACK_PREVIEW, 400 "Finishing gesture with event action: %d", keyAction); 401 if (keyAction == MotionEvent.ACTION_CANCEL) { 402 mTriggerBack = false; 403 } 404 onGestureFinished(true); 405 } 406 } 407 onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge)408 private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) { 409 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted); 410 if (mBackGestureStarted || mBackNavigationInfo != null) { 411 Log.e(TAG, "Animation is being initialized but is already started."); 412 finishBackNavigation(); 413 } 414 415 mTouchTracker.setGestureStartLocation(touchX, touchY, swipeEdge); 416 mBackGestureStarted = true; 417 418 try { 419 mBackNavigationInfo = mActivityTaskManager.startBackNavigation( 420 mNavigationObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null); 421 onBackNavigationInfoReceived(mBackNavigationInfo); 422 } catch (RemoteException remoteException) { 423 Log.e(TAG, "Failed to initAnimation", remoteException); 424 finishBackNavigation(); 425 } 426 } 427 onBackNavigationInfoReceived(@ullable BackNavigationInfo backNavigationInfo)428 private void onBackNavigationInfoReceived(@Nullable BackNavigationInfo backNavigationInfo) { 429 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo); 430 if (backNavigationInfo == null) { 431 Log.e(TAG, "Received BackNavigationInfo is null."); 432 return; 433 } 434 final int backType = backNavigationInfo.getType(); 435 final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(); 436 if (shouldDispatchToAnimator) { 437 if (mAnimationDefinition.contains(backType)) { 438 mAnimationDefinition.get(backType).startGesture(); 439 } else { 440 mActiveCallback = null; 441 } 442 } else { 443 mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback(); 444 dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null)); 445 } 446 } 447 onMove()448 private void onMove() { 449 if (!mBackGestureStarted || mBackNavigationInfo == null || mActiveCallback == null) { 450 return; 451 } 452 453 final BackMotionEvent backEvent = mTouchTracker.createProgressEvent(); 454 dispatchOnBackProgressed(mActiveCallback, backEvent); 455 } 456 injectBackKey()457 private void injectBackKey() { 458 sendBackEvent(KeyEvent.ACTION_DOWN); 459 sendBackEvent(KeyEvent.ACTION_UP); 460 } 461 sendBackEvent(int action)462 private void sendBackEvent(int action) { 463 final long when = SystemClock.uptimeMillis(); 464 final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */, 465 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 466 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 467 InputDevice.SOURCE_KEYBOARD); 468 469 ev.setDisplayId(mContext.getDisplay().getDisplayId()); 470 if (!mContext.getSystemService(InputManager.class) 471 .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { 472 Log.e(TAG, "Inject input event fail"); 473 } 474 } 475 shouldDispatchToAnimator()476 private boolean shouldDispatchToAnimator() { 477 return mEnableAnimations.get() 478 && mBackNavigationInfo != null 479 && mBackNavigationInfo.isPrepareRemoteAnimation(); 480 } 481 dispatchOnBackStarted(IOnBackInvokedCallback callback, BackMotionEvent backEvent)482 private void dispatchOnBackStarted(IOnBackInvokedCallback callback, 483 BackMotionEvent backEvent) { 484 if (callback == null) { 485 return; 486 } 487 try { 488 callback.onBackStarted(backEvent); 489 } catch (RemoteException e) { 490 Log.e(TAG, "dispatchOnBackStarted error: ", e); 491 } 492 } 493 494 495 /** 496 * Allows us to manage the fling gesture, it smoothly animates the current progress value to 497 * the final position, calculated based on the current velocity. 498 * 499 * @param callback the callback to be invoked when the animation ends. 500 */ dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback)501 private void dispatchOrAnimateOnBackInvoked(IOnBackInvokedCallback callback) { 502 if (callback == null) { 503 return; 504 } 505 506 boolean animationStarted = false; 507 508 if (mBackNavigationInfo != null && mBackNavigationInfo.isAnimationCallback()) { 509 510 final BackMotionEvent backMotionEvent = mTouchTracker.createProgressEvent(); 511 if (backMotionEvent != null) { 512 // Constraints - absolute values 513 float minVelocity = mFlingAnimationUtils.getMinVelocityPxPerSecond(); 514 float maxVelocity = mFlingAnimationUtils.getHighVelocityPxPerSecond(); 515 float maxX = mTouchTracker.getMaxDistance(); // px 516 float maxFlingDistance = maxX * MAX_FLING_PROGRESS; // px 517 518 // Current state 519 float currentX = backMotionEvent.getTouchX(); 520 float velocity = MathUtils.constrain(backMotionEvent.getVelocityX(), 521 -maxVelocity, maxVelocity); 522 523 // Target state 524 float animationFaction = velocity / maxVelocity; // value between -1 and 1 525 float flingDistance = animationFaction * maxFlingDistance; // px 526 float endX = MathUtils.constrain(currentX + flingDistance, 0f, maxX); 527 528 if (!Float.isNaN(endX) 529 && currentX != endX 530 && Math.abs(velocity) >= minVelocity) { 531 ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX); 532 533 mFlingAnimationUtils.apply( 534 /* animator = */ animator, 535 /* currValue = */ currentX, 536 /* endValue = */ endX, 537 /* velocity = */ velocity, 538 /* maxDistance = */ maxFlingDistance 539 ); 540 541 animator.addUpdateListener(animation -> { 542 Float animatedValue = (Float) animation.getAnimatedValue(); 543 float progress = mTouchTracker.getProgress(animatedValue); 544 final BackMotionEvent backEvent = mTouchTracker 545 .createProgressEvent(progress); 546 dispatchOnBackProgressed(mActiveCallback, backEvent); 547 }); 548 549 animator.addListener(new AnimatorListenerAdapter() { 550 @Override 551 public void onAnimationEnd(Animator animation) { 552 dispatchOnBackInvoked(callback); 553 } 554 }); 555 animator.start(); 556 animationStarted = true; 557 } 558 } 559 } 560 561 if (!animationStarted) { 562 dispatchOnBackInvoked(callback); 563 } 564 } 565 dispatchOnBackInvoked(IOnBackInvokedCallback callback)566 private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { 567 if (callback == null) { 568 return; 569 } 570 try { 571 callback.onBackInvoked(); 572 } catch (RemoteException e) { 573 Log.e(TAG, "dispatchOnBackInvoked error: ", e); 574 } 575 } 576 dispatchOnBackCancelled(IOnBackInvokedCallback callback)577 private void dispatchOnBackCancelled(IOnBackInvokedCallback callback) { 578 if (callback == null) { 579 return; 580 } 581 try { 582 callback.onBackCancelled(); 583 } catch (RemoteException e) { 584 Log.e(TAG, "dispatchOnBackCancelled error: ", e); 585 } 586 } 587 dispatchOnBackProgressed(IOnBackInvokedCallback callback, BackMotionEvent backEvent)588 private void dispatchOnBackProgressed(IOnBackInvokedCallback callback, 589 BackMotionEvent backEvent) { 590 if (callback == null) { 591 return; 592 } 593 try { 594 callback.onBackProgressed(backEvent); 595 } catch (RemoteException e) { 596 Log.e(TAG, "dispatchOnBackProgressed error: ", e); 597 } 598 } 599 600 /** 601 * Sets to true when the back gesture has passed the triggering threshold, false otherwise. 602 */ setTriggerBack(boolean triggerBack)603 public void setTriggerBack(boolean triggerBack) { 604 if (mPostCommitAnimationInProgress) { 605 return; 606 } 607 mTriggerBack = triggerBack; 608 mTouchTracker.setTriggerBack(triggerBack); 609 } 610 setSwipeThresholds( float linearDistance, float maxDistance, float nonLinearFactor)611 private void setSwipeThresholds( 612 float linearDistance, 613 float maxDistance, 614 float nonLinearFactor) { 615 mTouchTracker.setProgressThresholds(linearDistance, maxDistance, nonLinearFactor); 616 } 617 invokeOrCancelBack()618 private void invokeOrCancelBack() { 619 // Make a synchronized call to core before dispatch back event to client side. 620 // If the close transition happens before the core receives onAnimationFinished, there will 621 // play a second close animation for that transition. 622 if (mBackAnimationFinishedCallback != null) { 623 try { 624 mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack); 625 } catch (RemoteException e) { 626 Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e); 627 } 628 mBackAnimationFinishedCallback = null; 629 } 630 631 if (mBackNavigationInfo != null) { 632 final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); 633 if (mTriggerBack) { 634 dispatchOrAnimateOnBackInvoked(callback); 635 } else { 636 dispatchOnBackCancelled(callback); 637 } 638 } 639 finishBackNavigation(); 640 } 641 642 /** 643 * Called when the gesture is released, then it could start the post commit animation. 644 */ onGestureFinished(boolean fromTouch)645 private void onGestureFinished(boolean fromTouch) { 646 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack); 647 if (!mBackGestureStarted) { 648 finishBackNavigation(); 649 return; 650 } 651 652 if (fromTouch) { 653 // Let touch reset the flag otherwise it will start a new back navigation and refresh 654 // the info when received a new move event. 655 mBackGestureStarted = false; 656 } 657 658 if (mPostCommitAnimationInProgress) { 659 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation is still running"); 660 return; 661 } 662 663 if (mBackNavigationInfo == null) { 664 // No focus window found or core are running recents animation, inject back key as 665 // legacy behavior. 666 if (mTriggerBack) { 667 injectBackKey(); 668 } 669 finishBackNavigation(); 670 return; 671 } 672 673 final int backType = mBackNavigationInfo.getType(); 674 final BackAnimationRunner runner = mAnimationDefinition.get(backType); 675 // Simply trigger and finish back navigation when no animator defined. 676 if (!shouldDispatchToAnimator() || runner == null) { 677 invokeOrCancelBack(); 678 return; 679 } 680 if (runner.isWaitingAnimation()) { 681 ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); 682 // Supposed it is in post commit animation state, and start the timeout to watch 683 // if the animation is ready. 684 mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); 685 return; 686 } else if (runner.isAnimationCancelled()) { 687 invokeOrCancelBack(); 688 return; 689 } 690 startPostCommitAnimation(); 691 } 692 693 /** 694 * Start the phase 2 animation when gesture is released. 695 * Callback to {@link #onBackAnimationFinished} when it is finished or timeout. 696 */ startPostCommitAnimation()697 private void startPostCommitAnimation() { 698 if (mPostCommitAnimationInProgress) { 699 return; 700 } 701 702 mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); 703 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()"); 704 mPostCommitAnimationInProgress = true; 705 mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); 706 707 // The next callback should be {@link #onBackAnimationFinished}. 708 if (mTriggerBack) { 709 dispatchOrAnimateOnBackInvoked(mActiveCallback); 710 } else { 711 dispatchOnBackCancelled(mActiveCallback); 712 } 713 } 714 715 /** 716 * Called when the post commit animation is completed or timeout. 717 * This will trigger the real {@link IOnBackInvokedCallback} behavior. 718 */ 719 @VisibleForTesting onBackAnimationFinished()720 void onBackAnimationFinished() { 721 // Stop timeout runner. 722 mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); 723 mPostCommitAnimationInProgress = false; 724 725 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()"); 726 727 // Trigger the real back. 728 invokeOrCancelBack(); 729 } 730 731 /** 732 * This should be called after the whole back navigation is completed. 733 */ 734 @VisibleForTesting finishBackNavigation()735 void finishBackNavigation() { 736 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); 737 mShouldStartOnNextMoveEvent = false; 738 mTouchTracker.reset(); 739 mActiveCallback = null; 740 // reset to default 741 if (mDefaultActivityAnimation != null 742 && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) { 743 mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, 744 mDefaultActivityAnimation.mBackAnimationRunner); 745 } 746 if (mBackNavigationInfo != null) { 747 mBackNavigationInfo.onBackNavigationFinished(mTriggerBack); 748 mBackNavigationInfo = null; 749 } 750 mTriggerBack = false; 751 } 752 getAnimationRunnerAndInit()753 private BackAnimationRunner getAnimationRunnerAndInit() { 754 int type = mBackNavigationInfo.getType(); 755 // Initiate customized cross-activity animation, or fall back to cross activity animation 756 if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) { 757 final BackNavigationInfo.CustomAnimationInfo animationInfo = 758 mBackNavigationInfo.getCustomAnimationInfo(); 759 if (animationInfo != null && mCustomizeActivityAnimation != null 760 && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) { 761 mAnimationDefinition.get(type).resetWaitingAnimation(); 762 mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, 763 mCustomizeActivityAnimation.mBackAnimationRunner); 764 } 765 } 766 return mAnimationDefinition.get(type); 767 } 768 createAdapter()769 private void createAdapter() { 770 IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { 771 @Override 772 public void onAnimationStart(RemoteAnimationTarget[] apps, 773 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, 774 IBackAnimationFinishedCallback finishedCallback) { 775 mShellExecutor.execute(() -> { 776 if (mBackNavigationInfo == null) { 777 Log.e(TAG, "Lack of navigation info to start animation."); 778 return; 779 } 780 final int type = mBackNavigationInfo.getType(); 781 final BackAnimationRunner runner = getAnimationRunnerAndInit(); 782 if (runner == null) { 783 Log.e(TAG, "Animation didn't be defined for type " 784 + BackNavigationInfo.typeToString(type)); 785 if (finishedCallback != null) { 786 try { 787 finishedCallback.onAnimationFinished(false); 788 } catch (RemoteException e) { 789 Log.w(TAG, "Failed call IBackNaviAnimationController", e); 790 } 791 } 792 return; 793 } 794 mActiveCallback = runner.getCallback(); 795 mBackAnimationFinishedCallback = finishedCallback; 796 797 ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()"); 798 runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute( 799 BackAnimationController.this::onBackAnimationFinished)); 800 801 if (apps.length >= 1) { 802 dispatchOnBackStarted( 803 mActiveCallback, mTouchTracker.createStartEvent(apps[0])); 804 } 805 806 // Dispatch the first progress after animation start for smoothing the initial 807 // animation, instead of waiting for next onMove. 808 final BackMotionEvent backFinish = mTouchTracker.createProgressEvent(); 809 dispatchOnBackProgressed(mActiveCallback, backFinish); 810 if (!mBackGestureStarted) { 811 // if the down -> up gesture happened before animation start, we have to 812 // trigger the uninterruptible transition to finish the back animation. 813 startPostCommitAnimation(); 814 } 815 }); 816 } 817 818 @Override 819 public void onAnimationCancelled() { 820 mShellExecutor.execute(() -> { 821 final BackAnimationRunner runner = mAnimationDefinition.get( 822 mBackNavigationInfo.getType()); 823 if (runner == null) { 824 return; 825 } 826 runner.cancelAnimation(); 827 if (!mBackGestureStarted) { 828 invokeOrCancelBack(); 829 } 830 }); 831 } 832 }; 833 mBackAnimationAdapter = new BackAnimationAdapter(runner); 834 } 835 } 836