blob: d09c478e320f341415061c12a53f2a2c5c0fd586 [file] [log] [blame]
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.inputmethod;
18
Yohei Yukawa1fb13c52019-02-05 07:55:28 -080019import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080020import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
21
22import static java.lang.annotation.RetentionPolicy.SOURCE;
23
24import android.annotation.AnyThread;
25import android.annotation.BinderThread;
26import android.annotation.IntDef;
27import android.annotation.MainThread;
28import android.annotation.Nullable;
29import android.annotation.UserIdInt;
30import android.annotation.WorkerThread;
31import android.app.AppOpsManager;
Yohei Yukawae6e62f92019-03-09 01:15:04 -080032import android.app.Notification;
33import android.app.NotificationManager;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080034import android.app.PendingIntent;
35import android.content.BroadcastReceiver;
36import android.content.ComponentName;
37import android.content.Context;
38import android.content.Intent;
39import android.content.IntentFilter;
40import android.content.ServiceConnection;
41import android.content.pm.ApplicationInfo;
42import android.content.pm.PackageManager;
43import android.content.pm.ResolveInfo;
44import android.content.pm.ServiceInfo;
45import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
46import android.net.Uri;
47import android.os.Binder;
48import android.os.Build;
Yohei Yukawae6e62f92019-03-09 01:15:04 -080049import android.os.Bundle;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080050import android.os.Debug;
51import android.os.Handler;
52import android.os.HandlerThread;
53import android.os.IBinder;
54import android.os.RemoteException;
55import android.os.ResultReceiver;
56import android.os.ShellCallback;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080057import android.os.UserHandle;
58import android.provider.Settings;
59import android.text.TextUtils;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080060import android.util.ArrayMap;
61import android.util.ArraySet;
62import android.util.Slog;
63import android.util.SparseArray;
64import android.view.InputChannel;
65import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
Adam Hebc67f2e2019-11-13 14:34:56 -080066import android.view.autofill.AutofillId;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080067import android.view.inputmethod.EditorInfo;
68import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
69import android.view.inputmethod.InputMethodInfo;
70import android.view.inputmethod.InputMethodSubtype;
71
Yohei Yukawae6e62f92019-03-09 01:15:04 -080072import com.android.internal.R;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080073import com.android.internal.annotations.GuardedBy;
74import com.android.internal.inputmethod.IMultiClientInputMethod;
75import com.android.internal.inputmethod.IMultiClientInputMethodPrivilegedOperations;
76import com.android.internal.inputmethod.IMultiClientInputMethodSession;
77import com.android.internal.inputmethod.StartInputFlags;
78import com.android.internal.inputmethod.StartInputReason;
79import com.android.internal.inputmethod.UnbindReason;
Yohei Yukawae6e62f92019-03-09 01:15:04 -080080import com.android.internal.messages.nano.SystemMessageProto;
81import com.android.internal.notification.SystemNotificationChannels;
Keun young Park9b673eb2019-04-29 17:54:11 -070082import com.android.internal.os.TransferPipe;
83import com.android.internal.util.DumpUtils;
84import com.android.internal.util.IndentingPrintWriter;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080085import com.android.internal.util.function.pooled.PooledLambda;
Adam Hebc67f2e2019-11-13 14:34:56 -080086import com.android.internal.view.IInlineSuggestionsRequestCallback;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080087import com.android.internal.view.IInputContext;
88import com.android.internal.view.IInputMethodClient;
89import com.android.internal.view.IInputMethodManager;
90import com.android.internal.view.IInputMethodSession;
91import com.android.internal.view.InputBindResult;
92import com.android.server.LocalServices;
93import com.android.server.SystemService;
94import com.android.server.wm.WindowManagerInternal;
95
96import java.io.FileDescriptor;
Keun young Park9b673eb2019-04-29 17:54:11 -070097import java.io.IOException;
98import java.io.PrintWriter;
Yohei Yukawabae5bea2018-11-12 15:08:30 -080099import java.lang.annotation.Retention;
100import java.util.Collections;
101import java.util.List;
102import java.util.WeakHashMap;
103
104/**
105 * Actual implementation of multi-client InputMethodManagerService.
106 *
107 * <p>This system service is intentionally compatible with {@link InputMethodManagerService} so that
108 * we can switch the implementation at the boot time.</p>
109 */
110public final class MultiClientInputMethodManagerService {
Yohei Yukawae6e62f92019-03-09 01:15:04 -0800111 private static final String TAG = "MultiClientInputMethodManagerService";
112 private static final boolean DEBUG = false;
113
114 private static final String PER_DISPLAY_FOCUS_DISABLED_WARNING_TITLE =
115 "config_perDisplayFocusEnabled is not true.";
116
117 private static final String PER_DISPLAY_FOCUS_DISABLED_WARNING_MSG =
118 "Consider rebuilding the system image after enabling config_perDisplayFocusEnabled to "
119 + "make IME focus compatible with multi-client IME mode.";
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800120
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800121 private static final long RECONNECT_DELAY_MSEC = 1000;
122
123 /**
124 * Unlike {@link InputMethodManagerService}, {@link MultiClientInputMethodManagerService}
125 * always binds to the IME with {@link Context#BIND_FOREGROUND_SERVICE} for now for simplicity.
126 */
127 private static final int IME_CONNECTION_UNIFIED_BIND_FLAGS =
128 Context.BIND_AUTO_CREATE
129 | Context.BIND_NOT_VISIBLE
130 | Context.BIND_NOT_FOREGROUND
131 | Context.BIND_FOREGROUND_SERVICE;
132
Yohei Yukawa6048d892018-12-25 09:57:31 -0800133 private static final ComponentName sImeComponentName =
134 InputMethodSystemProperty.sMultiClientImeComponentName;
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800135
136 private static void reportNotSupported() {
137 if (DEBUG) {
138 Slog.d(TAG, "non-supported operation. callers=" + Debug.getCallers(3));
139 }
140 }
141
142 /**
143 * {@link MultiClientInputMethodManagerService} is not intended to be instantiated.
144 */
145 private MultiClientInputMethodManagerService() {
146 }
147
148 /**
149 * The implementation of {@link SystemService} for multi-client IME.
150 */
151 public static final class Lifecycle extends SystemService {
152 private final ApiCallbacks mApiCallbacks;
153 private final OnWorkerThreadCallback mOnWorkerThreadCallback;
154
155 @MainThread
156 public Lifecycle(Context context) {
157 super(context);
158
159 final UserToInputMethodInfoMap userIdToInputMethodInfoMapper =
160 new UserToInputMethodInfoMap();
161 final UserDataMap userDataMap = new UserDataMap();
162 final HandlerThread workerThread = new HandlerThread(TAG);
163 workerThread.start();
164 mApiCallbacks = new ApiCallbacks(context, userDataMap, userIdToInputMethodInfoMapper);
165 mOnWorkerThreadCallback = new OnWorkerThreadCallback(
166 context, userDataMap, userIdToInputMethodInfoMapper,
167 new Handler(workerThread.getLooper(), msg -> false, true));
168
169 LocalServices.addService(InputMethodManagerInternal.class,
170 new InputMethodManagerInternal() {
171 @Override
172 public void setInteractive(boolean interactive) {
173 reportNotSupported();
174 }
175
176 @Override
177 public void hideCurrentInputMethod() {
178 reportNotSupported();
179 }
180
181 @Override
Yohei Yukawaa878b952019-01-10 19:36:24 -0800182 public List<InputMethodInfo> getInputMethodListAsUser(
183 @UserIdInt int userId) {
184 return userIdToInputMethodInfoMapper.getAsList(userId);
185 }
186
187 @Override
188 public List<InputMethodInfo> getEnabledInputMethodListAsUser(
189 @UserIdInt int userId) {
190 return userIdToInputMethodInfoMapper.getAsList(userId);
191 }
Adam Hebc67f2e2019-11-13 14:34:56 -0800192
193 @Override
Feng Cao16b2de52020-01-09 17:27:27 -0800194 public void onCreateInlineSuggestionsRequest(int userId,
195 ComponentName componentName, AutofillId autofillId,
196 IInlineSuggestionsRequestCallback cb) {
Adam Hebc67f2e2019-11-13 14:34:56 -0800197 try {
198 //TODO(b/137800469): support multi client IMEs.
199 cb.onInlineSuggestionsUnsupported();
200 } catch (RemoteException e) {
Joanne Chungd9a916f2020-01-02 19:03:16 +0800201 Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
Adam Hebc67f2e2019-11-13 14:34:56 -0800202 }
203 }
mincheli850892b2019-12-05 19:47:59 +0800204
205 @Override
206 public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
207 reportNotSupported();
208 return false;
209 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800210 });
211 }
212
213 @MainThread
214 @Override
215 public void onBootPhase(int phase) {
216 mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
217 OnWorkerThreadCallback::onBootPhase, mOnWorkerThreadCallback, phase));
218 }
219
220 @MainThread
221 @Override
222 public void onStart() {
223 publishBinderService(Context.INPUT_METHOD_SERVICE, mApiCallbacks);
224 }
225
226 @MainThread
227 @Override
228 public void onStartUser(@UserIdInt int userId) {
229 mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
230 OnWorkerThreadCallback::onStartUser, mOnWorkerThreadCallback, userId));
231 }
232
233 @MainThread
234 @Override
235 public void onUnlockUser(@UserIdInt int userId) {
236 mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
237 OnWorkerThreadCallback::onUnlockUser, mOnWorkerThreadCallback, userId));
238 }
239
240 @MainThread
241 @Override
242 public void onStopUser(@UserIdInt int userId) {
243 mOnWorkerThreadCallback.getHandler().sendMessage(PooledLambda.obtainMessage(
244 OnWorkerThreadCallback::onStopUser, mOnWorkerThreadCallback, userId));
245 }
246 }
247
248 private static final class OnWorkerThreadCallback {
249 private final Context mContext;
250 private final UserDataMap mUserDataMap;
251 private final UserToInputMethodInfoMap mInputMethodInfoMap;
252 private final Handler mHandler;
253
254 OnWorkerThreadCallback(Context context, UserDataMap userDataMap,
255 UserToInputMethodInfoMap inputMethodInfoMap, Handler handler) {
256 mContext = context;
257 mUserDataMap = userDataMap;
258 mInputMethodInfoMap = inputMethodInfoMap;
259 mHandler = handler;
260 }
261
262 @AnyThread
263 Handler getHandler() {
264 return mHandler;
265 }
266
267 @WorkerThread
268 private void tryBindInputMethodService(@UserIdInt int userId) {
269 final PerUserData data = mUserDataMap.get(userId);
270 if (data == null) {
271 Slog.i(TAG, "tryBindInputMethodService is called for an unknown user=" + userId);
272 return;
273 }
274
Yohei Yukawa6048d892018-12-25 09:57:31 -0800275 final InputMethodInfo imi = queryInputMethod(mContext, userId, sImeComponentName);
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800276 if (imi == null) {
277 Slog.w(TAG, "Multi-client InputMethod is not found. component="
Yohei Yukawa6048d892018-12-25 09:57:31 -0800278 + sImeComponentName);
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800279 synchronized (data.mLock) {
280 switch (data.mState) {
281 case PerUserState.USER_LOCKED:
282 case PerUserState.SERVICE_NOT_QUERIED:
283 case PerUserState.SERVICE_RECOGNIZED:
284 case PerUserState.UNBIND_CALLED:
285 // Safe to clean up.
286 mInputMethodInfoMap.remove(userId);
287 break;
288 }
289 }
290 return;
291 }
292
293 synchronized (data.mLock) {
294 switch (data.mState) {
295 case PerUserState.USER_LOCKED:
296 // If the user is still locked, we currently do not try to start IME.
297 return;
298 case PerUserState.SERVICE_NOT_QUERIED:
299 case PerUserState.SERVICE_RECOGNIZED:
300 case PerUserState.UNBIND_CALLED:
301 break;
302 case PerUserState.WAITING_SERVICE_CONNECTED:
303 case PerUserState.SERVICE_CONNECTED:
304 // OK, nothing to do.
305 return;
306 default:
307 Slog.wtf(TAG, "Unknown state=" + data.mState);
308 return;
309 }
310 data.mState = PerUserState.SERVICE_RECOGNIZED;
311 data.mCurrentInputMethodInfo = imi;
312 mInputMethodInfoMap.put(userId, imi);
313 final boolean bindResult = data.bindServiceLocked(mContext, userId);
314 if (!bindResult) {
315 Slog.e(TAG, "Failed to bind Multi-client InputMethod.");
316 return;
317 }
318 data.mState = PerUserState.WAITING_SERVICE_CONNECTED;
319 }
320 }
321
322 @WorkerThread
323 void onStartUser(@UserIdInt int userId) {
324 if (DEBUG) {
325 Slog.v(TAG, "onStartUser userId=" + userId);
326 }
327 final PerUserData data = new PerUserData(userId, null, PerUserState.USER_LOCKED, this);
328 mUserDataMap.put(userId, data);
329 }
330
331 @WorkerThread
332 void onUnlockUser(@UserIdInt int userId) {
333 if (DEBUG) {
334 Slog.v(TAG, "onUnlockUser() userId=" + userId);
335 }
336 final PerUserData data = mUserDataMap.get(userId);
337 if (data == null) {
338 Slog.i(TAG, "onUnlockUser is called for an unknown user=" + userId);
339 return;
340 }
341 synchronized (data.mLock) {
342 switch (data.mState) {
343 case PerUserState.USER_LOCKED:
344 data.mState = PerUserState.SERVICE_NOT_QUERIED;
345 tryBindInputMethodService(userId);
346 break;
347 default:
348 Slog.wtf(TAG, "Unknown state=" + data.mState);
349 break;
350 }
351 }
352 }
353
354 @WorkerThread
355 void onStopUser(@UserIdInt int userId) {
356 if (DEBUG) {
357 Slog.v(TAG, "onStopUser() userId=" + userId);
358 }
359 mInputMethodInfoMap.remove(userId);
360 final PerUserData data = mUserDataMap.removeReturnOld(userId);
361 if (data == null) {
362 Slog.i(TAG, "onStopUser is called for an unknown user=" + userId);
363 return;
364 }
365 synchronized (data.mLock) {
366 switch (data.mState) {
367 case PerUserState.USER_LOCKED:
368 case PerUserState.SERVICE_RECOGNIZED:
369 case PerUserState.UNBIND_CALLED:
370 // OK, nothing to do.
371 return;
372 case PerUserState.SERVICE_CONNECTED:
373 case PerUserState.WAITING_SERVICE_CONNECTED:
374 break;
375 default:
376 Slog.wtf(TAG, "Unknown state=" + data.mState);
377 break;
378 }
379 data.unbindServiceLocked(mContext);
380 data.mState = PerUserState.UNBIND_CALLED;
381 data.mCurrentInputMethod = null;
382
383 // When a Service is explicitly unbound with Context.unbindService(),
384 // onServiceDisconnected() will not be triggered. Hence here we explicitly call
385 // onInputMethodDisconnectedLocked() as if the Service is already gone.
386 data.onInputMethodDisconnectedLocked();
387 }
388 }
389
390 @WorkerThread
391 void onServiceConnected(PerUserData data, IMultiClientInputMethod service) {
392 if (DEBUG) {
393 Slog.v(TAG, "onServiceConnected() data.mUserId=" + data.mUserId);
394 }
395 synchronized (data.mLock) {
396 switch (data.mState) {
397 case PerUserState.UNBIND_CALLED:
398 // We should ignore this callback.
399 return;
400 case PerUserState.WAITING_SERVICE_CONNECTED:
401 // OK.
402 data.mState = PerUserState.SERVICE_CONNECTED;
403 data.mCurrentInputMethod = service;
404 try {
405 data.mCurrentInputMethod.initialize(new ImeCallbacks(data));
406 } catch (RemoteException e) {
407 }
408 data.onInputMethodConnectedLocked();
409 break;
410 default:
411 Slog.wtf(TAG, "Unknown state=" + data.mState);
412 return;
413 }
414 }
415 }
416
417 @WorkerThread
418 void onServiceDisconnected(PerUserData data) {
419 if (DEBUG) {
420 Slog.v(TAG, "onServiceDisconnected() data.mUserId=" + data.mUserId);
421 }
422 final WindowManagerInternal windowManagerInternal =
423 LocalServices.getService(WindowManagerInternal.class);
424 synchronized (data.mLock) {
425 // We assume the number of tokens would not be that large (up to 10 or so) hence
426 // linear search should be acceptable.
427 final int numTokens = data.mDisplayIdToImeWindowTokenMap.size();
428 for (int i = 0; i < numTokens; ++i) {
429 final TokenInfo info = data.mDisplayIdToImeWindowTokenMap.valueAt(i);
430 windowManagerInternal.removeWindowToken(info.mToken, false, info.mDisplayId);
431 }
432 data.mDisplayIdToImeWindowTokenMap.clear();
433 switch (data.mState) {
434 case PerUserState.UNBIND_CALLED:
435 // We should ignore this callback.
436 return;
437 case PerUserState.WAITING_SERVICE_CONNECTED:
438 case PerUserState.SERVICE_CONNECTED:
439 // onServiceDisconnected() means the biding is still alive.
440 data.mState = PerUserState.WAITING_SERVICE_CONNECTED;
441 data.mCurrentInputMethod = null;
442 data.onInputMethodDisconnectedLocked();
443 break;
444 default:
445 Slog.wtf(TAG, "Unknown state=" + data.mState);
446 return;
447 }
448 }
449 }
450
451 @WorkerThread
452 void onBindingDied(PerUserData data) {
453 if (DEBUG) {
454 Slog.v(TAG, "onBindingDied() data.mUserId=" + data.mUserId);
455 }
456 final WindowManagerInternal windowManagerInternal =
457 LocalServices.getService(WindowManagerInternal.class);
458 synchronized (data.mLock) {
459 // We assume the number of tokens would not be that large (up to 10 or so) hence
460 // linear search should be acceptable.
461 final int numTokens = data.mDisplayIdToImeWindowTokenMap.size();
462 for (int i = 0; i < numTokens; ++i) {
463 final TokenInfo info = data.mDisplayIdToImeWindowTokenMap.valueAt(i);
464 windowManagerInternal.removeWindowToken(info.mToken, false, info.mDisplayId);
465 }
466 data.mDisplayIdToImeWindowTokenMap.clear();
467 switch (data.mState) {
468 case PerUserState.UNBIND_CALLED:
469 // We should ignore this callback.
470 return;
471 case PerUserState.WAITING_SERVICE_CONNECTED:
472 case PerUserState.SERVICE_CONNECTED: {
473 // onBindingDied() means the biding is dead.
474 data.mState = PerUserState.UNBIND_CALLED;
475 data.mCurrentInputMethod = null;
476 data.onInputMethodDisconnectedLocked();
477 // Schedule a retry
478 mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
479 OnWorkerThreadCallback::tryBindInputMethodService,
480 this, data.mUserId), RECONNECT_DELAY_MSEC);
481 break;
482 }
483 default:
484 Slog.wtf(TAG, "Unknown state=" + data.mState);
485 return;
486 }
487 }
488 }
489
490 @WorkerThread
491 void onBootPhase(int phase) {
492 if (DEBUG) {
493 Slog.v(TAG, "onBootPhase() phase=" + phase);
494 }
495 switch (phase) {
496 case SystemService.PHASE_ACTIVITY_MANAGER_READY: {
497 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
498 filter.addDataScheme("package");
499 mContext.registerReceiver(new BroadcastReceiver() {
500 @Override
501 public void onReceive(Context context, Intent intent) {
502 onPackageAdded(intent);
503 }
504 }, filter, null, mHandler);
Yohei Yukawae6e62f92019-03-09 01:15:04 -0800505 break;
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800506 }
Yohei Yukawae6e62f92019-03-09 01:15:04 -0800507 case SystemService.PHASE_BOOT_COMPLETED: {
508 final boolean perDisplayFocusEnabled = mContext.getResources().getBoolean(
509 com.android.internal.R.bool.config_perDisplayFocusEnabled);
510 if (!perDisplayFocusEnabled) {
511 final Bundle extras = new Bundle();
512 extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
513 mContext.getSystemService(NotificationManager.class).notifyAsUser(TAG,
514 SystemMessageProto.SystemMessage.NOTE_SELECT_INPUT_METHOD,
515 new Notification.Builder(mContext,
516 SystemNotificationChannels.VIRTUAL_KEYBOARD)
517 .setContentTitle(PER_DISPLAY_FOCUS_DISABLED_WARNING_TITLE)
518 .setStyle(new Notification.BigTextStyle()
519 .bigText(PER_DISPLAY_FOCUS_DISABLED_WARNING_MSG))
520 .setSmallIcon(R.drawable.ic_notification_ime_default)
521 .setWhen(0)
522 .setOngoing(true)
523 .setLocalOnly(true)
524 .addExtras(extras)
525 .setCategory(Notification.CATEGORY_SYSTEM)
526 .setColor(mContext.getColor(
527 R.color.system_notification_accent_color))
528 .build(), UserHandle.ALL);
529 }
530 break;
531 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800532 }
533 }
534
535 @WorkerThread
536 void onPackageAdded(Intent intent) {
537 if (DEBUG) {
538 Slog.v(TAG, "onPackageAdded() intent=" + intent);
539 }
540 final Uri uri = intent.getData();
541 if (uri == null) {
542 return;
543 }
544 if (!intent.hasExtra(Intent.EXTRA_UID)) {
545 return;
546 }
547 final String packageName = uri.getSchemeSpecificPart();
Yohei Yukawa6048d892018-12-25 09:57:31 -0800548 if (sImeComponentName == null
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800549 || packageName == null
Yohei Yukawa6048d892018-12-25 09:57:31 -0800550 || !TextUtils.equals(sImeComponentName.getPackageName(), packageName)) {
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800551 return;
552 }
553 final int userId = UserHandle.getUserId(intent.getIntExtra(Intent.EXTRA_UID, 0));
554 tryBindInputMethodService(userId);
555 }
556 }
557
558 private static final class WindowInfo {
559 final IBinder mWindowToken;
560 final int mWindowHandle;
561
562 WindowInfo(IBinder windowToken, int windowCookie) {
563 mWindowToken = windowToken;
564 mWindowHandle = windowCookie;
565 }
566 }
567
568 /**
569 * Describes the state of each IME client.
570 */
571 @Retention(SOURCE)
572 @IntDef({InputMethodClientState.REGISTERED,
573 InputMethodClientState.WAITING_FOR_IME_SESSION,
574 InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT,
575 InputMethodClientState.ALREADY_SENT_BIND_RESULT,
576 InputMethodClientState.UNREGISTERED})
577 private @interface InputMethodClientState {
578 /**
579 * {@link IInputMethodManager#addClient(IInputMethodClient, IInputContext, int)} is called
580 * and this client is now recognized by the system. When the system lost the connection to
581 * the current IME, all the clients need to be re-initialized from this state.
582 */
583 int REGISTERED = 1;
584 /**
585 * This client is notified to the current IME with {@link
586 * IMultiClientInputMethod#addClient(int, int, int, int)} but the IME is not yet responded
587 * with {@link IMultiClientInputMethodPrivilegedOperations#acceptClient(int,
588 * IInputMethodSession, IMultiClientInputMethodSession, InputChannel)}.
589 */
590 int WAITING_FOR_IME_SESSION = 2;
591 /**
592 * This client is already accepted by the IME but a valid {@link InputBindResult} has not
593 * been returned to the client yet.
594 */
595 int READY_TO_SEND_FIRST_BIND_RESULT = 3;
596 /**
597 * This client has already received a valid {@link InputBindResult} at least once. This
598 * means that the client can directly call {@link IInputMethodSession} IPCs and key events
599 * via {@link InputChannel}. When the current IME is unbound, these client end points also
600 * need to be cleared.
601 */
602 int ALREADY_SENT_BIND_RESULT = 4;
603 /**
604 * The client process is dying.
605 */
606 int UNREGISTERED = 5;
607 }
608
609 private static final class InputMethodClientIdSource {
610 @GuardedBy("InputMethodClientIdSource.class")
611 private static int sNextValue = 0;
612
613 private InputMethodClientIdSource() {
614 }
615
616 static synchronized int getNext() {
617 final int result = sNextValue;
618 sNextValue++;
619 if (sNextValue < 0) {
620 sNextValue = 0;
621 }
622 return result;
623 }
624 }
625
626 private static final class WindowHandleSource {
627 @GuardedBy("WindowHandleSource.class")
628 private static int sNextValue = 0;
629
630 private WindowHandleSource() {
631 }
632
633 static synchronized int getNext() {
634 final int result = sNextValue;
635 sNextValue++;
636 if (sNextValue < 0) {
637 sNextValue = 0;
638 }
639 return result;
640 }
641 }
642
643 private static final class InputMethodClientInfo {
644 final IInputMethodClient mClient;
645 final int mUid;
646 final int mPid;
647 final int mSelfReportedDisplayId;
648 final int mClientId;
649
650 @GuardedBy("PerUserData.mLock")
651 @InputMethodClientState
652 int mState;
653 @GuardedBy("PerUserData.mLock")
654 int mBindingSequence;
655 @GuardedBy("PerUserData.mLock")
656 InputChannel mWriteChannel;
657 @GuardedBy("PerUserData.mLock")
658 IInputMethodSession mInputMethodSession;
659 @GuardedBy("PerUserData.mLock")
660 IMultiClientInputMethodSession mMSInputMethodSession;
661 @GuardedBy("PerUserData.mLock")
662 final WeakHashMap<IBinder, WindowInfo> mWindowMap = new WeakHashMap<>();
663
664 InputMethodClientInfo(IInputMethodClient client, int uid, int pid,
665 int selfReportedDisplayId) {
666 mClient = client;
667 mUid = uid;
668 mPid = pid;
669 mSelfReportedDisplayId = selfReportedDisplayId;
670 mClientId = InputMethodClientIdSource.getNext();
671 }
Keun young Park9b673eb2019-04-29 17:54:11 -0700672
673 @GuardedBy("PerUserData.mLock")
674 void dumpLocked(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
675 ipw.println("mState=" + mState + ",mBindingSequence=" + mBindingSequence
676 + ",mWriteChannel=" + mWriteChannel
677 + ",mInputMethodSession=" + mInputMethodSession
678 + ",mMSInputMethodSession=" + mMSInputMethodSession);
679 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800680 }
681
682 private static final class UserDataMap {
683 @GuardedBy("mMap")
684 private final SparseArray<PerUserData> mMap = new SparseArray<>();
685
686 @AnyThread
687 @Nullable
688 PerUserData get(@UserIdInt int userId) {
689 synchronized (mMap) {
690 return mMap.get(userId);
691 }
692 }
693
694 @AnyThread
695 void put(@UserIdInt int userId, PerUserData data) {
696 synchronized (mMap) {
697 mMap.put(userId, data);
698 }
699 }
700
701 @AnyThread
702 @Nullable
703 PerUserData removeReturnOld(@UserIdInt int userId) {
704 synchronized (mMap) {
705 return mMap.removeReturnOld(userId);
706 }
707 }
Keun young Park9b673eb2019-04-29 17:54:11 -0700708
709 @AnyThread
710 void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
711 synchronized (mMap) {
712 for (int i = 0; i < mMap.size(); i++) {
713 int userId = mMap.keyAt(i);
714 PerUserData data = mMap.valueAt(i);
715 ipw.println("userId=" + userId + ", data=");
716 if (data != null) {
717 ipw.increaseIndent();
718 data.dump(fd, ipw, args);
719 ipw.decreaseIndent();
720 }
721 }
722 }
723 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -0800724 }
725
726 private static final class TokenInfo {
727 final Binder mToken;
728 final int mDisplayId;
729 TokenInfo(Binder token, int displayId) {
730 mToken = token;
731 mDisplayId = displayId;
732 }
733 }
734
735 @Retention(SOURCE)
736 @IntDef({
737 PerUserState.USER_LOCKED,
738 PerUserState.SERVICE_NOT_QUERIED,
739 PerUserState.SERVICE_RECOGNIZED,
740 PerUserState.WAITING_SERVICE_CONNECTED,
741 PerUserState.SERVICE_CONNECTED,
742 PerUserState.UNBIND_CALLED})
743 private @interface PerUserState {
744 /**
745 * The user is still locked.
746 */
747 int USER_LOCKED = 1;
748 /**
749 * The system has not queried whether there is a multi-client IME or not.
750 */
751 int SERVICE_NOT_QUERIED = 2;
752 /**
753 * A multi-client IME specified in {@link #PROP_DEBUG_MULTI_CLIENT_IME} is found in the
754 * system, but not bound yet.
755 */
756 int SERVICE_RECOGNIZED = 3;
757 /**
758 * {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, Handler, UserHandle)} is
759 * already called for the IME but
760 * {@link ServiceConnection#onServiceConnected(ComponentName, IBinder)} is not yet called
761 * back. This includes once the IME is bound but temporarily disconnected as notified with
762 * {@link ServiceConnection#onServiceDisconnected(ComponentName)}.
763 */
764 int WAITING_SERVICE_CONNECTED = 4;
765 /**
766 * {@link ServiceConnection#onServiceConnected(ComponentName, IBinder)} is already called
767 * back. The IME is ready to be used.
768 */
769 int SERVICE_CONNECTED = 5;
770 /**
771 * The binding is gone. Either {@link Context#unbindService(ServiceConnection)} is
772 * explicitly called or the system decided to destroy the binding as notified with
773 * {@link ServiceConnection#onBindingDied(ComponentName)}.
774 */
775 int UNBIND_CALLED = 6;
776 }
777
778 /**
779 * Takes care of per-user state separation.
780 */
781 private static final class PerUserData {
782 final Object mLock = new Object();
783
784 /**
785 * User ID (not UID) that is associated with this data.
786 */
787 @UserIdInt
788 private final int mUserId;
789
790 /**
791 * {@link IMultiClientInputMethod} of the currently connected multi-client IME. This
792 * must be non-{@code null} only while {@link #mState} is
793 * {@link PerUserState#SERVICE_CONNECTED}.
794 */
795 @Nullable
796 @GuardedBy("mLock")
797 IMultiClientInputMethod mCurrentInputMethod;
798
799 /**
800 * {@link InputMethodInfo} of the currently selected multi-client IME. This must be
801 * non-{@code null} unless {@link #mState} is {@link PerUserState#SERVICE_NOT_QUERIED}.
802 */
803 @GuardedBy("mLock")
804 @Nullable
805 InputMethodInfo mCurrentInputMethodInfo;
806
807 /**
808 * Describes the current service state.
809 */
810 @GuardedBy("mLock")
811 @PerUserState
812 int mState;
813
814 /**
815 * A {@link SparseArray} that maps display ID to IME Window token that is already issued to
816 * the IME.
817 */
818 @GuardedBy("mLock")
819 final ArraySet<TokenInfo> mDisplayIdToImeWindowTokenMap = new ArraySet<>();
820
821 @GuardedBy("mLock")
822 private final ArrayMap<IBinder, InputMethodClientInfo> mClientMap = new ArrayMap<>();
823
824 @GuardedBy("mLock")
825 private SparseArray<InputMethodClientInfo> mClientIdToClientMap = new SparseArray<>();
826
827 private final OnWorkerThreadServiceConnection mOnWorkerThreadServiceConnection;
828
829 /**
830 * A {@link ServiceConnection} that is designed to run on a certain worker thread with
831 * which {@link OnWorkerThreadCallback} is associated.
832 *
833 * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, Handler, UserHandle).
834 */
835 private static final class OnWorkerThreadServiceConnection implements ServiceConnection {
836 private final PerUserData mData;
837 private final OnWorkerThreadCallback mCallback;
838
839 OnWorkerThreadServiceConnection(PerUserData data, OnWorkerThreadCallback callback) {
840 mData = data;
841 mCallback = callback;
842 }
843
844 @WorkerThread
845 @Override
846 public void onServiceConnected(ComponentName name, IBinder service) {
847 mCallback.onServiceConnected(mData,
848 IMultiClientInputMethod.Stub.asInterface(service));
849 }
850
851 @WorkerThread
852 @Override
853 public void onServiceDisconnected(ComponentName name) {
854 mCallback.onServiceDisconnected(mData);
855 }
856
857 @WorkerThread
858 @Override
859 public void onBindingDied(ComponentName name) {
860 mCallback.onBindingDied(mData);
861 }
862
863 Handler getHandler() {
864 return mCallback.getHandler();
865 }
866 }
867
868 PerUserData(@UserIdInt int userId, @Nullable InputMethodInfo inputMethodInfo,
869 @PerUserState int initialState, OnWorkerThreadCallback callback) {
870 mUserId = userId;
871 mCurrentInputMethodInfo = inputMethodInfo;
872 mState = initialState;
873 mOnWorkerThreadServiceConnection =
874 new OnWorkerThreadServiceConnection(this, callback);
875 }
876
877 @GuardedBy("mLock")
878 boolean bindServiceLocked(Context context, @UserIdInt int userId) {
879 final Intent intent =
880 new Intent(MultiClientInputMethodServiceDelegate.SERVICE_INTERFACE)
881 .setComponent(mCurrentInputMethodInfo.getComponent())
882 .putExtra(Intent.EXTRA_CLIENT_LABEL,
883 com.android.internal.R.string.input_method_binding_label)
884 .putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
885 context, 0,
886 new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
887
888 // Note: Instead of re-dispatching callback from the main thread to the worker thread
889 // where OnWorkerThreadCallback is running, we pass the Handler object here so that
890 // the callbacks will be directly dispatched to the worker thread.
891 return context.bindServiceAsUser(intent, mOnWorkerThreadServiceConnection,
892 IME_CONNECTION_UNIFIED_BIND_FLAGS,
893 mOnWorkerThreadServiceConnection.getHandler(), UserHandle.of(userId));
894 }
895
896 @GuardedBy("mLock")
897 void unbindServiceLocked(Context context) {
898 context.unbindService(mOnWorkerThreadServiceConnection);
899 }
900
901 @GuardedBy("mLock")
902 @Nullable
903 InputMethodClientInfo getClientLocked(IInputMethodClient client) {
904 return mClientMap.get(client.asBinder());
905 }
906
907 @GuardedBy("mLock")
908 @Nullable
909 InputMethodClientInfo getClientFromIdLocked(int clientId) {
910 return mClientIdToClientMap.get(clientId);
911 }
912
913 @GuardedBy("mLock")
914 @Nullable
915 InputMethodClientInfo removeClientLocked(IInputMethodClient client) {
916 final InputMethodClientInfo info = mClientMap.remove(client.asBinder());
917 if (info != null) {
918 mClientIdToClientMap.remove(info.mClientId);
919 }
920 return info;
921 }
922
923 @GuardedBy("mLock")
924 void addClientLocked(int uid, int pid, IInputMethodClient client,
925 int selfReportedDisplayId) {
926 if (getClientLocked(client) != null) {
927 Slog.wtf(TAG, "The same client is added multiple times");
928 return;
929 }
930 final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
931 try {
932 client.asBinder().linkToDeath(deathRecipient, 0);
933 } catch (RemoteException e) {
934 throw new IllegalStateException(e);
935 }
936 final InputMethodClientInfo clientInfo =
937 new InputMethodClientInfo(client, uid, pid, selfReportedDisplayId);
938 clientInfo.mState = InputMethodClientState.REGISTERED;
939 mClientMap.put(client.asBinder(), clientInfo);
940 mClientIdToClientMap.put(clientInfo.mClientId, clientInfo);
941 switch (mState) {
942 case PerUserState.SERVICE_CONNECTED:
943 try {
944 mCurrentInputMethod.addClient(
945 clientInfo.mClientId, clientInfo.mPid, clientInfo.mUid,
946 clientInfo.mSelfReportedDisplayId);
947 clientInfo.mState = InputMethodClientState.WAITING_FOR_IME_SESSION;
948 } catch (RemoteException e) {
949 // TODO(yukawa): Need logging and expected behavior
950 }
951 break;
952 }
953 }
954
955 @GuardedBy("mLock")
956 void onInputMethodConnectedLocked() {
957 final int numClients = mClientMap.size();
958 for (int i = 0; i < numClients; ++i) {
959 final InputMethodClientInfo clientInfo = mClientMap.valueAt(i);
960 switch (clientInfo.mState) {
961 case InputMethodClientState.REGISTERED:
962 // OK
963 break;
964 default:
965 Slog.e(TAG, "Unexpected state=" + clientInfo.mState);
966 return;
967 }
968 try {
969 mCurrentInputMethod.addClient(
970 clientInfo.mClientId, clientInfo.mUid, clientInfo.mPid,
971 clientInfo.mSelfReportedDisplayId);
972 clientInfo.mState = InputMethodClientState.WAITING_FOR_IME_SESSION;
973 } catch (RemoteException e) {
974 }
975 }
976 }
977
978 @GuardedBy("mLock")
979 void onInputMethodDisconnectedLocked() {
980 final int numClients = mClientMap.size();
981 for (int i = 0; i < numClients; ++i) {
982 final InputMethodClientInfo clientInfo = mClientMap.valueAt(i);
983 switch (clientInfo.mState) {
984 case InputMethodClientState.REGISTERED:
985 // Disconnected before onInputMethodConnectedLocked().
986 break;
987 case InputMethodClientState.WAITING_FOR_IME_SESSION:
988 // Disconnected between addClient() and acceptClient().
989 clientInfo.mState = InputMethodClientState.REGISTERED;
990 break;
991 case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
992 clientInfo.mState = InputMethodClientState.REGISTERED;
993 clientInfo.mInputMethodSession = null;
994 clientInfo.mMSInputMethodSession = null;
995 if (clientInfo.mWriteChannel != null) {
996 clientInfo.mWriteChannel.dispose();
997 clientInfo.mWriteChannel = null;
998 }
999 break;
1000 case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
1001 try {
1002 clientInfo.mClient.onUnbindMethod(clientInfo.mBindingSequence,
1003 UnbindReason.DISCONNECT_IME);
1004 } catch (RemoteException e) {
1005 }
1006 clientInfo.mState = InputMethodClientState.REGISTERED;
1007 clientInfo.mInputMethodSession = null;
1008 clientInfo.mMSInputMethodSession = null;
1009 if (clientInfo.mWriteChannel != null) {
1010 clientInfo.mWriteChannel.dispose();
1011 clientInfo.mWriteChannel = null;
1012 }
1013 break;
1014 }
1015 }
1016 }
1017
Keun young Park9b673eb2019-04-29 17:54:11 -07001018 @AnyThread
1019 void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
1020 synchronized (mLock) {
1021 ipw.println("mState=" + mState
1022 + ",mCurrentInputMethod=" + mCurrentInputMethod
1023 + ",mCurrentInputMethodInfo=" + mCurrentInputMethodInfo);
1024
1025 if (mCurrentInputMethod != null) {
1026 // indentation will not be kept. So add visual separator here.
1027 ipw.println(">>Dump CurrentInputMethod>>");
1028 ipw.flush();
1029 try {
1030 TransferPipe.dumpAsync(mCurrentInputMethod.asBinder(), fd, args);
1031 } catch (IOException | RemoteException e) {
1032 ipw.println("Failed to dump input method service: " + e);
1033 }
1034 ipw.println("<<Dump CurrentInputMethod<<");
1035 }
1036
1037 ipw.println("mDisplayIdToImeWindowTokenMap=");
1038 for (TokenInfo info : mDisplayIdToImeWindowTokenMap) {
1039 ipw.println(" display=" + info.mDisplayId + ",token="
1040 + info.mToken);
1041 }
1042 ipw.println("mClientMap=");
1043 ipw.increaseIndent();
1044 for (int i = 0; i < mClientMap.size(); i++) {
1045
1046 ipw.println("binder=" + mClientMap.keyAt(i));
1047 ipw.println(" InputMethodClientInfo=");
1048 InputMethodClientInfo info = mClientMap.valueAt(i);
1049 if (info != null) {
1050 ipw.increaseIndent();
1051 info.dumpLocked(fd, ipw, args);
1052 ipw.decreaseIndent();
1053 }
1054 }
1055 ipw.decreaseIndent();
1056 ipw.println("mClientIdToClientMap=");
1057 ipw.increaseIndent();
1058 for (int i = 0; i < mClientIdToClientMap.size(); i++) {
1059 ipw.println("clientId=" + mClientIdToClientMap.keyAt(i));
1060 ipw.println(" InputMethodClientInfo=");
1061 InputMethodClientInfo info = mClientIdToClientMap.valueAt(i);
1062 if (info != null) {
1063 ipw.increaseIndent();
1064 info.dumpLocked(fd, ipw, args);
1065 ipw.decreaseIndent();
1066 }
1067 if (info.mClient != null) {
1068 // indentation will not be kept. So add visual separator here.
1069 ipw.println(">>DumpClientStart>>");
1070 ipw.flush(); // all writes should be flushed to guarantee order.
1071 try {
1072 TransferPipe.dumpAsync(info.mClient.asBinder(), fd, args);
1073 } catch (IOException | RemoteException e) {
1074 ipw.println(" Failed to dump client:" + e);
1075 }
1076 ipw.println("<<DumpClientEnd<<");
1077 }
1078 }
1079 ipw.decreaseIndent();
1080 }
1081 }
1082
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001083 private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
1084 private final PerUserData mPerUserData;
1085 private final IInputMethodClient mClient;
1086
1087 ClientDeathRecipient(PerUserData perUserData, IInputMethodClient client) {
1088 mPerUserData = perUserData;
1089 mClient = client;
1090 }
1091
1092 @BinderThread
1093 @Override
1094 public void binderDied() {
1095 synchronized (mPerUserData.mLock) {
1096 mClient.asBinder().unlinkToDeath(this, 0);
1097
1098 final InputMethodClientInfo clientInfo =
1099 mPerUserData.removeClientLocked(mClient);
1100 if (clientInfo == null) {
1101 return;
1102 }
1103
1104 if (clientInfo.mWriteChannel != null) {
1105 clientInfo.mWriteChannel.dispose();
1106 clientInfo.mWriteChannel = null;
1107 }
1108 if (clientInfo.mInputMethodSession != null) {
1109 try {
1110 clientInfo.mInputMethodSession.finishSession();
1111 } catch (RemoteException e) {
1112 }
1113 clientInfo.mInputMethodSession = null;
1114 }
1115 clientInfo.mMSInputMethodSession = null;
1116 clientInfo.mState = InputMethodClientState.UNREGISTERED;
1117 switch (mPerUserData.mState) {
1118 case PerUserState.SERVICE_CONNECTED:
1119 try {
1120 mPerUserData.mCurrentInputMethod.removeClient(clientInfo.mClientId);
1121 } catch (RemoteException e) {
1122 // TODO(yukawa): Need logging and expected behavior
1123 }
1124 break;
1125 }
1126 }
1127 }
1128 }
1129 }
1130
1131 /**
1132 * Queries for multi-client IME specified with {@code componentName}.
1133 *
1134 * @param context {@link Context} to be used to query component.
1135 * @param userId User ID for which the multi-client IME is queried.
1136 * @param componentName {@link ComponentName} to be queried.
1137 * @return {@link InputMethodInfo} when multi-client IME is found. Otherwise {@code null}.
1138 */
1139 @Nullable
1140 private static InputMethodInfo queryInputMethod(Context context, @UserIdInt int userId,
1141 @Nullable ComponentName componentName) {
1142 if (componentName == null) {
1143 return null;
1144 }
1145
1146 // Use for queryIntentServicesAsUser
1147 final PackageManager pm = context.getPackageManager();
1148 final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
1149 new Intent(MultiClientInputMethodServiceDelegate.SERVICE_INTERFACE)
1150 .setComponent(componentName),
1151 PackageManager.GET_META_DATA, userId);
1152
1153 if (services.isEmpty()) {
1154 Slog.e(TAG, "No IME found");
1155 return null;
1156 }
1157
1158 if (services.size() > 1) {
1159 Slog.e(TAG, "Only one IME service is supported.");
1160 return null;
1161 }
1162
1163 final ResolveInfo ri = services.get(0);
1164 ServiceInfo si = ri.serviceInfo;
1165 final String imeId = InputMethodInfo.computeId(ri);
1166 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
1167 Slog.e(TAG, imeId + " must have required"
1168 + android.Manifest.permission.BIND_INPUT_METHOD);
1169 return null;
1170 }
1171
1172 if (!Build.IS_DEBUGGABLE && (si.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1173 Slog.e(TAG, imeId + " must be pre-installed when Build.IS_DEBUGGABLE is false");
1174 return null;
1175 }
1176
1177 try {
1178 return new InputMethodInfo(context, ri);
1179 } catch (Exception e) {
1180 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
1181 }
1182 return null;
1183 }
1184
1185 /**
1186 * Manages the mapping rule from user ID to {@link InputMethodInfo}.
1187 */
1188 private static final class UserToInputMethodInfoMap {
1189 @GuardedBy("mArray")
1190 private final SparseArray<InputMethodInfo> mArray = new SparseArray<>();
1191
1192 @AnyThread
1193 void put(@UserIdInt int userId, InputMethodInfo imi) {
1194 synchronized (mArray) {
1195 mArray.put(userId, imi);
1196 }
1197 }
1198
1199 @AnyThread
1200 void remove(@UserIdInt int userId) {
1201 synchronized (mArray) {
1202 mArray.remove(userId);
1203 }
1204 }
1205
1206 @AnyThread
1207 @Nullable
1208 InputMethodInfo get(@UserIdInt int userId) {
1209 synchronized (mArray) {
1210 return mArray.get(userId);
1211 }
1212 }
1213
1214 @AnyThread
1215 List<InputMethodInfo> getAsList(@UserIdInt int userId) {
1216 final InputMethodInfo info = get(userId);
1217 if (info == null) {
1218 return Collections.emptyList();
1219 }
1220 return Collections.singletonList(info);
1221 }
Keun young Park9b673eb2019-04-29 17:54:11 -07001222
1223 @AnyThread
1224 void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
1225 synchronized (mArray) {
1226 for (int i = 0; i < mArray.size(); i++) {
1227 ipw.println("userId=" + mArray.keyAt(i));
1228 ipw.println(" InputMethodInfo=" + mArray.valueAt(i));
1229 }
1230 }
1231 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001232 }
1233
1234 /**
1235 * Takes care of IPCs exposed to the multi-client IME.
1236 */
1237 private static final class ImeCallbacks
1238 extends IMultiClientInputMethodPrivilegedOperations.Stub {
1239 private final PerUserData mPerUserData;
1240 private final WindowManagerInternal mIWindowManagerInternal;
1241
1242 ImeCallbacks(PerUserData perUserData) {
1243 mPerUserData = perUserData;
1244 mIWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
1245 }
1246
1247 @BinderThread
1248 @Override
1249 public IBinder createInputMethodWindowToken(int displayId) {
1250 synchronized (mPerUserData.mLock) {
1251 // We assume the number of tokens would not be that large (up to 10 or so) hence
1252 // linear search should be acceptable.
1253 final int numTokens = mPerUserData.mDisplayIdToImeWindowTokenMap.size();
1254 for (int i = 0; i < numTokens; ++i) {
1255 final TokenInfo tokenInfo =
1256 mPerUserData.mDisplayIdToImeWindowTokenMap.valueAt(i);
1257 // Currently we issue up to one window token per display.
1258 if (tokenInfo.mDisplayId == displayId) {
1259 return tokenInfo.mToken;
1260 }
1261 }
1262
1263 final Binder token = new Binder();
1264 Binder.withCleanCallingIdentity(
1265 PooledLambda.obtainRunnable(WindowManagerInternal::addWindowToken,
1266 mIWindowManagerInternal, token, TYPE_INPUT_METHOD, displayId));
1267 mPerUserData.mDisplayIdToImeWindowTokenMap.add(new TokenInfo(token, displayId));
1268 return token;
1269 }
1270 }
1271
1272 @BinderThread
1273 @Override
1274 public void deleteInputMethodWindowToken(IBinder token) {
1275 synchronized (mPerUserData.mLock) {
1276 // We assume the number of tokens would not be that large (up to 10 or so) hence
1277 // linear search should be acceptable.
1278 final int numTokens = mPerUserData.mDisplayIdToImeWindowTokenMap.size();
1279 for (int i = 0; i < numTokens; ++i) {
1280 final TokenInfo tokenInfo =
1281 mPerUserData.mDisplayIdToImeWindowTokenMap.valueAt(i);
1282 if (tokenInfo.mToken == token) {
1283 mPerUserData.mDisplayIdToImeWindowTokenMap.remove(tokenInfo);
1284 break;
1285 }
1286 }
1287 }
1288 }
1289
1290 @BinderThread
1291 @Override
1292 public void acceptClient(int clientId, IInputMethodSession inputMethodSession,
1293 IMultiClientInputMethodSession multiSessionInputMethodSession,
1294 InputChannel writeChannel) {
1295 synchronized (mPerUserData.mLock) {
1296 final InputMethodClientInfo clientInfo =
1297 mPerUserData.getClientFromIdLocked(clientId);
1298 if (clientInfo == null) {
1299 Slog.e(TAG, "Unknown clientId=" + clientId);
1300 return;
1301 }
1302 switch (clientInfo.mState) {
1303 case InputMethodClientState.WAITING_FOR_IME_SESSION:
1304 try {
1305 clientInfo.mClient.setActive(true, false);
1306 } catch (RemoteException e) {
1307 // TODO(yukawa): Remove this client.
1308 return;
1309 }
1310 clientInfo.mState = InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT;
1311 clientInfo.mWriteChannel = writeChannel;
1312 clientInfo.mInputMethodSession = inputMethodSession;
1313 clientInfo.mMSInputMethodSession = multiSessionInputMethodSession;
1314 break;
1315 default:
1316 Slog.e(TAG, "Unexpected state=" + clientInfo.mState);
1317 break;
1318 }
1319 }
1320 }
1321
1322 @BinderThread
1323 @Override
1324 public void reportImeWindowTarget(int clientId, int targetWindowHandle,
1325 IBinder imeWindowToken) {
1326 synchronized (mPerUserData.mLock) {
1327 final InputMethodClientInfo clientInfo =
1328 mPerUserData.getClientFromIdLocked(clientId);
1329 if (clientInfo == null) {
1330 Slog.e(TAG, "Unknown clientId=" + clientId);
1331 return;
1332 }
1333 for (WindowInfo windowInfo : clientInfo.mWindowMap.values()) {
1334 if (windowInfo.mWindowHandle == targetWindowHandle) {
1335 final IBinder targetWindowToken = windowInfo.mWindowToken;
1336 // TODO(yukawa): Report targetWindowToken and targetWindowToken to WMS.
1337 if (DEBUG) {
1338 Slog.v(TAG, "reportImeWindowTarget"
1339 + " clientId=" + clientId
1340 + " imeWindowToken=" + imeWindowToken
1341 + " targetWindowToken=" + targetWindowToken);
1342 }
1343 }
1344 }
1345 // not found.
1346 }
1347 }
1348
1349 @BinderThread
1350 @Override
1351 public boolean isUidAllowedOnDisplay(int displayId, int uid) {
1352 return mIWindowManagerInternal.isUidAllowedOnDisplay(displayId, uid);
1353 }
Tarandeep Singhe1921a72019-04-11 15:55:28 -07001354
1355 @BinderThread
1356 @Override
1357 public void setActive(int clientId, boolean active) {
1358 synchronized (mPerUserData.mLock) {
1359 final InputMethodClientInfo clientInfo =
1360 mPerUserData.getClientFromIdLocked(clientId);
1361 if (clientInfo == null) {
1362 Slog.e(TAG, "Unknown clientId=" + clientId);
1363 return;
1364 }
1365 try {
1366 clientInfo.mClient.setActive(active, false /* fullscreen */);
1367 } catch (RemoteException e) {
1368 return;
1369 }
1370 }
1371 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001372 }
1373
1374 /**
1375 * Takes care of IPCs exposed to the IME client.
1376 */
1377 private static final class ApiCallbacks extends IInputMethodManager.Stub {
Yohei Yukawa1fb13c52019-02-05 07:55:28 -08001378 private final Context mContext;
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001379 private final UserDataMap mUserDataMap;
1380 private final UserToInputMethodInfoMap mInputMethodInfoMap;
1381 private final AppOpsManager mAppOpsManager;
1382 private final WindowManagerInternal mWindowManagerInternal;
1383
1384 ApiCallbacks(Context context, UserDataMap userDataMap,
1385 UserToInputMethodInfoMap inputMethodInfoMap) {
Yohei Yukawa1fb13c52019-02-05 07:55:28 -08001386 mContext = context;
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001387 mUserDataMap = userDataMap;
1388 mInputMethodInfoMap = inputMethodInfoMap;
1389 mAppOpsManager = context.getSystemService(AppOpsManager.class);
1390 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
1391 }
1392
1393 @AnyThread
1394 private boolean checkFocus(int uid, int pid, int displayId) {
1395 return mWindowManagerInternal.isInputMethodClientFocus(uid, pid, displayId);
1396 }
1397
1398 @BinderThread
1399 @Override
1400 public void addClient(IInputMethodClient client, IInputContext inputContext,
1401 int selfReportedDisplayId) {
1402 final int callingUid = Binder.getCallingUid();
1403 final int callingPid = Binder.getCallingPid();
1404 final int userId = UserHandle.getUserId(callingUid);
1405 final PerUserData data = mUserDataMap.get(userId);
1406 if (data == null) {
1407 Slog.e(TAG, "addClient() from unknown userId=" + userId
1408 + " uid=" + callingUid + " pid=" + callingPid);
1409 return;
1410 }
1411 synchronized (data.mLock) {
1412 data.addClientLocked(callingUid, callingPid, client, selfReportedDisplayId);
1413 }
1414 }
1415
1416 @BinderThread
1417 @Override
Yohei Yukawad20eef82019-02-05 10:45:32 -08001418 public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) {
1419 if (UserHandle.getCallingUserId() != userId) {
1420 mContext.enforceCallingPermission(INTERACT_ACROSS_USERS_FULL, null);
1421 }
1422 return mInputMethodInfoMap.getAsList(userId);
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001423 }
1424
1425 @BinderThread
1426 @Override
Yohei Yukawa1fb13c52019-02-05 07:55:28 -08001427 public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
1428 if (UserHandle.getCallingUserId() != userId) {
1429 mContext.enforceCallingPermission(INTERACT_ACROSS_USERS_FULL, null);
1430 }
1431 return mInputMethodInfoMap.getAsList(userId);
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001432 }
1433
1434 @BinderThread
1435 @Override
1436 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
1437 boolean allowsImplicitlySelectedSubtypes) {
1438 reportNotSupported();
1439 return Collections.emptyList();
1440 }
1441
1442 @BinderThread
1443 @Override
1444 public InputMethodSubtype getLastInputMethodSubtype() {
1445 reportNotSupported();
1446 return null;
1447 }
1448
1449 @BinderThread
1450 @Override
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001451 public boolean showSoftInput(
Tarandeep Singhbb0e2f72020-01-15 13:58:29 -08001452 IInputMethodClient client, IBinder token, int flags,
1453 ResultReceiver resultReceiver) {
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001454 final int callingUid = Binder.getCallingUid();
1455 final int callingPid = Binder.getCallingPid();
1456 final int userId = UserHandle.getUserId(callingUid);
1457 final PerUserData data = mUserDataMap.get(userId);
1458 if (data == null) {
1459 Slog.e(TAG, "showSoftInput() from unknown userId=" + userId
1460 + " uid=" + callingUid + " pid=" + callingPid);
1461 return false;
1462 }
1463 synchronized (data.mLock) {
1464 final InputMethodClientInfo clientInfo = data.getClientLocked(client);
1465 if (clientInfo == null) {
1466 Slog.e(TAG, "showSoftInput. client not found. ignoring.");
1467 return false;
1468 }
1469 if (clientInfo.mUid != callingUid) {
1470 Slog.e(TAG, "Expected calling UID=" + clientInfo.mUid
1471 + " actual=" + callingUid);
1472 return false;
1473 }
1474 switch (clientInfo.mState) {
1475 case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
1476 case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
1477 try {
1478 clientInfo.mMSInputMethodSession.showSoftInput(flags, resultReceiver);
1479 } catch (RemoteException e) {
1480 }
1481 break;
1482 default:
1483 if (DEBUG) {
1484 Slog.e(TAG, "Ignoring showSoftInput(). clientState="
1485 + clientInfo.mState);
1486 }
1487 break;
1488 }
1489 return true;
1490 }
1491 }
1492
1493 @BinderThread
1494 @Override
1495 public boolean hideSoftInput(
1496 IInputMethodClient client, int flags, ResultReceiver resultReceiver) {
1497 final int callingUid = Binder.getCallingUid();
1498 final int callingPid = Binder.getCallingPid();
1499 final int userId = UserHandle.getUserId(callingUid);
1500 final PerUserData data = mUserDataMap.get(userId);
1501 if (data == null) {
1502 Slog.e(TAG, "hideSoftInput() from unknown userId=" + userId
1503 + " uid=" + callingUid + " pid=" + callingPid);
1504 return false;
1505 }
1506 synchronized (data.mLock) {
1507 final InputMethodClientInfo clientInfo = data.getClientLocked(client);
1508 if (clientInfo == null) {
1509 return false;
1510 }
1511 if (clientInfo.mUid != callingUid) {
1512 Slog.e(TAG, "Expected calling UID=" + clientInfo.mUid
1513 + " actual=" + callingUid);
1514 return false;
1515 }
1516 switch (clientInfo.mState) {
1517 case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
1518 case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
1519 try {
1520 clientInfo.mMSInputMethodSession.hideSoftInput(flags, resultReceiver);
1521 } catch (RemoteException e) {
1522 }
1523 break;
1524 default:
1525 if (DEBUG) {
1526 Slog.e(TAG, "Ignoring hideSoftInput(). clientState="
1527 + clientInfo.mState);
1528 }
1529 break;
1530 }
1531 return true;
1532 }
1533 }
1534
1535 @BinderThread
1536 @Override
1537 public InputBindResult startInputOrWindowGainedFocus(
1538 @StartInputReason int startInputReason,
1539 @Nullable IInputMethodClient client,
1540 @Nullable IBinder windowToken,
1541 @StartInputFlags int startInputFlags,
1542 @SoftInputModeFlags int softInputMode,
1543 int windowFlags,
1544 @Nullable EditorInfo editorInfo,
1545 @Nullable IInputContext inputContext,
1546 @MissingMethodFlags int missingMethods,
1547 int unverifiedTargetSdkVersion) {
1548 final int callingUid = Binder.getCallingUid();
1549 final int callingPid = Binder.getCallingPid();
1550 final int userId = UserHandle.getUserId(callingUid);
1551
1552 if (client == null) {
1553 return InputBindResult.INVALID_CLIENT;
1554 }
1555
1556 final boolean packageNameVerified =
1557 editorInfo != null && InputMethodUtils.checkIfPackageBelongsToUid(
1558 mAppOpsManager, callingUid, editorInfo.packageName);
1559 if (editorInfo != null && !packageNameVerified) {
1560 Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
1561 + " uid=" + callingUid + " package=" + editorInfo.packageName);
1562 return InputBindResult.INVALID_PACKAGE_NAME;
1563 }
1564
1565 final PerUserData data = mUserDataMap.get(userId);
1566 if (data == null) {
1567 Slog.e(TAG, "startInputOrWindowGainedFocus() from unknown userId=" + userId
1568 + " uid=" + callingUid + " pid=" + callingPid);
1569 return InputBindResult.INVALID_USER;
1570 }
1571
1572 synchronized (data.mLock) {
1573 final InputMethodClientInfo clientInfo = data.getClientLocked(client);
1574 if (clientInfo == null) {
1575 return InputBindResult.INVALID_CLIENT;
1576 }
1577 if (clientInfo.mUid != callingUid) {
1578 Slog.e(TAG, "Expected calling UID=" + clientInfo.mUid
1579 + " actual=" + callingUid);
1580 return InputBindResult.INVALID_CLIENT;
1581 }
1582
1583 switch (data.mState) {
1584 case PerUserState.USER_LOCKED:
1585 case PerUserState.SERVICE_NOT_QUERIED:
1586 case PerUserState.SERVICE_RECOGNIZED:
1587 case PerUserState.WAITING_SERVICE_CONNECTED:
1588 case PerUserState.UNBIND_CALLED:
1589 return InputBindResult.IME_NOT_CONNECTED;
1590 case PerUserState.SERVICE_CONNECTED:
1591 // OK
1592 break;
1593 default:
1594 Slog.wtf(TAG, "Unexpected state=" + data.mState);
1595 return InputBindResult.IME_NOT_CONNECTED;
1596 }
1597
1598 WindowInfo windowInfo = null;
1599 if (windowToken != null) {
1600 windowInfo = clientInfo.mWindowMap.get(windowToken);
1601 if (windowInfo == null) {
1602 windowInfo = new WindowInfo(windowToken, WindowHandleSource.getNext());
1603 clientInfo.mWindowMap.put(windowToken, windowInfo);
1604 }
1605 }
1606
1607 if (!checkFocus(clientInfo.mUid, clientInfo.mPid,
1608 clientInfo.mSelfReportedDisplayId)) {
1609 return InputBindResult.NOT_IME_TARGET_WINDOW;
1610 }
1611
1612 if (editorInfo == null) {
1613 // So-called dummy InputConnection scenario. For app compatibility, we still
1614 // notify this to the IME.
1615 switch (clientInfo.mState) {
1616 case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
1617 case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
1618 final int windowHandle = windowInfo != null
1619 ? windowInfo.mWindowHandle
1620 : MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE;
1621 try {
1622 clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus(
1623 inputContext, missingMethods, editorInfo, startInputFlags,
1624 softInputMode, windowHandle);
1625 } catch (RemoteException e) {
1626 }
1627 break;
1628 }
1629 return InputBindResult.NULL_EDITOR_INFO;
1630 }
1631
1632 switch (clientInfo.mState) {
1633 case InputMethodClientState.REGISTERED:
1634 case InputMethodClientState.WAITING_FOR_IME_SESSION:
1635 clientInfo.mBindingSequence++;
1636 if (clientInfo.mBindingSequence < 0) {
1637 clientInfo.mBindingSequence = 0;
1638 }
1639 return new InputBindResult(
1640 InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
1641 null, null, data.mCurrentInputMethodInfo.getId(),
Yohei Yukawab4f328a2019-05-02 08:41:27 -07001642 clientInfo.mBindingSequence, null);
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001643 case InputMethodClientState.READY_TO_SEND_FIRST_BIND_RESULT:
1644 case InputMethodClientState.ALREADY_SENT_BIND_RESULT:
1645 clientInfo.mBindingSequence++;
1646 if (clientInfo.mBindingSequence < 0) {
1647 clientInfo.mBindingSequence = 0;
1648 }
1649 // Successful start input.
1650 final int windowHandle = windowInfo != null
1651 ? windowInfo.mWindowHandle
1652 : MultiClientInputMethodServiceDelegate.INVALID_WINDOW_HANDLE;
1653 try {
1654 clientInfo.mMSInputMethodSession.startInputOrWindowGainedFocus(
1655 inputContext, missingMethods, editorInfo, startInputFlags,
1656 softInputMode, windowHandle);
1657 } catch (RemoteException e) {
1658 }
1659 clientInfo.mState = InputMethodClientState.ALREADY_SENT_BIND_RESULT;
1660 return new InputBindResult(
1661 InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
1662 clientInfo.mInputMethodSession,
1663 clientInfo.mWriteChannel.dup(),
1664 data.mCurrentInputMethodInfo.getId(),
Yohei Yukawab4f328a2019-05-02 08:41:27 -07001665 clientInfo.mBindingSequence, null);
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001666 case InputMethodClientState.UNREGISTERED:
1667 Slog.e(TAG, "The client is already unregistered.");
1668 return InputBindResult.INVALID_CLIENT;
1669 }
1670 }
1671 return null;
1672 }
1673
1674 @BinderThread
1675 @Override
1676 public void showInputMethodPickerFromClient(
1677 IInputMethodClient client, int auxiliarySubtypeMode) {
1678 reportNotSupported();
1679 }
1680
1681 @BinderThread
1682 @Override
lumark0b05f9e2018-11-26 15:09:06 +08001683 public void showInputMethodPickerFromSystem(
1684 IInputMethodClient client, int auxiliarySubtypeMode, int displayId) {
1685 reportNotSupported();
1686 }
1687
1688 @BinderThread
1689 @Override
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001690 public void showInputMethodAndSubtypeEnablerFromClient(
1691 IInputMethodClient client, String inputMethodId) {
1692 reportNotSupported();
1693 }
1694
1695 @BinderThread
1696 @Override
1697 public boolean isInputMethodPickerShownForTest() {
1698 reportNotSupported();
1699 return false;
1700 }
1701
1702 @BinderThread
1703 @Override
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001704 public InputMethodSubtype getCurrentInputMethodSubtype() {
1705 reportNotSupported();
1706 return null;
1707 }
1708
1709 @BinderThread
1710 @Override
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001711 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
1712 reportNotSupported();
1713 }
1714
1715 @BinderThread
1716 @Override
1717 public int getInputMethodWindowVisibleHeight() {
1718 reportNotSupported();
1719 return 0;
1720 }
1721
1722 @BinderThread
1723 @Override
Yohei Yukawab4f328a2019-05-02 08:41:27 -07001724 public void reportActivityView(IInputMethodClient parentClient, int childDisplayId,
1725 float[] matrixValues) {
1726 reportNotSupported();
1727 }
1728
1729 @BinderThread
1730 @Override
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001731 public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
1732 @Nullable FileDescriptor err, String[] args, @Nullable ShellCallback callback,
1733 ResultReceiver resultReceiver) {
1734 }
Keun young Park9b673eb2019-04-29 17:54:11 -07001735
1736 @BinderThread
1737 @Override
1738 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1739 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
1740 final String prefixChild = " ";
1741 pw.println("Current Multi Client Input Method Manager state:");
1742 IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
1743 ipw.println("mUserDataMap=");
1744 if (mUserDataMap != null) {
1745 ipw.increaseIndent();
1746 mUserDataMap.dump(fd, ipw, args);
1747 }
1748 }
Yohei Yukawabae5bea2018-11-12 15:08:30 -08001749 }
1750}