blob: 0147b1ac3794d188266cef9eccbf63db25b9e92b [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006-2008 The Android Open Source Project
Doug Zongkerab5c49c2009-12-04 10:31:43 -08003 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
Doug Zongkerab5c49c2009-12-04 10:31:43 -08007 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08008 * http://www.apache.org/licenses/LICENSE-2.0
Doug Zongkerab5c49c2009-12-04 10:31:43 -08009 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080010 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.server;
18
Dianne Hackborn21f1bd12010-02-19 17:02:21 -080019import com.android.internal.content.PackageMonitor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import com.android.internal.os.HandlerCaller;
21import com.android.internal.view.IInputContext;
22import com.android.internal.view.IInputMethod;
23import com.android.internal.view.IInputMethodCallback;
24import com.android.internal.view.IInputMethodClient;
25import com.android.internal.view.IInputMethodManager;
26import com.android.internal.view.IInputMethodSession;
27import com.android.internal.view.InputBindResult;
28
Joe Onorato7a0f36b2010-06-07 10:24:36 -070029import com.android.server.StatusBarManagerService;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
31import org.xmlpull.v1.XmlPullParserException;
32
33import android.app.ActivityManagerNative;
34import android.app.AlertDialog;
Dianne Hackborndd9b82c2009-09-03 00:18:47 -070035import android.app.PendingIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.content.ComponentName;
37import android.content.ContentResolver;
38import android.content.Context;
39import android.content.DialogInterface;
40import android.content.IntentFilter;
41import android.content.DialogInterface.OnCancelListener;
42import android.content.Intent;
43import android.content.ServiceConnection;
Brandon Ballinger6da35a02009-10-21 00:38:13 -070044import android.content.pm.ApplicationInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.content.pm.PackageManager;
46import android.content.pm.ResolveInfo;
47import android.content.pm.ServiceInfo;
Amith Yamasanie861ec12010-03-24 21:39:27 -070048import android.content.res.Configuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.content.res.Resources;
50import android.content.res.TypedArray;
51import android.database.ContentObserver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.os.Binder;
53import android.os.Handler;
54import android.os.IBinder;
55import android.os.IInterface;
56import android.os.Message;
57import android.os.Parcel;
58import android.os.RemoteException;
The Android Open Source Project4df24232009-03-05 14:34:35 -080059import android.os.ResultReceiver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060import android.os.ServiceManager;
61import android.os.SystemClock;
62import android.provider.Settings;
Amith Yamasanie861ec12010-03-24 21:39:27 -070063import android.provider.Settings.Secure;
satokab751aa2010-09-14 19:17:36 +090064import android.provider.Settings.SettingNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065import android.text.TextUtils;
66import android.util.EventLog;
satokab751aa2010-09-14 19:17:36 +090067import android.util.Pair;
Joe Onorato8a9b2202010-02-26 18:56:32 -080068import android.util.Slog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069import android.util.PrintWriterPrinter;
70import android.util.Printer;
71import android.view.IWindowManager;
72import android.view.WindowManager;
satokab751aa2010-09-14 19:17:36 +090073import android.view.inputmethod.EditorInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074import android.view.inputmethod.InputBinding;
75import android.view.inputmethod.InputMethod;
76import android.view.inputmethod.InputMethodInfo;
77import android.view.inputmethod.InputMethodManager;
satokab751aa2010-09-14 19:17:36 +090078import android.view.inputmethod.InputMethodSubtype;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079
80import java.io.FileDescriptor;
81import java.io.IOException;
82import java.io.PrintWriter;
satok913a8922010-08-26 21:53:41 +090083import java.text.Collator;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084import java.util.ArrayList;
85import java.util.HashMap;
satok7f35c8c2010-10-07 21:13:11 +090086import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087import java.util.List;
satok913a8922010-08-26 21:53:41 +090088import java.util.Map;
89import java.util.TreeMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090
91/**
92 * This class provides a system service that manages input methods.
93 */
94public class InputMethodManagerService extends IInputMethodManager.Stub
95 implements ServiceConnection, Handler.Callback {
96 static final boolean DEBUG = false;
97 static final String TAG = "InputManagerService";
98
99 static final int MSG_SHOW_IM_PICKER = 1;
satokab751aa2010-09-14 19:17:36 +0900100 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
satok47a44912010-10-06 16:03:58 +0900101 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
satok217f5482010-12-15 05:19:19 +0900102 static final int MSG_SHOW_IM_CONFIG = 4;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800103
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 static final int MSG_UNBIND_INPUT = 1000;
105 static final int MSG_BIND_INPUT = 1010;
106 static final int MSG_SHOW_SOFT_INPUT = 1020;
107 static final int MSG_HIDE_SOFT_INPUT = 1030;
108 static final int MSG_ATTACH_TOKEN = 1040;
109 static final int MSG_CREATE_SESSION = 1050;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800110
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 static final int MSG_START_INPUT = 2000;
112 static final int MSG_RESTART_INPUT = 2010;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800113
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 static final int MSG_UNBIND_METHOD = 3000;
115 static final int MSG_BIND_METHOD = 3010;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 static final long TIME_TO_RECONNECT = 10*1000;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800118
satokab751aa2010-09-14 19:17:36 +0900119 private static final int NOT_A_SUBTYPE_ID = -1;
satok723a27e2010-11-11 14:58:11 +0900120 private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
satok4e4569d2010-11-19 18:45:53 +0900121 private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
122 private static final String SUBTYPE_MODE_VOICE = "voice";
123
satok57ffc002011-01-25 00:11:47 +0900124 // TODO: Will formalize this value as API
125 private static final String SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME =
126 "excludeFromLastInputMethod";
127
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 final Context mContext;
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -0800129 final Resources mRes;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 final Handler mHandler;
satokd87c2592010-09-29 11:52:06 +0900131 final InputMethodSettings mSettings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 final SettingsObserver mSettingsObserver;
Joe Onorato089de882010-04-12 08:18:45 -0700133 final StatusBarManagerService mStatusBar;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800134 final IWindowManager mIWindowManager;
135 final HandlerCaller mCaller;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800136
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 // All known input methods. mMethodMap also serves as the global
140 // lock for this class.
satokd87c2592010-09-29 11:52:06 +0900141 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
142 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800143
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 class SessionState {
145 final ClientState client;
146 final IInputMethod method;
147 final IInputMethodSession session;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800148
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 @Override
150 public String toString() {
151 return "SessionState{uid " + client.uid + " pid " + client.pid
152 + " method " + Integer.toHexString(
153 System.identityHashCode(method))
154 + " session " + Integer.toHexString(
155 System.identityHashCode(session))
156 + "}";
157 }
158
159 SessionState(ClientState _client, IInputMethod _method,
160 IInputMethodSession _session) {
161 client = _client;
162 method = _method;
163 session = _session;
164 }
165 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800166
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 class ClientState {
168 final IInputMethodClient client;
169 final IInputContext inputContext;
170 final int uid;
171 final int pid;
172 final InputBinding binding;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 boolean sessionRequested;
175 SessionState curSession;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800176
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 @Override
178 public String toString() {
179 return "ClientState{" + Integer.toHexString(
180 System.identityHashCode(this)) + " uid " + uid
181 + " pid " + pid + "}";
182 }
183
184 ClientState(IInputMethodClient _client, IInputContext _inputContext,
185 int _uid, int _pid) {
186 client = _client;
187 inputContext = _inputContext;
188 uid = _uid;
189 pid = _pid;
190 binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
191 }
192 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800193
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 final HashMap<IBinder, ClientState> mClients
195 = new HashMap<IBinder, ClientState>();
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800196
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 /**
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700198 * Set once the system is ready to run third party code.
199 */
200 boolean mSystemReady;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800201
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700202 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 * Id of the currently selected input method.
204 */
205 String mCurMethodId;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800206
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 /**
208 * The current binding sequence number, incremented every time there is
209 * a new bind performed.
210 */
211 int mCurSeq;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800212
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 /**
214 * The client that is currently bound to an input method.
215 */
216 ClientState mCurClient;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700219 * The last window token that gained focus.
220 */
221 IBinder mCurFocusedWindow;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800222
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700223 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 * The input context last provided by the current client.
225 */
226 IInputContext mCurInputContext;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800227
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 /**
229 * The attributes last provided by the current client.
230 */
231 EditorInfo mCurAttribute;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800232
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 /**
234 * The input method ID of the input method service that we are currently
235 * connected to or in the process of connecting to.
236 */
237 String mCurId;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800238
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 /**
satokab751aa2010-09-14 19:17:36 +0900240 * The current subtype of the current input method.
241 */
242 private InputMethodSubtype mCurrentSubtype;
243
satok4e4569d2010-11-19 18:45:53 +0900244 // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
satokf3db1af2010-11-23 13:34:33 +0900245 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
246 mShortcutInputMethodsAndSubtypes =
247 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
satokab751aa2010-09-14 19:17:36 +0900248
249 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 * Set to true if our ServiceConnection is currently actively bound to
251 * a service (whether or not we have gotten its IBinder back yet).
252 */
253 boolean mHaveConnection;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800254
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 /**
256 * Set if the client has asked for the input method to be shown.
257 */
258 boolean mShowRequested;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800259
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 /**
261 * Set if we were explicitly told to show the input method.
262 */
263 boolean mShowExplicitlyRequested;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 /**
266 * Set if we were forced to be shown.
267 */
268 boolean mShowForced;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800269
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 /**
271 * Set if we last told the input method to show itself.
272 */
273 boolean mInputShown;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800274
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 /**
276 * The Intent used to connect to the current input method.
277 */
278 Intent mCurIntent;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800279
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 /**
281 * The token we have made for the currently active input method, to
282 * identify it in the future.
283 */
284 IBinder mCurToken;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800285
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 /**
287 * If non-null, this is the input method service we are currently connected
288 * to.
289 */
290 IInputMethod mCurMethod;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 /**
293 * Time that we last initiated a bind to the input method, to determine
294 * if we should try to disconnect and reconnect to it.
295 */
296 long mLastBindTime;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800297
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 /**
299 * Have we called mCurMethod.bindInput()?
300 */
301 boolean mBoundToMethod;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800302
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 /**
304 * Currently enabled session. Only touched by service thread, not
305 * protected by a lock.
306 */
307 SessionState mEnabledSession;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800308
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 /**
310 * True if the screen is on. The value is true initially.
311 */
312 boolean mScreenOn = true;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800313
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 AlertDialog.Builder mDialogBuilder;
315 AlertDialog mSwitchingDialog;
316 InputMethodInfo[] mIms;
317 CharSequence[] mItems;
satokab751aa2010-09-14 19:17:36 +0900318 int[] mSubtypeIds;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800319
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 class SettingsObserver extends ContentObserver {
321 SettingsObserver(Handler handler) {
322 super(handler);
323 ContentResolver resolver = mContext.getContentResolver();
324 resolver.registerContentObserver(Settings.Secure.getUriFor(
325 Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
satokab751aa2010-09-14 19:17:36 +0900326 resolver.registerContentObserver(Settings.Secure.getUriFor(
327 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 @Override public void onChange(boolean selfChange) {
331 synchronized (mMethodMap) {
332 updateFromSettingsLocked();
333 }
334 }
335 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800336
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337 class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
338 @Override
339 public void onReceive(Context context, Intent intent) {
340 if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
341 mScreenOn = true;
342 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
343 mScreenOn = false;
The Android Open Source Project10592532009-03-18 17:39:46 -0700344 } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
345 hideInputMethodMenu();
346 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800347 } else {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800348 Slog.w(TAG, "Unexpected intent " + intent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800349 }
350
351 // Inform the current client of the change in active status
352 try {
353 if (mCurClient != null && mCurClient.client != null) {
354 mCurClient.client.setActive(mScreenOn);
355 }
356 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800357 Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 + mCurClient.pid + " uid " + mCurClient.uid);
359 }
360 }
361 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800362
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800363 class MyPackageMonitor extends PackageMonitor {
364
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 @Override
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800366 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 synchronized (mMethodMap) {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800368 String curInputMethodId = Settings.Secure.getString(mContext
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
370 final int N = mMethodList.size();
371 if (curInputMethodId != null) {
372 for (int i=0; i<N; i++) {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800373 InputMethodInfo imi = mMethodList.get(i);
374 if (imi.getId().equals(curInputMethodId)) {
375 for (String pkg : packages) {
376 if (imi.getPackageName().equals(pkg)) {
377 if (!doit) {
378 return true;
379 }
satok723a27e2010-11-11 14:58:11 +0900380 resetSelectedInputMethodAndSubtypeLocked("");
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800381 chooseNewDefaultIMELocked();
382 return true;
383 }
384 }
385 }
386 }
387 }
388 }
389 return false;
390 }
391
392 @Override
393 public void onSomePackagesChanged() {
394 synchronized (mMethodMap) {
395 InputMethodInfo curIm = null;
396 String curInputMethodId = Settings.Secure.getString(mContext
397 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
398 final int N = mMethodList.size();
399 if (curInputMethodId != null) {
400 for (int i=0; i<N; i++) {
401 InputMethodInfo imi = mMethodList.get(i);
402 if (imi.getId().equals(curInputMethodId)) {
403 curIm = imi;
404 }
405 int change = isPackageDisappearing(imi.getPackageName());
406 if (change == PACKAGE_TEMPORARY_CHANGE
407 || change == PACKAGE_PERMANENT_CHANGE) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800408 Slog.i(TAG, "Input method uninstalled, disabling: "
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800409 + imi.getComponent());
410 setInputMethodEnabledLocked(imi.getId(), false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 }
412 }
413 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800414
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800415 buildInputMethodListLocked(mMethodList, mMethodMap);
416
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 boolean changed = false;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800418
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800419 if (curIm != null) {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800420 int change = isPackageDisappearing(curIm.getPackageName());
421 if (change == PACKAGE_TEMPORARY_CHANGE
422 || change == PACKAGE_PERMANENT_CHANGE) {
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800423 ServiceInfo si = null;
424 try {
425 si = mContext.getPackageManager().getServiceInfo(
426 curIm.getComponent(), 0);
427 } catch (PackageManager.NameNotFoundException ex) {
428 }
429 if (si == null) {
430 // Uh oh, current input method is no longer around!
431 // Pick another one...
Joe Onorato8a9b2202010-02-26 18:56:32 -0800432 Slog.i(TAG, "Current input method removed: " + curInputMethodId);
satokca830212011-01-13 21:15:04 +0900433 mStatusBar.setIMEButtonVisible(mCurToken, false);
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800434 if (!chooseNewDefaultIMELocked()) {
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800435 changed = true;
436 curIm = null;
Joe Onorato8a9b2202010-02-26 18:56:32 -0800437 Slog.i(TAG, "Unsetting current input method");
satok723a27e2010-11-11 14:58:11 +0900438 resetSelectedInputMethodAndSubtypeLocked("");
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800439 }
440 }
Suchi Amalapurapu08675a32010-01-28 09:57:30 -0800441 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800442 }
satokab751aa2010-09-14 19:17:36 +0900443
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800444 if (curIm == null) {
445 // We currently don't have a default input method... is
446 // one now available?
447 changed = chooseNewDefaultIMELocked();
448 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800449
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800450 if (changed) {
451 updateFromSettingsLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 }
453 }
454 }
455 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800456
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800457 class MethodCallback extends IInputMethodCallback.Stub {
458 final IInputMethod mMethod;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800459
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800460 MethodCallback(IInputMethod method) {
461 mMethod = method;
462 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800463
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 public void finishedEvent(int seq, boolean handled) throws RemoteException {
465 }
466
467 public void sessionCreated(IInputMethodSession session) throws RemoteException {
468 onSessionCreated(mMethod, session);
469 }
470 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800471
Joe Onorato089de882010-04-12 08:18:45 -0700472 public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 mContext = context;
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -0800474 mRes = context.getResources();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 mHandler = new Handler(this);
476 mIWindowManager = IWindowManager.Stub.asInterface(
477 ServiceManager.getService(Context.WINDOW_SERVICE));
478 mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
479 public void executeMessage(Message msg) {
480 handleMessage(msg);
481 }
482 });
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800483
Dianne Hackborn21f1bd12010-02-19 17:02:21 -0800484 (new MyPackageMonitor()).register(mContext, true);
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800485
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 IntentFilter screenOnOffFilt = new IntentFilter();
487 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
488 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
The Android Open Source Project10592532009-03-18 17:39:46 -0700489 screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800491
satok913a8922010-08-26 21:53:41 +0900492 mStatusBar = statusBar;
493 statusBar.setIconVisibility("ime", false);
494
satokd87c2592010-09-29 11:52:06 +0900495 // mSettings should be created before buildInputMethodListLocked
satokdf31ae62011-01-15 06:19:44 +0900496 mSettings = new InputMethodSettings(
497 mRes, context.getContentResolver(), mMethodMap, mMethodList);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 buildInputMethodListLocked(mMethodList, mMethodMap);
satokd87c2592010-09-29 11:52:06 +0900499 mSettings.enableAllIMEsIfThereIsNoEnabledIME();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500
satokd87c2592010-09-29 11:52:06 +0900501 if (TextUtils.isEmpty(Settings.Secure.getString(
502 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 InputMethodInfo defIm = null;
satokd87c2592010-09-29 11:52:06 +0900504 for (InputMethodInfo imi: mMethodList) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
506 try {
satokd87c2592010-09-29 11:52:06 +0900507 Resources res = context.createPackageContext(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 imi.getPackageName(), 0).getResources();
509 if (res.getBoolean(imi.getIsDefaultResourceId())) {
510 defIm = imi;
Joe Onorato8a9b2202010-02-26 18:56:32 -0800511 Slog.i(TAG, "Selected default: " + imi.getId());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800512 }
513 } catch (PackageManager.NameNotFoundException ex) {
514 } catch (Resources.NotFoundException ex) {
515 }
516 }
517 }
satokd87c2592010-09-29 11:52:06 +0900518 if (defIm == null && mMethodList.size() > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800519 defIm = mMethodList.get(0);
Joe Onorato8a9b2202010-02-26 18:56:32 -0800520 Slog.i(TAG, "No default found, using " + defIm.getId());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800521 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 if (defIm != null) {
satok723a27e2010-11-11 14:58:11 +0900523 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 }
525 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800526
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 mSettingsObserver = new SettingsObserver(mHandler);
528 updateFromSettingsLocked();
529 }
530
531 @Override
532 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
533 throws RemoteException {
534 try {
535 return super.onTransact(code, data, reply, flags);
536 } catch (RuntimeException e) {
537 // The input method manager only throws security exceptions, so let's
538 // log all others.
539 if (!(e instanceof SecurityException)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800540 Slog.e(TAG, "Input Method Manager Crash", e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 }
542 throw e;
543 }
544 }
545
546 public void systemReady() {
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700547 synchronized (mMethodMap) {
548 if (!mSystemReady) {
549 mSystemReady = true;
Dianne Hackborncc278702009-09-02 23:07:23 -0700550 try {
551 startInputInnerLocked();
552 } catch (RuntimeException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800553 Slog.w(TAG, "Unexpected exception", e);
Dianne Hackborncc278702009-09-02 23:07:23 -0700554 }
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700555 }
556 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800558
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 public List<InputMethodInfo> getInputMethodList() {
560 synchronized (mMethodMap) {
561 return new ArrayList<InputMethodInfo>(mMethodList);
562 }
563 }
564
565 public List<InputMethodInfo> getEnabledInputMethodList() {
566 synchronized (mMethodMap) {
satokd87c2592010-09-29 11:52:06 +0900567 return mSettings.getEnabledInputMethodListLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 }
569 }
570
satokbb4aa062011-01-19 21:40:27 +0900571 private HashMap<InputMethodInfo, List<InputMethodSubtype>>
572 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
573 HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
574 new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
575 for (InputMethodInfo imi: getEnabledInputMethodList()) {
576 enabledInputMethodAndSubtypes.put(
577 imi, getEnabledInputMethodSubtypeListLocked(imi, true));
578 }
579 return enabledInputMethodAndSubtypes;
580 }
581
582 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi,
583 boolean allowsImplicitlySelectedSubtypes) {
584 if (imi == null && mCurMethodId != null) {
585 imi = mMethodMap.get(mCurMethodId);
586 }
587 final List<InputMethodSubtype> enabledSubtypes =
588 mSettings.getEnabledInputMethodSubtypeListLocked(imi);
589 if (!allowsImplicitlySelectedSubtypes || enabledSubtypes.size() > 0) {
590 return enabledSubtypes;
591 } else {
592 return getApplicableSubtypesLocked(mRes, getSubtypes(imi));
593 }
594 }
595
satok16331c82010-12-20 23:48:46 +0900596 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
597 boolean allowsImplicitlySelectedSubtypes) {
satok67ddf9c2010-11-17 09:45:54 +0900598 synchronized (mMethodMap) {
satokbb4aa062011-01-19 21:40:27 +0900599 return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
satok67ddf9c2010-11-17 09:45:54 +0900600 }
601 }
602
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 public void addClient(IInputMethodClient client,
604 IInputContext inputContext, int uid, int pid) {
605 synchronized (mMethodMap) {
606 mClients.put(client.asBinder(), new ClientState(client,
607 inputContext, uid, pid));
608 }
609 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800610
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800611 public void removeClient(IInputMethodClient client) {
612 synchronized (mMethodMap) {
613 mClients.remove(client.asBinder());
614 }
615 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 void executeOrSendMessage(IInterface target, Message msg) {
618 if (target.asBinder() instanceof Binder) {
619 mCaller.sendMessage(msg);
620 } else {
621 handleMessage(msg);
622 msg.recycle();
623 }
624 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800625
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700626 void unbindCurrentClientLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627 if (mCurClient != null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800628 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800629 + mCurClient.client.asBinder());
630 if (mBoundToMethod) {
631 mBoundToMethod = false;
632 if (mCurMethod != null) {
633 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
634 MSG_UNBIND_INPUT, mCurMethod));
635 }
636 }
637 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
638 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
639 mCurClient.sessionRequested = false;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800640
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800641 // Call setActive(false) on the old client
642 try {
643 mCurClient.client.setActive(false);
644 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800645 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646 + mCurClient.pid + " uid " + mCurClient.uid);
647 }
648 mCurClient = null;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800649
The Android Open Source Project10592532009-03-18 17:39:46 -0700650 hideInputMethodMenuLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 }
652 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800654 private int getImeShowFlags() {
655 int flags = 0;
656 if (mShowForced) {
657 flags |= InputMethod.SHOW_FORCED
658 | InputMethod.SHOW_EXPLICIT;
659 } else if (mShowExplicitlyRequested) {
660 flags |= InputMethod.SHOW_EXPLICIT;
661 }
662 return flags;
663 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800664
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800665 private int getAppShowFlags() {
666 int flags = 0;
667 if (mShowForced) {
668 flags |= InputMethodManager.SHOW_FORCED;
669 } else if (!mShowExplicitlyRequested) {
670 flags |= InputMethodManager.SHOW_IMPLICIT;
671 }
672 return flags;
673 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800674
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800675 InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
676 if (!mBoundToMethod) {
677 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
678 MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
679 mBoundToMethod = true;
680 }
681 final SessionState session = mCurClient.curSession;
682 if (initial) {
683 executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
684 MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
685 } else {
686 executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
687 MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
688 }
689 if (mShowRequested) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800690 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
The Android Open Source Project4df24232009-03-05 14:34:35 -0800691 showCurrentInputLocked(getAppShowFlags(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800692 }
693 return needResult
694 ? new InputBindResult(session.session, mCurId, mCurSeq)
695 : null;
696 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800697
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 InputBindResult startInputLocked(IInputMethodClient client,
699 IInputContext inputContext, EditorInfo attribute,
700 boolean initial, boolean needResult) {
701 // If no method is currently selected, do nothing.
702 if (mCurMethodId == null) {
703 return mNoBinding;
704 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800706 ClientState cs = mClients.get(client.asBinder());
707 if (cs == null) {
708 throw new IllegalArgumentException("unknown client "
709 + client.asBinder());
710 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800711
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800712 try {
713 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
714 // Check with the window manager to make sure this client actually
715 // has a window with focus. If not, reject. This is thread safe
716 // because if the focus changes some time before or after, the
717 // next client receiving focus that has any interest in input will
718 // be calling through here after that change happens.
Joe Onorato8a9b2202010-02-26 18:56:32 -0800719 Slog.w(TAG, "Starting input on non-focused client " + cs.client
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800720 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
721 return null;
722 }
723 } catch (RemoteException e) {
724 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800725
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 if (mCurClient != cs) {
727 // If the client is changing, we need to switch over to the new
728 // one.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700729 unbindCurrentClientLocked();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800730 if (DEBUG) Slog.v(TAG, "switching to client: client = "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 + cs.client.asBinder());
732
733 // If the screen is on, inform the new client it is active
734 if (mScreenOn) {
735 try {
736 cs.client.setActive(mScreenOn);
737 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800738 Slog.w(TAG, "Got RemoteException sending setActive notification to pid "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739 + cs.pid + " uid " + cs.uid);
740 }
741 }
742 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800743
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 // Bump up the sequence for this client and attach it.
745 mCurSeq++;
746 if (mCurSeq <= 0) mCurSeq = 1;
747 mCurClient = cs;
748 mCurInputContext = inputContext;
749 mCurAttribute = attribute;
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800750
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 // Check if the input method is changing.
752 if (mCurId != null && mCurId.equals(mCurMethodId)) {
753 if (cs.curSession != null) {
754 // Fast case: if we are already connected to the input method,
755 // then just return it.
756 return attachNewInputLocked(initial, needResult);
757 }
758 if (mHaveConnection) {
759 if (mCurMethod != null) {
760 if (!cs.sessionRequested) {
761 cs.sessionRequested = true;
Joe Onorato8a9b2202010-02-26 18:56:32 -0800762 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800763 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
764 MSG_CREATE_SESSION, mCurMethod,
765 new MethodCallback(mCurMethod)));
766 }
767 // Return to client, and we will get back with it when
768 // we have had a session made for it.
769 return new InputBindResult(null, mCurId, mCurSeq);
770 } else if (SystemClock.uptimeMillis()
771 < (mLastBindTime+TIME_TO_RECONNECT)) {
772 // In this case we have connected to the service, but
773 // don't yet have its interface. If it hasn't been too
774 // long since we did the connection, we'll return to
775 // the client and wait to get the service interface so
776 // we can report back. If it has been too long, we want
777 // to fall through so we can try a disconnect/reconnect
778 // to see if we can get back in touch with the service.
779 return new InputBindResult(null, mCurId, mCurSeq);
780 } else {
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800781 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
782 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800783 }
784 }
785 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800786
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700787 return startInputInnerLocked();
788 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800789
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700790 InputBindResult startInputInnerLocked() {
791 if (mCurMethodId == null) {
792 return mNoBinding;
793 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800794
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700795 if (!mSystemReady) {
796 // If the system is not yet ready, we shouldn't be running third
797 // party code.
Dianne Hackborncc278702009-09-02 23:07:23 -0700798 return new InputBindResult(null, mCurMethodId, mCurSeq);
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700799 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800800
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800801 InputMethodInfo info = mMethodMap.get(mCurMethodId);
802 if (info == null) {
803 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
804 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800805
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700806 unbindCurrentMethodLocked(false);
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800807
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
809 mCurIntent.setComponent(info.getComponent());
Dianne Hackborndd9b82c2009-09-03 00:18:47 -0700810 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
811 com.android.internal.R.string.input_method_binding_label);
812 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
813 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800814 if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
815 mLastBindTime = SystemClock.uptimeMillis();
816 mHaveConnection = true;
817 mCurId = info.getId();
818 mCurToken = new Binder();
819 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800820 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800821 mIWindowManager.addWindowToken(mCurToken,
822 WindowManager.LayoutParams.TYPE_INPUT_METHOD);
823 } catch (RemoteException e) {
824 }
825 return new InputBindResult(null, mCurId, mCurSeq);
826 } else {
827 mCurIntent = null;
Joe Onorato8a9b2202010-02-26 18:56:32 -0800828 Slog.w(TAG, "Failure connecting to input method service: "
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 + mCurIntent);
830 }
831 return null;
832 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800833
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 public InputBindResult startInput(IInputMethodClient client,
835 IInputContext inputContext, EditorInfo attribute,
836 boolean initial, boolean needResult) {
837 synchronized (mMethodMap) {
838 final long ident = Binder.clearCallingIdentity();
839 try {
840 return startInputLocked(client, inputContext, attribute,
841 initial, needResult);
842 } finally {
843 Binder.restoreCallingIdentity(ident);
844 }
845 }
846 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800847
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800848 public void finishInput(IInputMethodClient client) {
849 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800850
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800851 public void onServiceConnected(ComponentName name, IBinder service) {
852 synchronized (mMethodMap) {
853 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
854 mCurMethod = IInputMethod.Stub.asInterface(service);
Dianne Hackborncc278702009-09-02 23:07:23 -0700855 if (mCurToken == null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800856 Slog.w(TAG, "Service connected without a token!");
Dianne Hackborncc278702009-09-02 23:07:23 -0700857 unbindCurrentMethodLocked(false);
858 return;
859 }
Joe Onorato8a9b2202010-02-26 18:56:32 -0800860 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
Dianne Hackborncc278702009-09-02 23:07:23 -0700861 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
862 MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800863 if (mCurClient != null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800864 if (DEBUG) Slog.v(TAG, "Creating first session while with client "
Dianne Hackborncc278702009-09-02 23:07:23 -0700865 + mCurClient);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800866 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
Dianne Hackborncc278702009-09-02 23:07:23 -0700867 MSG_CREATE_SESSION, mCurMethod,
868 new MethodCallback(mCurMethod)));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 }
870 }
871 }
872 }
873
874 void onSessionCreated(IInputMethod method, IInputMethodSession session) {
875 synchronized (mMethodMap) {
876 if (mCurMethod != null && method != null
877 && mCurMethod.asBinder() == method.asBinder()) {
878 if (mCurClient != null) {
879 mCurClient.curSession = new SessionState(mCurClient,
880 method, session);
881 mCurClient.sessionRequested = false;
882 InputBindResult res = attachNewInputLocked(true, true);
883 if (res.method != null) {
884 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
885 MSG_BIND_METHOD, mCurClient.client, res));
886 }
887 }
888 }
889 }
890 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800891
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700892 void unbindCurrentMethodLocked(boolean reportToClient) {
893 if (mHaveConnection) {
894 mContext.unbindService(this);
895 mHaveConnection = false;
896 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800897
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700898 if (mCurToken != null) {
899 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800900 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700901 mIWindowManager.removeWindowToken(mCurToken);
902 } catch (RemoteException e) {
903 }
904 mCurToken = null;
905 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800906
The Android Open Source Project10592532009-03-18 17:39:46 -0700907 mCurId = null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700908 clearCurMethodLocked();
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800909
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700910 if (reportToClient && mCurClient != null) {
911 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
912 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
913 }
914 }
915
Devin Taylor0c33ed22010-02-23 13:26:46 -0600916 private void finishSession(SessionState sessionState) {
917 if (sessionState != null && sessionState.session != null) {
918 try {
919 sessionState.session.finishSession();
920 } catch (RemoteException e) {
Jean-Baptiste Queru9d0f6df2010-03-29 12:55:09 -0700921 Slog.w(TAG, "Session failed to close due to remote exception", e);
Devin Taylor0c33ed22010-02-23 13:26:46 -0600922 }
923 }
924 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800925
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700926 void clearCurMethodLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800927 if (mCurMethod != null) {
928 for (ClientState cs : mClients.values()) {
929 cs.sessionRequested = false;
Devin Taylor0c33ed22010-02-23 13:26:46 -0600930 finishSession(cs.curSession);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800931 cs.curSession = null;
932 }
Devin Taylor0c33ed22010-02-23 13:26:46 -0600933
934 finishSession(mEnabledSession);
935 mEnabledSession = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936 mCurMethod = null;
937 }
Joe Onorato0cbda992010-05-02 16:28:15 -0700938 mStatusBar.setIconVisibility("ime", false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800940
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 public void onServiceDisconnected(ComponentName name) {
942 synchronized (mMethodMap) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800943 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800944 + " mCurIntent=" + mCurIntent);
945 if (mCurMethod != null && mCurIntent != null
946 && name.equals(mCurIntent.getComponent())) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700947 clearCurMethodLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 // We consider this to be a new bind attempt, since the system
949 // should now try to restart the service for us.
950 mLastBindTime = SystemClock.uptimeMillis();
951 mShowRequested = mInputShown;
952 mInputShown = false;
953 if (mCurClient != null) {
954 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
955 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
956 }
957 }
958 }
959 }
960
961 public void updateStatusIcon(IBinder token, String packageName, int iconId) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700962 int uid = Binder.getCallingUid();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800963 long ident = Binder.clearCallingIdentity();
964 try {
965 if (token == null || mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -0700966 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800967 return;
968 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -0800969
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800970 synchronized (mMethodMap) {
971 if (iconId == 0) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800972 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
Joe Onorato0cbda992010-05-02 16:28:15 -0700973 mStatusBar.setIconVisibility("ime", false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800974 } else if (packageName != null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800975 if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
Joe Onorato0cbda992010-05-02 16:28:15 -0700976 mStatusBar.setIcon("ime", packageName, iconId, 0);
977 mStatusBar.setIconVisibility("ime", true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800978 }
979 }
980 } finally {
981 Binder.restoreCallingIdentity(ident);
982 }
983 }
984
satok06487a52010-10-29 11:37:18 +0900985 public void setIMEButtonVisible(IBinder token, boolean visible) {
986 int uid = Binder.getCallingUid();
987 long ident = Binder.clearCallingIdentity();
988 try {
989 if (token == null || mCurToken != token) {
990 Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token);
991 return;
992 }
993
994 synchronized (mMethodMap) {
satokcd7cd292010-11-20 15:46:23 +0900995 mStatusBar.setIMEButtonVisible(token, visible);
satok06487a52010-10-29 11:37:18 +0900996 }
997 } finally {
998 Binder.restoreCallingIdentity(ident);
999 }
1000 }
1001
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 void updateFromSettingsLocked() {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001003 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1004 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1005 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1006 // enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001007 String id = Settings.Secure.getString(mContext.getContentResolver(),
satokab751aa2010-09-14 19:17:36 +09001008 Settings.Secure.DEFAULT_INPUT_METHOD);
satok03eb319a2010-11-11 18:17:42 +09001009 // There is no input method selected, try to choose new applicable input method.
1010 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1011 id = Settings.Secure.getString(mContext.getContentResolver(),
1012 Settings.Secure.DEFAULT_INPUT_METHOD);
1013 }
1014 if (!TextUtils.isEmpty(id)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001015 try {
satokab751aa2010-09-14 19:17:36 +09001016 setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 } catch (IllegalArgumentException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001018 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
The Android Open Source Project10592532009-03-18 17:39:46 -07001019 mCurMethodId = null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001020 unbindCurrentMethodLocked(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 }
satokf3db1af2010-11-23 13:34:33 +09001022 mShortcutInputMethodsAndSubtypes.clear();
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001023 } else {
1024 // There is no longer an input method set, so stop any current one.
The Android Open Source Project10592532009-03-18 17:39:46 -07001025 mCurMethodId = null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001026 unbindCurrentMethodLocked(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 }
1028 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001029
satokab751aa2010-09-14 19:17:36 +09001030 /* package */ void setInputMethodLocked(String id, int subtypeId) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031 InputMethodInfo info = mMethodMap.get(id);
1032 if (info == null) {
satok913a8922010-08-26 21:53:41 +09001033 throw new IllegalArgumentException("Unknown id: " + id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001034 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001036 if (id.equals(mCurMethodId)) {
satokcd7cd292010-11-20 15:46:23 +09001037 InputMethodSubtype subtype = null;
Ken Wakasa586f0512011-01-20 22:31:01 +09001038 if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
1039 subtype = info.getSubtypeAt(subtypeId);
satokcd7cd292010-11-20 15:46:23 +09001040 }
1041 if (subtype != mCurrentSubtype) {
1042 synchronized (mMethodMap) {
satokca830212011-01-13 21:15:04 +09001043 if (subtype != null) {
1044 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1045 }
satokcd7cd292010-11-20 15:46:23 +09001046 if (mCurMethod != null) {
1047 try {
satokcd7cd292010-11-20 15:46:23 +09001048 if (mInputShown) {
1049 // If mInputShown is false, there is no IME button on the
1050 // system bar.
1051 // Thus there is no need to make it invisible explicitly.
1052 mStatusBar.setIMEButtonVisible(mCurToken, true);
satokab751aa2010-09-14 19:17:36 +09001053 }
satokcd7cd292010-11-20 15:46:23 +09001054 // If subtype is null, try to find the most applicable one from
1055 // getCurrentInputMethodSubtype.
1056 if (subtype == null) {
1057 subtype = getCurrentInputMethodSubtype();
1058 }
1059 mCurMethod.changeInputMethodSubtype(subtype);
1060 } catch (RemoteException e) {
1061 return;
satokab751aa2010-09-14 19:17:36 +09001062 }
1063 }
1064 }
1065 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066 return;
1067 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001068
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069 final long ident = Binder.clearCallingIdentity();
1070 try {
satokab751aa2010-09-14 19:17:36 +09001071 // Set a subtype to this input method.
1072 // subtypeId the name of a subtype which will be set.
satok723a27e2010-11-11 14:58:11 +09001073 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1074 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1075 // because mCurMethodId is stored as a history in
1076 // setSelectedInputMethodAndSubtypeLocked().
1077 mCurMethodId = id;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078
1079 if (ActivityManagerNative.isSystemReady()) {
1080 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
Dianne Hackborn1c633fc2009-12-08 19:45:14 -08001081 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001082 intent.putExtra("input_method_id", id);
1083 mContext.sendBroadcast(intent);
1084 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001085 unbindCurrentClientLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001086 } finally {
1087 Binder.restoreCallingIdentity(ident);
1088 }
1089 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001090
The Android Open Source Project4df24232009-03-05 14:34:35 -08001091 public boolean showSoftInput(IInputMethodClient client, int flags,
1092 ResultReceiver resultReceiver) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001093 int uid = Binder.getCallingUid();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 long ident = Binder.clearCallingIdentity();
1095 try {
1096 synchronized (mMethodMap) {
1097 if (mCurClient == null || client == null
1098 || mCurClient.client.asBinder() != client.asBinder()) {
1099 try {
1100 // We need to check if this is the current client with
1101 // focus in the window manager, to allow this call to
1102 // be made before input is started in it.
1103 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001104 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001105 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106 }
1107 } catch (RemoteException e) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001108 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001109 }
1110 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001111
Joe Onorato8a9b2202010-02-26 18:56:32 -08001112 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001113 return showCurrentInputLocked(flags, resultReceiver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001114 }
1115 } finally {
1116 Binder.restoreCallingIdentity(ident);
1117 }
1118 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001119
The Android Open Source Project4df24232009-03-05 14:34:35 -08001120 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001121 mShowRequested = true;
1122 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1123 mShowExplicitlyRequested = true;
1124 }
1125 if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1126 mShowExplicitlyRequested = true;
1127 mShowForced = true;
1128 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001129
Dianne Hackborncc278702009-09-02 23:07:23 -07001130 if (!mSystemReady) {
1131 return false;
1132 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001133
The Android Open Source Project4df24232009-03-05 14:34:35 -08001134 boolean res = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001135 if (mCurMethod != null) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001136 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1137 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1138 resultReceiver));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 mInputShown = true;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001140 res = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141 } else if (mHaveConnection && SystemClock.uptimeMillis()
1142 < (mLastBindTime+TIME_TO_RECONNECT)) {
1143 // The client has asked to have the input method shown, but
1144 // we have been sitting here too long with a connection to the
1145 // service and no interface received, so let's disconnect/connect
1146 // to try to prod things along.
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001147 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001148 SystemClock.uptimeMillis()-mLastBindTime,1);
1149 mContext.unbindService(this);
1150 mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1151 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001152
The Android Open Source Project4df24232009-03-05 14:34:35 -08001153 return res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001154 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001155
The Android Open Source Project4df24232009-03-05 14:34:35 -08001156 public boolean hideSoftInput(IInputMethodClient client, int flags,
1157 ResultReceiver resultReceiver) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001158 int uid = Binder.getCallingUid();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001159 long ident = Binder.clearCallingIdentity();
1160 try {
1161 synchronized (mMethodMap) {
1162 if (mCurClient == null || client == null
1163 || mCurClient.client.asBinder() != client.asBinder()) {
1164 try {
1165 // We need to check if this is the current client with
1166 // focus in the window manager, to allow this call to
1167 // be made before input is started in it.
1168 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001169 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1170 + uid + ": " + client);
satok865b9772011-01-21 02:45:06 +09001171 mStatusBar.setIMEButtonVisible(mCurToken, false);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001172 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001173 }
1174 } catch (RemoteException e) {
satok865b9772011-01-21 02:45:06 +09001175 mStatusBar.setIMEButtonVisible(mCurToken, false);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001176 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001177 }
1178 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001179
Joe Onorato8a9b2202010-02-26 18:56:32 -08001180 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001181 return hideCurrentInputLocked(flags, resultReceiver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182 }
1183 } finally {
1184 Binder.restoreCallingIdentity(ident);
1185 }
1186 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001187
The Android Open Source Project4df24232009-03-05 14:34:35 -08001188 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1190 && (mShowExplicitlyRequested || mShowForced)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001191 if (DEBUG) Slog.v(TAG,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 "Not hiding: explicit show not cancelled by non-explicit hide");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001193 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 }
1195 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001196 if (DEBUG) Slog.v(TAG,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 "Not hiding: forced show not cancelled by not-always hide");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001198 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001200 boolean res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 if (mInputShown && mCurMethod != null) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001202 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1203 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1204 res = true;
1205 } else {
1206 res = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 }
1208 mInputShown = false;
1209 mShowRequested = false;
1210 mShowExplicitlyRequested = false;
1211 mShowForced = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001212 return res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001213 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001214
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001215 public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001216 boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1217 boolean first, int windowFlags) {
1218 long ident = Binder.clearCallingIdentity();
1219 try {
1220 synchronized (mMethodMap) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001221 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001222 + " viewHasFocus=" + viewHasFocus
1223 + " isTextEditor=" + isTextEditor
1224 + " softInputMode=#" + Integer.toHexString(softInputMode)
1225 + " first=" + first + " flags=#"
1226 + Integer.toHexString(windowFlags));
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001227
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 if (mCurClient == null || client == null
1229 || mCurClient.client.asBinder() != client.asBinder()) {
1230 try {
1231 // We need to check if this is the current client with
1232 // focus in the window manager, to allow this call to
1233 // be made before input is started in it.
1234 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001235 Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001236 return;
1237 }
1238 } catch (RemoteException e) {
1239 }
1240 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001241
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001242 if (mCurFocusedWindow == windowToken) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001243 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001244 return;
1245 }
1246 mCurFocusedWindow = windowToken;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001247
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -08001248 // Should we auto-show the IME even if the caller has not
1249 // specified what should be done with it?
1250 // We only do this automatically if the window can resize
1251 // to accommodate the IME (so what the user sees will give
1252 // them good context without input information being obscured
1253 // by the IME) or if running on a large screen where there
1254 // is more room for the target window + IME.
1255 final boolean doAutoShow =
1256 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1257 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1258 || mRes.getConfiguration().isLayoutSizeAtLeast(
1259 Configuration.SCREENLAYOUT_SIZE_LARGE);
1260
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001261 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1262 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -08001263 if (!isTextEditor || !doAutoShow) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001264 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1265 // There is no focus view, and this window will
1266 // be behind any soft input window, so hide the
1267 // soft input window if it is shown.
Joe Onorato8a9b2202010-02-26 18:56:32 -08001268 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001269 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001270 }
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -08001271 } else if (isTextEditor && doAutoShow && (softInputMode &
1272 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001273 // There is a focus view, and we are navigating forward
1274 // into the window, so show the input window for the user.
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -08001275 // We only do this automatically if the window an resize
1276 // to accomodate the IME (so what the user sees will give
1277 // them good context without input information being obscured
1278 // by the IME) or if running on a large screen where there
1279 // is more room for the target window + IME.
Joe Onorato8a9b2202010-02-26 18:56:32 -08001280 if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001281 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001282 }
1283 break;
1284 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1285 // Do nothing.
1286 break;
1287 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1288 if ((softInputMode &
1289 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001290 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001291 hideCurrentInputLocked(0, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001292 }
1293 break;
1294 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
Joe Onorato8a9b2202010-02-26 18:56:32 -08001295 if (DEBUG) Slog.v(TAG, "Window asks to hide input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001296 hideCurrentInputLocked(0, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 break;
1298 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1299 if ((softInputMode &
1300 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001301 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001302 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001303 }
1304 break;
1305 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
Joe Onorato8a9b2202010-02-26 18:56:32 -08001306 if (DEBUG) Slog.v(TAG, "Window asks to always show input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001307 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001308 break;
1309 }
1310 }
1311 } finally {
1312 Binder.restoreCallingIdentity(ident);
1313 }
1314 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001315
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001316 public void showInputMethodPickerFromClient(IInputMethodClient client) {
1317 synchronized (mMethodMap) {
1318 if (mCurClient == null || client == null
1319 || mCurClient.client.asBinder() != client.asBinder()) {
satok47a44912010-10-06 16:03:58 +09001320 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001321 + Binder.getCallingUid() + ": " + client);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001322 }
1323
satok440aab52010-11-25 09:43:11 +09001324 // Always call subtype picker, because subtype picker is a superset of input method
1325 // picker.
satokab751aa2010-09-14 19:17:36 +09001326 mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1327 }
1328 }
1329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001330 public void setInputMethod(IBinder token, String id) {
satok28203512010-11-24 11:06:49 +09001331 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
1332 }
1333
1334 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
1335 synchronized (mMethodMap) {
1336 if (subtype != null) {
1337 setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
1338 mMethodMap.get(id), subtype.hashCode()));
1339 } else {
1340 setInputMethod(token, id);
1341 }
1342 }
satokab751aa2010-09-14 19:17:36 +09001343 }
1344
satokb416a71e2010-11-25 20:42:14 +09001345 public void showInputMethodAndSubtypeEnablerFromClient(
satok217f5482010-12-15 05:19:19 +09001346 IInputMethodClient client, String inputMethodId) {
satokb416a71e2010-11-25 20:42:14 +09001347 synchronized (mMethodMap) {
1348 if (mCurClient == null || client == null
1349 || mCurClient.client.asBinder() != client.asBinder()) {
1350 Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1351 }
satok7fee71f2010-12-17 18:54:26 +09001352 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1353 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
satokb416a71e2010-11-25 20:42:14 +09001354 }
1355 }
1356
satok735cf382010-11-11 20:40:09 +09001357 public boolean switchToLastInputMethod(IBinder token) {
1358 synchronized (mMethodMap) {
satokc445bcd2011-01-25 18:57:24 +09001359 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1360 if (lastIme == null) return false;
1361 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
1362 if (lastImi == null) return false;
1363
1364 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
1365 final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1366 // If the last IME is the same as the current IME and the last subtype is not defined,
1367 // there is no need to switch to the last IME.
1368 if (imiIdIsSame && lastSubtypeHash == NOT_A_SUBTYPE_ID) return false;
1369
1370 int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
1371 : mCurrentSubtype.hashCode();
1372 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
1373 if (DEBUG) {
1374 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second + ", from: "
1375 + mCurMethodId + ", " + currentSubtypeHash);
satok735cf382010-11-11 20:40:09 +09001376 }
satokc445bcd2011-01-25 18:57:24 +09001377 setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode(
1378 lastImi, lastSubtypeHash));
1379 return true;
satok735cf382010-11-11 20:40:09 +09001380 }
1381 return false;
1382 }
1383 }
1384
satok28203512010-11-24 11:06:49 +09001385 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001386 synchronized (mMethodMap) {
1387 if (token == null) {
1388 if (mContext.checkCallingOrSelfPermission(
1389 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1390 != PackageManager.PERMISSION_GRANTED) {
1391 throw new SecurityException(
1392 "Using null token requires permission "
1393 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1394 }
1395 } else if (mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001396 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1397 + " token: " + token);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001398 return;
1399 }
1400
1401 long ident = Binder.clearCallingIdentity();
1402 try {
satokab751aa2010-09-14 19:17:36 +09001403 setInputMethodLocked(id, subtypeId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404 } finally {
1405 Binder.restoreCallingIdentity(ident);
1406 }
1407 }
1408 }
1409
1410 public void hideMySoftInput(IBinder token, int flags) {
1411 synchronized (mMethodMap) {
1412 if (token == null || mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001413 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1414 + Binder.getCallingUid() + " token: " + token);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001415 return;
1416 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001417 long ident = Binder.clearCallingIdentity();
1418 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001419 hideCurrentInputLocked(flags, null);
1420 } finally {
1421 Binder.restoreCallingIdentity(ident);
1422 }
1423 }
1424 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001425
The Android Open Source Project4df24232009-03-05 14:34:35 -08001426 public void showMySoftInput(IBinder token, int flags) {
1427 synchronized (mMethodMap) {
1428 if (token == null || mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001429 Slog.w(TAG, "Ignoring showMySoftInput of uid "
1430 + Binder.getCallingUid() + " token: " + token);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001431 return;
1432 }
1433 long ident = Binder.clearCallingIdentity();
1434 try {
1435 showCurrentInputLocked(flags, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001436 } finally {
1437 Binder.restoreCallingIdentity(ident);
1438 }
1439 }
1440 }
1441
1442 void setEnabledSessionInMainThread(SessionState session) {
1443 if (mEnabledSession != session) {
1444 if (mEnabledSession != null) {
1445 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001446 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001447 mEnabledSession.method.setSessionEnabled(
1448 mEnabledSession.session, false);
1449 } catch (RemoteException e) {
1450 }
1451 }
1452 mEnabledSession = session;
1453 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001454 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001455 session.method.setSessionEnabled(
1456 session.session, true);
1457 } catch (RemoteException e) {
1458 }
1459 }
1460 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001461
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001462 public boolean handleMessage(Message msg) {
1463 HandlerCaller.SomeArgs args;
1464 switch (msg.what) {
1465 case MSG_SHOW_IM_PICKER:
1466 showInputMethodMenu();
1467 return true;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001468
satokab751aa2010-09-14 19:17:36 +09001469 case MSG_SHOW_IM_SUBTYPE_PICKER:
1470 showInputMethodSubtypeMenu();
1471 return true;
1472
satok47a44912010-10-06 16:03:58 +09001473 case MSG_SHOW_IM_SUBTYPE_ENABLER:
satok217f5482010-12-15 05:19:19 +09001474 args = (HandlerCaller.SomeArgs)msg.obj;
satok7fee71f2010-12-17 18:54:26 +09001475 showInputMethodAndSubtypeEnabler((String)args.arg1);
satok217f5482010-12-15 05:19:19 +09001476 return true;
1477
1478 case MSG_SHOW_IM_CONFIG:
1479 showConfigureInputMethods();
satok47a44912010-10-06 16:03:58 +09001480 return true;
1481
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001482 // ---------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001484 case MSG_UNBIND_INPUT:
1485 try {
1486 ((IInputMethod)msg.obj).unbindInput();
1487 } catch (RemoteException e) {
1488 // There is nothing interesting about the method dying.
1489 }
1490 return true;
1491 case MSG_BIND_INPUT:
1492 args = (HandlerCaller.SomeArgs)msg.obj;
1493 try {
1494 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1495 } catch (RemoteException e) {
1496 }
1497 return true;
1498 case MSG_SHOW_SOFT_INPUT:
The Android Open Source Project4df24232009-03-05 14:34:35 -08001499 args = (HandlerCaller.SomeArgs)msg.obj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001500 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001501 ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1502 (ResultReceiver)args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001503 } catch (RemoteException e) {
1504 }
1505 return true;
1506 case MSG_HIDE_SOFT_INPUT:
The Android Open Source Project4df24232009-03-05 14:34:35 -08001507 args = (HandlerCaller.SomeArgs)msg.obj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001508 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001509 ((IInputMethod)args.arg1).hideSoftInput(0,
1510 (ResultReceiver)args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511 } catch (RemoteException e) {
1512 }
1513 return true;
1514 case MSG_ATTACH_TOKEN:
1515 args = (HandlerCaller.SomeArgs)msg.obj;
1516 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001517 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001518 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1519 } catch (RemoteException e) {
1520 }
1521 return true;
1522 case MSG_CREATE_SESSION:
1523 args = (HandlerCaller.SomeArgs)msg.obj;
1524 try {
1525 ((IInputMethod)args.arg1).createSession(
1526 (IInputMethodCallback)args.arg2);
1527 } catch (RemoteException e) {
1528 }
1529 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001530 // ---------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001531
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001532 case MSG_START_INPUT:
1533 args = (HandlerCaller.SomeArgs)msg.obj;
1534 try {
1535 SessionState session = (SessionState)args.arg1;
1536 setEnabledSessionInMainThread(session);
1537 session.method.startInput((IInputContext)args.arg2,
1538 (EditorInfo)args.arg3);
1539 } catch (RemoteException e) {
1540 }
1541 return true;
1542 case MSG_RESTART_INPUT:
1543 args = (HandlerCaller.SomeArgs)msg.obj;
1544 try {
1545 SessionState session = (SessionState)args.arg1;
1546 setEnabledSessionInMainThread(session);
1547 session.method.restartInput((IInputContext)args.arg2,
1548 (EditorInfo)args.arg3);
1549 } catch (RemoteException e) {
1550 }
1551 return true;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001552
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001553 // ---------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001555 case MSG_UNBIND_METHOD:
1556 try {
1557 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1558 } catch (RemoteException e) {
1559 // There is nothing interesting about the last client dying.
1560 }
1561 return true;
1562 case MSG_BIND_METHOD:
1563 args = (HandlerCaller.SomeArgs)msg.obj;
1564 try {
1565 ((IInputMethodClient)args.arg1).onBindMethod(
1566 (InputBindResult)args.arg2);
1567 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001568 Slog.w(TAG, "Client died receiving input method " + args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001569 }
1570 return true;
1571 }
1572 return false;
1573 }
1574
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001575 private boolean isSystemIme(InputMethodInfo inputMethod) {
1576 return (inputMethod.getServiceInfo().applicationInfo.flags
1577 & ApplicationInfo.FLAG_SYSTEM) != 0;
1578 }
1579
Ken Wakasa586f0512011-01-20 22:31:01 +09001580 private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
1581 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
1582 final int subtypeCount = imi.getSubtypeCount();
1583 for (int i = 0; i < subtypeCount; ++i) {
1584 subtypes.add(imi.getSubtypeAt(i));
1585 }
1586 return subtypes;
1587 }
1588
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001589 private boolean chooseNewDefaultIMELocked() {
satokd87c2592010-09-29 11:52:06 +09001590 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001591 if (enabled != null && enabled.size() > 0) {
Dianne Hackborn83e48f52010-03-23 23:03:25 -07001592 // We'd prefer to fall back on a system IME, since that is safer.
1593 int i=enabled.size();
1594 while (i > 0) {
1595 i--;
1596 if ((enabled.get(i).getServiceInfo().applicationInfo.flags
1597 & ApplicationInfo.FLAG_SYSTEM) != 0) {
1598 break;
1599 }
1600 }
satokab751aa2010-09-14 19:17:36 +09001601 InputMethodInfo imi = enabled.get(i);
satok03eb319a2010-11-11 18:17:42 +09001602 if (DEBUG) {
1603 Slog.d(TAG, "New default IME was selected: " + imi.getId());
1604 }
satok723a27e2010-11-11 14:58:11 +09001605 resetSelectedInputMethodAndSubtypeLocked(imi.getId());
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001606 return true;
1607 }
1608
1609 return false;
1610 }
1611
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001612 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1613 HashMap<String, InputMethodInfo> map) {
1614 list.clear();
1615 map.clear();
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001616
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001617 PackageManager pm = mContext.getPackageManager();
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -08001618 final Configuration config = mRes.getConfiguration();
Amith Yamasanie861ec12010-03-24 21:39:27 -07001619 final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1620 String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1621 Secure.DISABLED_SYSTEM_INPUT_METHODS);
1622 if (disabledSysImes == null) disabledSysImes = "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001623
1624 List<ResolveInfo> services = pm.queryIntentServices(
1625 new Intent(InputMethod.SERVICE_INTERFACE),
1626 PackageManager.GET_META_DATA);
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001627
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001628 for (int i = 0; i < services.size(); ++i) {
1629 ResolveInfo ri = services.get(i);
1630 ServiceInfo si = ri.serviceInfo;
1631 ComponentName compName = new ComponentName(si.packageName, si.name);
1632 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1633 si.permission)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001634 Slog.w(TAG, "Skipping input method " + compName
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001635 + ": it does not require the permission "
1636 + android.Manifest.permission.BIND_INPUT_METHOD);
1637 continue;
1638 }
1639
Joe Onorato8a9b2202010-02-26 18:56:32 -08001640 if (DEBUG) Slog.d(TAG, "Checking " + compName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001641
1642 try {
1643 InputMethodInfo p = new InputMethodInfo(mContext, ri);
1644 list.add(p);
Amith Yamasanie861ec12010-03-24 21:39:27 -07001645 final String id = p.getId();
1646 map.put(id, p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001647
Amith Yamasanie861ec12010-03-24 21:39:27 -07001648 // System IMEs are enabled by default, unless there's a hard keyboard
1649 // and the system IME was explicitly disabled
1650 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1651 setInputMethodEnabledLocked(id, true);
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001652 }
1653
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001654 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001655 Slog.d(TAG, "Found a third-party input method " + p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001656 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001657
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001658 } catch (XmlPullParserException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001659 Slog.w(TAG, "Unable to load input method " + compName, e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001660 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001661 Slog.w(TAG, "Unable to load input method " + compName, e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001662 }
1663 }
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001664
1665 String defaultIme = Settings.Secure.getString(mContext
1666 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
satok913a8922010-08-26 21:53:41 +09001667 if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001668 if (chooseNewDefaultIMELocked()) {
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001669 updateFromSettingsLocked();
1670 }
1671 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001672 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001673
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001674 // ----------------------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001675
satokab751aa2010-09-14 19:17:36 +09001676 private void showInputMethodMenu() {
1677 showInputMethodMenuInternal(false);
1678 }
1679
1680 private void showInputMethodSubtypeMenu() {
1681 showInputMethodMenuInternal(true);
1682 }
1683
satok217f5482010-12-15 05:19:19 +09001684 private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
Tadashi G. Takaokaf49688f2011-01-20 17:56:13 +09001685 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
satok47a44912010-10-06 16:03:58 +09001686 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
satok86417ea2010-10-27 14:11:03 +09001687 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1688 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
satok7fee71f2010-12-17 18:54:26 +09001689 if (!TextUtils.isEmpty(inputMethodId)) {
Tadashi G. Takaoka25480202011-01-20 23:13:02 +09001690 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
satok7fee71f2010-12-17 18:54:26 +09001691 }
satok217f5482010-12-15 05:19:19 +09001692 mContext.startActivity(intent);
1693 }
1694
1695 private void showConfigureInputMethods() {
1696 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
1697 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1698 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1699 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
satok47a44912010-10-06 16:03:58 +09001700 mContext.startActivity(intent);
1701 }
1702
satokab751aa2010-09-14 19:17:36 +09001703 private void showInputMethodMenuInternal(boolean showSubtypes) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001704 if (DEBUG) Slog.v(TAG, "Show switching menu");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001705
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001706 final Context context = mContext;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001707
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001708 final PackageManager pm = context.getPackageManager();
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001709
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001710 String lastInputMethodId = Settings.Secure.getString(context
1711 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
satokab751aa2010-09-14 19:17:36 +09001712 int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
Joe Onorato8a9b2202010-02-26 18:56:32 -08001713 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001714
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001715 synchronized (mMethodMap) {
satokbb4aa062011-01-19 21:40:27 +09001716 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
1717 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
satok7f35c8c2010-10-07 21:13:11 +09001718 if (immis == null || immis.size() == 0) {
1719 return;
1720 }
1721
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001722 hideInputMethodMenuLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001723
satokab751aa2010-09-14 19:17:36 +09001724 final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
1725 new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
satok913a8922010-08-26 21:53:41 +09001726
satokbb4aa062011-01-19 21:40:27 +09001727 for (InputMethodInfo imi: immis.keySet()) {
1728 if (imi == null) continue;
1729 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
satok7f35c8c2010-10-07 21:13:11 +09001730 HashSet<String> enabledSubtypeSet = new HashSet<String>();
satokbb4aa062011-01-19 21:40:27 +09001731 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
1732 enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
satok7f35c8c2010-10-07 21:13:11 +09001733 }
satokbb4aa062011-01-19 21:40:27 +09001734 ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
1735 CharSequence label = imi.loadLabel(pm);
satok7f35c8c2010-10-07 21:13:11 +09001736 if (showSubtypes && enabledSubtypeSet.size() > 0) {
satokbb4aa062011-01-19 21:40:27 +09001737 final int subtypeCount = imi.getSubtypeCount();
Ken Wakasa586f0512011-01-20 22:31:01 +09001738 for (int j = 0; j < subtypeCount; ++j) {
satokbb4aa062011-01-19 21:40:27 +09001739 InputMethodSubtype subtype = imi.getSubtypeAt(j);
satok7f35c8c2010-10-07 21:13:11 +09001740 if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) {
1741 CharSequence title;
1742 int nameResId = subtype.getNameResId();
satok9ef02832010-11-04 21:17:48 +09001743 String mode = subtype.getMode();
satok7f35c8c2010-10-07 21:13:11 +09001744 if (nameResId != 0) {
satokbb4aa062011-01-19 21:40:27 +09001745 title = pm.getText(imi.getPackageName(), nameResId,
1746 imi.getServiceInfo().applicationInfo);
satok7f35c8c2010-10-07 21:13:11 +09001747 } else {
1748 CharSequence language = subtype.getLocale();
satok7f35c8c2010-10-07 21:13:11 +09001749 // TODO: Use more friendly Title and UI
1750 title = label + "," + (mode == null ? "" : mode) + ","
1751 + (language == null ? "" : language);
1752 }
satokbb4aa062011-01-19 21:40:27 +09001753 imMap.put(title, new Pair<InputMethodInfo, Integer>(imi, j));
satokab751aa2010-09-14 19:17:36 +09001754 }
satokab751aa2010-09-14 19:17:36 +09001755 }
1756 } else {
1757 imMap.put(label,
satokbb4aa062011-01-19 21:40:27 +09001758 new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID));
satokab751aa2010-09-14 19:17:36 +09001759 }
Dianne Hackborn97106ab2010-03-03 00:08:31 -08001760 }
satok913a8922010-08-26 21:53:41 +09001761
satokbb4aa062011-01-19 21:40:27 +09001762 final int N = imMap.size();
satok913a8922010-08-26 21:53:41 +09001763 mItems = imMap.keySet().toArray(new CharSequence[N]);
satokab751aa2010-09-14 19:17:36 +09001764 mIms = new InputMethodInfo[N];
1765 mSubtypeIds = new int[N];
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001766 int checkedItem = 0;
1767 for (int i = 0; i < N; ++i) {
satokab751aa2010-09-14 19:17:36 +09001768 Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
1769 mIms[i] = value.first;
1770 mSubtypeIds[i] = value.second;
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001771 if (mIms[i].getId().equals(lastInputMethodId)) {
satokab751aa2010-09-14 19:17:36 +09001772 int subtypeId = mSubtypeIds[i];
1773 if ((subtypeId == NOT_A_SUBTYPE_ID)
1774 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
1775 || (subtypeId == lastInputMethodSubtypeId)) {
1776 checkedItem = i;
1777 }
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001778 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001779 }
satokab751aa2010-09-14 19:17:36 +09001780
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001781 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1782 public void onClick(DialogInterface dialog, int which) {
1783 hideInputMethodMenu();
1784 }
1785 };
satokd87c2592010-09-29 11:52:06 +09001786
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001787 TypedArray a = context.obtainStyledAttributes(null,
1788 com.android.internal.R.styleable.DialogPreference,
1789 com.android.internal.R.attr.alertDialogStyle, 0);
1790 mDialogBuilder = new AlertDialog.Builder(context)
1791 .setTitle(com.android.internal.R.string.select_input_method)
1792 .setOnCancelListener(new OnCancelListener() {
1793 public void onCancel(DialogInterface dialog) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001794 hideInputMethodMenu();
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001795 }
1796 })
1797 .setIcon(a.getDrawable(
1798 com.android.internal.R.styleable.DialogPreference_dialogTitle));
1799 a.recycle();
satokd87c2592010-09-29 11:52:06 +09001800
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001801 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1802 new AlertDialog.OnClickListener() {
1803 public void onClick(DialogInterface dialog, int which) {
1804 synchronized (mMethodMap) {
satokab751aa2010-09-14 19:17:36 +09001805 if (mIms == null || mIms.length <= which
1806 || mSubtypeIds == null || mSubtypeIds.length <= which) {
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001807 return;
1808 }
1809 InputMethodInfo im = mIms[which];
satokab751aa2010-09-14 19:17:36 +09001810 int subtypeId = mSubtypeIds[which];
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001811 hideInputMethodMenu();
1812 if (im != null) {
satokab751aa2010-09-14 19:17:36 +09001813 if ((subtypeId < 0)
Ken Wakasa586f0512011-01-20 22:31:01 +09001814 || (subtypeId >= im.getSubtypeCount())) {
satokab751aa2010-09-14 19:17:36 +09001815 subtypeId = NOT_A_SUBTYPE_ID;
1816 }
1817 setInputMethodLocked(im.getId(), subtypeId);
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001818 }
Dianne Hackborn20cb56e2010-03-04 00:58:29 -08001819 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001820 }
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001821 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001822
satok7f35c8c2010-10-07 21:13:11 +09001823 if (showSubtypes) {
satok82beadf2010-12-27 19:03:06 +09001824 mDialogBuilder.setPositiveButton(
1825 com.android.internal.R.string.configure_input_methods,
satok7f35c8c2010-10-07 21:13:11 +09001826 new DialogInterface.OnClickListener() {
1827 public void onClick(DialogInterface dialog, int whichButton) {
satok217f5482010-12-15 05:19:19 +09001828 showConfigureInputMethods();
satok7f35c8c2010-10-07 21:13:11 +09001829 }
1830 });
1831 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001832 mSwitchingDialog = mDialogBuilder.create();
1833 mSwitchingDialog.getWindow().setType(
1834 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1835 mSwitchingDialog.show();
1836 }
1837 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001838
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001839 void hideInputMethodMenu() {
The Android Open Source Project10592532009-03-18 17:39:46 -07001840 synchronized (mMethodMap) {
1841 hideInputMethodMenuLocked();
1842 }
1843 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001844
The Android Open Source Project10592532009-03-18 17:39:46 -07001845 void hideInputMethodMenuLocked() {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001846 if (DEBUG) Slog.v(TAG, "Hide switching menu");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001847
The Android Open Source Project10592532009-03-18 17:39:46 -07001848 if (mSwitchingDialog != null) {
1849 mSwitchingDialog.dismiss();
1850 mSwitchingDialog = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001851 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001852
The Android Open Source Project10592532009-03-18 17:39:46 -07001853 mDialogBuilder = null;
1854 mItems = null;
1855 mIms = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001856 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001857
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001858 // ----------------------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001859
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001860 public boolean setInputMethodEnabled(String id, boolean enabled) {
1861 synchronized (mMethodMap) {
1862 if (mContext.checkCallingOrSelfPermission(
1863 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1864 != PackageManager.PERMISSION_GRANTED) {
1865 throw new SecurityException(
1866 "Requires permission "
1867 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1868 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001869
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001870 long ident = Binder.clearCallingIdentity();
1871 try {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001872 return setInputMethodEnabledLocked(id, enabled);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001873 } finally {
1874 Binder.restoreCallingIdentity(ident);
1875 }
1876 }
1877 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001878
1879 boolean setInputMethodEnabledLocked(String id, boolean enabled) {
1880 // Make sure this is a valid input method.
1881 InputMethodInfo imm = mMethodMap.get(id);
1882 if (imm == null) {
satokd87c2592010-09-29 11:52:06 +09001883 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001884 }
1885
satokd87c2592010-09-29 11:52:06 +09001886 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
1887 .getEnabledInputMethodsAndSubtypeListLocked();
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001888
satokd87c2592010-09-29 11:52:06 +09001889 if (enabled) {
1890 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
1891 if (pair.first.equals(id)) {
1892 // We are enabling this input method, but it is already enabled.
1893 // Nothing to do. The previous state was enabled.
1894 return true;
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001895 }
1896 }
satokd87c2592010-09-29 11:52:06 +09001897 mSettings.appendAndPutEnabledInputMethodLocked(id, false);
1898 // Previous state was disabled.
1899 return false;
1900 } else {
1901 StringBuilder builder = new StringBuilder();
1902 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
1903 builder, enabledInputMethodsList, id)) {
1904 // Disabled input method is currently selected, switch to another one.
1905 String selId = Settings.Secure.getString(mContext.getContentResolver(),
1906 Settings.Secure.DEFAULT_INPUT_METHOD);
satok03eb319a2010-11-11 18:17:42 +09001907 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
1908 Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
1909 resetSelectedInputMethodAndSubtypeLocked("");
satokd87c2592010-09-29 11:52:06 +09001910 }
1911 // Previous state was enabled.
1912 return true;
1913 } else {
1914 // We are disabling the input method but it is already disabled.
1915 // Nothing to do. The previous state was disabled.
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001916 return false;
1917 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001918 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001919 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001920
satok57ffc002011-01-25 00:11:47 +09001921 private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
1922 if (subtype == null) return true;
1923 String[] extraValues = subtype.getExtraValue().split(",");
1924 final int N = extraValues.length;
1925 for (int i = 0; i < N; ++i) {
1926 if (SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME.equals(extraValues[i])) {
1927 return false;
1928 }
1929 }
1930 return true;
1931 }
1932
satok723a27e2010-11-11 14:58:11 +09001933 private void saveCurrentInputMethodAndSubtypeToHistory() {
1934 String subtypeId = NOT_A_SUBTYPE_ID_STR;
1935 if (mCurrentSubtype != null) {
1936 subtypeId = String.valueOf(mCurrentSubtype.hashCode());
1937 }
satok57ffc002011-01-25 00:11:47 +09001938 if (canAddToLastInputMethod(mCurrentSubtype)) {
1939 mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
1940 }
satokab751aa2010-09-14 19:17:36 +09001941 }
1942
satok723a27e2010-11-11 14:58:11 +09001943 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
1944 boolean setSubtypeOnly) {
1945 // Update the history of InputMethod and Subtype
1946 saveCurrentInputMethodAndSubtypeToHistory();
1947
1948 // Set Subtype here
1949 if (imi == null || subtypeId < 0) {
1950 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
Tadashi G. Takaoka0ba75bb2010-11-09 12:19:32 -08001951 mCurrentSubtype = null;
satok723a27e2010-11-11 14:58:11 +09001952 } else {
Ken Wakasa586f0512011-01-20 22:31:01 +09001953 if (subtypeId < imi.getSubtypeCount()) {
1954 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
1955 mSettings.putSelectedSubtype(subtype.hashCode());
1956 mCurrentSubtype = subtype;
satok723a27e2010-11-11 14:58:11 +09001957 } else {
1958 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1959 mCurrentSubtype = null;
1960 }
satokab751aa2010-09-14 19:17:36 +09001961 }
satok723a27e2010-11-11 14:58:11 +09001962
1963 if (!setSubtypeOnly) {
1964 // Set InputMethod here
1965 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
1966 }
1967 }
1968
1969 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
1970 InputMethodInfo imi = mMethodMap.get(newDefaultIme);
1971 int lastSubtypeId = NOT_A_SUBTYPE_ID;
1972 // newDefaultIme is empty when there is no candidate for the selected IME.
1973 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
1974 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
1975 if (subtypeHashCode != null) {
1976 try {
1977 lastSubtypeId = getSubtypeIdFromHashCode(
1978 imi, Integer.valueOf(subtypeHashCode));
1979 } catch (NumberFormatException e) {
1980 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
1981 }
1982 }
1983 }
1984 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
satokab751aa2010-09-14 19:17:36 +09001985 }
1986
1987 private int getSelectedInputMethodSubtypeId(String id) {
1988 InputMethodInfo imi = mMethodMap.get(id);
1989 if (imi == null) {
1990 return NOT_A_SUBTYPE_ID;
1991 }
satokab751aa2010-09-14 19:17:36 +09001992 int subtypeId;
1993 try {
1994 subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
1995 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
1996 } catch (SettingNotFoundException e) {
1997 return NOT_A_SUBTYPE_ID;
1998 }
satok723a27e2010-11-11 14:58:11 +09001999 return getSubtypeIdFromHashCode(imi, subtypeId);
2000 }
2001
2002 private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
satok28203512010-11-24 11:06:49 +09002003 if (imi != null) {
Ken Wakasa586f0512011-01-20 22:31:01 +09002004 final int subtypeCount = imi.getSubtypeCount();
2005 for (int i = 0; i < subtypeCount; ++i) {
2006 InputMethodSubtype ims = imi.getSubtypeAt(i);
satok28203512010-11-24 11:06:49 +09002007 if (subtypeHashCode == ims.hashCode()) {
2008 return i;
2009 }
satokab751aa2010-09-14 19:17:36 +09002010 }
2011 }
2012 return NOT_A_SUBTYPE_ID;
2013 }
2014
satokdf31ae62011-01-15 06:19:44 +09002015 private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
2016 Resources res, List<InputMethodSubtype> subtypes) {
2017 final String systemLocale = res.getConfiguration().locale.toString();
satok3da92232011-01-11 22:46:30 +09002018 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2019 HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2020 new HashMap<String, InputMethodSubtype>();
satok16331c82010-12-20 23:48:46 +09002021 final int N = subtypes.size();
2022 boolean containsKeyboardSubtype = false;
2023 for (int i = 0; i < N; ++i) {
2024 InputMethodSubtype subtype = subtypes.get(i);
satok3da92232011-01-11 22:46:30 +09002025 final String locale = subtype.getLocale();
2026 final String mode = subtype.getMode();
2027 // When system locale starts with subtype's locale, that subtype will be applicable
2028 // for system locale
2029 // For instance, it's clearly applicable for cases like system locale = en_US and
2030 // subtype = en, but it is not necessarily considered applicable for cases like system
2031 // locale = en and subtype = en_US.
2032 // We just call systemLocale.startsWith(locale) in this function because there is no
2033 // need to find applicable subtypes aggressively unlike
2034 // findLastResortApplicableSubtypeLocked.
2035 if (systemLocale.startsWith(locale)) {
2036 InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2037 // If more applicable subtypes are contained, skip.
2038 if (applicableSubtype != null
2039 && systemLocale.equals(applicableSubtype.getLocale())) continue;
2040 applicableModeAndSubtypesMap.put(mode, subtype);
satok16331c82010-12-20 23:48:46 +09002041 if (!containsKeyboardSubtype
2042 && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
2043 containsKeyboardSubtype = true;
2044 }
2045 }
2046 }
satok3da92232011-01-11 22:46:30 +09002047 ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2048 applicableModeAndSubtypesMap.values());
satok16331c82010-12-20 23:48:46 +09002049 if (!containsKeyboardSubtype) {
2050 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
satokdf31ae62011-01-15 06:19:44 +09002051 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
satok16331c82010-12-20 23:48:46 +09002052 if (lastResortKeyboardSubtype != null) {
2053 applicableSubtypes.add(lastResortKeyboardSubtype);
2054 }
2055 }
2056 return applicableSubtypes;
2057 }
2058
satok4e4569d2010-11-19 18:45:53 +09002059 /**
2060 * If there are no selected subtypes, tries finding the most applicable one according to the
2061 * given locale.
2062 * @param subtypes this function will search the most applicable subtype in subtypes
2063 * @param mode subtypes will be filtered by mode
2064 * @param locale subtypes will be filtered by locale
satok7599a7fb2010-12-22 13:45:23 +09002065 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
2066 * it will return the first subtype matched with mode
satok4e4569d2010-11-19 18:45:53 +09002067 * @return the most applicable subtypeId
2068 */
satokdf31ae62011-01-15 06:19:44 +09002069 private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
2070 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
satok7599a7fb2010-12-22 13:45:23 +09002071 boolean canIgnoreLocaleAsLastResort) {
satok8fbb1e82010-11-02 23:15:58 +09002072 if (subtypes == null || subtypes.size() == 0) {
satokcd7cd292010-11-20 15:46:23 +09002073 return null;
satok8fbb1e82010-11-02 23:15:58 +09002074 }
satok4e4569d2010-11-19 18:45:53 +09002075 if (TextUtils.isEmpty(locale)) {
satokdf31ae62011-01-15 06:19:44 +09002076 locale = res.getConfiguration().locale.toString();
satok4e4569d2010-11-19 18:45:53 +09002077 }
satok8fbb1e82010-11-02 23:15:58 +09002078 final String language = locale.substring(0, 2);
2079 boolean partialMatchFound = false;
satokcd7cd292010-11-20 15:46:23 +09002080 InputMethodSubtype applicableSubtype = null;
satok7599a7fb2010-12-22 13:45:23 +09002081 InputMethodSubtype firstMatchedModeSubtype = null;
satok16331c82010-12-20 23:48:46 +09002082 final int N = subtypes.size();
2083 for (int i = 0; i < N; ++i) {
satokcd7cd292010-11-20 15:46:23 +09002084 InputMethodSubtype subtype = subtypes.get(i);
2085 final String subtypeLocale = subtype.getLocale();
satokd8713432011-01-18 00:55:13 +09002086 // An applicable subtype should match "mode". If mode is null, mode will be ignored,
2087 // and all subtypes with all modes can be candidates.
2088 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
satok7599a7fb2010-12-22 13:45:23 +09002089 if (firstMatchedModeSubtype == null) {
2090 firstMatchedModeSubtype = subtype;
2091 }
satok9ef02832010-11-04 21:17:48 +09002092 if (locale.equals(subtypeLocale)) {
2093 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
satokcd7cd292010-11-20 15:46:23 +09002094 applicableSubtype = subtype;
satok9ef02832010-11-04 21:17:48 +09002095 break;
2096 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
2097 // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
satokcd7cd292010-11-20 15:46:23 +09002098 applicableSubtype = subtype;
satok9ef02832010-11-04 21:17:48 +09002099 partialMatchFound = true;
2100 }
satok8fbb1e82010-11-02 23:15:58 +09002101 }
2102 }
2103
satok7599a7fb2010-12-22 13:45:23 +09002104 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
2105 return firstMatchedModeSubtype;
satok16331c82010-12-20 23:48:46 +09002106 }
2107
satok8fbb1e82010-11-02 23:15:58 +09002108 // The first subtype applicable to the system locale will be defined as the most applicable
2109 // subtype.
2110 if (DEBUG) {
satok16331c82010-12-20 23:48:46 +09002111 if (applicableSubtype != null) {
2112 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
2113 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
2114 }
satok8fbb1e82010-11-02 23:15:58 +09002115 }
satokcd7cd292010-11-20 15:46:23 +09002116 return applicableSubtype;
satok8fbb1e82010-11-02 23:15:58 +09002117 }
2118
satok4e4569d2010-11-19 18:45:53 +09002119 // If there are no selected shortcuts, tries finding the most applicable ones.
2120 private Pair<InputMethodInfo, InputMethodSubtype>
2121 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2122 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2123 InputMethodInfo mostApplicableIMI = null;
satokcd7cd292010-11-20 15:46:23 +09002124 InputMethodSubtype mostApplicableSubtype = null;
satok4e4569d2010-11-19 18:45:53 +09002125 boolean foundInSystemIME = false;
2126
2127 // Search applicable subtype for each InputMethodInfo
2128 for (InputMethodInfo imi: imis) {
satok7599a7fb2010-12-22 13:45:23 +09002129 final String imiId = imi.getId();
2130 if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2131 continue;
2132 }
satokcd7cd292010-11-20 15:46:23 +09002133 InputMethodSubtype subtype = null;
satokdf31ae62011-01-15 06:19:44 +09002134 final List<InputMethodSubtype> enabledSubtypes =
2135 getEnabledInputMethodSubtypeList(imi, true);
2136 // 1. Search by the current subtype's locale from enabledSubtypes.
satok4e4569d2010-11-19 18:45:53 +09002137 if (mCurrentSubtype != null) {
satokcd7cd292010-11-20 15:46:23 +09002138 subtype = findLastResortApplicableSubtypeLocked(
satokdf31ae62011-01-15 06:19:44 +09002139 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
satok4e4569d2010-11-19 18:45:53 +09002140 }
satokdf31ae62011-01-15 06:19:44 +09002141 // 2. Search by the system locale from enabledSubtypes.
2142 // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
satokcd7cd292010-11-20 15:46:23 +09002143 if (subtype == null) {
2144 subtype = findLastResortApplicableSubtypeLocked(
satokdf31ae62011-01-15 06:19:44 +09002145 mRes, enabledSubtypes, mode, null, true);
satok4e4569d2010-11-19 18:45:53 +09002146 }
satok7599a7fb2010-12-22 13:45:23 +09002147 // 4. Search by the current subtype's locale from all subtypes.
2148 if (subtype == null && mCurrentSubtype != null) {
2149 subtype = findLastResortApplicableSubtypeLocked(
Ken Wakasa586f0512011-01-20 22:31:01 +09002150 mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
satok7599a7fb2010-12-22 13:45:23 +09002151 }
2152 // 5. Search by the system locale from all subtypes.
2153 // 6. Search the first enabled subtype matched with mode from all subtypes.
satokcd7cd292010-11-20 15:46:23 +09002154 if (subtype == null) {
satok7599a7fb2010-12-22 13:45:23 +09002155 subtype = findLastResortApplicableSubtypeLocked(
Ken Wakasa586f0512011-01-20 22:31:01 +09002156 mRes, getSubtypes(imi), mode, null, true);
satok4e4569d2010-11-19 18:45:53 +09002157 }
satokcd7cd292010-11-20 15:46:23 +09002158 if (subtype != null) {
satok7599a7fb2010-12-22 13:45:23 +09002159 if (imiId.equals(mCurMethodId)) {
satok4e4569d2010-11-19 18:45:53 +09002160 // The current input method is the most applicable IME.
2161 mostApplicableIMI = imi;
satokcd7cd292010-11-20 15:46:23 +09002162 mostApplicableSubtype = subtype;
satok4e4569d2010-11-19 18:45:53 +09002163 break;
satok7599a7fb2010-12-22 13:45:23 +09002164 } else if (!foundInSystemIME) {
satok4e4569d2010-11-19 18:45:53 +09002165 // The system input method is 2nd applicable IME.
2166 mostApplicableIMI = imi;
satokcd7cd292010-11-20 15:46:23 +09002167 mostApplicableSubtype = subtype;
satok7599a7fb2010-12-22 13:45:23 +09002168 if ((imi.getServiceInfo().applicationInfo.flags
2169 & ApplicationInfo.FLAG_SYSTEM) != 0) {
2170 foundInSystemIME = true;
2171 }
satok4e4569d2010-11-19 18:45:53 +09002172 }
2173 }
2174 }
2175 if (DEBUG) {
satokcd7cd292010-11-20 15:46:23 +09002176 if (mostApplicableIMI != null) {
2177 Slog.w(TAG, "Most applicable shortcut input method was:"
2178 + mostApplicableIMI.getId());
2179 if (mostApplicableSubtype != null) {
2180 Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2181 + "," + mostApplicableSubtype.getMode() + ","
2182 + mostApplicableSubtype.getLocale());
2183 }
2184 }
satok4e4569d2010-11-19 18:45:53 +09002185 }
satokcd7cd292010-11-20 15:46:23 +09002186 if (mostApplicableIMI != null) {
satok4e4569d2010-11-19 18:45:53 +09002187 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
satokcd7cd292010-11-20 15:46:23 +09002188 mostApplicableSubtype);
satok4e4569d2010-11-19 18:45:53 +09002189 } else {
2190 return null;
2191 }
2192 }
2193
satokab751aa2010-09-14 19:17:36 +09002194 /**
2195 * @return Return the current subtype of this input method.
2196 */
2197 public InputMethodSubtype getCurrentInputMethodSubtype() {
satok4e4569d2010-11-19 18:45:53 +09002198 boolean subtypeIsSelected = false;
2199 try {
2200 subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2201 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2202 } catch (SettingNotFoundException e) {
2203 }
satok3ef8b292010-11-23 06:06:29 +09002204 synchronized (mMethodMap) {
satok3ef8b292010-11-23 06:06:29 +09002205 if (!subtypeIsSelected || mCurrentSubtype == null) {
satok4e4569d2010-11-19 18:45:53 +09002206 String lastInputMethodId = Settings.Secure.getString(
2207 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
satok3ef8b292010-11-23 06:06:29 +09002208 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2209 if (subtypeId == NOT_A_SUBTYPE_ID) {
satok4e4569d2010-11-19 18:45:53 +09002210 InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2211 if (imi != null) {
2212 // If there are no selected subtypes, the framework will try to find
satokd8713432011-01-18 00:55:13 +09002213 // the most applicable subtype from explicitly or implicitly enabled
2214 // subtypes.
2215 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
2216 getEnabledInputMethodSubtypeList(imi, true);
2217 // If there is only one explicitly or implicitly enabled subtype,
2218 // just returns it.
2219 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
2220 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
2221 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
2222 mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2223 mRes, explicitlyOrImplicitlyEnabledSubtypes,
2224 SUBTYPE_MODE_KEYBOARD, null, true);
2225 if (mCurrentSubtype == null) {
2226 mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2227 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
2228 true);
2229 }
2230 }
satok4e4569d2010-11-19 18:45:53 +09002231 }
satokcd7cd292010-11-20 15:46:23 +09002232 } else {
satok3ef8b292010-11-23 06:06:29 +09002233 mCurrentSubtype =
Ken Wakasa586f0512011-01-20 22:31:01 +09002234 getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
satok3ef8b292010-11-23 06:06:29 +09002235 }
satok8fbb1e82010-11-02 23:15:58 +09002236 }
satok3ef8b292010-11-23 06:06:29 +09002237 return mCurrentSubtype;
satok8fbb1e82010-11-02 23:15:58 +09002238 }
satokab751aa2010-09-14 19:17:36 +09002239 }
2240
satokf3db1af2010-11-23 13:34:33 +09002241 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2242 InputMethodSubtype subtype) {
2243 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2244 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2245 } else {
2246 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2247 subtypes.add(subtype);
2248 mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2249 }
2250 }
2251
satok4e4569d2010-11-19 18:45:53 +09002252 // TODO: We should change the return type from List to List<Parcelable>
2253 public List getShortcutInputMethodsAndSubtypes() {
2254 synchronized (mMethodMap) {
satok3da92232011-01-11 22:46:30 +09002255 ArrayList<Object> ret = new ArrayList<Object>();
satokf3db1af2010-11-23 13:34:33 +09002256 if (mShortcutInputMethodsAndSubtypes.size() == 0) {
satok4e4569d2010-11-19 18:45:53 +09002257 // If there are no selected shortcut subtypes, the framework will try to find
2258 // the most applicable subtype from all subtypes whose mode is
2259 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
satokf3db1af2010-11-23 13:34:33 +09002260 Pair<InputMethodInfo, InputMethodSubtype> info =
2261 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2262 SUBTYPE_MODE_VOICE);
satok7599a7fb2010-12-22 13:45:23 +09002263 if (info != null) {
satok3da92232011-01-11 22:46:30 +09002264 ret.add(info.first);
2265 ret.add(info.second);
satok7599a7fb2010-12-22 13:45:23 +09002266 }
satok3da92232011-01-11 22:46:30 +09002267 return ret;
satokf3db1af2010-11-23 13:34:33 +09002268 }
satokf3db1af2010-11-23 13:34:33 +09002269 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2270 ret.add(imi);
2271 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2272 ret.add(subtype);
satok4e4569d2010-11-19 18:45:53 +09002273 }
2274 }
satokf3db1af2010-11-23 13:34:33 +09002275 return ret;
satok4e4569d2010-11-19 18:45:53 +09002276 }
2277 }
2278
satokb66d2872010-11-10 01:04:04 +09002279 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2280 synchronized (mMethodMap) {
2281 if (subtype != null && mCurMethodId != null) {
2282 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2283 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2284 if (subtypeId != NOT_A_SUBTYPE_ID) {
2285 setInputMethodLocked(mCurMethodId, subtypeId);
2286 return true;
2287 }
2288 }
2289 return false;
2290 }
2291 }
2292
satokd87c2592010-09-29 11:52:06 +09002293 /**
2294 * Utility class for putting and getting settings for InputMethod
2295 * TODO: Move all putters and getters of settings to this class.
2296 */
2297 private static class InputMethodSettings {
2298 // The string for enabled input method is saved as follows:
2299 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2300 private static final char INPUT_METHOD_SEPARATER = ':';
2301 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
satok723a27e2010-11-11 14:58:11 +09002302 private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
satokd87c2592010-09-29 11:52:06 +09002303 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2304
satok723a27e2010-11-11 14:58:11 +09002305 private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
satokd87c2592010-09-29 11:52:06 +09002306 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2307
satokdf31ae62011-01-15 06:19:44 +09002308 private final Resources mRes;
satokd87c2592010-09-29 11:52:06 +09002309 private final ContentResolver mResolver;
2310 private final HashMap<String, InputMethodInfo> mMethodMap;
2311 private final ArrayList<InputMethodInfo> mMethodList;
2312
2313 private String mEnabledInputMethodsStrCache;
2314
2315 private static void buildEnabledInputMethodsSettingString(
2316 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2317 String id = pair.first;
2318 ArrayList<String> subtypes = pair.second;
2319 builder.append(id);
satok57c767c2010-11-01 22:34:08 +09002320 // Inputmethod and subtypes are saved in the settings as follows:
2321 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2322 for (String subtypeId: subtypes) {
2323 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
satokd87c2592010-09-29 11:52:06 +09002324 }
2325 }
2326
2327 public InputMethodSettings(
satokdf31ae62011-01-15 06:19:44 +09002328 Resources res, ContentResolver resolver,
2329 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
2330 mRes = res;
satokd87c2592010-09-29 11:52:06 +09002331 mResolver = resolver;
2332 mMethodMap = methodMap;
2333 mMethodList = methodList;
2334 }
2335
2336 public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2337 return createEnabledInputMethodListLocked(
2338 getEnabledInputMethodsAndSubtypeListLocked());
2339 }
2340
satok7f35c8c2010-10-07 21:13:11 +09002341 public List<Pair<InputMethodInfo, ArrayList<String>>>
satok67ddf9c2010-11-17 09:45:54 +09002342 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2343 return createEnabledInputMethodAndSubtypeHashCodeListLocked(
satok7f35c8c2010-10-07 21:13:11 +09002344 getEnabledInputMethodsAndSubtypeListLocked());
2345 }
2346
satok67ddf9c2010-11-17 09:45:54 +09002347 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2348 InputMethodInfo imi) {
2349 List<Pair<String, ArrayList<String>>> imsList =
2350 getEnabledInputMethodsAndSubtypeListLocked();
2351 ArrayList<InputMethodSubtype> enabledSubtypes =
2352 new ArrayList<InputMethodSubtype>();
satok884ef9a2010-11-18 10:39:46 +09002353 if (imi != null) {
2354 for (Pair<String, ArrayList<String>> imsPair : imsList) {
2355 InputMethodInfo info = mMethodMap.get(imsPair.first);
2356 if (info != null && info.getId().equals(imi.getId())) {
Ken Wakasa586f0512011-01-20 22:31:01 +09002357 final int subtypeCount = info.getSubtypeCount();
2358 for (int i = 0; i < subtypeCount; ++i) {
2359 InputMethodSubtype ims = info.getSubtypeAt(i);
satok884ef9a2010-11-18 10:39:46 +09002360 for (String s: imsPair.second) {
2361 if (String.valueOf(ims.hashCode()).equals(s)) {
2362 enabledSubtypes.add(ims);
2363 }
satok67ddf9c2010-11-17 09:45:54 +09002364 }
2365 }
satok884ef9a2010-11-18 10:39:46 +09002366 break;
satok67ddf9c2010-11-17 09:45:54 +09002367 }
satok67ddf9c2010-11-17 09:45:54 +09002368 }
2369 }
2370 return enabledSubtypes;
2371 }
2372
satokd87c2592010-09-29 11:52:06 +09002373 // At the initial boot, the settings for input methods are not set,
2374 // so we need to enable IME in that case.
2375 public void enableAllIMEsIfThereIsNoEnabledIME() {
2376 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2377 StringBuilder sb = new StringBuilder();
2378 final int N = mMethodList.size();
2379 for (int i = 0; i < N; i++) {
2380 InputMethodInfo imi = mMethodList.get(i);
2381 Slog.i(TAG, "Adding: " + imi.getId());
2382 if (i > 0) sb.append(':');
2383 sb.append(imi.getId());
2384 }
2385 putEnabledInputMethodsStr(sb.toString());
2386 }
2387 }
2388
satokbb4aa062011-01-19 21:40:27 +09002389 private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
satokd87c2592010-09-29 11:52:06 +09002390 ArrayList<Pair<String, ArrayList<String>>> imsList
2391 = new ArrayList<Pair<String, ArrayList<String>>>();
2392 final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2393 if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2394 return imsList;
2395 }
satok723a27e2010-11-11 14:58:11 +09002396 mInputMethodSplitter.setString(enabledInputMethodsStr);
2397 while (mInputMethodSplitter.hasNext()) {
2398 String nextImsStr = mInputMethodSplitter.next();
2399 mSubtypeSplitter.setString(nextImsStr);
2400 if (mSubtypeSplitter.hasNext()) {
satokd87c2592010-09-29 11:52:06 +09002401 ArrayList<String> subtypeHashes = new ArrayList<String>();
2402 // The first element is ime id.
satok723a27e2010-11-11 14:58:11 +09002403 String imeId = mSubtypeSplitter.next();
2404 while (mSubtypeSplitter.hasNext()) {
2405 subtypeHashes.add(mSubtypeSplitter.next());
satokd87c2592010-09-29 11:52:06 +09002406 }
2407 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2408 }
2409 }
2410 return imsList;
2411 }
2412
2413 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2414 if (reloadInputMethodStr) {
2415 getEnabledInputMethodsStr();
2416 }
2417 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2418 // Add in the newly enabled input method.
2419 putEnabledInputMethodsStr(id);
2420 } else {
2421 putEnabledInputMethodsStr(
2422 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2423 }
2424 }
2425
2426 /**
2427 * Build and put a string of EnabledInputMethods with removing specified Id.
2428 * @return the specified id was removed or not.
2429 */
2430 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2431 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2432 boolean isRemoved = false;
2433 boolean needsAppendSeparator = false;
2434 for (Pair<String, ArrayList<String>> ims: imsList) {
2435 String curId = ims.first;
2436 if (curId.equals(id)) {
2437 // We are disabling this input method, and it is
2438 // currently enabled. Skip it to remove from the
2439 // new list.
2440 isRemoved = true;
2441 } else {
2442 if (needsAppendSeparator) {
2443 builder.append(INPUT_METHOD_SEPARATER);
2444 } else {
2445 needsAppendSeparator = true;
2446 }
2447 buildEnabledInputMethodsSettingString(builder, ims);
2448 }
2449 }
2450 if (isRemoved) {
2451 // Update the setting with the new list of input methods.
2452 putEnabledInputMethodsStr(builder.toString());
2453 }
2454 return isRemoved;
2455 }
2456
2457 private List<InputMethodInfo> createEnabledInputMethodListLocked(
2458 List<Pair<String, ArrayList<String>>> imsList) {
2459 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2460 for (Pair<String, ArrayList<String>> ims: imsList) {
2461 InputMethodInfo info = mMethodMap.get(ims.first);
2462 if (info != null) {
2463 res.add(info);
2464 }
2465 }
2466 return res;
2467 }
2468
satok7f35c8c2010-10-07 21:13:11 +09002469 private List<Pair<InputMethodInfo, ArrayList<String>>>
satok67ddf9c2010-11-17 09:45:54 +09002470 createEnabledInputMethodAndSubtypeHashCodeListLocked(
satok7f35c8c2010-10-07 21:13:11 +09002471 List<Pair<String, ArrayList<String>>> imsList) {
2472 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2473 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2474 for (Pair<String, ArrayList<String>> ims : imsList) {
2475 InputMethodInfo info = mMethodMap.get(ims.first);
2476 if (info != null) {
2477 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2478 }
2479 }
2480 return res;
2481 }
2482
satokd87c2592010-09-29 11:52:06 +09002483 private void putEnabledInputMethodsStr(String str) {
2484 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2485 mEnabledInputMethodsStrCache = str;
2486 }
2487
2488 private String getEnabledInputMethodsStr() {
2489 mEnabledInputMethodsStrCache = Settings.Secure.getString(
2490 mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
satok723a27e2010-11-11 14:58:11 +09002491 if (DEBUG) {
2492 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2493 }
satokd87c2592010-09-29 11:52:06 +09002494 return mEnabledInputMethodsStrCache;
2495 }
satok723a27e2010-11-11 14:58:11 +09002496
2497 private void saveSubtypeHistory(
2498 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2499 StringBuilder builder = new StringBuilder();
2500 boolean isImeAdded = false;
2501 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2502 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2503 newSubtypeId);
2504 isImeAdded = true;
2505 }
2506 for (Pair<String, String> ime: savedImes) {
2507 String imeId = ime.first;
2508 String subtypeId = ime.second;
2509 if (TextUtils.isEmpty(subtypeId)) {
2510 subtypeId = NOT_A_SUBTYPE_ID_STR;
2511 }
2512 if (isImeAdded) {
2513 builder.append(INPUT_METHOD_SEPARATER);
2514 } else {
2515 isImeAdded = true;
2516 }
2517 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2518 subtypeId);
2519 }
2520 // Remove the last INPUT_METHOD_SEPARATER
2521 putSubtypeHistoryStr(builder.toString());
2522 }
2523
2524 public void addSubtypeToHistory(String imeId, String subtypeId) {
2525 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2526 for (Pair<String, String> ime: subtypeHistory) {
2527 if (ime.first.equals(imeId)) {
2528 if (DEBUG) {
satokbb4aa062011-01-19 21:40:27 +09002529 Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
satok723a27e2010-11-11 14:58:11 +09002530 + ime.second);
2531 }
2532 // We should break here
2533 subtypeHistory.remove(ime);
2534 break;
2535 }
2536 }
satokbb4aa062011-01-19 21:40:27 +09002537 if (DEBUG) {
2538 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
2539 }
satok723a27e2010-11-11 14:58:11 +09002540 saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2541 }
2542
2543 private void putSubtypeHistoryStr(String str) {
2544 if (DEBUG) {
2545 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2546 }
2547 Settings.Secure.putString(
2548 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2549 }
2550
2551 public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2552 // Gets the first one from the history
2553 return getLastSubtypeForInputMethodLockedInternal(null);
2554 }
2555
2556 public String getLastSubtypeForInputMethodLocked(String imeId) {
2557 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2558 if (ime != null) {
2559 return ime.second;
2560 } else {
2561 return null;
2562 }
2563 }
2564
2565 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
2566 List<Pair<String, ArrayList<String>>> enabledImes =
2567 getEnabledInputMethodsAndSubtypeListLocked();
2568 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2569 for (Pair<String, String> imeAndSubtype: subtypeHistory) {
2570 final String imeInTheHistory = imeAndSubtype.first;
2571 // If imeId is empty, returns the first IME and subtype in the history
2572 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
2573 final String subtypeInTheHistory = imeAndSubtype.second;
satokdf31ae62011-01-15 06:19:44 +09002574 final String subtypeHashCode =
2575 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
2576 enabledImes, imeInTheHistory, subtypeInTheHistory);
satok723a27e2010-11-11 14:58:11 +09002577 if (!TextUtils.isEmpty(subtypeHashCode)) {
2578 if (DEBUG) {
satokbb4aa062011-01-19 21:40:27 +09002579 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
satok723a27e2010-11-11 14:58:11 +09002580 }
2581 return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
2582 }
2583 }
2584 }
2585 if (DEBUG) {
2586 Slog.d(TAG, "No enabled IME found in the history");
2587 }
2588 return null;
2589 }
2590
satokdf31ae62011-01-15 06:19:44 +09002591 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
satok723a27e2010-11-11 14:58:11 +09002592 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
2593 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
2594 if (enabledIme.first.equals(imeId)) {
satokf6cafb62011-01-17 16:29:02 +09002595 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
2596 if (explicitlyEnabledSubtypes.size() == 0) {
2597 // If there are no explicitly enabled subtypes, applicable subtypes are
2598 // enabled implicitly.
satokdf31ae62011-01-15 06:19:44 +09002599 InputMethodInfo ime = mMethodMap.get(imeId);
2600 // If IME is enabled and no subtypes are enabled, applicable subtypes
2601 // are enabled implicitly, so needs to treat them to be enabled.
Ken Wakasa586f0512011-01-20 22:31:01 +09002602 if (ime != null && ime.getSubtypeCount() > 0) {
satokdf31ae62011-01-15 06:19:44 +09002603 List<InputMethodSubtype> implicitlySelectedSubtypes =
Ken Wakasa586f0512011-01-20 22:31:01 +09002604 getApplicableSubtypesLocked(mRes, getSubtypes(ime));
satokdf31ae62011-01-15 06:19:44 +09002605 if (implicitlySelectedSubtypes != null) {
2606 final int N = implicitlySelectedSubtypes.size();
2607 for (int i = 0; i < N; ++i) {
2608 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
2609 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
2610 return subtypeHashCode;
2611 }
2612 }
2613 }
2614 }
2615 } else {
satokf6cafb62011-01-17 16:29:02 +09002616 for (String s: explicitlyEnabledSubtypes) {
satokdf31ae62011-01-15 06:19:44 +09002617 if (s.equals(subtypeHashCode)) {
2618 // If both imeId and subtypeId are enabled, return subtypeId.
2619 return s;
2620 }
satok723a27e2010-11-11 14:58:11 +09002621 }
2622 }
2623 // If imeId was enabled but subtypeId was disabled.
2624 return NOT_A_SUBTYPE_ID_STR;
2625 }
2626 }
2627 // If both imeId and subtypeId are disabled, return null
2628 return null;
2629 }
2630
2631 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
2632 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
2633 final String subtypeHistoryStr = getSubtypeHistoryStr();
2634 if (TextUtils.isEmpty(subtypeHistoryStr)) {
2635 return imsList;
2636 }
2637 mInputMethodSplitter.setString(subtypeHistoryStr);
2638 while (mInputMethodSplitter.hasNext()) {
2639 String nextImsStr = mInputMethodSplitter.next();
2640 mSubtypeSplitter.setString(nextImsStr);
2641 if (mSubtypeSplitter.hasNext()) {
2642 String subtypeId = NOT_A_SUBTYPE_ID_STR;
2643 // The first element is ime id.
2644 String imeId = mSubtypeSplitter.next();
2645 while (mSubtypeSplitter.hasNext()) {
2646 subtypeId = mSubtypeSplitter.next();
2647 break;
2648 }
2649 imsList.add(new Pair<String, String>(imeId, subtypeId));
2650 }
2651 }
2652 return imsList;
2653 }
2654
2655 private String getSubtypeHistoryStr() {
2656 if (DEBUG) {
2657 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
2658 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
2659 }
2660 return Settings.Secure.getString(
2661 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
2662 }
2663
2664 public void putSelectedInputMethod(String imeId) {
2665 Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
2666 }
2667
2668 public void putSelectedSubtype(int subtypeId) {
2669 Settings.Secure.putInt(
2670 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
2671 }
satokd87c2592010-09-29 11:52:06 +09002672 }
2673
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002674 // ----------------------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002675
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002676 @Override
2677 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2678 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2679 != PackageManager.PERMISSION_GRANTED) {
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002680
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002681 pw.println("Permission Denial: can't dump InputMethodManager from from pid="
2682 + Binder.getCallingPid()
2683 + ", uid=" + Binder.getCallingUid());
2684 return;
2685 }
2686
2687 IInputMethod method;
2688 ClientState client;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002689
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002690 final Printer p = new PrintWriterPrinter(pw);
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002691
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002692 synchronized (mMethodMap) {
2693 p.println("Current Input Method Manager state:");
2694 int N = mMethodList.size();
2695 p.println(" Input Methods:");
2696 for (int i=0; i<N; i++) {
2697 InputMethodInfo info = mMethodList.get(i);
2698 p.println(" InputMethod #" + i + ":");
2699 info.dump(p, " ");
2700 }
2701 p.println(" Clients:");
2702 for (ClientState ci : mClients.values()) {
2703 p.println(" Client " + ci + ":");
2704 p.println(" client=" + ci.client);
2705 p.println(" inputContext=" + ci.inputContext);
2706 p.println(" sessionRequested=" + ci.sessionRequested);
2707 p.println(" curSession=" + ci.curSession);
2708 }
The Android Open Source Project10592532009-03-18 17:39:46 -07002709 p.println(" mCurMethodId=" + mCurMethodId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002710 client = mCurClient;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002711 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
2712 p.println(" mCurFocusedWindow=" + mCurFocusedWindow);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002713 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
2714 + " mBoundToMethod=" + mBoundToMethod);
2715 p.println(" mCurToken=" + mCurToken);
2716 p.println(" mCurIntent=" + mCurIntent);
2717 method = mCurMethod;
2718 p.println(" mCurMethod=" + mCurMethod);
2719 p.println(" mEnabledSession=" + mEnabledSession);
2720 p.println(" mShowRequested=" + mShowRequested
2721 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
2722 + " mShowForced=" + mShowForced
2723 + " mInputShown=" + mInputShown);
Dianne Hackborncc278702009-09-02 23:07:23 -07002724 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002725 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002726
Jeff Brownb88102f2010-09-08 11:49:43 -07002727 p.println(" ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002728 if (client != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002729 pw.flush();
2730 try {
2731 client.client.asBinder().dump(fd, args);
2732 } catch (RemoteException e) {
2733 p.println("Input method client dead: " + e);
2734 }
Jeff Brownb88102f2010-09-08 11:49:43 -07002735 } else {
2736 p.println("No input method client.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002737 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002738
Jeff Brownb88102f2010-09-08 11:49:43 -07002739 p.println(" ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002740 if (method != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002741 pw.flush();
2742 try {
2743 method.asBinder().dump(fd, args);
2744 } catch (RemoteException e) {
2745 p.println("Input method service dead: " + e);
2746 }
Jeff Brownb88102f2010-09-08 11:49:43 -07002747 } else {
2748 p.println("No input method service.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002749 }
2750 }
2751}