Merge "Let view focus move across adjacent task fragments" into main

This commit is contained in:
Tiger Huang 2024-01-10 08:40:00 +00:00 committed by Android (Google) Code Review
commit 6c202d41c8
6 changed files with 181 additions and 1 deletions

View File

@ -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);
}

View File

@ -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;
@ -7236,7 +7237,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()) {
@ -7288,6 +7289,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.
@ -7297,12 +7300,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()) {

View File

@ -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();

View File

@ -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) {

View File

@ -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(

View File

@ -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;
}
}