Merge "Migrate InputMethodService to use OnBackInvokedDispatcher" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
93e5edd944
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user