Clarify which InputMethodManagerInternal method takes lock internally

InputMethodManagerInternal is the callback where we need to pay extra
attention to potential dead-locks.

To make code reading easier, this CL makes it clear about which method
needs to acquire ImfLock and which method is always thread-safe.

Basically this is a mechanical code refactoring.  There should be no
observable behavior change.

Bug: 192412909
Test: presubmit
Change-Id: I98958e6c149582b0a24997c9ca814e355f8b2a2b
This commit is contained in:
Yohei Yukawa
2022-02-01 13:25:24 -08:00
parent 052cb99b8f
commit 0fee8d1035

View File

@@ -1495,8 +1495,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onStart() {
LocalServices.addService(InputMethodManagerInternal.class,
new LocalServiceImpl(mService));
mService.publishLocalService();
publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@@ -4866,26 +4865,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return mCurrentSubtype;
}
private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
return getInputMethodListLocked(userId, DirectBootAwareness.AUTO);
}
}
private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
return getEnabledInputMethodListLocked(userId);
}
}
private void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo,
IInlineSuggestionsRequestCallback callback) {
synchronized (ImfLock.class) {
onCreateInlineSuggestionsRequestLocked(userId, requestInfo, callback);
}
}
private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
@@ -4897,161 +4876,153 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return methodMap;
}
private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
synchronized (ImfLock.class) {
if (userId == mSettings.getCurrentUserId()) {
if (!mMethodMap.containsKey(imeId)
|| !mSettings.getEnabledInputMethodListLocked()
.contains(mMethodMap.get(imeId))) {
return false; // IME is not found or not enabled.
}
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
final InputMethodSettings settings = new InputMethodSettings(
mContext.getResources(), mContext.getContentResolver(), methodMap,
userId, false);
if (!methodMap.containsKey(imeId)
|| !settings.getEnabledInputMethodListLocked()
.contains(methodMap.get(imeId))) {
@GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
if (userId == mSettings.getCurrentUserId()) {
if (!mMethodMap.containsKey(imeId)
|| !mSettings.getEnabledInputMethodListLocked()
.contains(mMethodMap.get(imeId))) {
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
final InputMethodSettings settings = new InputMethodSettings(
mContext.getResources(), mContext.getContentResolver(), methodMap,
userId, false);
if (!methodMap.containsKey(imeId)
|| !settings.getEnabledInputMethodListLocked()
.contains(methodMap.get(imeId))) {
return false; // IME is not found or not enabled.
}
settings.putSelectedInputMethod(imeId);
settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
return true;
}
private boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
synchronized (ImfLock.class) {
if (userId == mSettings.getCurrentUserId()) {
if (!mMethodMap.containsKey(imeId)) {
return false; // IME is not found.
}
setInputMethodEnabledLocked(imeId, enabled);
return true;
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
final InputMethodSettings settings = new InputMethodSettings(
mContext.getResources(), mContext.getContentResolver(), methodMap,
userId, false);
if (!methodMap.containsKey(imeId)) {
return false; // IME is not found.
}
if (enabled) {
if (!settings.getEnabledInputMethodListLocked().contains(methodMap.get(imeId))) {
settings.appendAndPutEnabledInputMethodLocked(imeId, false);
}
} else {
settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
new StringBuilder(),
settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
}
return true;
}
private void publishLocalService() {
LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
}
private boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
int displayId) {
//TODO(b/150843766): Check if Input Token is valid.
final IBinder curHostInputToken;
synchronized (ImfLock.class) {
if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) {
return false;
}
curHostInputToken = mCurHostInputToken;
}
return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken);
}
private void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) {
synchronized (ImfLock.class) {
if (mCurFocusedWindow != windowToken) {
// mCurPerceptible was set by the focused window, but it is no longer in control,
// so we reset mCurPerceptible.
mCurPerceptible = true;
}
if (imeParentChanged) {
// Hide the IME method menu earlier when the IME surface parent will change in
// case seeing the dialog dismiss flickering during the next focused window
// starting the input connection.
mMenuController.hideInputMethodMenu();
}
}
}
private static final class LocalServiceImpl extends InputMethodManagerInternal {
@NonNull
private final InputMethodManagerService mService;
LocalServiceImpl(@NonNull InputMethodManagerService service) {
mService = service;
}
private final class LocalServiceImpl extends InputMethodManagerInternal {
@Override
public void setInteractive(boolean interactive) {
// Do everything in handler so as not to block the caller.
mService.mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0)
.sendToTarget();
mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget();
}
@Override
public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) {
mService.mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
mService.mHandler.obtainMessage(MSG_HIDE_CURRENT_INPUT_METHOD, reason).sendToTarget();
mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
mHandler.obtainMessage(MSG_HIDE_CURRENT_INPUT_METHOD, reason).sendToTarget();
}
@Override
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return mService.getInputMethodListAsUser(userId);
synchronized (ImfLock.class) {
return getInputMethodListLocked(userId, DirectBootAwareness.AUTO);
}
}
@Override
public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
return mService.getEnabledInputMethodListAsUser(userId);
synchronized (ImfLock.class) {
return getEnabledInputMethodListLocked(userId);
}
}
@Override
public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) {
mService.onCreateInlineSuggestionsRequest(userId, requestInfo, cb);
synchronized (ImfLock.class) {
onCreateInlineSuggestionsRequestLocked(userId, requestInfo, cb);
}
}
@Override
public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
return mService.switchToInputMethod(imeId, userId);
synchronized (ImfLock.class) {
return switchToInputMethodLocked(imeId, userId);
}
}
@Override
public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
return mService.setInputMethodEnabled(imeId, enabled, userId);
synchronized (ImfLock.class) {
if (userId == mSettings.getCurrentUserId()) {
if (!mMethodMap.containsKey(imeId)) {
return false; // IME is not found.
}
setInputMethodEnabledLocked(imeId, enabled);
return true;
}
final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
final InputMethodSettings settings = new InputMethodSettings(
mContext.getResources(), mContext.getContentResolver(), methodMap,
userId, false);
if (!methodMap.containsKey(imeId)) {
return false; // IME is not found.
}
if (enabled) {
if (!settings.getEnabledInputMethodListLocked().contains(
methodMap.get(imeId))) {
settings.appendAndPutEnabledInputMethodLocked(imeId, false);
}
} else {
settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
new StringBuilder(),
settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
}
return true;
}
}
@Override
public void registerInputMethodListListener(InputMethodListListener listener) {
mService.mInputMethodListListeners.addIfAbsent(listener);
mInputMethodListListeners.addIfAbsent(listener);
}
@Override
public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
int displayId) {
return mService.transferTouchFocusToImeWindow(sourceInputToken, displayId);
//TODO(b/150843766): Check if Input Token is valid.
final IBinder curHostInputToken;
synchronized (ImfLock.class) {
if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) {
return false;
}
curHostInputToken = mCurHostInputToken;
}
return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken);
}
@Override
public void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) {
mService.reportImeControl(windowToken, imeParentChanged);
synchronized (ImfLock.class) {
if (mCurFocusedWindow != windowToken) {
// mCurPerceptible was set by the focused window, but it is no longer in
// control, so we reset mCurPerceptible.
mCurPerceptible = true;
}
if (imeParentChanged) {
// Hide the IME method menu earlier when the IME surface parent will change in
// case seeing the dialog dismiss flickering during the next focused window
// starting the input connection.
mMenuController.hideInputMethodMenu();
}
}
}
@Override
public void removeImeSurface() {
mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@Override
public void updateImeWindowStatus(boolean disableImeIcon) {
mService.mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
.sendToTarget();
}
}
@@ -5689,7 +5660,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
}
boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId);
boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId, userId);
if (failedToSelectUnknownIme) {
error.print("Unknown input method ");
error.print(imeId);