• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.window;
18 
19 import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
20 
21 import static com.android.window.flags.Flags.predictiveBackSystemOverrideCallback;
22 import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
23 import static com.android.window.flags.Flags.predictiveBackTimestampApi;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.Activity;
28 import android.content.Context;
29 import android.content.ContextWrapper;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.ApplicationInfo;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.content.res.TypedArray;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.os.RemoteException;
38 import android.os.SystemProperties;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.util.TypedValue;
42 import android.view.IWindow;
43 import android.view.IWindowSession;
44 import android.view.ImeBackAnimationController;
45 import android.view.MotionEvent;
46 import android.view.ViewRootImpl;
47 
48 import androidx.annotation.VisibleForTesting;
49 
50 import com.android.internal.R;
51 import com.android.internal.annotations.GuardedBy;
52 
53 import java.io.PrintWriter;
54 import java.lang.ref.WeakReference;
55 import java.util.ArrayList;
56 import java.util.HashMap;
57 import java.util.Objects;
58 import java.util.TreeMap;
59 import java.util.function.BooleanSupplier;
60 import java.util.function.Supplier;
61 
62 /**
63  * Provides window based implementation of {@link OnBackInvokedDispatcher}.
64  * <p>
65  * Callbacks with higher priorities receive back dispatching first.
66  * Within the same priority, callbacks receive back dispatching in the reverse order
67  * in which they are added.
68  * <p>
69  * When the top priority callback is updated, the new callback is propagated to the Window Manager
70  * if the window the instance is associated with has been attached. It is allowed to register /
71  * unregister {@link OnBackInvokedCallback}s before the window is attached, although
72  * callbacks will not receive dispatches until window attachment.
73  *
74  * @hide
75  */
76 public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
77     private IWindowSession mWindowSession;
78     private IWindow mWindow;
79     private ViewRootImpl mViewRoot;
80     @VisibleForTesting
81     public final BackTouchTracker mTouchTracker = new BackTouchTracker();
82     @VisibleForTesting
83     public final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
84     // The handler to run callbacks on.
85     // This should be on the same thread the ViewRootImpl holding this instance is created on.
86     @NonNull
87     private final Handler mHandler;
88     private static final String TAG = "WindowOnBackDispatcher";
89     private static final boolean ENABLE_PREDICTIVE_BACK = SystemProperties
90             .getInt("persist.wm.debug.predictive_back", 1) != 0;
91     private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties
92             .getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0;
93     private static final boolean PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE =
94             SystemProperties.getInt("persist.wm.debug.predictive_back_fallback_window_attribute", 0)
95                     != 0;
96     @Nullable
97     private ImeOnBackInvokedDispatcher mImeDispatcher;
98 
99     @Nullable
100     private ImeBackAnimationController mImeBackAnimationController;
101 
102     @GuardedBy("mLock")
103     /** Convenience hashmap to quickly decide if a callback has been added. */
104     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
105     /** Holds all callbacks by priorities. */
106 
107     @VisibleForTesting
108     @GuardedBy("mLock")
109     public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
110             mOnBackInvokedCallbacks = new TreeMap<>();
111 
112     @VisibleForTesting
113     public OnBackInvokedCallback mSystemNavigationObserverCallback = null;
114 
115     private Checker mChecker;
116     private final Object mLock = new Object();
117     // The threshold for back swipe full progress.
118     private float mBackSwipeLinearThreshold;
119     private float mNonLinearProgressFactor;
120 
WindowOnBackInvokedDispatcher(@onNull Context context, Looper looper)121     public WindowOnBackInvokedDispatcher(@NonNull Context context, Looper looper) {
122         mChecker = new Checker(context);
123         mHandler = new Handler(looper);
124     }
125 
126     /** Updates the dispatcher state on a new {@link MotionEvent}. */
onMotionEvent(MotionEvent ev)127     public void onMotionEvent(MotionEvent ev) {
128         if (!isBackGestureInProgress() || ev == null || ev.getAction() != MotionEvent.ACTION_MOVE) {
129             return;
130         }
131         mTouchTracker.update(ev.getX(), ev.getY());
132         if (mTouchTracker.shouldUpdateStartLocation()) {
133             // Reset the start location on the first event after starting back, so that
134             // the beginning of the animation feels smooth.
135             mTouchTracker.updateStartLocation();
136         }
137         if (!mProgressAnimator.isBackAnimationInProgress()) {
138             return;
139         }
140         final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
141         mProgressAnimator.onBackProgressed(backEvent);
142     }
143 
144     /**
145      * Sends the pending top callback (if one exists) to WM when the view root
146      * is attached a window.
147      */
attachToWindow(@onNull IWindowSession windowSession, @NonNull IWindow window, @Nullable ViewRootImpl viewRoot, @Nullable ImeBackAnimationController imeBackAnimationController)148     public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
149             @Nullable ViewRootImpl viewRoot,
150             @Nullable ImeBackAnimationController imeBackAnimationController) {
151         synchronized (mLock) {
152             mWindowSession = windowSession;
153             mWindow = window;
154             mViewRoot = viewRoot;
155             mImeBackAnimationController = imeBackAnimationController;
156             if (!mAllCallbacks.isEmpty()) {
157                 setTopOnBackInvokedCallback(getTopCallback());
158             }
159         }
160     }
161 
162     /** Detaches the dispatcher instance from its window. */
detachFromWindow()163     public void detachFromWindow() {
164         synchronized (mLock) {
165             clear();
166             mWindow = null;
167             mWindowSession = null;
168             mViewRoot = null;
169             mImeBackAnimationController = null;
170         }
171     }
172 
173     // TODO: Take an Executor for the callback to run on.
174     @Override
registerOnBackInvokedCallback( @riority int priority, @NonNull OnBackInvokedCallback callback)175     public void registerOnBackInvokedCallback(
176             @Priority int priority, @NonNull OnBackInvokedCallback callback) {
177         if (mChecker.checkApplicationCallbackRegistration(priority, callback)) {
178             registerOnBackInvokedCallbackUnchecked(callback, priority);
179         }
180     }
181 
registerSystemNavigationObserverCallback(@onNull OnBackInvokedCallback callback)182     private void registerSystemNavigationObserverCallback(@NonNull OnBackInvokedCallback callback) {
183         synchronized (mLock) {
184             // If callback has already been added as regular callback, remove it.
185             if (mAllCallbacks.containsKey(callback)) {
186                 if (DEBUG) {
187                     Log.i(TAG, "Callback already added. Removing and re-adding it as "
188                             + "system-navigation-observer-callback.");
189                 }
190                 removeCallbackInternal(callback);
191             }
192             mSystemNavigationObserverCallback = callback;
193         }
194     }
195 
196     /**
197      * Register a callback bypassing platform checks. This is used to register compatibility
198      * callbacks.
199      */
registerOnBackInvokedCallbackUnchecked( @onNull OnBackInvokedCallback callback, @Priority int priority)200     public void registerOnBackInvokedCallbackUnchecked(
201             @NonNull OnBackInvokedCallback callback, @Priority int priority) {
202         synchronized (mLock) {
203             if (mImeDispatcher != null) {
204                 mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
205                 return;
206             }
207             if (predictiveBackPrioritySystemNavigationObserver()
208                     && predictiveBackSystemOverrideCallback()) {
209                 if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER
210                         && callback instanceof SystemOverrideOnBackInvokedCallback) {
211                     Log.e(TAG, "System override callbacks cannot be registered to "
212                             + "NAVIGATION_OBSERVER");
213                     return;
214                 }
215             }
216             if (predictiveBackPrioritySystemNavigationObserver()) {
217                 if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
218                     registerSystemNavigationObserverCallback(callback);
219                     return;
220                 }
221             }
222             if (callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
223                 if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback
224                         && mImeBackAnimationController != null) {
225                     // register ImeBackAnimationController instead to play predictive back animation
226                     callback = mImeBackAnimationController;
227                 }
228             }
229 
230             if (!mOnBackInvokedCallbacks.containsKey(priority)) {
231                 mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
232             }
233             ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
234 
235             // If callback has already been added, remove it and re-add it.
236             if (mAllCallbacks.containsKey(callback)) {
237                 if (DEBUG) {
238                     Log.i(TAG, "Callback already added. Removing and re-adding it.");
239                 }
240                 Integer prevPriority = mAllCallbacks.get(callback);
241                 mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
242             }
243             if (mSystemNavigationObserverCallback == callback) {
244                 mSystemNavigationObserverCallback = null;
245                 if (DEBUG) {
246                     Log.i(TAG, "Callback already registered (as system-navigation-observer "
247                             + "callback). Removing and re-adding it.");
248                 }
249             }
250 
251             OnBackInvokedCallback previousTopCallback = getTopCallback();
252             callbacks.add(callback);
253             mAllCallbacks.put(callback, priority);
254             if (previousTopCallback == null
255                     || (previousTopCallback != callback
256                     && mAllCallbacks.get(previousTopCallback) <= priority)) {
257                 setTopOnBackInvokedCallback(callback);
258             }
259         }
260     }
261 
262     @Override
unregisterOnBackInvokedCallback(@onNull OnBackInvokedCallback callback)263     public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
264         synchronized (mLock) {
265             if (mImeDispatcher != null) {
266                 mImeDispatcher.unregisterOnBackInvokedCallback(callback);
267                 return;
268             }
269             if (mSystemNavigationObserverCallback == callback) {
270                 mSystemNavigationObserverCallback = null;
271                 return;
272             }
273             if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
274                 callback = mImeBackAnimationController;
275             }
276             if (!mAllCallbacks.containsKey(callback)) {
277                 if (DEBUG) {
278                     Log.i(TAG, "Callback not found. returning...");
279                 }
280                 return;
281             }
282             removeCallbackInternal(callback);
283         }
284     }
285 
removeCallbackInternal(@onNull OnBackInvokedCallback callback)286     private void removeCallbackInternal(@NonNull OnBackInvokedCallback callback) {
287         OnBackInvokedCallback previousTopCallback = getTopCallback();
288         Integer priority = mAllCallbacks.get(callback);
289         ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
290         callbacks.remove(callback);
291         if (callbacks.isEmpty()) {
292             mOnBackInvokedCallbacks.remove(priority);
293         }
294         mAllCallbacks.remove(callback);
295         // Re-populate the top callback to WM if the removed callback was previously the top
296         // one.
297         if (previousTopCallback == callback) {
298             // We should call onBackCancelled() when an active callback is removed from
299             // dispatcher.
300             mProgressAnimator.removeOnBackCancelledFinishCallback();
301             mProgressAnimator.removeOnBackInvokedFinishCallback();
302             sendCancelledIfInProgress(callback);
303             mHandler.post(mProgressAnimator::reset);
304             setTopOnBackInvokedCallback(getTopCallback());
305         }
306     }
307 
308     /**
309      * Indicates if a user gesture is currently in progress.
310      */
isBackGestureInProgress()311     public boolean isBackGestureInProgress() {
312         synchronized (mLock) {
313             return mTouchTracker.isActive();
314         }
315     }
316 
sendCancelledIfInProgress(@onNull OnBackInvokedCallback callback)317     private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
318         boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
319         if (isInProgress && callback instanceof OnBackAnimationCallback) {
320             OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
321             animatedCallback.onBackCancelled();
322             if (DEBUG) {
323                 Log.d(TAG, "sendCancelIfRunning: callback canceled");
324             }
325         } else {
326             Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
327                     + " callback=" + callback);
328         }
329     }
330 
331     @Override
registerSystemOnBackInvokedCallback(@onNull OnBackInvokedCallback callback)332     public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
333         registerOnBackInvokedCallbackUnchecked(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM);
334     }
335 
336     /** Clears all registered callbacks on the instance. */
clear()337     public void clear() {
338         synchronized (mLock) {
339             if (mImeDispatcher != null) {
340                 mImeDispatcher.clear();
341                 mImeDispatcher = null;
342             }
343             if (!mAllCallbacks.isEmpty()) {
344                 OnBackInvokedCallback topCallback = getTopCallback();
345                 if (topCallback != null) {
346                     sendCancelledIfInProgress(topCallback);
347                 } else {
348                     // Should not be possible
349                     Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
350                 }
351                 // Clear binder references in WM.
352                 setTopOnBackInvokedCallback(null);
353             }
354 
355             // We should also stop running animations since all callbacks have been removed.
356             // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
357             mHandler.post(mProgressAnimator::reset);
358             mAllCallbacks.clear();
359             mOnBackInvokedCallbacks.clear();
360             mSystemNavigationObserverCallback = null;
361         }
362     }
363 
callOnKeyPreIme()364     private boolean callOnKeyPreIme() {
365         if (mViewRoot != null && !isOnBackInvokedCallbackEnabled()) {
366             return mViewRoot.injectBackKeyEvents(/*preImeOnly*/ true);
367         } else {
368             return false;
369         }
370     }
371 
372     /**
373      * Tries to call {@link OnBackInvokedCallback#onBackInvoked} on the system navigation observer
374      * callback (if one is set and if the top-most regular callback has
375      * {@link OnBackInvokedDispatcher#PRIORITY_SYSTEM})
376      */
tryInvokeSystemNavigationObserverCallback()377     public void tryInvokeSystemNavigationObserverCallback() {
378         OnBackInvokedCallback topCallback = getTopCallback();
379         Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
380         final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback;
381         if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) {
382             invokeSystemNavigationObserverCallback();
383         }
384     }
385 
invokeSystemNavigationObserverCallback()386     private void invokeSystemNavigationObserverCallback() {
387         if (mSystemNavigationObserverCallback != null) {
388             mSystemNavigationObserverCallback.onBackInvoked();
389         }
390     }
391 
setTopOnBackInvokedCallback(@ullable OnBackInvokedCallback callback)392     private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
393         if (mWindowSession == null || mWindow == null) {
394             return;
395         }
396         try {
397             OnBackInvokedCallbackInfo callbackInfo = null;
398             if (callback != null) {
399                 int priority = mAllCallbacks.get(callback);
400                 int overrideAnimation = OVERRIDE_UNDEFINED;
401                 if (callback instanceof SystemOverrideOnBackInvokedCallback) {
402                     overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback)
403                             .overrideBehavior();
404                 }
405                 final boolean isSystemCallback = priority == PRIORITY_SYSTEM
406                         || overrideAnimation != OVERRIDE_UNDEFINED;
407                 final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
408                         mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
409                         this::invokeSystemNavigationObserverCallback,
410                         isSystemCallback /*isSystemCallback*/);
411                 callbackInfo = new OnBackInvokedCallbackInfo(
412                         iCallback,
413                         priority,
414                         callback instanceof OnBackAnimationCallback,
415                         overrideAnimation);
416             }
417             mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
418         } catch (RemoteException e) {
419             Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e);
420         }
421     }
422 
getTopCallback()423     public OnBackInvokedCallback getTopCallback() {
424         synchronized (mLock) {
425             if (mAllCallbacks.isEmpty()) {
426                 return null;
427             }
428             for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
429                 ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
430                 if (!callbacks.isEmpty()) {
431                     return callbacks.get(callbacks.size() - 1);
432                 }
433             }
434         }
435         return null;
436     }
437 
438     /**
439      * The {@link Context} in ViewRootImp and Activity could be different, this will make sure it
440      * could update the checker condition base on the real context when binding the proxy
441      * dispatcher in PhoneWindow.
442      */
updateContext(@onNull Context context)443     public void updateContext(@NonNull Context context) {
444         mChecker = new Checker(context);
445         // Set swipe threshold values.
446         Resources res = context.getResources();
447         mBackSwipeLinearThreshold =
448                 res.getDimension(R.dimen.navigation_edge_action_progress_threshold);
449         TypedValue typedValue = new TypedValue();
450         res.getValue(R.dimen.back_progress_non_linear_factor, typedValue, true);
451         mNonLinearProgressFactor = typedValue.getFloat();
452         onConfigurationChanged(context.getResources().getConfiguration());
453     }
454 
455     /** Updates the threshold values for computing progress. */
onConfigurationChanged(Configuration configuration)456     public void onConfigurationChanged(Configuration configuration) {
457         float maxDistance = configuration.windowConfiguration.getMaxBounds().width();
458         float linearDistance = Math.min(maxDistance, mBackSwipeLinearThreshold);
459         mTouchTracker.setProgressThresholds(
460                 linearDistance, maxDistance, mNonLinearProgressFactor);
461     }
462 
463     /**
464      * Returns false if the legacy back behavior should be used.
465      */
isOnBackInvokedCallbackEnabled()466     public boolean isOnBackInvokedCallbackEnabled() {
467         final Context hostContext = mChecker.getContext();
468         if (hostContext == null) {
469             Log.w(TAG, "OnBackInvokedCallback is disabled, host context is removed!");
470             return false;
471         }
472         return isOnBackInvokedCallbackEnabled(hostContext);
473     }
474 
475     /**
476      * Dump information about this WindowOnBackInvokedDispatcher
477      * @param prefix the prefix that will be prepended to each line of the produced output
478      * @param writer the writer that will receive the resulting text
479      */
dump(String prefix, PrintWriter writer)480     public void dump(String prefix, PrintWriter writer) {
481         String innerPrefix = prefix + "    ";
482         writer.println(prefix + "WindowOnBackDispatcher:");
483         synchronized (mLock) {
484             if (mAllCallbacks.isEmpty()) {
485                 writer.println(prefix + "<None>");
486                 return;
487             }
488 
489             writer.println(innerPrefix + "Top Callback: " + getTopCallback());
490             writer.println(innerPrefix + "Callbacks: ");
491             mAllCallbacks.forEach((callback, priority) -> {
492                 writer.println(innerPrefix + "  Callback: " + callback + " Priority=" + priority);
493             });
494         }
495     }
496 
497     private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
498         @NonNull
499         private final WeakReference<OnBackInvokedCallback> mCallback;
500         @NonNull
501         private final BackProgressAnimator mProgressAnimator;
502         @NonNull
503         private final BackTouchTracker mTouchTracker;
504         @NonNull
505         private final Handler mHandler;
506         @NonNull
507         private final BooleanSupplier mOnKeyPreIme;
508         @NonNull
509         private final Runnable mSystemNavigationObserverCallbackRunnable;
510         private final boolean mIsSystemCallback;
511 
OnBackInvokedCallbackWrapper( @onNull OnBackInvokedCallback callback, @NonNull BackTouchTracker touchTracker, @NonNull BackProgressAnimator progressAnimator, @NonNull Handler handler, @NonNull BooleanSupplier onKeyPreIme, @NonNull Runnable systemNavigationObserverCallbackRunnable, boolean isSystemCallback )512         OnBackInvokedCallbackWrapper(
513                 @NonNull OnBackInvokedCallback callback,
514                 @NonNull BackTouchTracker touchTracker,
515                 @NonNull BackProgressAnimator progressAnimator,
516                 @NonNull Handler handler,
517                 @NonNull BooleanSupplier onKeyPreIme,
518                 @NonNull Runnable systemNavigationObserverCallbackRunnable,
519                 boolean isSystemCallback
520         ) {
521             mCallback = new WeakReference<>(callback);
522             mTouchTracker = touchTracker;
523             mProgressAnimator = progressAnimator;
524             mHandler = handler;
525             mOnKeyPreIme = onKeyPreIme;
526             mSystemNavigationObserverCallbackRunnable = systemNavigationObserverCallbackRunnable;
527             mIsSystemCallback = isSystemCallback;
528         }
529 
530         @Override
onBackStarted(BackMotionEvent backEvent)531         public void onBackStarted(BackMotionEvent backEvent) {
532             mHandler.post(() -> {
533                 final OnBackAnimationCallback callback = getBackAnimationCallback();
534 
535                 // reset progress animator before dispatching onBackStarted to callback. This
536                 // ensures that onBackCancelled (of a previous gesture) is always dispatched
537                 // before onBackStarted
538                 if (callback != null && mProgressAnimator.isBackAnimationInProgress()) {
539                     mProgressAnimator.reset();
540                 }
541                 mTouchTracker.setState(BackTouchTracker.TouchTrackerState.ACTIVE);
542                 mTouchTracker.setShouldUpdateStartLocation(true);
543                 mTouchTracker.setGestureStartLocation(
544                         backEvent.getTouchX(), backEvent.getTouchY(), backEvent.getSwipeEdge());
545 
546                 if (callback != null) {
547                     callback.onBackStarted(BackEvent.fromBackMotionEvent(backEvent));
548                     mProgressAnimator.onBackStarted(backEvent, callback::onBackProgressed);
549                 }
550             });
551         }
552 
553         @Override
setHandoffHandler(IBackAnimationHandoffHandler handoffHandler)554         public void setHandoffHandler(IBackAnimationHandoffHandler handoffHandler) {
555             // no-op
556         }
557 
558         @Override
onBackProgressed(BackMotionEvent backEvent)559         public void onBackProgressed(BackMotionEvent backEvent) {
560             // This is only called in some special cases such as when activity embedding is active
561             // or when the activity is letterboxed. Otherwise mProgressAnimator#onBackProgressed is
562             // called from WindowOnBackInvokedDispatcher#onMotionEvent
563             mHandler.post(() -> {
564                 if (getBackAnimationCallback() != null) {
565                     mProgressAnimator.onBackProgressed(backEvent);
566                 }
567             });
568         }
569 
570         @Override
onBackCancelled()571         public void onBackCancelled() {
572             mHandler.post(() -> {
573                 final OnBackAnimationCallback callback = getBackAnimationCallback();
574                 mTouchTracker.reset();
575                 if (callback == null) return;
576                 mProgressAnimator.onBackCancelled(callback::onBackCancelled);
577             });
578         }
579 
580         @Override
onBackInvoked()581         public void onBackInvoked() throws RemoteException {
582             mHandler.post(() -> {
583                 mTouchTracker.reset();
584                 if (consumedByOnKeyPreIme()) return;
585                 boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
586                 final OnBackInvokedCallback callback = mCallback.get();
587                 if (callback == null) {
588                     mProgressAnimator.reset();
589                     Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference.");
590                     return;
591                 }
592                 if (callback instanceof OnBackAnimationCallback && !isInProgress) {
593                     Log.w(TAG, "ProgressAnimator was not in progress, skip onBackInvoked().");
594                     return;
595                 }
596                 OnBackAnimationCallback animationCallback = getBackAnimationCallback();
597                 if (animationCallback != null
598                         && !(callback instanceof ImeBackAnimationController)
599                         && !predictiveBackTimestampApi()) {
600                     mProgressAnimator.onBackInvoked(() -> {
601                         if (mIsSystemCallback) {
602                             mSystemNavigationObserverCallbackRunnable.run();
603                         }
604                         callback.onBackInvoked();
605                     });
606                 } else {
607                     mProgressAnimator.reset();
608                     if (mIsSystemCallback) {
609                         mSystemNavigationObserverCallbackRunnable.run();
610                     }
611                     callback.onBackInvoked();
612                 }
613             });
614         }
615 
consumedByOnKeyPreIme()616         private boolean consumedByOnKeyPreIme() {
617             final OnBackInvokedCallback callback = mCallback.get();
618             if (callback instanceof ImeBackAnimationController
619                     || callback instanceof ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) {
620                 // call onKeyPreIme API if the current callback is an IME callback and the app has
621                 // not set enableOnBackInvokedCallback="true"
622                 try {
623                     boolean consumed = mOnKeyPreIme.getAsBoolean();
624                     if (consumed) {
625                         // back event intercepted by app in onKeyPreIme -> cancel the IME animation.
626                         final OnBackAnimationCallback animationCallback =
627                                 getBackAnimationCallback();
628                         if (animationCallback != null) {
629                             mProgressAnimator.onBackCancelled(animationCallback::onBackCancelled);
630                         }
631                         return true;
632                     }
633                 } catch (Exception e) {
634                     Log.d(TAG, "Failed to call onKeyPreIme", e);
635                 }
636             }
637             return false;
638         }
639 
640         @Override
setTriggerBack(boolean triggerBack)641         public void setTriggerBack(boolean triggerBack) throws RemoteException {
642             mTouchTracker.setTriggerBack(triggerBack);
643         }
644 
645         @Nullable
getBackAnimationCallback()646         private OnBackAnimationCallback getBackAnimationCallback() {
647             OnBackInvokedCallback callback = mCallback.get();
648             return callback instanceof OnBackAnimationCallback ? (OnBackAnimationCallback) callback
649                     : null;
650         }
651     }
652 
653     /**
654      * Returns false if the legacy back behavior should be used.
655      * <p>
656      * Legacy back behavior dispatches KEYCODE_BACK instead of invoking the application registered
657      * {@link OnBackInvokedCallback}.
658      */
isOnBackInvokedCallbackEnabled(@onNull Context context)659     public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) {
660         final Context originalContext = context;
661         while ((context instanceof ContextWrapper) && !(context instanceof Activity)) {
662             context = ((ContextWrapper) context).getBaseContext();
663         }
664         final ActivityInfo activityInfo = (context instanceof Activity)
665                 ? ((Activity) context).getActivityInfo() : null;
666         final ApplicationInfo applicationInfo = context.getApplicationInfo();
667 
668         return WindowOnBackInvokedDispatcher
669                 .isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo,
670                         () -> originalContext);
671     }
672 
673     @Override
setImeOnBackInvokedDispatcher( @onNull ImeOnBackInvokedDispatcher imeDispatcher)674     public void setImeOnBackInvokedDispatcher(
675             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
676         mImeDispatcher = imeDispatcher;
677         mImeDispatcher.setHandler(mHandler);
678     }
679 
680     /** Returns true if a non-null {@link ImeOnBackInvokedDispatcher} has been set. **/
hasImeOnBackInvokedDispatcher()681     public boolean hasImeOnBackInvokedDispatcher() {
682         return mImeDispatcher != null;
683     }
684 
685     /**
686      * Class used to check whether a callback can be registered or not. This is meant to be
687      * shared with {@link ProxyOnBackInvokedDispatcher} which needs to do the same checks.
688      */
689     public static class Checker {
690         private WeakReference<Context> mContext;
691 
Checker(@onNull Context context)692         public Checker(@NonNull Context context) {
693             mContext = new WeakReference<>(context);
694         }
695 
696         /**
697          * Checks whether the given callback can be registered with the given priority.
698          * @return true if the callback can be added.
699          * @throws IllegalArgumentException if the priority is negative.
700          */
checkApplicationCallbackRegistration(int priority, OnBackInvokedCallback callback)701         public boolean checkApplicationCallbackRegistration(int priority,
702                 OnBackInvokedCallback callback) {
703             final Context hostContext = getContext();
704             if (hostContext == null) {
705                 Log.w(TAG, "OnBackInvokedCallback is disabled, host context is removed!");
706                 return false;
707             }
708             if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext)
709                     && !(callback instanceof CompatOnBackInvokedCallback)) {
710                 Log.w(TAG,
711                         "OnBackInvokedCallback is not enabled for the application."
712                                 + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the"
713                                 + " application manifest.");
714                 return false;
715             }
716             if (predictiveBackPrioritySystemNavigationObserver()) {
717                 if (priority < 0 && priority != PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
718                     throw new IllegalArgumentException("Application registered "
719                             + "OnBackInvokedCallback cannot have negative priority. Priority: "
720                             + priority);
721                 }
722             } else {
723                 if (priority < 0) {
724                     throw new IllegalArgumentException("Application registered "
725                             + "OnBackInvokedCallback cannot have negative priority. Priority: "
726                             + priority);
727                 }
728             }
729             Objects.requireNonNull(callback);
730             return true;
731         }
732 
getContext()733         @Nullable private Context getContext() {
734             return mContext.get();
735         }
736     }
737 
738     /**
739      * @hide
740      */
isOnBackInvokedCallbackEnabled(@ullable ActivityInfo activityInfo, @NonNull ApplicationInfo applicationInfo, @NonNull Supplier<Context> contextSupplier)741     public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo,
742             @NonNull ApplicationInfo applicationInfo,
743             @NonNull Supplier<Context> contextSupplier) {
744         // new back is enabled if the feature flag is enabled AND the app does not explicitly
745         // request legacy back.
746         if (!ENABLE_PREDICTIVE_BACK) {
747             return false;
748         }
749 
750         if (ALWAYS_ENFORCE_PREDICTIVE_BACK) {
751             return true;
752         }
753 
754         boolean requestsPredictiveBack;
755         // Activity
756         if (activityInfo != null && activityInfo.hasOnBackInvokedCallbackEnabled()) {
757             requestsPredictiveBack = activityInfo.isOnBackInvokedCallbackEnabled();
758             if (DEBUG) {
759                 Log.d(TAG, TextUtils.formatSimple(
760                         "Activity: %s isPredictiveBackEnabled=%s",
761                         activityInfo.getComponentName(),
762                         requestsPredictiveBack));
763             }
764             return requestsPredictiveBack;
765         }
766 
767         // Application
768         requestsPredictiveBack = applicationInfo.isOnBackInvokedCallbackEnabled();
769         if (DEBUG) {
770             Log.d(TAG, TextUtils.formatSimple("App: %s requestsPredictiveBack=%s",
771                     applicationInfo.packageName,
772                     requestsPredictiveBack));
773         }
774         if (requestsPredictiveBack) {
775             return true;
776         }
777 
778         if (PREDICTIVE_BACK_FALLBACK_WINDOW_ATTRIBUTE) {
779             // Compatibility check for legacy window style flag used by Wear OS.
780             // Note on compatibility behavior:
781             // 1. windowSwipeToDismiss should be respected for all apps not opted in.
782             // 2. windowSwipeToDismiss should be true for all apps not opted in, which
783             //    enables the PB animation for them.
784             // 3. windowSwipeToDismiss=false should be respected for apps not opted in,
785             //    which disables PB & onBackPressed caused by BackAnimController's
786             //    setTrigger(true)
787             // Use the original context to resolve the styled attribute so that they stay
788             // true to the window.
789             final Context context = contextSupplier.get();
790             boolean windowSwipeToDismiss = true;
791             if (context != null) {
792                 final TypedArray array = context.obtainStyledAttributes(
793                             new int[]{android.R.attr.windowSwipeToDismiss});
794                 if (array.getIndexCount() > 0) {
795                     windowSwipeToDismiss = array.getBoolean(0, true);
796                 }
797                 array.recycle();
798             }
799 
800             if (DEBUG) {
801                 Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss);
802             }
803 
804             requestsPredictiveBack = windowSwipeToDismiss;
805         }
806         return requestsPredictiveBack;
807     }
808 }
809