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