Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39a2a6d9 authored by wilsonshih's avatar wilsonshih
Browse files

Forward back key event to host app for SCVH.

Forward the back event to host app for SCVH by default. So host app
can decide what to do, also app developer can register their own
callback if they want.

And since the VRI of host window will not have mHasWindowFocus, set
mProcessingBackKey flag to make sure the VRI won't drop back key event
while dispatch back key event to it. This would be needed either back
event is triggered from gesture or back key.

Bug: 305924324
Test: force enable OnBackInvokedCallbackEnabled, verify the host
activity can be closed when back event is dispatch to remote view.
Test: disable OnBackInvokedCallbackEnabled, verify above test pass.
Test: also verify above cases can pass when either trigger from gesture
or back key.

Change-Id: I50bf5a259b2c80aab896d4c7fc38e28e0b22b252
parent 2559674d
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view;

import android.view.KeyEvent;
import android.view.WindowManager;

/**
@@ -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);
}
+1 −0
Original line number Diff line number Diff line
@@ -447,6 +447,7 @@ public class SurfaceControlViewHost {
        addWindowToken(attrs);
        view.setLayoutParams(attrs);
        mViewRoot.setView(view, attrs, null);
        mViewRoot.setBackKeyCallbackForWindowlessWindow(mWm::forwardBackKeyToParent);
    }

    /**
+38 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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")
@@ -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();
+51 −10
Original line number Diff line number Diff line
@@ -255,7 +255,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
@@ -620,6 +620,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;
@@ -3194,7 +3201,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
@@ -6660,7 +6671,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
@@ -6883,11 +6895,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);
@@ -10529,6 +10551,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.
     *
@@ -11753,13 +11780,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");
@@ -12196,4 +12228,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;
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.view;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
@@ -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;
    }
}