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