Merge "Migrate InputMethodService to use OnBackInvokedDispatcher" into tm-dev

This commit is contained in:
TreeHugger Robot
2022-04-14 02:19:39 +00:00
committed by Android (Google) Code Review
3 changed files with 135 additions and 14 deletions

View File

@@ -101,6 +101,7 @@ import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEventReceiver;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -134,7 +135,10 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowMetricsHelper;
import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IInputContentUriToken;
@@ -345,6 +349,9 @@ public class InputMethodService extends AbstractInputMethodService {
**/
private RingBuffer<MotionEvent> mPendingEvents;
/** Callback to handle back invocation when IME window is shown. */
private OnBackInvokedCallback mBackCallback;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
* the IME switcher button or not when the gestural navigation is enabled.
@@ -1605,6 +1612,7 @@ public class InputMethodService extends AbstractInputMethodService {
@Override public void onDestroy() {
mDestroyed = true;
super.onDestroy();
unregisterOnBackInvokedCallback();
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
mInsetsComputer);
doFinishInput();
@@ -2579,6 +2587,7 @@ public class InputMethodService extends AbstractInputMethodService {
cancelImeSurfaceRemoval();
mInShowWindow = false;
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
registerOnBackInvokedCallback();
}
@@ -2624,6 +2633,56 @@ public class InputMethodService extends AbstractInputMethodService {
if (doShowInput) startExtractingText(false);
}
/**
* Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time
* back dispatching is enabled. We keep the KEYCODE_BACK based legacy code around to handle
* back on older devices.
*/
private void registerOnBackInvokedCallback() {
if (mBackCallback != null) {
// A back callback has already been registered.
return;
}
final ViewRootImpl viewRootImpl = mRootView == null ? null : mRootView.getViewRootImpl();
if (viewRootImpl != null && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(
viewRootImpl.mContext)) {
final OnBackInvokedCallback callback = () -> {
KeyEvent downEvent = createKeyEvent(
KeyEvent.ACTION_DOWN, false /* isTracking */);
onKeyDown(KeyEvent.KEYCODE_BACK, downEvent);
boolean hasStartedTracking =
(downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0;
KeyEvent upEvent = createKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking);
onKeyUp(KeyEvent.KEYCODE_BACK, upEvent);
};
viewRootImpl.getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback);
mBackCallback = callback;
}
}
private KeyEvent createKeyEvent(int action, boolean isTracking) {
final long when = SystemClock.uptimeMillis();
return new KeyEvent(when, when, action,
KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY
| (isTracking ? KeyEvent.FLAG_TRACKING : 0),
InputDevice.SOURCE_KEYBOARD);
}
private void unregisterOnBackInvokedCallback() {
final ViewRootImpl viewRootImpl = mRootView == null ? null : mRootView.getViewRootImpl();
if (viewRootImpl != null
&& mBackCallback != null
&& WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(
viewRootImpl.mContext)) {
viewRootImpl.getOnBackInvokedDispatcher()
.unregisterOnBackInvokedCallback(mBackCallback);
}
mBackCallback = null;
}
/**
* Applies the IME visibility in {@link android.view.ImeInsetsSourceConsumer}.
*
@@ -2669,6 +2728,7 @@ public class InputMethodService extends AbstractInputMethodService {
}
mLastWasInFullscreenMode = mIsFullscreen;
updateFullscreenMode();
unregisterOnBackInvokedCallback();
}
/**

View File

@@ -167,6 +167,23 @@ class BackNavigationController {
currentActivity = window.mActivityRecord;
currentTask = window.getTask();
callbackInfo = window.getOnBackInvokedCallbackInfo();
final DisplayContent displayContent = window.getDisplayContent();
// When IME is shown, return the more prioritized callback between IME and app.
// Priority ordering follows: OVERLAY, IME, DEFAULT.
if (displayContent != null && displayContent.getImeContainer().isVisible()) {
WindowState imeWindow = displayContent.getImeContainer().getWindow(
windowState -> windowState.getOnBackInvokedCallbackInfo() != null);
if (imeWindow != null) {
OnBackInvokedCallbackInfo imeCallbackInfo =
imeWindow.getOnBackInvokedCallbackInfo();
if (imeCallbackInfo != null && (callbackInfo == null
|| callbackInfo.getPriority() <= imeCallbackInfo.getPriority())) {
callbackInfo = imeCallbackInfo;
}
}
}
if (callbackInfo == null) {
Slog.e(TAG, "No callback registered, returning null.");
return null;
@@ -189,12 +206,10 @@ class BackNavigationController {
// If we don't need to set up the animation, we return early. This is the case when
// - We have an application callback.
// - We don't have any ActivityRecord or Task to animate.
// - The IME is opened, and we just need to close it.
// - The home activity is the focused activity.
if (backType == BackNavigationInfo.TYPE_CALLBACK
|| currentActivity == null
|| currentTask == null
|| currentTask.getDisplayContent().getImeContainer().isVisible()
|| currentActivity.isActivityTypeHome()) {
return infoBuilder
.setType(backType)

View File

@@ -20,6 +20,8 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.window.BackNavigationInfo.typeToString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -81,8 +83,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Test
public void backNavInfo_HomeWhenBackToLauncher() {
Task task = createTopTaskWithActivity();
IOnBackInvokedCallback callback = withSystemCallback(task);
IOnBackInvokedCallback callback =
withCallback(createTopTaskWithActivity(), OnBackInvokedDispatcher.PRIORITY_SYSTEM);
SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
BackNavigationInfo backNavigationInfo = mBackNavigationController.startBackNavigation(mWm,
@@ -103,7 +105,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
public void backTypeCrossTaskWhenBackToPreviousTask() {
Task taskA = createTask(mDefaultDisplay);
createActivityRecord(taskA);
withSystemCallback(createTopTaskWithActivity());
withCallback(createTopTaskWithActivity(), OnBackInvokedDispatcher.PRIORITY_SYSTEM);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -155,7 +157,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Test
public void preparesForBackToHome() {
Task task = createTopTaskWithActivity();
withSystemCallback(task);
withCallback(task, OnBackInvokedDispatcher.PRIORITY_SYSTEM);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -165,7 +167,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Test
public void backTypeCallback() {
Task task = createTopTaskWithActivity();
IOnBackInvokedCallback appCallback = withAppCallback(task);
IOnBackInvokedCallback appCallback =
withCallback(task, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertThat(typeToString(backNavigationInfo.getType()))
@@ -226,18 +229,61 @@ public class BackNavigationControllerTests extends WindowTestsBase {
1, appLatch.getCount());
}
private IOnBackInvokedCallback withSystemCallback(Task task) {
@Test
public void returnsImeCallback_imeVisible() {
// Set up a top activity with a default priority callback.
IOnBackInvokedCallback appCallback =
withCallback(createTopTaskWithActivity(), OnBackInvokedDispatcher.PRIORITY_DEFAULT);
IOnBackInvokedCallback imeCallback = createOnBackInvokedCallback();
// Set up an IME window with also a default priority callback.
final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
final WindowState imeWindow = createImeWindow();
imeWindow.setOnBackInvokedCallbackInfo(
new OnBackInvokedCallbackInfo(
imeCallback, OnBackInvokedDispatcher.PRIORITY_DEFAULT));
spyOn(imeContainer);
// Simulate IME becoming visible.
doReturn(true).when(imeContainer).isVisible();
doReturn(imeWindow).when(imeContainer).getWindow(any());
BackNavigationInfo backNavigationInfo = startBackNavigation();
// Expect the IME callback to be selected.
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(imeCallback);
}
@Test
public void returnsAppOverlayCallback_imeVisible() {
// Set up a top activity with an overlay priority callback.
IOnBackInvokedCallback appCallback =
withCallback(createTopTaskWithActivity(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
IOnBackInvokedCallback imeCallback = createOnBackInvokedCallback();
// Set up an IME window with a default priority callback.
final DisplayArea.Tokens imeContainer = mDisplayContent.getImeContainer();
final WindowState imeWindow = createImeWindow();
imeWindow.setOnBackInvokedCallbackInfo(
new OnBackInvokedCallbackInfo(
imeCallback, OnBackInvokedDispatcher.PRIORITY_DEFAULT));
spyOn(imeContainer);
// Simulate IME becoming visible.
doReturn(true).when(imeContainer).isVisible();
doReturn(imeWindow).when(imeContainer).getWindow(any());
BackNavigationInfo backNavigationInfo = startBackNavigation();
// Expect the app callback to be selected.
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(appCallback);
}
private IOnBackInvokedCallback withCallback(Task task, int priority) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM));
new OnBackInvokedCallbackInfo(callback, priority));
return callback;
}
private IOnBackInvokedCallback withAppCallback(Task task) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT));
return callback;
private WindowState createImeWindow() {
return createWindow(null, W_INPUT_METHOD, "mImeWindow", 12345 /* fake ime uide */);
}
@Nullable