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