diff options
author | 2020-09-10 15:46:52 -0700 | |
---|---|---|
committer | 2020-09-15 14:48:13 -0700 | |
commit | 204c023ae2fd775bdca8247ef16c4601821a7328 (patch) | |
tree | 5a554ad1a83a557a83ead6a6d570e41d5c79675c | |
parent | 38cc1531f3f52a35dcb8435bd90b677c91574b35 (diff) |
Add focus support for SurfaceControlViewHost
This change introduces a new call for clients to request focus on their
embedded views. In order to accomplish this, the owner of the
SurfacePackage sends an input token that can identify the input channel
of the embedded window. This token is forwarded as part of the focus
request.
WindowManager will authenticate the calling window and the relationship
between the host and the embedded window. Once authenticated, the
request will be forwarded to InputDispatcher. InputDispatcher will grant
the focus to the SurfaceControlViewHost if the host window is currently
focused or transfer focus back to the host window if the
SurfaceControlViewHost is focused. Otherwise the request is dropped.
In addition, SurfaceView, the host in this scenario, now overrides the
onFocused function to transfer focus to and from the embedded view.
Test: atest SurfaceControlViewHostTests
Bug: 151179149
Change-Id: Ia95545a60d73db4c03679bce9747d2e275806838
9 files changed, 182 insertions, 19 deletions
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 819e89b67b38..01e8c4c64f27 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -344,4 +344,17 @@ interface IWindowSession { */ void updateInputChannel(in IBinder channelToken, int displayId, in SurfaceControl surface, int flags, int privateFlags, in Region region); + + /** + * Transfer window focus to an embedded window if the calling window has focus. + * + * @param window - calling window owned by the caller. Window can be null if there + * is no host window but the caller must have permissions to create an embedded + * window without a host window. + * @param inputToken - token identifying the embedded window that should gain focus. + * @param grantFocus - true if focus should be granted to the embedded window, false if focus + * should be transferred back to the host window. If there is no host + * window, the system will try to find a new focus target. + */ + void grantEmbeddedWindowFocus(IWindow window, in IBinder inputToken, boolean grantFocus); } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 66ab3a32edfa..4d1faf79273f 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -70,10 +70,13 @@ public class SurfaceControlViewHost { public static final class SurfacePackage implements Parcelable { private SurfaceControl mSurfaceControl; private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; + private final IBinder mInputToken; - SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection) { + SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection, + IBinder inputToken) { mSurfaceControl = sc; mAccessibilityEmbeddedConnection = connection; + mInputToken = inputToken; } private SurfacePackage(Parcel in) { @@ -81,6 +84,7 @@ public class SurfaceControlViewHost { mSurfaceControl.readFromParcel(in); mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface( in.readStrongBinder()); + mInputToken = in.readStrongBinder(); } /** @@ -126,6 +130,15 @@ public class SurfaceControlViewHost { mSurfaceControl = null; } + /** + * Returns an input token used which can be used to request focus on the embedded surface. + * + * @hide + */ + public IBinder getInputToken() { + return mInputToken; + } + public static final @NonNull Creator<SurfacePackage> CREATOR = new Creator<SurfacePackage>() { public SurfacePackage createFromParcel(Parcel in) { @@ -198,7 +211,8 @@ public class SurfaceControlViewHost { */ public @Nullable SurfacePackage getSurfacePackage() { if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { - return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection); + return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection, + mViewRoot.getInputToken()); } else { return null; } @@ -210,6 +224,7 @@ public class SurfaceControlViewHost { @TestApi public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) { Objects.requireNonNull(view); + view.setLayoutParams(attrs); mViewRoot.setView(view, attrs, null); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index f937bc9e84a9..03822d60fa12 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1846,6 +1846,23 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } + @Override + protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction, + @Nullable Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + final ViewRootImpl viewRoot = getViewRootImpl(); + if (mSurfacePackage == null || viewRoot == null) { + return; + } + try { + viewRoot.mWindowSession.grantEmbeddedWindowFocus(viewRoot.mWindow, + mSurfacePackage.getInputToken(), gainFocus); + } catch (Exception e) { + Log.e(TAG, System.identityHashCode(this) + + "Exception requesting focus on embedded window", e); + } + } + /** * Wrapper of accessibility embedded connection for embedded view hierarchy. */ diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 060311ec3da8..67abb4c933cc 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -457,4 +457,9 @@ public class WindowlessWindowManager implements IWindowSession { return surfaceInsets != null ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height; } + + @Override + public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken, + boolean grantFocus) { + } } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index e69319896762..217de84cfc3e 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -31,6 +31,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/RemoteAnimationController.java" }, + "-2107721178": { + "message": "grantEmbeddedWindowFocus win=%s grantFocus=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_FOCUS", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "-2101985723": { "message": "Failed looking up window session=%s callers=%s", "level": "WARN", @@ -1597,6 +1603,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "397105698": { + "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found", + "level": "VERBOSE", + "group": "WM_DEBUG_FOCUS", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "399841913": { "message": "SURFACE RECOVER DESTROY: %s", "level": "INFO", diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index 5a2484735fb9..6d67f62a248a 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -134,10 +134,13 @@ class EmbeddedWindowController { final int mOwnerUid; final int mOwnerPid; final WindowManagerService mWmService; + final int mDisplayId; + public Session mSession; InputChannel mInputChannel; final int mWindowType; /** + * @param session calling session to check ownership of the window * @param clientToken client token used to clean up the map if the embedding process dies * @param hostWindowState input channel token belonging to the host window. This is needed * to handle input callbacks to wm. It's used when raising ANR and @@ -145,9 +148,13 @@ class EmbeddedWindowController { * can be null if there is no host window. * @param ownerUid calling uid * @param ownerPid calling pid used for anr blaming + * @param windowType to forward to input + * @param displayId used for focus requests */ - EmbeddedWindow(WindowManagerService service, IWindow clientToken, - WindowState hostWindowState, int ownerUid, int ownerPid, int windowType) { + EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken, + WindowState hostWindowState, int ownerUid, int ownerPid, int windowType, + int displayId) { + mSession = session; mWmService = service; mClient = clientToken; mHostWindowState = hostWindowState; @@ -156,6 +163,7 @@ class EmbeddedWindowController { mOwnerUid = ownerUid; mOwnerPid = ownerPid; mWindowType = windowType; + mDisplayId = displayId; } String getName() { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 3b32a9d76258..f1e15550b4ff 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -677,7 +677,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final long identity = Binder.clearCallingIdentity(); try { - mService.grantInputChannel(mUid, mPid, displayId, surface, window, hostInputToken, + mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0, mCanAddInternalSystemWindow ? type : 0, outInputChannel); } finally { @@ -696,4 +696,25 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Binder.restoreCallingIdentity(identity); } } + + @Override + public void grantEmbeddedWindowFocus(IWindow callingWindow, IBinder targetInputToken, + boolean grantFocus) { + final long identity = Binder.clearCallingIdentity(); + try { + if (callingWindow == null) { + if (!mCanAddInternalSystemWindow) { + // Callers without INTERNAL_SYSTEM_WINDOW permission cannot request focus on + // embedded windows without providing the calling window + throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); + } + mService.grantEmbeddedWindowFocus(this, targetInputToken, grantFocus); + } else { + mService.grantEmbeddedWindowFocus(this, callingWindow, targetInputToken, + grantFocus); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3fd8aaf9102b..1e7764252cb0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7995,16 +7995,17 @@ public class WindowManagerService extends IWindowManager.Stub * Used by WindowlessWindowManager to enable input on SurfaceControl embedded * views. */ - void grantInputChannel(int callingUid, int callingPid, int displayId, SurfaceControl surface, - IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, - InputChannel outInputChannel) { + void grantInputChannel(Session session, int callingUid, int callingPid, int displayId, + SurfaceControl surface, IWindow window, IBinder hostInputToken, + int flags, int privateFlags, int type, InputChannel outInputChannel) { final InputApplicationHandle applicationHandle; final String name; final InputChannel clientChannel; synchronized (mGlobalLock) { EmbeddedWindowController.EmbeddedWindow win = - new EmbeddedWindowController.EmbeddedWindow(this, window, - mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type); + new EmbeddedWindowController.EmbeddedWindow(session, this, window, + mInputToWindowMap.get(hostInputToken), callingUid, callingPid, type, + displayId); clientChannel = win.openInputChannel(); mEmbeddedWindowController.add(clientChannel.getToken(), win); applicationHandle = win.getApplicationHandle(); @@ -8019,19 +8020,19 @@ public class WindowManagerService extends IWindowManager.Stub } private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid, - int displayId, SurfaceControl surface, String name, - InputApplicationHandle applicationHandle, int flags, int privateFlags, int type, - Region region) { + int displayId, SurfaceControl surface, String name, + InputApplicationHandle applicationHandle, int flags, + int privateFlags, int type, Region region) { InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId); h.token = channelToken; h.name = name; final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE - | LayoutParams.FLAG_SLIPPERY); + | LayoutParams.FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE); h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | sanitizedFlags; h.layoutParamsType = type; h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS; - h.focusable = false; + h.focusable = (flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0; h.hasWallpaper = false; h.paused = false; @@ -8217,4 +8218,75 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } } + + void grantEmbeddedWindowFocus(Session session, IBinder targetInputToken, boolean grantFocus) { + synchronized (mGlobalLock) { + final EmbeddedWindowController.EmbeddedWindow embeddedWindow = + mEmbeddedWindowController.get(targetInputToken); + if (embeddedWindow == null) { + Slog.e(TAG, "Embedded window not found"); + return; + } + if (embeddedWindow.mSession != session) { + Slog.e(TAG, "Window not in session:" + session); + return; + } + SurfaceControl.Transaction t = mTransactionFactory.get(); + final int displayId = embeddedWindow.mDisplayId; + if (grantFocus) { + t.setFocusedWindow(targetInputToken, displayId).apply(); + } else { + // Search for a new focus target + DisplayContent displayContent = mRoot.getDisplayContent(displayId); + WindowState newFocusTarget = displayContent == null + ? null : displayContent.findFocusedWindow(); + if (newFocusTarget == null) { + ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for " + + "win=%s dropped since no candidate was found", + embeddedWindow.getName()); + return; + } + t.requestFocusTransfer(newFocusTarget.mInputWindowHandle.token, targetInputToken, + displayId).apply(); + } + ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", + embeddedWindow.getName(), grantFocus); + } + } + + void grantEmbeddedWindowFocus(Session session, IWindow callingWindow, IBinder targetInputToken, + boolean grantFocus) { + synchronized (mGlobalLock) { + final WindowState hostWindow = + windowForClientLocked(session, callingWindow, false /* throwOnError*/); + if (hostWindow == null) { + Slog.e(TAG, "Host window not found"); + return; + } + if (hostWindow.mInputChannel == null) { + Slog.e(TAG, "Host window does not have an input channel"); + return; + } + final EmbeddedWindowController.EmbeddedWindow embeddedWindow = + mEmbeddedWindowController.get(targetInputToken); + if (embeddedWindow == null) { + Slog.e(TAG, "Embedded window not found"); + return; + } + if (embeddedWindow.mHostWindowState != hostWindow) { + Slog.e(TAG, "Embedded window does not belong to the host"); + return; + } + SurfaceControl.Transaction t = mTransactionFactory.get(); + if (grantFocus) { + t.requestFocusTransfer(targetInputToken, hostWindow.mInputChannel.getToken(), + hostWindow.getDisplayId()).apply(); + } else { + t.requestFocusTransfer(hostWindow.mInputChannel.getToken(), targetInputToken, + hostWindow.getDisplayId()).apply(); + } + ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", + embeddedWindow.getName(), grantFocus); + } + } } diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java index 3bc530975580..73e01634709e 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java @@ -22,12 +22,11 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.Gravity; -import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; -import android.view.SurfaceControlViewHost; import android.widget.Button; import android.widget.FrameLayout; @@ -46,8 +45,6 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde mView.setZOrderOnTop(true); mView.getHolder().addCallback(this); - - addEmbeddedView(); } void addEmbeddedView() { @@ -63,6 +60,8 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde v.setBackgroundColor(Color.RED); } }); + v.getViewTreeObserver().addOnWindowFocusChangeListener(focused -> + v.setBackgroundColor(focused ? Color.MAGENTA : Color.DKGRAY)); WindowManager.LayoutParams lp = new WindowManager.LayoutParams(500, 500, WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE); @@ -71,6 +70,7 @@ public class SurfaceControlViewHostTest extends Activity implements SurfaceHolde @Override public void surfaceCreated(SurfaceHolder holder) { + addEmbeddedView(); } @Override |