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