summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tiger <tigerhuang@google.com> 2024-01-03 21:32:41 +0800
committer Tiger <tigerhuang@google.com> 2024-01-10 14:46:12 +0800
commit75f3b1b8f5b39e72017e2927b9c6a4cde3888a53 (patch)
tree5b1ab944fcc959b9aa65829199c967290499c9a9
parent3d460a1e91d7544a32a61530ccf05177f64f5a8f (diff)
Let view focus move across adjacent task fragments
This CL moves the focus to the adjacent task/activity/window (if there is any) while the next focused view cannot be found with the given direction in the local window. Fix: 283618824 Test: atest TaskFragmentTest#testMoveFocusToAdjacentWindow Test: Run commands: `adb shell input keyevent KEYCODE_DPAD_XXX` while running 2-pane Settings. Change-Id: I45681150e9edd25d59136c5762586c504cb301f9
-rw-r--r--core/java/android/view/IWindowSession.aidl10
-rw-r--r--core/java/android/view/ViewRootImpl.java19
-rw-r--r--core/java/android/view/WindowlessWindowManager.java8
-rw-r--r--services/core/java/com/android/server/wm/Session.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java72
6 files changed, 181 insertions, 1 deletions
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 9bf43a390d70..1d81be17f580 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -370,4 +370,14 @@ interface IWindowSession {
boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow);
boolean transferHostTouchGestureToEmbedded(IWindow hostWindow, IBinder transferTouchToken);
+
+ /**
+ * Moves the focus to the adjacent window if there is one in the given direction. This can only
+ * move the focus to the window in the same leaf task.
+ *
+ * @param fromWindow The calling window that the focus is moved from.
+ * @param direction The {@link android.view.View.FocusDirection} that the new focus should go.
+ * @return {@code true} if the focus changes. Otherwise, {@code false}.
+ */
+ boolean moveFocusToAdjacentWindow(IWindow fromWindow, int direction);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8529b4e044fa..e89dbbdb5082 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
@@ -7232,7 +7233,7 @@ public final class ViewRootImpl implements ViewParent,
}
private boolean performFocusNavigation(KeyEvent event) {
- int direction = 0;
+ @FocusDirection int direction = 0;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (event.hasNoModifiers()) {
@@ -7284,6 +7285,8 @@ public final class ViewRootImpl implements ViewParent,
isFastScrolling));
return true;
}
+ } else if (moveFocusToAdjacentWindow(direction)) {
+ return true;
}
// Give the focused view a last chance to handle the dpad key.
@@ -7293,12 +7296,26 @@ public final class ViewRootImpl implements ViewParent,
} else {
if (mView.restoreDefaultFocus()) {
return true;
+ } else if (moveFocusToAdjacentWindow(direction)) {
+ return true;
}
}
}
return false;
}
+ private boolean moveFocusToAdjacentWindow(@FocusDirection int direction) {
+ if (getConfiguration().windowConfiguration.getWindowingMode()
+ != WINDOWING_MODE_MULTI_WINDOW) {
+ return false;
+ }
+ try {
+ return mWindowSession.moveFocusToAdjacentWindow(mWindow, direction);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
private boolean performKeyboardGroupNavigation(int direction) {
final View focused = mView.findFocus();
if (focused == null && mView.restoreDefaultFocus()) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d6ac56239aed..b95e4595d6b9 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -30,6 +30,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
+import android.view.View.FocusDirection;
import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -665,6 +666,13 @@ public class WindowlessWindowManager implements IWindowSession {
return false;
}
+ @Override
+ public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) {
+ Log.e(TAG, "Received request to moveFocusToAdjacentWindow on"
+ + " WindowlessWindowManager. We shouldn't get here!");
+ return false;
+ }
+
void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index ed54ea8229fe..f10a733040ed 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -75,6 +75,7 @@ import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.View.FocusDirection;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
@@ -1000,6 +1001,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
return didTransfer;
}
+
+ @Override
+ public boolean moveFocusToAdjacentWindow(IWindow fromWindow, @FocusDirection int direction) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mService.moveFocusToAdjacentWindow(this, fromWindow, direction);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public void generateDisplayHash(IWindow window, Rect boundsInWindow, String hashAlgorithm,
RemoteCallback callback) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c63cc4373472..95448352736f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -287,6 +287,7 @@ import android.view.SurfaceControlViewHost;
import android.view.SurfaceSession;
import android.view.TaskTransitionSpec;
import android.view.View;
+import android.view.View.FocusDirection;
import android.view.ViewDebug;
import android.view.WindowContentFrameStats;
import android.view.WindowInsets;
@@ -9104,6 +9105,66 @@ public class WindowManagerService extends IWindowManager.Stub
win.mClient);
}
+ boolean moveFocusToAdjacentWindow(Session session, IWindow fromWindow,
+ @FocusDirection int direction) {
+ synchronized (mGlobalLock) {
+ final WindowState fromWin = windowForClientLocked(session, fromWindow, false);
+ if (fromWin == null || !fromWin.isFocused()) {
+ return false;
+ }
+ final TaskFragment fromFragment = fromWin.getTaskFragment();
+ if (fromFragment == null) {
+ return false;
+ }
+ final TaskFragment adjacentFragment = fromFragment.getAdjacentTaskFragment();
+ if (adjacentFragment == null || adjacentFragment.asTask() != null) {
+ // Don't move the focus to another task.
+ return false;
+ }
+ final Rect fromBounds = fromFragment.getBounds();
+ final Rect adjacentBounds = adjacentFragment.getBounds();
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ if (adjacentBounds.left >= fromBounds.left) {
+ return false;
+ }
+ break;
+ case View.FOCUS_UP:
+ if (adjacentBounds.top >= fromBounds.top) {
+ return false;
+ }
+ break;
+ case View.FOCUS_RIGHT:
+ if (adjacentBounds.right <= fromBounds.right) {
+ return false;
+ }
+ break;
+ case View.FOCUS_DOWN:
+ if (adjacentBounds.bottom <= fromBounds.bottom) {
+ return false;
+ }
+ break;
+ case View.FOCUS_BACKWARD:
+ case View.FOCUS_FORWARD:
+ // These are not absolute directions. Skip checking the bounds.
+ break;
+ default:
+ return false;
+ }
+ final ActivityRecord topRunningActivity = adjacentFragment.topRunningActivity(
+ true /* focusableOnly */);
+ if (topRunningActivity == null) {
+ return false;
+ }
+ moveDisplayToTopInternal(topRunningActivity.getDisplayId());
+ handleTaskFocusChange(topRunningActivity.getTask(), topRunningActivity);
+ if (fromWin.isFocused()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/** Return whether layer tracing is enabled */
public boolean isLayerTracing() {
if (!checkCallingPermission(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index e9fe4bb91329..22ddf8420121 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -53,6 +53,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl;
+import android.view.View;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
@@ -695,4 +696,75 @@ public class TaskFragmentTest extends WindowTestsBase {
mTaskFragment.getDimBounds(dimBounds);
assertEquals(taskFragmentBounds, dimBounds);
}
+
+ @Test
+ public void testMoveFocusToAdjacentWindow() {
+ // Setup two activities in ActivityEmbedding split.
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment taskFragmentLeft = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(2)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ final TaskFragment taskFragmentRight = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(1)
+ .setOrganizer(mOrganizer)
+ .setFragmentToken(new Binder())
+ .build();
+ taskFragmentLeft.setAdjacentTaskFragment(taskFragmentRight);
+ taskFragmentLeft.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ taskFragmentRight.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ task.setBounds(0, 0, 1200, 1000);
+ taskFragmentLeft.setBounds(0, 0, 600, 1000);
+ taskFragmentRight.setBounds(600, 0, 1200, 1000);
+ final ActivityRecord appLeftTop = taskFragmentLeft.getTopMostActivity();
+ final ActivityRecord appLeftBottom = taskFragmentLeft.getBottomMostActivity();
+ final ActivityRecord appRightTop = taskFragmentRight.getTopMostActivity();
+ appLeftTop.setVisibleRequested(true);
+ appRightTop.setVisibleRequested(true);
+ final WindowState winLeftTop = createAppWindow(appLeftTop, "winLeftTop");
+ final WindowState winLeftBottom = createAppWindow(appLeftBottom, "winLeftBottom");
+ final WindowState winRightTop = createAppWindow(appRightTop, "winRightTop");
+ winLeftTop.setHasSurface(true);
+ winRightTop.setHasSurface(true);
+
+ taskFragmentLeft.setResumedActivity(appLeftTop, "test");
+ taskFragmentRight.setResumedActivity(appRightTop, "test");
+ appLeftTop.setState(RESUMED, "test");
+ appRightTop.setState(RESUMED, "test");
+ mDisplayContent.mFocusedApp = appRightTop;
+
+ // Make the appLeftTop be the focused activity and ensure the focused app is updated.
+ appLeftTop.moveFocusableActivityToTop("test");
+ assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+
+ // Send request from a non-focused window with valid direction.
+ assertFalse(mWm.moveFocusToAdjacentWindow(null, winLeftBottom.mClient, View.FOCUS_RIGHT));
+ // The focus should remain the same.
+ assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+
+ // Send request from the focused window with valid direction.
+ assertTrue(mWm.moveFocusToAdjacentWindow(null, winLeftTop.mClient, View.FOCUS_RIGHT));
+ // The focus should change.
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+ // Send request from the focused window with invalid direction.
+ assertFalse(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_UP));
+ // The focus should remain the same.
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+ // Send request from the focused window with valid direction.
+ assertTrue(mWm.moveFocusToAdjacentWindow(null, winRightTop.mClient, View.FOCUS_BACKWARD));
+ // The focus should change.
+ assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
+ }
+
+ private WindowState createAppWindow(ActivityRecord app, String name) {
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
+ 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
+ mWm.mWindowMap.put(win.mClient.asBinder(), win);
+ return win;
+ }
}