Loading core/java/android/view/ISurfaceControlViewHostParent.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.view; import android.view.KeyEvent; import android.view.WindowManager; /** Loading @@ -24,4 +25,6 @@ import android.view.WindowManager; */ oneway interface ISurfaceControlViewHostParent { void updateParams(in WindowManager.LayoutParams[] childAttrs); // To forward the back key event from embedded to host app. void forwardBackKeyToParent(in KeyEvent keyEvent); } core/java/android/view/SurfaceControlViewHost.java +1 −0 Original line number Diff line number Diff line Loading @@ -447,6 +447,7 @@ public class SurfaceControlViewHost { addWindowToken(attrs); view.setLayoutParams(attrs); mViewRoot.setView(view, attrs, null); mViewRoot.setBackKeyCallbackForWindowlessWindow(mWm::forwardBackKeyToParent); } /** Loading core/java/android/view/SurfaceView.java +38 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; import android.graphics.RenderNode; import android.hardware.input.InputManager; import android.os.Build; import android.os.Handler; import android.os.IBinder; Loading Loading @@ -159,6 +160,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private static final boolean DEBUG = false; private static final boolean DEBUG_POSITION = false; private static final long FORWARD_BACK_KEY_TOLERANCE_MS = 100; @UnsupportedAppUsage( maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead") Loading Loading @@ -326,6 +329,41 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall }); } } @Override public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { runOnUiThread(() -> { if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { return; } final ViewRootImpl vri = getViewRootImpl(); if (vri == null) { return; } final InputManager inputManager = mContext.getSystemService(InputManager.class); if (inputManager == null) { return; } // Check that the event was created recently. final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { Log.e(TAG, "Ignore the input event that exceed the tolerance time, " + "exceed " + timeDiff + "ms"); return; } if (inputManager.verifyInputEvent(keyEvent) == null) { Log.e(TAG, "Received invalid input event"); return; } try { vri.processingBackKey(true); vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, true /* processImmediately */); } finally { vri.processingBackKey(false); } }); } }; private final boolean mRtDrivenClipping = Flags.clipSurfaceviews(); Loading core/java/android/view/ViewRootImpl.java +51 −10 Original line number Diff line number Diff line Loading @@ -250,7 +250,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Predicate; /** * The top of a view hierarchy, implementing the needed protocol between View Loading Loading @@ -615,6 +615,13 @@ public final class ViewRootImpl implements ViewParent, boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; // While set, allow this VRI to handle back key without drop it. private boolean mProcessingBackKey; /** * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back * key event host app. */ private Predicate<KeyEvent> mWindowlessBackKeyCallback; public boolean mTraversalScheduled; int mTraversalBarrier; Loading Loading @@ -3189,7 +3196,11 @@ public final class ViewRootImpl implements ViewParent, host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled() // Don't register compat OnBackInvokedCallback for windowless window. // The onBackInvoked event by default should forward to host app, so the // host app can decide the behavior. && mWindowlessBackKeyCallback == null) { // 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 Loading Loading @@ -6655,7 +6666,8 @@ public final class ViewRootImpl implements ViewParent, // Find a reason for dropping or canceling the event. final String reason; if (!mAttachInfo.mHasWindowFocus // The embedded window is focused, allow this VRI to handle back key. if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent)) && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus Loading Loading @@ -6878,11 +6890,21 @@ public final class ViewRootImpl implements ViewParent, // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}. if (isBack(keyEvent) && mContext != null if (isBack(keyEvent)) { if (mWindowlessBackKeyCallback != null) { if (mWindowlessBackKeyCallback.test(keyEvent)) { return keyEvent.getAction() == KeyEvent.ACTION_UP && !keyEvent.isCanceled() ? FINISH_HANDLED : FINISH_NOT_HANDLED; } else { // Unable to forward the back key to host, forward to next stage. return FORWARD; } } else if (mContext != null && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { return doOnBackKeyEvent(keyEvent); } } if (mInputQueue != null) { mInputQueue.sendInputEvent(q.mEvent, q, true, this); Loading Loading @@ -10524,6 +10546,11 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget(); } // Make this VRI able to process back key without drop it. void processingBackKey(boolean processing) { mProcessingBackKey = processing; } /** * Collect and include any ScrollCaptureCallback instances registered with the window. * Loading Loading @@ -11748,13 +11775,18 @@ public final class ViewRootImpl implements ViewParent, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); enqueueInputEvent(ev); enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */); } private void registerCompatOnBackInvokedCallback() { mCompatOnBackInvokedCallback = () -> { try { processingBackKey(true); sendBackKeyEvent(KeyEvent.ACTION_DOWN); sendBackKeyEvent(KeyEvent.ACTION_UP); } finally { processingBackKey(false); } }; if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) { Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher"); Loading Loading @@ -12189,4 +12221,13 @@ public final class ViewRootImpl implements ViewParent, } return false; } /** * Set the default back key callback for windowless window, to forward the back key event * to host app. * MUST NOT call this method for normal window. */ void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) { mWindowlessBackKeyCallback = callback; } } core/java/android/view/WindowlessWindowManager.java +14 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.res.Configuration; Loading Loading @@ -703,4 +704,17 @@ public class WindowlessWindowManager implements IWindowSession { } } } boolean forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { if (mParentInterface == null) { return false; } try { mParentInterface.forwardBackKeyToParent(keyEvent); } catch (RemoteException e) { Log.e(TAG, "Failed to forward back key To Parent: ", e); return false; } return true; } } Loading
core/java/android/view/ISurfaceControlViewHostParent.aidl +3 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.view; import android.view.KeyEvent; import android.view.WindowManager; /** Loading @@ -24,4 +25,6 @@ import android.view.WindowManager; */ oneway interface ISurfaceControlViewHostParent { void updateParams(in WindowManager.LayoutParams[] childAttrs); // To forward the back key event from embedded to host app. void forwardBackKeyToParent(in KeyEvent keyEvent); }
core/java/android/view/SurfaceControlViewHost.java +1 −0 Original line number Diff line number Diff line Loading @@ -447,6 +447,7 @@ public class SurfaceControlViewHost { addWindowToken(attrs); view.setLayoutParams(attrs); mViewRoot.setView(view, attrs, null); mViewRoot.setBackKeyCallbackForWindowlessWindow(mWm::forwardBackKeyToParent); } /** Loading
core/java/android/view/SurfaceView.java +38 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; import android.graphics.RenderNode; import android.hardware.input.InputManager; import android.os.Build; import android.os.Handler; import android.os.IBinder; Loading Loading @@ -159,6 +160,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private static final boolean DEBUG = false; private static final boolean DEBUG_POSITION = false; private static final long FORWARD_BACK_KEY_TOLERANCE_MS = 100; @UnsupportedAppUsage( maxTargetSdk = Build.VERSION_CODES.TIRAMISU, publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead") Loading Loading @@ -326,6 +329,41 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall }); } } @Override public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { runOnUiThread(() -> { if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) { return; } final ViewRootImpl vri = getViewRootImpl(); if (vri == null) { return; } final InputManager inputManager = mContext.getSystemService(InputManager.class); if (inputManager == null) { return; } // Check that the event was created recently. final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime(); if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) { Log.e(TAG, "Ignore the input event that exceed the tolerance time, " + "exceed " + timeDiff + "ms"); return; } if (inputManager.verifyInputEvent(keyEvent) == null) { Log.e(TAG, "Received invalid input event"); return; } try { vri.processingBackKey(true); vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */, true /* processImmediately */); } finally { vri.processingBackKey(false); } }); } }; private final boolean mRtDrivenClipping = Flags.clipSurfaceviews(); Loading
core/java/android/view/ViewRootImpl.java +51 −10 Original line number Diff line number Diff line Loading @@ -250,7 +250,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Predicate; /** * The top of a view hierarchy, implementing the needed protocol between View Loading Loading @@ -615,6 +615,13 @@ public final class ViewRootImpl implements ViewParent, boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; // While set, allow this VRI to handle back key without drop it. private boolean mProcessingBackKey; /** * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back * key event host app. */ private Predicate<KeyEvent> mWindowlessBackKeyCallback; public boolean mTraversalScheduled; int mTraversalBarrier; Loading Loading @@ -3189,7 +3196,11 @@ public final class ViewRootImpl implements ViewParent, host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled() // Don't register compat OnBackInvokedCallback for windowless window. // The onBackInvoked event by default should forward to host app, so the // host app can decide the behavior. && mWindowlessBackKeyCallback == null) { // 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 Loading Loading @@ -6655,7 +6666,8 @@ public final class ViewRootImpl implements ViewParent, // Find a reason for dropping or canceling the event. final String reason; if (!mAttachInfo.mHasWindowFocus // The embedded window is focused, allow this VRI to handle back key. if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent)) && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) { // This is a non-pointer event and the window doesn't currently have input focus Loading Loading @@ -6878,11 +6890,21 @@ public final class ViewRootImpl implements ViewParent, // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}. if (isBack(keyEvent) && mContext != null if (isBack(keyEvent)) { if (mWindowlessBackKeyCallback != null) { if (mWindowlessBackKeyCallback.test(keyEvent)) { return keyEvent.getAction() == KeyEvent.ACTION_UP && !keyEvent.isCanceled() ? FINISH_HANDLED : FINISH_NOT_HANDLED; } else { // Unable to forward the back key to host, forward to next stage. return FORWARD; } } else if (mContext != null && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) { return doOnBackKeyEvent(keyEvent); } } if (mInputQueue != null) { mInputQueue.sendInputEvent(q.mEvent, q, true, this); Loading Loading @@ -10524,6 +10546,11 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget(); } // Make this VRI able to process back key without drop it. void processingBackKey(boolean processing) { mProcessingBackKey = processing; } /** * Collect and include any ScrollCaptureCallback instances registered with the window. * Loading Loading @@ -11748,13 +11775,18 @@ public final class ViewRootImpl implements ViewParent, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); enqueueInputEvent(ev); enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */); } private void registerCompatOnBackInvokedCallback() { mCompatOnBackInvokedCallback = () -> { try { processingBackKey(true); sendBackKeyEvent(KeyEvent.ACTION_DOWN); sendBackKeyEvent(KeyEvent.ACTION_UP); } finally { processingBackKey(false); } }; if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) { Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher"); Loading Loading @@ -12189,4 +12221,13 @@ public final class ViewRootImpl implements ViewParent, } return false; } /** * Set the default back key callback for windowless window, to forward the back key event * to host app. * MUST NOT call this method for normal window. */ void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) { mWindowlessBackKeyCallback = callback; } }
core/java/android/view/WindowlessWindowManager.java +14 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.res.Configuration; Loading Loading @@ -703,4 +704,17 @@ public class WindowlessWindowManager implements IWindowSession { } } } boolean forwardBackKeyToParent(@NonNull KeyEvent keyEvent) { if (mParentInterface == null) { return false; } try { mParentInterface.forwardBackKeyToParent(keyEvent); } catch (RemoteException e) { Log.e(TAG, "Failed to forward back key To Parent: ", e); return false; } return true; } }