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