Merge "Add compatibility back callbacks."

This commit is contained in:
Shan Huang
2022-02-05 06:31:15 +00:00
committed by Android (Google) Code Review
8 changed files with 234 additions and 34 deletions

View File

@@ -48950,10 +48950,9 @@ package android.view {
method public default void onBackInvoked();
}
public abstract class OnBackInvokedDispatcher {
ctor public OnBackInvokedDispatcher();
method public abstract void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, int);
method public abstract void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback);
public interface OnBackInvokedDispatcher {
method public void registerOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback, @IntRange(from=0) int);
method public void unregisterOnBackInvokedCallback(@NonNull android.view.OnBackInvokedCallback);
field public static final int PRIORITY_DEFAULT = 0; // 0x0
field public static final int PRIORITY_OVERLAY = 1000000; // 0xf4240
}

View File

@@ -2802,6 +2802,10 @@ package android.view {
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
}
public interface OnBackInvokedDispatcher {
field public static final long DISPATCH_BACK_INVOCATION_AHEAD_OF_TIME = 195946584L; // 0xbade858L
}
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod {
method public abstract String asyncImpl() default "";
}

View File

@@ -115,6 +115,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.OnBackInvokedCallback;
import android.view.OnBackInvokedDispatcher;
import android.view.OnBackInvokedDispatcherOwner;
import android.view.RemoteAnimationDefinition;
@@ -144,6 +145,7 @@ import android.widget.AdapterView;
import android.widget.Toast;
import android.widget.Toolbar;
import android.window.SplashScreen;
import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -791,6 +793,7 @@ public class Activity extends ContextThemeWrapper
private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062;
private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
private OnBackInvokedCallback mDefaultBackCallback;
/**
* After {@link Build.VERSION_CODES#TIRAMISU},
@@ -1617,7 +1620,16 @@ public class Activity extends ContextThemeWrapper
}
mRestoredFromBundle = savedInstanceState != null;
mCalled = true;
if (!WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) {
// Add onBackPressed as default back behavior.
mDefaultBackCallback = new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
navigateBack();
}
};
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
}
}
/**
@@ -2653,6 +2665,10 @@ public class Activity extends ContextThemeWrapper
if (mUiTranslationController != null) {
mUiTranslationController.onActivityDestroyed();
}
if (mDefaultBackCallback != null) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
}
}
/**
@@ -3773,10 +3789,13 @@ public class Activity extends ContextThemeWrapper
* @see KeyEvent
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
int sdkVersion = getApplicationInfo().targetSdkVersion;
if (sdkVersion >= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.isTracking()
&& !event.isCanceled()
&& mDefaultBackCallback == null) {
// Using legacy back handling.
onBackPressed();
return true;
}
@@ -3841,6 +3860,10 @@ public class Activity extends ContextThemeWrapper
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
navigateBack();
}
private void navigateBack() {
if (!isTaskRoot()) {
// If the activity is not the root of the task, allow finish to proceed normally.
finishAfterTransition();

View File

@@ -52,6 +52,7 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.OnBackInvokedCallback;
import android.view.OnBackInvokedDispatcher;
import android.view.OnBackInvokedDispatcherOwner;
import android.view.SearchEvent;
@@ -62,6 +63,7 @@ import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.R;
import com.android.internal.app.WindowDecorActionBar;
@@ -156,6 +158,7 @@ public class Dialog implements DialogInterface, Window.Callback,
/** A {@link Runnable} to run instead of dismissing when {@link #dismiss()} is called. */
private Runnable mDismissOverride;
private OnBackInvokedCallback mDefaultBackCallback;
/**
* Creates a dialog window that uses the default dialog theme.
@@ -453,6 +456,16 @@ public class Dialog implements DialogInterface, Window.Callback,
*/
protected void onStart() {
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
if (mContext != null && !WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) {
// Add onBackPressed as default back behavior.
mDefaultBackCallback = new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
onBackPressed();
}
};
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
}
}
/**
@@ -460,6 +473,9 @@ public class Dialog implements DialogInterface, Window.Callback,
*/
protected void onStop() {
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
if (mDefaultBackCallback != null) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mDefaultBackCallback);
}
}
private static final String DIALOG_SHOWING_TAG = "android:dialogShowing";
@@ -685,7 +701,8 @@ public class Dialog implements DialogInterface, Window.Callback,
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
&& event.isTracking()
&& !event.isCanceled()) {
&& !event.isCanceled()
&& WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) {
onBackPressed();
return true;
}

View File

@@ -17,8 +17,13 @@
package android.view;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.os.Build;
import java.lang.annotation.Retention;
@@ -32,13 +37,29 @@ import java.lang.annotation.RetentionPolicy;
* Attribute updates are proactively pushed to the window manager if they change the dispatch
* target (a.k.a. the callback to be invoked next), or its behavior.
*/
public abstract class OnBackInvokedDispatcher {
public interface OnBackInvokedDispatcher {
/**
* Enables dispatching the "back" action via {@link android.view.OnBackInvokedDispatcher}.
*
* When enabled, the following APIs are no longer invoked:
* <ul>
* <li> {@link android.app.Activity#onBackPressed}
* <li> {@link android.app.Dialog#onBackPressed}
* <li> {@link android.view.KeyEvent#KEYCODE_BACK} is no longer dispatched.
* </ul>
*
* @hide
*/
@TestApi
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
long DISPATCH_BACK_INVOCATION_AHEAD_OF_TIME = 195946584L;
/** @hide */
public static final String TAG = "OnBackInvokedDispatcher";
String TAG = "OnBackInvokedDispatcher";
/** @hide */
public static final boolean DEBUG = Build.isDebuggable();
boolean DEBUG = Build.isDebuggable();
/** @hide */
@IntDef({
@@ -46,18 +67,26 @@ public abstract class OnBackInvokedDispatcher {
PRIORITY_OVERLAY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Priority{}
@interface Priority{}
/**
* Priority level of {@link OnBackInvokedCallback}s for overlays such as menus and
* navigation drawers that should receive back dispatch before non-overlays.
*/
public static final int PRIORITY_OVERLAY = 1000000;
int PRIORITY_OVERLAY = 1000000;
/**
* Default priority level of {@link OnBackInvokedCallback}s.
*/
public static final int PRIORITY_DEFAULT = 0;
int PRIORITY_DEFAULT = 0;
/**
* Priority level of {@link OnBackInvokedCallback}s registered by the system.
*
* System back animation will play when the callback to receive dispatch has this priority.
* @hide
*/
int PRIORITY_SYSTEM = -1;
/**
* Registers a {@link OnBackInvokedCallback}.
@@ -69,10 +98,11 @@ public abstract class OnBackInvokedDispatcher {
* registered, the existing instance (no matter its priority) will be
* unregistered and registered again.
* @param priority The priority of the callback.
* @throws {@link IllegalArgumentException} if the priority is negative.
*/
@SuppressLint("SamShouldBeLast")
public abstract void registerOnBackInvokedCallback(
@NonNull OnBackInvokedCallback callback, @Priority int priority);
void registerOnBackInvokedCallback(
@NonNull OnBackInvokedCallback callback, @Priority @IntRange(from = 0) int priority);
/**
* Unregisters a {@link OnBackInvokedCallback}.
@@ -80,5 +110,20 @@ public abstract class OnBackInvokedDispatcher {
* @param callback The callback to be unregistered. Does nothing if the callback has not been
* registered.
*/
public abstract void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback);
void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback);
/**
* Returns the most prioritized callback to receive back dispatch next.
* @hide
*/
@Nullable
default OnBackInvokedCallback getTopCallback() {
return null;
}
/**
* Registers a {@link OnBackInvokedCallback} with system priority.
* @hide
*/
default void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { }
}

View File

@@ -317,6 +317,11 @@ public final class ViewRootImpl implements ViewParent,
*/
private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher =
new WindowOnBackInvokedDispatcher();
/**
* Compatibility {@link OnBackInvokedCallback} that dispatches KEYCODE_BACK events
* to view root for apps using legacy back behavior.
*/
private OnBackInvokedCallback mCompatOnBackInvokedCallback;
/**
* Callback for notifying about global configuration changes.
@@ -1190,6 +1195,14 @@ public final class ViewRootImpl implements ViewParent,
mTmpFrames.displayFrame, mTempRect2, mTmpFrames.frame);
setFrame(mTmpFrames.frame);
registerBackCallbackOnWindow();
if (WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) {
// For apps requesting legacy back behavior, we add a compat callback that
// dispatches {@link KeyEvent#KEYCODE_BACK} to their root views.
// This way from system point of view, these apps are providing custom
// {@link OnBackInvokedCallback}s, and will not play system back animations
// for them.
registerCompatOnBackInvokedCallback();
}
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
@@ -5931,7 +5944,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
private boolean isBack(InputEvent event) {
boolean isBack(InputEvent event) {
if (event instanceof KeyEvent) {
return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK;
} else {
@@ -6471,6 +6484,19 @@ public final class ViewRootImpl implements ViewParent,
return FINISH_NOT_HANDLED;
}
if (isBack(event)
&& mContext != null
&& !WindowOnBackInvokedDispatcher.shouldUseLegacyBack()) {
// Invoke the appropriate {@link OnBackInvokedCallback} if the new back
// navigation should be used, and the key event is not handled by anything else.
OnBackInvokedCallback topCallback =
getOnBackInvokedDispatcher().getTopCallback();
if (topCallback != null) {
topCallback.onBackInvoked();
return FINISH_HANDLED;
}
}
// This dispatch is for windows that don't have a Window.Callback. Otherwise,
// the Window.Callback usually will have already called this (see
// DecorView.superDispatchKeyEvent) leaving this call a no-op.
@@ -8414,6 +8440,7 @@ public final class ViewRootImpl implements ViewParent,
mAdded = false;
}
unregisterCompatOnBackInvokedCallback();
mOnBackInvokedDispatcher.detachFromWindow();
WindowManagerGlobal.getInstance().doRemoveView(this);
}
@@ -10782,6 +10809,38 @@ public final class ViewRootImpl implements ViewParent,
mOnBackInvokedDispatcher.attachToWindow(mWindowSession, mWindow);
}
private void sendBackKeyEvent(int action) {
long when = SystemClock.uptimeMillis();
final KeyEvent ev = new KeyEvent(when, when, action,
KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
ev.setDisplayId(mContext.getDisplay().getDisplayId());
if (mView != null) {
mView.dispatchKeyEvent(ev);
}
}
private void registerCompatOnBackInvokedCallback() {
mCompatOnBackInvokedCallback = new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
sendBackKeyEvent(KeyEvent.ACTION_DOWN);
sendBackKeyEvent(KeyEvent.ACTION_UP);
}
};
mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
mCompatOnBackInvokedCallback, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
}
private void unregisterCompatOnBackInvokedCallback() {
if (mCompatOnBackInvokedCallback != null) {
mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback);
}
}
@Override
public void setTouchableRegion(Region r) {
if (r != null) {

View File

@@ -42,7 +42,7 @@ import java.util.List;
*
* @hide
*/
public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
/**
* List of pair representing an {@link OnBackInvokedCallback} and its associated priority.
@@ -60,14 +60,16 @@ public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
Log.v(TAG, String.format("Pending register %s. Actual=%s", callback,
mActualDispatcherOwner));
}
synchronized (mLock) {
mCallbacks.add(Pair.create(callback, priority));
if (mActualDispatcherOwner != null) {
mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
callback, priority);
}
if (priority < 0) {
throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
+ "cannot have negative priority. Priority: " + priority);
}
registerOnBackInvokedCallbackUnchecked(callback, priority);
}
@Override
public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
registerOnBackInvokedCallbackUnchecked(callback, PRIORITY_SYSTEM);
}
@Override
@@ -82,6 +84,17 @@ public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
}
}
private void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, int priority) {
synchronized (mLock) {
mCallbacks.add(Pair.create(callback, priority));
if (mActualDispatcherOwner != null) {
mActualDispatcherOwner.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
callback, priority);
}
}
}
/**
* Transfers all the pending callbacks to the provided dispatcher.
* <p>
@@ -99,8 +112,12 @@ public class ProxyOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
dispatcher));
}
for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) {
dispatcher.registerOnBackInvokedCallback(callbackPair.first,
callbackPair.second);
int priority = callbackPair.second;
if (priority >= 0) {
dispatcher.registerOnBackInvokedCallback(callbackPair.first, priority);
} else {
dispatcher.registerSystemOnBackInvokedCallback(callbackPair.first);
}
}
mCallbacks.clear();
}

View File

@@ -18,8 +18,10 @@ package android.window;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Log;
import android.view.IWindow;
import android.view.IWindowSession;
@@ -31,7 +33,7 @@ import java.util.HashMap;
import java.util.TreeMap;
/**
* Provides window based implementation of {@link OnBackInvokedDispatcher}.
* Provides window based implementation of {@link android.view.OnBackInvokedDispatcher}.
*
* Callbacks with higher priorities receive back dispatching first.
* Within the same priority, callbacks receive back dispatching in the reverse order
@@ -39,16 +41,19 @@ import java.util.TreeMap;
*
* When the top priority callback is updated, the new callback is propagated to the Window Manager
* if the window the instance is associated with has been attached. It is allowed to register /
* unregister {@link OnBackInvokedCallback}s before the window is attached, although callbacks
* will not receive dispatches until window attachment.
* unregister {@link android.view.OnBackInvokedCallback}s before the window is attached, although
* callbacks will not receive dispatches until window attachment.
*
* @hide
*/
public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private IWindowSession mWindowSession;
private IWindow mWindow;
private static final String TAG = "WindowOnBackDispatcher";
private static final boolean DEBUG = false;
private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability";
private static final boolean IS_BACK_PREDICTABILITY_ENABLED = SystemProperties
.getInt(BACK_PREDICTABILITY_PROP, 0) > 0;
/** The currently most prioritized callback. */
@Nullable
@@ -82,6 +87,15 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
@Override
public void registerOnBackInvokedCallback(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
if (priority < 0) {
throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
+ "cannot have negative priority. Priority: " + priority);
}
registerOnBackInvokedCallbackUnchecked(callback, priority);
}
private void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
if (!mOnBackInvokedCallbacks.containsKey(priority)) {
mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
}
@@ -120,6 +134,11 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
}
}
@Override
public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
registerOnBackInvokedCallbackUnchecked(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM);
}
/** Clears all registered callbacks on the instance. */
public void clear() {
mAllCallbacks.clear();
@@ -198,4 +217,21 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher {
Handler.getMain().post(() -> mCallback.onBackInvoked());
}
}
@Override
public OnBackInvokedCallback getTopCallback() {
return mTopCallback == null ? null : mTopCallback.getCallback();
}
/**
* Returns if the legacy back behavior should be used.
*
* Legacy back behavior dispatches KEYCODE_BACK instead of invoking the application registered
* {@link android.view.OnBackInvokedCallback}.
*
*/
public static boolean shouldUseLegacyBack() {
return !CompatChanges.isChangeEnabled(DISPATCH_BACK_INVOCATION_AHEAD_OF_TIME)
|| !IS_BACK_PREDICTABILITY_ENABLED;
}
}