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