Loading core/java/android/inputmethodservice/InputMethodService.java +60 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -1605,6 +1612,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void onDestroy() { mDestroyed = true; super.onDestroy(); unregisterOnBackInvokedCallback(); mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); Loading Loading @@ -2579,6 +2587,7 @@ public class InputMethodService extends AbstractInputMethodService { cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); registerOnBackInvokedCallback(); } Loading Loading @@ -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}. * Loading Loading @@ -2669,6 +2728,7 @@ public class InputMethodService extends AbstractInputMethodService { } mLastWasInFullscreenMode = mIsFullscreen; updateFullscreenMode(); unregisterOnBackInvokedCallback(); } /** Loading services/core/java/com/android/server/wm/BackNavigationController.java +17 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) Loading services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +58 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading @@ -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())) Loading Loading @@ -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())) Loading @@ -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())) Loading Loading @@ -226,20 +229,63 @@ public class BackNavigationControllerTests extends WindowTestsBase { 1, appLatch.getCount()); } private IOnBackInvokedCallback withSystemCallback(Task task) { IOnBackInvokedCallback callback = createOnBackInvokedCallback(); task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM)); return callback; @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); } private IOnBackInvokedCallback withAppCallback(Task task) { @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_DEFAULT)); new OnBackInvokedCallbackInfo(callback, priority)); return callback; } private WindowState createImeWindow() { return createWindow(null, W_INPUT_METHOD, "mImeWindow", 12345 /* fake ime uide */); } @Nullable private BackNavigationInfo startBackNavigation() { return mBackNavigationController.startBackNavigation(mWm, new StubTransaction()); Loading Loading
core/java/android/inputmethodservice/InputMethodService.java +60 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -1605,6 +1612,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void onDestroy() { mDestroyed = true; super.onDestroy(); unregisterOnBackInvokedCallback(); mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); Loading Loading @@ -2579,6 +2587,7 @@ public class InputMethodService extends AbstractInputMethodService { cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); registerOnBackInvokedCallback(); } Loading Loading @@ -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}. * Loading Loading @@ -2669,6 +2728,7 @@ public class InputMethodService extends AbstractInputMethodService { } mLastWasInFullscreenMode = mIsFullscreen; updateFullscreenMode(); unregisterOnBackInvokedCallback(); } /** Loading
services/core/java/com/android/server/wm/BackNavigationController.java +17 −2 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) Loading
services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +58 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading @@ -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())) Loading Loading @@ -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())) Loading @@ -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())) Loading Loading @@ -226,20 +229,63 @@ public class BackNavigationControllerTests extends WindowTestsBase { 1, appLatch.getCount()); } private IOnBackInvokedCallback withSystemCallback(Task task) { IOnBackInvokedCallback callback = createOnBackInvokedCallback(); task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM)); return callback; @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); } private IOnBackInvokedCallback withAppCallback(Task task) { @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_DEFAULT)); new OnBackInvokedCallbackInfo(callback, priority)); return callback; } private WindowState createImeWindow() { return createWindow(null, W_INPUT_METHOD, "mImeWindow", 12345 /* fake ime uide */); } @Nullable private BackNavigationInfo startBackNavigation() { return mBackNavigationController.startBackNavigation(mWm, new StubTransaction()); Loading