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