diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1e6abd9753387..39d5f5c396a13 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1007,6 +1007,13 @@ public final class InputMethodManager { mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0).sendToTarget(); } + @Override + public void scheduleStartInputIfNecessary(boolean fullscreen) { + // TODO(b/149859205): See if we can optimize this by having a fused dedicated operation. + mH.obtainMessage(MSG_SET_ACTIVE, 0 /* active */, fullscreen ? 1 : 0).sendToTarget(); + mH.obtainMessage(MSG_SET_ACTIVE, 1 /* active */, fullscreen ? 1 : 0).sendToTarget(); + } + @Override public void reportFullscreenMode(boolean fullscreen) { mH.obtainMessage(MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0) diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 41f902e2f8520..45090320c1922 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -28,6 +28,7 @@ oneway interface IInputMethodClient { void onBindMethod(in InputBindResult res); void onUnbindMethod(int sequence, int unbindReason); void setActive(boolean active, boolean fullscreen); + void scheduleStartInputIfNecessary(boolean fullscreen); void reportFullscreenMode(boolean fullscreen); void reportPreRendered(in EditorInfo info); void applyImeVisibility(boolean setVisible); diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index a5964b509b3c2..f29e95ccdf658 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -88,58 +88,65 @@ public final class InputBindResult implements Parcelable { * @see android.content.ServiceConnection#onServiceConnected(ComponentName, IBinder) */ int SUCCESS_WAITING_IME_BINDING = 2; + /** + * Indicates that {@link com.android.server.inputmethod.InputMethodManagerService} has a + * pending operation to switch to a different user. + * + *

Note that in this state even what would be the next current IME is not determined.

+ */ + int SUCCESS_WAITING_USER_SWITCHING = 3; /** * Indicates that this is not intended for starting input but just for reporting window * focus change from the application process. * *

All other fields do not have meaningful value.

*/ - int SUCCESS_REPORT_WINDOW_FOCUS_ONLY = 3; + int SUCCESS_REPORT_WINDOW_FOCUS_ONLY = 4; /** * Indicates somehow * {@link * com.android.server.inputmethod.InputMethodManagerService#startInputOrWindowGainedFocus} * is trying to return null {@link InputBindResult}, which must never happen. */ - int ERROR_NULL = 4; + int ERROR_NULL = 5; /** * Indicates that {@link com.android.server.inputmethod.InputMethodManagerService} * recognizes no IME. */ - int ERROR_NO_IME = 5; + int ERROR_NO_IME = 6; /** * Indicates that {@link android.view.inputmethod.EditorInfo#packageName} does not match * the caller UID. * * @see android.view.inputmethod.EditorInfo#packageName */ - int ERROR_INVALID_PACKAGE_NAME = 6; + int ERROR_INVALID_PACKAGE_NAME = 7; /** * Indicates that the system is still in an early stage of the boot process and any 3rd * party application is not allowed to run. * * @see com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START */ - int ERROR_SYSTEM_NOT_READY = 7; + int ERROR_SYSTEM_NOT_READY = 8; /** * Indicates that {@link com.android.server.inputmethod.InputMethodManagerService} tried to * connect to an {@link android.inputmethodservice.InputMethodService} but failed. * * @see android.content.Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle) */ - int ERROR_IME_NOT_CONNECTED = 8; + int ERROR_IME_NOT_CONNECTED = 9; /** * Indicates that the caller is not the foreground user, does not have * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission, or the user * specified in {@link android.view.inputmethod.EditorInfo#targetInputMethodUser} is not * running. */ - int ERROR_INVALID_USER = 9; + int ERROR_INVALID_USER = 10; /** * Indicates that the caller should have specified non-null * {@link android.view.inputmethod.EditorInfo}. */ - int ERROR_NULL_EDITOR_INFO = 10; + int ERROR_NULL_EDITOR_INFO = 11; /** * Indicates that the target window the client specified cannot be the IME target right now. * @@ -149,24 +156,24 @@ public final class InputBindResult implements Parcelable { * * @see com.android.server.wm.WindowManagerInternal#isInputMethodClientFocus(int, int, int) */ - int ERROR_NOT_IME_TARGET_WINDOW = 11; + int ERROR_NOT_IME_TARGET_WINDOW = 12; /** * Indicates that focused view in the current window is not an editor. */ - int ERROR_NO_EDITOR = 12; + int ERROR_NO_EDITOR = 13; /** * Indicates that there is a mismatch in display ID between IME client and focused Window. */ - int ERROR_DISPLAY_ID_MISMATCH = 13; + int ERROR_DISPLAY_ID_MISMATCH = 14; /** * Indicates that current IME client is no longer allowed to access to the associated * display. */ - int ERROR_INVALID_DISPLAY_ID = 14; + int ERROR_INVALID_DISPLAY_ID = 15; /** * Indicates that the client is not recognized by the system. */ - int ERROR_INVALID_CLIENT = 15; + int ERROR_INVALID_CLIENT = 16; } @ResultCode @@ -299,6 +306,8 @@ public final class InputBindResult implements Parcelable { return "SUCCESS_WAITING_IME_SESSION"; case ResultCode.SUCCESS_WAITING_IME_BINDING: return "SUCCESS_WAITING_IME_BINDING"; + case ResultCode.SUCCESS_WAITING_USER_SWITCHING: + return "SUCCESS_WAITING_USER_SWITCHING"; case ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY: return "SUCCESS_REPORT_WINDOW_FOCUS_ONLY"; case ResultCode.ERROR_NULL: @@ -386,4 +395,11 @@ public final class InputBindResult implements Parcelable { * Predefined error object for {@link ResultCode#ERROR_INVALID_CLIENT}. */ public static final InputBindResult INVALID_CLIENT = error(ResultCode.ERROR_INVALID_CLIENT); + + /** + * Predefined success object for + * {@link ResultCode#SUCCESS_WAITING_USER_SWITCHING}. + */ + public static final InputBindResult USER_SWITCHING = + error(ResultCode.SUCCESS_WAITING_USER_SWITCHING); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d7cb192fdd36e..87262a844b18f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1388,6 +1388,44 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private static final class UserSwitchHandlerTask implements Runnable { + final InputMethodManagerService mService; + + @UserIdInt + final int mToUserId; + + @Nullable + IInputMethodClient mClientToBeReset; + + UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId, + @Nullable IInputMethodClient clientToBeReset) { + mService = service; + mToUserId = toUserId; + mClientToBeReset = clientToBeReset; + } + + @Override + public void run() { + synchronized (mService.mMethodMap) { + if (mService.mUserSwitchHandlerTask != this) { + // This task was already canceled before it is handled here. So do nothing. + return; + } + mService.switchUserOnHandlerLocked(mService.mUserSwitchHandlerTask.mToUserId, + mClientToBeReset); + mService.mUserSwitchHandlerTask = null; + } + } + } + + /** + * When non-{@code null}, this represents pending user-switch task, which is to be executed as + * a handler callback. This needs to be set and unset only within the lock. + */ + @Nullable + @GuardedBy("mMethodMap") + private UserSwitchHandlerTask mUserSwitchHandlerTask; + public static final class Lifecycle extends SystemService { private InputMethodManagerService mService; @@ -1406,8 +1444,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onSwitchUser(@UserIdInt int userHandle) { // Called on ActivityManager thread. - // TODO: Dispatch this to a worker thread as needed. - mService.onSwitchUser(userHandle); + synchronized (mService.mMethodMap) { + mService.scheduleSwitchUserTaskLocked(userHandle, null /* clientToBeReset */); + } } @Override @@ -1447,10 +1486,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void onSwitchUser(@UserIdInt int userId) { - synchronized (mMethodMap) { - switchUserLocked(userId); + @GuardedBy("mMethodMap") + void scheduleSwitchUserTaskLocked(@UserIdInt int userId, + @Nullable IInputMethodClient clientToBeReset) { + if (mUserSwitchHandlerTask != null) { + if (mUserSwitchHandlerTask.mToUserId == userId) { + mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset; + return; + } + mHandler.removeCallbacks(mUserSwitchHandlerTask); } + final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, + clientToBeReset); + mUserSwitchHandlerTask = task; + mHandler.post(task); } public InputMethodManagerService(Context context) { @@ -1538,7 +1587,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - private void switchUserLocked(int newUserId) { + private void switchUserOnHandlerLocked(@UserIdInt int newUserId, + IInputMethodClient clientToBeReset) { if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId + " currentUserId=" + mSettings.getCurrentUserId()); @@ -1589,6 +1639,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " selectedIme=" + mSettings.getSelectedInputMethod()); mLastSwitchUserId = newUserId; + + if (mIsInteractive && clientToBeReset != null) { + final ClientState cs = mClients.get(clientToBeReset.asBinder()); + if (cs == null) { + // The client is already gone. + return; + } + try { + cs.client.scheduleStartInputIfNecessary(mInFullscreenMode); + } catch (RemoteException e) { + } + } } void updateCurrentProfileIds() { @@ -3072,6 +3134,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return InputBindResult.NOT_IME_TARGET_WINDOW; } + if (mUserSwitchHandlerTask != null) { + // There is already an on-going pending user switch task. + final int nextUserId = mUserSwitchHandlerTask.mToUserId; + if (userId == nextUserId) { + scheduleSwitchUserTaskLocked(userId, cs.client); + return InputBindResult.USER_SWITCHING; + } + for (int profileId : mUserManager.getProfileIdsWithDisabled(nextUserId)) { + if (profileId == userId) { + scheduleSwitchUserTaskLocked(userId, cs.client); + return InputBindResult.USER_SWITCHING; + } + } + return InputBindResult.INVALID_USER; + } + // cross-profile access is always allowed here to allow profile-switching. if (!mSettings.isCurrentProfile(userId)) { Slog.w(TAG, "A background user is requesting window. Hiding IME."); @@ -3083,8 +3161,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (userId != mSettings.getCurrentUserId()) { - switchUserLocked(userId); + scheduleSwitchUserTaskLocked(userId, cs.client); + return InputBindResult.USER_SWITCHING; } + // Master feature flag that overrides other conditions and forces IME preRendering. if (DEBUG) { Slog.v(TAG, "IME PreRendering MASTER flag: "