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