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