Request focus using setFocusedWindow
This change introduces two new SurfaceControl apis, one to request focus
to a particular window and the other to transfer focus to an embedded
window if the host window is currently focused.
WindowHandleInfo is updated to specify focusability instead of the
currently focused window. This will provide more information to
InputDipatcher on what all the focusable windows are on screen and
in the future allow it to determine a new focus target.
Lastly, after updating all the WindowHandles in InputMonitor, we switch
over to calling setFocusedWindow to specify the desired focused window.
There should be no functional changes with this cl.
Test: atest inputflinger_tests
Test: atest CtsWindowManagerDeviceTestCases
Test: atest FlickerTests
Bug: 151179149
Change-Id: Iefda51ce73247b208115e64f9f8159a182d93ded
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 6ef086b..2bd3c06 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -221,6 +221,8 @@
private static native void nativeReleaseFrameRateFlexibilityToken(long token);
private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject,
int transformHint);
+ private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken,
+ IBinder focusedToken, int displayId);
@Nullable
@GuardedBy("mLock")
@@ -3184,6 +3186,39 @@
}
/**
+ * Sets focus on the window identified by the input {@code token} if the window is focusable
+ * otherwise the request is dropped.
+ *
+ * If the window is not visible, the request will be queued until the window becomes
+ * visible or the request is overrriden by another request. The currently focused window
+ * will lose focus immediately. This is to send the newly focused window any focus
+ * dispatched events that occur while it is completing its first draw.
+ *
+ * @hide
+ */
+ public Transaction setFocusedWindow(@NonNull IBinder token, int displayId) {
+ nativeSetFocusedWindow(mNativeObject, token, null /* focusedToken */, displayId);
+ return this;
+ }
+
+ /**
+ * Set focus on the window identified by the input {@code token} if the window identified by
+ * the input {@code focusedToken} is currently focused. If the {@code focusedToken} does not
+ * have focus, the request is dropped.
+ *
+ * This is used by forward focus transfer requests from clients that host embedded windows,
+ * and want to transfer focus to/from them.
+ *
+ * @hide
+ */
+ public Transaction requestFocusTransfer(@NonNull IBinder token,
+ @NonNull IBinder focusedToken,
+ int displayId) {
+ nativeSetFocusedWindow(mNativeObject, token, focusedToken, displayId);
+ return this;
+ }
+
+ /**
* Merge the other transaction into this transaction, clearing the
* other transaction as if it had been applied.
*
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 85b4fe1..4ef15d7 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1505,9 +1505,24 @@
return reinterpret_cast<jlong>(surfaceControl->getHandle().get());
}
+static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jobject toTokenObj, jobject focusedTokenObj, jint displayId) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ if (toTokenObj == NULL) return;
+
+ sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
+ sp<IBinder> focusedToken;
+ if (focusedTokenObj != NULL) {
+ focusedToken = ibinderForJavaObject(env, focusedTokenObj);
+ }
+ transaction->setFocusedWindow(toToken, focusedToken, systemTime(SYSTEM_TIME_MONOTONIC),
+ displayId);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sSurfaceControlMethods[] = {
+ // clang-format off
{"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
(void*)nativeCreate },
{"nativeReadFromParcel", "(Landroid/os/Parcel;)J",
@@ -1686,7 +1701,11 @@
(void*)nativeSetGlobalShadowSettings },
{"nativeGetHandle", "(J)J",
(void*)nativeGetHandle },
- {"nativeSetFixedTransformHint", "(JJI)V", (void*)nativeSetFixedTransformHint},
+ {"nativeSetFixedTransformHint", "(JJI)V",
+ (void*)nativeSetFixedTransformHint},
+ {"nativeSetFocusedWindow", "(JLandroid/os/IBinder;Landroid/os/IBinder;I)V",
+ (void*)nativeSetFocusedWindow},
+ // clang-format on
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index a2a2216..e693198 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1393,6 +1393,12 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/BlackFrame.java"
},
+ "155482615": {
+ "message": "Focus requested for window=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/InputMonitor.java"
+ },
"174572959": {
"message": "DisplayArea info changed name=%s",
"level": "VERBOSE",
@@ -2635,6 +2641,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java"
},
+ "2081291430": {
+ "message": "Focus not requested for window=%s because it has no surface",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS_LIGHT",
+ "at": "com\/android\/server\/wm\/InputMonitor.java"
+ },
"2083556954": {
"message": "Set mOrientationChanging of %s",
"level": "VERBOSE",
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index aa8069a..b92adc0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -618,6 +618,12 @@
*/
private boolean mInEnsureActivitiesVisible = false;
+ /**
+ * Last window to be requested focus via {@code SurfaceControl.Transaction#setFocusedWindow} to
+ * prevent duplicate requests to input.
+ */
+ WindowState mLastRequestedFocus = null;
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 4efd687..c493b66 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -275,7 +275,7 @@
void populateInputWindowHandle(final InputWindowHandle inputWindowHandle,
final WindowState child, int flags, final int type, final boolean isVisible,
- final boolean hasFocus, final boolean hasWallpaper) {
+ final boolean focusable, final boolean hasWallpaper) {
// Add a window to our list of input windows.
inputWindowHandle.name = child.toString();
flags = child.getSurfaceTouchableRegion(inputWindowHandle, flags);
@@ -283,7 +283,7 @@
inputWindowHandle.layoutParamsType = type;
inputWindowHandle.dispatchingTimeoutMillis = child.getInputDispatchingTimeoutMillis();
inputWindowHandle.visible = isVisible;
- inputWindowHandle.focusable = hasFocus;
+ inputWindowHandle.focusable = focusable;
inputWindowHandle.hasWallpaper = hasWallpaper;
inputWindowHandle.paused = child.mActivityRecord != null ? child.mActivityRecord.paused : false;
inputWindowHandle.ownerPid = child.mSession.mPid;
@@ -472,8 +472,9 @@
resetInputConsumers(mInputTransaction);
- mDisplayContent.forAllWindows(this,
- true /* traverseTopToBottom */);
+ mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
+
+ updateInputFocusRequest();
if (!mUpdateInputWindowsImmediately) {
mDisplayContent.getPendingTransaction().merge(mInputTransaction);
@@ -483,6 +484,29 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ private void updateInputFocusRequest() {
+ if (mDisplayContent.mLastRequestedFocus == mDisplayContent.mCurrentFocus) {
+ return;
+ }
+
+ final WindowState focus = mDisplayContent.mCurrentFocus;
+ if (focus == null || focus.mInputWindowHandle.token == null) {
+ mDisplayContent.mLastRequestedFocus = focus;
+ return;
+ }
+
+ if (!focus.mWinAnimator.hasSurface()) {
+ ProtoLog.d(WM_DEBUG_FOCUS_LIGHT,
+ "Focus not requested for window=%s because it has no surface",
+ focus);
+ return;
+ }
+
+ mInputTransaction.setFocusedWindow(focus.mInputWindowHandle.token, mDisplayId);
+ mDisplayContent.mLastRequestedFocus = focus;
+ ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", focus);
+ }
+
@Override
public void accept(WindowState w) {
final InputChannel inputChannel = w.mInputChannel;
@@ -510,11 +534,12 @@
final int flags = w.mAttrs.flags;
final int privateFlags = w.mAttrs.privateFlags;
- final boolean hasFocus = w.isFocused();
+ final boolean focusable = w.canReceiveKeys()
+ && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
- mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
+ mRecentsAnimationInputConsumer.mWindowHandle, focusable)) {
mRecentsAnimationInputConsumer.show(mInputTransaction, w);
mAddRecentsAnimationInputConsumerHandle = false;
}
@@ -559,7 +584,7 @@
}
populateInputWindowHandle(
- inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
+ inputWindowHandle, w, flags, type, isVisible, focusable, hasWallpaper);
// register key interception info
mService.mKeyInterceptionInfoForToken.put(inputWindowHandle.token,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 6882dc4..b50cb4c 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -814,14 +814,14 @@
}
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
- boolean hasFocus) {
+ boolean focusable) {
// Update the input consumer touchable region to match the target app main window
final WindowState targetAppMainWindow = mTargetActivityRecord != null
? mTargetActivityRecord.findMainWindow()
: null;
if (targetAppMainWindow != null) {
targetAppMainWindow.getBounds(mTmpRect);
- inputWindowHandle.focusable = hasFocus;
+ inputWindowHandle.focusable = focusable;
inputWindowHandle.touchableRegion.set(mTmpRect);
return true;
}