Improve InsetsPolicy#adjustVisibilityForIme when switching apps am: 4950787a43

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15639321

Change-Id: I3278c0243dad6fa8d88d84262cc9c82897edeedf
This commit is contained in:
Ming-Shin Lu
2021-08-23 17:54:54 +00:00
committed by Automerger Merge Worker
6 changed files with 160 additions and 10 deletions

View File

@@ -655,6 +655,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Whether the IME is showing when transitioning away from this activity. */
boolean mLastImeShown;
/**
* When set to true, the IME insets will be frozen until the next app becomes IME input target.
* @see InsetsPolicy#adjustVisibilityForIme
*/
boolean mImeInsetsFrozenUntilStartInput;
/**
* A flag to determine if this AR is in the process of closing or entering PIP. This is needed
* to help AR know that the app is in the process of closing but hasn't yet started closing on
@@ -1363,6 +1369,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
if (newTask != null && isState(RESUMED)) {
newTask.setResumedActivity(this, "onParentChanged");
mImeInsetsFrozenUntilStartInput = false;
}
if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -4759,6 +4766,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
&& imeInputTarget.getWindow().mActivityRecord == this
&& mDisplayContent.mInputMethodWindow != null
&& mDisplayContent.mInputMethodWindow.isVisible();
mImeInsetsFrozenUntilStartInput = true;
}
final DisplayContent displayContent = getDisplayContent();
@@ -5877,6 +5885,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// closing activity having to wait until idle timeout to be stopped or destroyed if the
// next activity won't report idle (e.g. repeated view animation).
mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
// If the activity is visible, but no windows are eligible to start input, unfreeze
// to avoid permanently frozen IME insets.
if (mImeInsetsFrozenUntilStartInput && getWindow(
win -> WindowManager.LayoutParams.mayUseInputMethod(win.mAttrs.flags))
== null) {
mImeInsetsFrozenUntilStartInput = false;
}
}
}
@@ -7792,6 +7808,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
@Override
void onResize() {
// Reset freezing IME insets flag when the activity resized.
mImeInsetsFrozenUntilStartInput = false;
super.onResize();
}
/** Returns true if the configuration is compatible with this activity. */
boolean isConfigurationCompatible(Configuration config) {
final int orientation = getRequestedOrientation();

View File

@@ -3971,6 +3971,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void updateImeInputAndControlTarget(WindowState target) {
if (mImeInputTarget != target) {
ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
if (target != null && target.mActivityRecord != null) {
target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
}
setImeInputTarget(target);
updateImeControlTarget();
}

View File

@@ -211,7 +211,7 @@ class InsetsPolicy {
InsetsState getInsetsForWindow(WindowState target) {
final InsetsState originalState = mStateController.getInsetsForWindow(target);
final InsetsState state = adjustVisibilityForTransientTypes(originalState);
return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state;
return adjustVisibilityForIme(target, state, state == originalState);
}
/**
@@ -241,16 +241,37 @@ class InsetsPolicy {
return state;
}
// Navigation bar insets is always visible to IME.
private static InsetsState adjustVisibilityForIme(InsetsState originalState,
private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
boolean copyState) {
final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
if (originalNavSource != null && !originalNavSource.isVisible()) {
final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
final InsetsSource navSource = new InsetsSource(originalNavSource);
navSource.setVisible(true);
state.addSource(navSource);
return state;
if (w.mIsImWindow) {
// Navigation bar insets is always visible to IME.
final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR);
if (originalNavSource != null && !originalNavSource.isVisible()) {
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
final InsetsSource navSource = new InsetsSource(originalNavSource);
navSource.setVisible(true);
state.addSource(navSource);
return state;
}
} else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
// During switching tasks with gestural navigation, if the IME is attached to
// one app window on that time, even the next app window is behind the IME window,
// conceptually the window should not receive the IME insets if the next window is
// not eligible IME requester and ready to show IME on top of it.
final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
if (shouldImeAttachedToApp && originalImeSource != null) {
final boolean imeVisibility =
w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
final InsetsSource imeSource = new InsetsSource(originalImeSource);
imeSource.setVisible(imeVisibility);
state.addSource(imeSource);
return state;
}
}
return originalState;
}

View File

@@ -39,6 +39,7 @@ import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -2816,6 +2817,73 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
@Test
public void testImeInsetsFrozenFlag_resetWhenReparented() {
final ActivityRecord activity = createActivityWithTask();
final WindowState app = createWindow(null, TYPE_APPLICATION, activity, "app");
final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
final Task newTask = new TaskBuilder(mSupervisor).build();
makeWindowVisible(app, imeWindow);
mDisplayContent.mInputMethodWindow = imeWindow;
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
// Simulate app is closing and expect the last IME is shown and IME insets is frozen.
app.mActivityRecord.commitVisibility(false, false);
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Expect IME insets frozen state will reset when the activity is reparent to the new task.
activity.setState(RESUMED, "test");
activity.reparent(newTask, 0 /* top */, "test");
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenResized() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
makeWindowVisibleAndDrawn(app, mImeWindow);
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
// Simulate app is closing and expect the last IME is shown and IME insets is frozen.
app.mActivityRecord.commitVisibility(false, false);
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Expect IME insets frozen state will reset when the activity is reparent to the new task.
app.mActivityRecord.onResize();
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
@UseTestDisplay(addWindows = W_INPUT_METHOD)
@Test
public void testImeInsetsFrozenFlag_resetWhenNoImeFocusableInActivity() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
makeWindowVisibleAndDrawn(app, mImeWindow);
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
// Simulate app is closing and expect the last IME is shown and IME insets is frozen.
app.mActivityRecord.commitVisibility(false, false);
app.mActivityRecord.onWindowsGone();
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Expect IME insets frozen state will reset when the activity has no IME focusable window.
app.mActivityRecord.forAllWindowsUnchecked(w -> {
w.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
return true;
}, true);
app.mActivityRecord.commitVisibility(true, false);
app.mActivityRecord.onWindowsVisible();
assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
}
private void assertHasStartingWindow(ActivityRecord atoken) {
assertNotNull(atoken.mStartingSurface);
assertNotNull(atoken.mStartingData);

View File

@@ -258,6 +258,7 @@ public class SystemServicesTestRule implements TestRule {
final ActivityManagerInternal amInternal = mAmService.mInternal;
spyOn(amInternal);
doNothing().when(amInternal).trimApplications();
doNothing().when(amInternal).scheduleAppGcs();
doNothing().when(amInternal).updateCpuStats();
doNothing().when(amInternal).updateOomAdj();
doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean());

View File

@@ -891,6 +891,40 @@ public class WindowStateTests extends WindowTestsBase {
assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
}
@Test
public void testAdjustImeInsetsVisibilityWhenSwitchingApps() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
spyOn(imeWindow);
doReturn(true).when(imeWindow).isVisible();
mDisplayContent.mInputMethodWindow = imeWindow;
final InsetsStateController controller = mDisplayContent.getInsetsStateController();
controller.getImeSourceProvider().setWindow(imeWindow, null, null);
// Simulate app requests IME with updating all windows Insets State when IME is above app.
mDisplayContent.setImeLayeringTarget(app);
mDisplayContent.setImeInputTarget(app);
assertTrue(mDisplayContent.shouldImeAttachedToApp());
controller.getImeSourceProvider().scheduleShowImePostLayout(app);
controller.getImeSourceProvider().getSource().setVisible(true);
controller.updateAboveInsetsState(imeWindow, false);
// Expect all app windows behind IME can receive IME insets visible.
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
// Simulate app plays closing transition to app2.
app.mActivityRecord.commitVisibility(false, false);
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
@UseTestDisplay(addWindows = { W_ACTIVITY })
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {