blob: 21c1e81462182a7227f6ebdc51f2d5b47f91cc5f [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) {
1359 Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1360 if (lastIme != null) {
1361 InputMethodInfo imi = mMethodMap.get(lastIme.first);
1362 if (imi != null) {
satok28203512010-11-24 11:06:49 +09001363 setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode(
satok735cf382010-11-11 20:40:09 +09001364 imi, Integer.valueOf(lastIme.second)));
1365 return true;
1366 }
1367 }
1368 return false;
1369 }
1370 }
1371
satok28203512010-11-24 11:06:49 +09001372 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001373 synchronized (mMethodMap) {
1374 if (token == null) {
1375 if (mContext.checkCallingOrSelfPermission(
1376 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1377 != PackageManager.PERMISSION_GRANTED) {
1378 throw new SecurityException(
1379 "Using null token requires permission "
1380 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1381 }
1382 } else if (mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001383 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1384 + " token: " + token);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001385 return;
1386 }
1387
1388 long ident = Binder.clearCallingIdentity();
1389 try {
satokab751aa2010-09-14 19:17:36 +09001390 setInputMethodLocked(id, subtypeId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001391 } finally {
1392 Binder.restoreCallingIdentity(ident);
1393 }
1394 }
1395 }
1396
1397 public void hideMySoftInput(IBinder token, int flags) {
1398 synchronized (mMethodMap) {
1399 if (token == null || mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001400 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1401 + Binder.getCallingUid() + " token: " + token);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001402 return;
1403 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001404 long ident = Binder.clearCallingIdentity();
1405 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001406 hideCurrentInputLocked(flags, null);
1407 } finally {
1408 Binder.restoreCallingIdentity(ident);
1409 }
1410 }
1411 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001412
The Android Open Source Project4df24232009-03-05 14:34:35 -08001413 public void showMySoftInput(IBinder token, int flags) {
1414 synchronized (mMethodMap) {
1415 if (token == null || mCurToken != token) {
Dianne Hackborncef65ee2010-09-30 18:27:22 -07001416 Slog.w(TAG, "Ignoring showMySoftInput of uid "
1417 + Binder.getCallingUid() + " token: " + token);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001418 return;
1419 }
1420 long ident = Binder.clearCallingIdentity();
1421 try {
1422 showCurrentInputLocked(flags, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001423 } finally {
1424 Binder.restoreCallingIdentity(ident);
1425 }
1426 }
1427 }
1428
1429 void setEnabledSessionInMainThread(SessionState session) {
1430 if (mEnabledSession != session) {
1431 if (mEnabledSession != null) {
1432 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001433 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001434 mEnabledSession.method.setSessionEnabled(
1435 mEnabledSession.session, false);
1436 } catch (RemoteException e) {
1437 }
1438 }
1439 mEnabledSession = session;
1440 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001441 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 session.method.setSessionEnabled(
1443 session.session, true);
1444 } catch (RemoteException e) {
1445 }
1446 }
1447 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001449 public boolean handleMessage(Message msg) {
1450 HandlerCaller.SomeArgs args;
1451 switch (msg.what) {
1452 case MSG_SHOW_IM_PICKER:
1453 showInputMethodMenu();
1454 return true;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001455
satokab751aa2010-09-14 19:17:36 +09001456 case MSG_SHOW_IM_SUBTYPE_PICKER:
1457 showInputMethodSubtypeMenu();
1458 return true;
1459
satok47a44912010-10-06 16:03:58 +09001460 case MSG_SHOW_IM_SUBTYPE_ENABLER:
satok217f5482010-12-15 05:19:19 +09001461 args = (HandlerCaller.SomeArgs)msg.obj;
satok7fee71f2010-12-17 18:54:26 +09001462 showInputMethodAndSubtypeEnabler((String)args.arg1);
satok217f5482010-12-15 05:19:19 +09001463 return true;
1464
1465 case MSG_SHOW_IM_CONFIG:
1466 showConfigureInputMethods();
satok47a44912010-10-06 16:03:58 +09001467 return true;
1468
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001469 // ---------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001470
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001471 case MSG_UNBIND_INPUT:
1472 try {
1473 ((IInputMethod)msg.obj).unbindInput();
1474 } catch (RemoteException e) {
1475 // There is nothing interesting about the method dying.
1476 }
1477 return true;
1478 case MSG_BIND_INPUT:
1479 args = (HandlerCaller.SomeArgs)msg.obj;
1480 try {
1481 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1482 } catch (RemoteException e) {
1483 }
1484 return true;
1485 case MSG_SHOW_SOFT_INPUT:
The Android Open Source Project4df24232009-03-05 14:34:35 -08001486 args = (HandlerCaller.SomeArgs)msg.obj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001487 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001488 ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1489 (ResultReceiver)args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001490 } catch (RemoteException e) {
1491 }
1492 return true;
1493 case MSG_HIDE_SOFT_INPUT:
The Android Open Source Project4df24232009-03-05 14:34:35 -08001494 args = (HandlerCaller.SomeArgs)msg.obj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001495 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001496 ((IInputMethod)args.arg1).hideSoftInput(0,
1497 (ResultReceiver)args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001498 } catch (RemoteException e) {
1499 }
1500 return true;
1501 case MSG_ATTACH_TOKEN:
1502 args = (HandlerCaller.SomeArgs)msg.obj;
1503 try {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001504 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1506 } catch (RemoteException e) {
1507 }
1508 return true;
1509 case MSG_CREATE_SESSION:
1510 args = (HandlerCaller.SomeArgs)msg.obj;
1511 try {
1512 ((IInputMethod)args.arg1).createSession(
1513 (IInputMethodCallback)args.arg2);
1514 } catch (RemoteException e) {
1515 }
1516 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 // ---------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001518
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001519 case MSG_START_INPUT:
1520 args = (HandlerCaller.SomeArgs)msg.obj;
1521 try {
1522 SessionState session = (SessionState)args.arg1;
1523 setEnabledSessionInMainThread(session);
1524 session.method.startInput((IInputContext)args.arg2,
1525 (EditorInfo)args.arg3);
1526 } catch (RemoteException e) {
1527 }
1528 return true;
1529 case MSG_RESTART_INPUT:
1530 args = (HandlerCaller.SomeArgs)msg.obj;
1531 try {
1532 SessionState session = (SessionState)args.arg1;
1533 setEnabledSessionInMainThread(session);
1534 session.method.restartInput((IInputContext)args.arg2,
1535 (EditorInfo)args.arg3);
1536 } catch (RemoteException e) {
1537 }
1538 return true;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001539
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001540 // ---------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001541
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001542 case MSG_UNBIND_METHOD:
1543 try {
1544 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1545 } catch (RemoteException e) {
1546 // There is nothing interesting about the last client dying.
1547 }
1548 return true;
1549 case MSG_BIND_METHOD:
1550 args = (HandlerCaller.SomeArgs)msg.obj;
1551 try {
1552 ((IInputMethodClient)args.arg1).onBindMethod(
1553 (InputBindResult)args.arg2);
1554 } catch (RemoteException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001555 Slog.w(TAG, "Client died receiving input method " + args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001556 }
1557 return true;
1558 }
1559 return false;
1560 }
1561
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001562 private boolean isSystemIme(InputMethodInfo inputMethod) {
1563 return (inputMethod.getServiceInfo().applicationInfo.flags
1564 & ApplicationInfo.FLAG_SYSTEM) != 0;
1565 }
1566
Ken Wakasa586f0512011-01-20 22:31:01 +09001567 private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
1568 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
1569 final int subtypeCount = imi.getSubtypeCount();
1570 for (int i = 0; i < subtypeCount; ++i) {
1571 subtypes.add(imi.getSubtypeAt(i));
1572 }
1573 return subtypes;
1574 }
1575
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001576 private boolean chooseNewDefaultIMELocked() {
satokd87c2592010-09-29 11:52:06 +09001577 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001578 if (enabled != null && enabled.size() > 0) {
Dianne Hackborn83e48f52010-03-23 23:03:25 -07001579 // We'd prefer to fall back on a system IME, since that is safer.
1580 int i=enabled.size();
1581 while (i > 0) {
1582 i--;
1583 if ((enabled.get(i).getServiceInfo().applicationInfo.flags
1584 & ApplicationInfo.FLAG_SYSTEM) != 0) {
1585 break;
1586 }
1587 }
satokab751aa2010-09-14 19:17:36 +09001588 InputMethodInfo imi = enabled.get(i);
satok03eb319a2010-11-11 18:17:42 +09001589 if (DEBUG) {
1590 Slog.d(TAG, "New default IME was selected: " + imi.getId());
1591 }
satok723a27e2010-11-11 14:58:11 +09001592 resetSelectedInputMethodAndSubtypeLocked(imi.getId());
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001593 return true;
1594 }
1595
1596 return false;
1597 }
1598
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001599 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1600 HashMap<String, InputMethodInfo> map) {
1601 list.clear();
1602 map.clear();
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001603
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001604 PackageManager pm = mContext.getPackageManager();
Dianne Hackborn7d3a5bc2010-11-29 22:52:12 -08001605 final Configuration config = mRes.getConfiguration();
Amith Yamasanie861ec12010-03-24 21:39:27 -07001606 final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1607 String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1608 Secure.DISABLED_SYSTEM_INPUT_METHODS);
1609 if (disabledSysImes == null) disabledSysImes = "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001610
1611 List<ResolveInfo> services = pm.queryIntentServices(
1612 new Intent(InputMethod.SERVICE_INTERFACE),
1613 PackageManager.GET_META_DATA);
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001614
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001615 for (int i = 0; i < services.size(); ++i) {
1616 ResolveInfo ri = services.get(i);
1617 ServiceInfo si = ri.serviceInfo;
1618 ComponentName compName = new ComponentName(si.packageName, si.name);
1619 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1620 si.permission)) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001621 Slog.w(TAG, "Skipping input method " + compName
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001622 + ": it does not require the permission "
1623 + android.Manifest.permission.BIND_INPUT_METHOD);
1624 continue;
1625 }
1626
Joe Onorato8a9b2202010-02-26 18:56:32 -08001627 if (DEBUG) Slog.d(TAG, "Checking " + compName);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001628
1629 try {
1630 InputMethodInfo p = new InputMethodInfo(mContext, ri);
1631 list.add(p);
Amith Yamasanie861ec12010-03-24 21:39:27 -07001632 final String id = p.getId();
1633 map.put(id, p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001634
Amith Yamasanie861ec12010-03-24 21:39:27 -07001635 // System IMEs are enabled by default, unless there's a hard keyboard
1636 // and the system IME was explicitly disabled
1637 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1638 setInputMethodEnabledLocked(id, true);
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001639 }
1640
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001641 if (DEBUG) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001642 Slog.d(TAG, "Found a third-party input method " + p);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001643 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001644
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001645 } catch (XmlPullParserException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001646 Slog.w(TAG, "Unable to load input method " + compName, e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001647 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001648 Slog.w(TAG, "Unable to load input method " + compName, e);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001649 }
1650 }
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001651
1652 String defaultIme = Settings.Secure.getString(mContext
1653 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
satok913a8922010-08-26 21:53:41 +09001654 if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001655 if (chooseNewDefaultIMELocked()) {
Brandon Ballinger6da35a02009-10-21 00:38:13 -07001656 updateFromSettingsLocked();
1657 }
1658 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001659 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001661 // ----------------------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001662
satokab751aa2010-09-14 19:17:36 +09001663 private void showInputMethodMenu() {
1664 showInputMethodMenuInternal(false);
1665 }
1666
1667 private void showInputMethodSubtypeMenu() {
1668 showInputMethodMenuInternal(true);
1669 }
1670
satok217f5482010-12-15 05:19:19 +09001671 private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
Tadashi G. Takaokaf49688f2011-01-20 17:56:13 +09001672 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
satok47a44912010-10-06 16:03:58 +09001673 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
satok86417ea2010-10-27 14:11:03 +09001674 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1675 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
satok7fee71f2010-12-17 18:54:26 +09001676 if (!TextUtils.isEmpty(inputMethodId)) {
Tadashi G. Takaoka25480202011-01-20 23:13:02 +09001677 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
satok7fee71f2010-12-17 18:54:26 +09001678 }
satok217f5482010-12-15 05:19:19 +09001679 mContext.startActivity(intent);
1680 }
1681
1682 private void showConfigureInputMethods() {
1683 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
1684 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1685 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1686 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
satok47a44912010-10-06 16:03:58 +09001687 mContext.startActivity(intent);
1688 }
1689
satokab751aa2010-09-14 19:17:36 +09001690 private void showInputMethodMenuInternal(boolean showSubtypes) {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001691 if (DEBUG) Slog.v(TAG, "Show switching menu");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001692
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 final Context context = mContext;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001694
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001695 final PackageManager pm = context.getPackageManager();
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001696
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001697 String lastInputMethodId = Settings.Secure.getString(context
1698 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
satokab751aa2010-09-14 19:17:36 +09001699 int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
Joe Onorato8a9b2202010-02-26 18:56:32 -08001700 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001701
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001702 synchronized (mMethodMap) {
satokbb4aa062011-01-19 21:40:27 +09001703 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
1704 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
satok7f35c8c2010-10-07 21:13:11 +09001705 if (immis == null || immis.size() == 0) {
1706 return;
1707 }
1708
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001709 hideInputMethodMenuLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001710
satokab751aa2010-09-14 19:17:36 +09001711 final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
1712 new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
satok913a8922010-08-26 21:53:41 +09001713
satokbb4aa062011-01-19 21:40:27 +09001714 for (InputMethodInfo imi: immis.keySet()) {
1715 if (imi == null) continue;
1716 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
satok7f35c8c2010-10-07 21:13:11 +09001717 HashSet<String> enabledSubtypeSet = new HashSet<String>();
satokbb4aa062011-01-19 21:40:27 +09001718 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
1719 enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
satok7f35c8c2010-10-07 21:13:11 +09001720 }
satokbb4aa062011-01-19 21:40:27 +09001721 ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
1722 CharSequence label = imi.loadLabel(pm);
satok7f35c8c2010-10-07 21:13:11 +09001723 if (showSubtypes && enabledSubtypeSet.size() > 0) {
satokbb4aa062011-01-19 21:40:27 +09001724 final int subtypeCount = imi.getSubtypeCount();
Ken Wakasa586f0512011-01-20 22:31:01 +09001725 for (int j = 0; j < subtypeCount; ++j) {
satokbb4aa062011-01-19 21:40:27 +09001726 InputMethodSubtype subtype = imi.getSubtypeAt(j);
satok7f35c8c2010-10-07 21:13:11 +09001727 if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) {
1728 CharSequence title;
1729 int nameResId = subtype.getNameResId();
satok9ef02832010-11-04 21:17:48 +09001730 String mode = subtype.getMode();
satok7f35c8c2010-10-07 21:13:11 +09001731 if (nameResId != 0) {
satokbb4aa062011-01-19 21:40:27 +09001732 title = pm.getText(imi.getPackageName(), nameResId,
1733 imi.getServiceInfo().applicationInfo);
satok7f35c8c2010-10-07 21:13:11 +09001734 } else {
1735 CharSequence language = subtype.getLocale();
satok7f35c8c2010-10-07 21:13:11 +09001736 // TODO: Use more friendly Title and UI
1737 title = label + "," + (mode == null ? "" : mode) + ","
1738 + (language == null ? "" : language);
1739 }
satokbb4aa062011-01-19 21:40:27 +09001740 imMap.put(title, new Pair<InputMethodInfo, Integer>(imi, j));
satokab751aa2010-09-14 19:17:36 +09001741 }
satokab751aa2010-09-14 19:17:36 +09001742 }
1743 } else {
1744 imMap.put(label,
satokbb4aa062011-01-19 21:40:27 +09001745 new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID));
satokab751aa2010-09-14 19:17:36 +09001746 }
Dianne Hackborn97106ab2010-03-03 00:08:31 -08001747 }
satok913a8922010-08-26 21:53:41 +09001748
satokbb4aa062011-01-19 21:40:27 +09001749 final int N = imMap.size();
satok913a8922010-08-26 21:53:41 +09001750 mItems = imMap.keySet().toArray(new CharSequence[N]);
satokab751aa2010-09-14 19:17:36 +09001751 mIms = new InputMethodInfo[N];
1752 mSubtypeIds = new int[N];
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001753 int checkedItem = 0;
1754 for (int i = 0; i < N; ++i) {
satokab751aa2010-09-14 19:17:36 +09001755 Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
1756 mIms[i] = value.first;
1757 mSubtypeIds[i] = value.second;
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001758 if (mIms[i].getId().equals(lastInputMethodId)) {
satokab751aa2010-09-14 19:17:36 +09001759 int subtypeId = mSubtypeIds[i];
1760 if ((subtypeId == NOT_A_SUBTYPE_ID)
1761 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
1762 || (subtypeId == lastInputMethodSubtypeId)) {
1763 checkedItem = i;
1764 }
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001765 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001766 }
satokab751aa2010-09-14 19:17:36 +09001767
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001768 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1769 public void onClick(DialogInterface dialog, int which) {
1770 hideInputMethodMenu();
1771 }
1772 };
satokd87c2592010-09-29 11:52:06 +09001773
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001774 TypedArray a = context.obtainStyledAttributes(null,
1775 com.android.internal.R.styleable.DialogPreference,
1776 com.android.internal.R.attr.alertDialogStyle, 0);
1777 mDialogBuilder = new AlertDialog.Builder(context)
1778 .setTitle(com.android.internal.R.string.select_input_method)
1779 .setOnCancelListener(new OnCancelListener() {
1780 public void onCancel(DialogInterface dialog) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001781 hideInputMethodMenu();
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001782 }
1783 })
1784 .setIcon(a.getDrawable(
1785 com.android.internal.R.styleable.DialogPreference_dialogTitle));
1786 a.recycle();
satokd87c2592010-09-29 11:52:06 +09001787
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001788 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1789 new AlertDialog.OnClickListener() {
1790 public void onClick(DialogInterface dialog, int which) {
1791 synchronized (mMethodMap) {
satokab751aa2010-09-14 19:17:36 +09001792 if (mIms == null || mIms.length <= which
1793 || mSubtypeIds == null || mSubtypeIds.length <= which) {
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001794 return;
1795 }
1796 InputMethodInfo im = mIms[which];
satokab751aa2010-09-14 19:17:36 +09001797 int subtypeId = mSubtypeIds[which];
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001798 hideInputMethodMenu();
1799 if (im != null) {
satokab751aa2010-09-14 19:17:36 +09001800 if ((subtypeId < 0)
Ken Wakasa586f0512011-01-20 22:31:01 +09001801 || (subtypeId >= im.getSubtypeCount())) {
satokab751aa2010-09-14 19:17:36 +09001802 subtypeId = NOT_A_SUBTYPE_ID;
1803 }
1804 setInputMethodLocked(im.getId(), subtypeId);
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001805 }
Dianne Hackborn20cb56e2010-03-04 00:58:29 -08001806 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001807 }
Dianne Hackborn8cf1bcd2010-03-16 13:06:10 -07001808 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001809
satok7f35c8c2010-10-07 21:13:11 +09001810 if (showSubtypes) {
satok82beadf2010-12-27 19:03:06 +09001811 mDialogBuilder.setPositiveButton(
1812 com.android.internal.R.string.configure_input_methods,
satok7f35c8c2010-10-07 21:13:11 +09001813 new DialogInterface.OnClickListener() {
1814 public void onClick(DialogInterface dialog, int whichButton) {
satok217f5482010-12-15 05:19:19 +09001815 showConfigureInputMethods();
satok7f35c8c2010-10-07 21:13:11 +09001816 }
1817 });
1818 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001819 mSwitchingDialog = mDialogBuilder.create();
1820 mSwitchingDialog.getWindow().setType(
1821 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1822 mSwitchingDialog.show();
1823 }
1824 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001825
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001826 void hideInputMethodMenu() {
The Android Open Source Project10592532009-03-18 17:39:46 -07001827 synchronized (mMethodMap) {
1828 hideInputMethodMenuLocked();
1829 }
1830 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001831
The Android Open Source Project10592532009-03-18 17:39:46 -07001832 void hideInputMethodMenuLocked() {
Joe Onorato8a9b2202010-02-26 18:56:32 -08001833 if (DEBUG) Slog.v(TAG, "Hide switching menu");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834
The Android Open Source Project10592532009-03-18 17:39:46 -07001835 if (mSwitchingDialog != null) {
1836 mSwitchingDialog.dismiss();
1837 mSwitchingDialog = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001838 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001839
The Android Open Source Project10592532009-03-18 17:39:46 -07001840 mDialogBuilder = null;
1841 mItems = null;
1842 mIms = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001843 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001844
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001845 // ----------------------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08001846
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001847 public boolean setInputMethodEnabled(String id, boolean enabled) {
1848 synchronized (mMethodMap) {
1849 if (mContext.checkCallingOrSelfPermission(
1850 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1851 != PackageManager.PERMISSION_GRANTED) {
1852 throw new SecurityException(
1853 "Requires permission "
1854 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1855 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001856
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001857 long ident = Binder.clearCallingIdentity();
1858 try {
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001859 return setInputMethodEnabledLocked(id, enabled);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001860 } finally {
1861 Binder.restoreCallingIdentity(ident);
1862 }
1863 }
1864 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001865
1866 boolean setInputMethodEnabledLocked(String id, boolean enabled) {
1867 // Make sure this is a valid input method.
1868 InputMethodInfo imm = mMethodMap.get(id);
1869 if (imm == null) {
satokd87c2592010-09-29 11:52:06 +09001870 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001871 }
1872
satokd87c2592010-09-29 11:52:06 +09001873 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
1874 .getEnabledInputMethodsAndSubtypeListLocked();
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001875
satokd87c2592010-09-29 11:52:06 +09001876 if (enabled) {
1877 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
1878 if (pair.first.equals(id)) {
1879 // We are enabling this input method, but it is already enabled.
1880 // Nothing to do. The previous state was enabled.
1881 return true;
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001882 }
1883 }
satokd87c2592010-09-29 11:52:06 +09001884 mSettings.appendAndPutEnabledInputMethodLocked(id, false);
1885 // Previous state was disabled.
1886 return false;
1887 } else {
1888 StringBuilder builder = new StringBuilder();
1889 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
1890 builder, enabledInputMethodsList, id)) {
1891 // Disabled input method is currently selected, switch to another one.
1892 String selId = Settings.Secure.getString(mContext.getContentResolver(),
1893 Settings.Secure.DEFAULT_INPUT_METHOD);
satok03eb319a2010-11-11 18:17:42 +09001894 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
1895 Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
1896 resetSelectedInputMethodAndSubtypeLocked("");
satokd87c2592010-09-29 11:52:06 +09001897 }
1898 // Previous state was enabled.
1899 return true;
1900 } else {
1901 // We are disabling the input method but it is already disabled.
1902 // Nothing to do. The previous state was disabled.
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001903 return false;
1904 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001905 }
Dianne Hackborn21f1bd12010-02-19 17:02:21 -08001906 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001907
satok57ffc002011-01-25 00:11:47 +09001908 private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
1909 if (subtype == null) return true;
1910 String[] extraValues = subtype.getExtraValue().split(",");
1911 final int N = extraValues.length;
1912 for (int i = 0; i < N; ++i) {
1913 if (SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME.equals(extraValues[i])) {
1914 return false;
1915 }
1916 }
1917 return true;
1918 }
1919
satok723a27e2010-11-11 14:58:11 +09001920 private void saveCurrentInputMethodAndSubtypeToHistory() {
1921 String subtypeId = NOT_A_SUBTYPE_ID_STR;
1922 if (mCurrentSubtype != null) {
1923 subtypeId = String.valueOf(mCurrentSubtype.hashCode());
1924 }
satok57ffc002011-01-25 00:11:47 +09001925 if (canAddToLastInputMethod(mCurrentSubtype)) {
1926 mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
1927 }
satokab751aa2010-09-14 19:17:36 +09001928 }
1929
satok723a27e2010-11-11 14:58:11 +09001930 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
1931 boolean setSubtypeOnly) {
1932 // Update the history of InputMethod and Subtype
1933 saveCurrentInputMethodAndSubtypeToHistory();
1934
1935 // Set Subtype here
1936 if (imi == null || subtypeId < 0) {
1937 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
Tadashi G. Takaoka0ba75bb2010-11-09 12:19:32 -08001938 mCurrentSubtype = null;
satok723a27e2010-11-11 14:58:11 +09001939 } else {
Ken Wakasa586f0512011-01-20 22:31:01 +09001940 if (subtypeId < imi.getSubtypeCount()) {
1941 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
1942 mSettings.putSelectedSubtype(subtype.hashCode());
1943 mCurrentSubtype = subtype;
satok723a27e2010-11-11 14:58:11 +09001944 } else {
1945 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1946 mCurrentSubtype = null;
1947 }
satokab751aa2010-09-14 19:17:36 +09001948 }
satok723a27e2010-11-11 14:58:11 +09001949
1950 if (!setSubtypeOnly) {
1951 // Set InputMethod here
1952 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
1953 }
1954 }
1955
1956 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
1957 InputMethodInfo imi = mMethodMap.get(newDefaultIme);
1958 int lastSubtypeId = NOT_A_SUBTYPE_ID;
1959 // newDefaultIme is empty when there is no candidate for the selected IME.
1960 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
1961 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
1962 if (subtypeHashCode != null) {
1963 try {
1964 lastSubtypeId = getSubtypeIdFromHashCode(
1965 imi, Integer.valueOf(subtypeHashCode));
1966 } catch (NumberFormatException e) {
1967 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
1968 }
1969 }
1970 }
1971 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
satokab751aa2010-09-14 19:17:36 +09001972 }
1973
1974 private int getSelectedInputMethodSubtypeId(String id) {
1975 InputMethodInfo imi = mMethodMap.get(id);
1976 if (imi == null) {
1977 return NOT_A_SUBTYPE_ID;
1978 }
satokab751aa2010-09-14 19:17:36 +09001979 int subtypeId;
1980 try {
1981 subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
1982 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
1983 } catch (SettingNotFoundException e) {
1984 return NOT_A_SUBTYPE_ID;
1985 }
satok723a27e2010-11-11 14:58:11 +09001986 return getSubtypeIdFromHashCode(imi, subtypeId);
1987 }
1988
1989 private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
satok28203512010-11-24 11:06:49 +09001990 if (imi != null) {
Ken Wakasa586f0512011-01-20 22:31:01 +09001991 final int subtypeCount = imi.getSubtypeCount();
1992 for (int i = 0; i < subtypeCount; ++i) {
1993 InputMethodSubtype ims = imi.getSubtypeAt(i);
satok28203512010-11-24 11:06:49 +09001994 if (subtypeHashCode == ims.hashCode()) {
1995 return i;
1996 }
satokab751aa2010-09-14 19:17:36 +09001997 }
1998 }
1999 return NOT_A_SUBTYPE_ID;
2000 }
2001
satokdf31ae62011-01-15 06:19:44 +09002002 private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
2003 Resources res, List<InputMethodSubtype> subtypes) {
2004 final String systemLocale = res.getConfiguration().locale.toString();
satok3da92232011-01-11 22:46:30 +09002005 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2006 HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2007 new HashMap<String, InputMethodSubtype>();
satok16331c82010-12-20 23:48:46 +09002008 final int N = subtypes.size();
2009 boolean containsKeyboardSubtype = false;
2010 for (int i = 0; i < N; ++i) {
2011 InputMethodSubtype subtype = subtypes.get(i);
satok3da92232011-01-11 22:46:30 +09002012 final String locale = subtype.getLocale();
2013 final String mode = subtype.getMode();
2014 // When system locale starts with subtype's locale, that subtype will be applicable
2015 // for system locale
2016 // For instance, it's clearly applicable for cases like system locale = en_US and
2017 // subtype = en, but it is not necessarily considered applicable for cases like system
2018 // locale = en and subtype = en_US.
2019 // We just call systemLocale.startsWith(locale) in this function because there is no
2020 // need to find applicable subtypes aggressively unlike
2021 // findLastResortApplicableSubtypeLocked.
2022 if (systemLocale.startsWith(locale)) {
2023 InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2024 // If more applicable subtypes are contained, skip.
2025 if (applicableSubtype != null
2026 && systemLocale.equals(applicableSubtype.getLocale())) continue;
2027 applicableModeAndSubtypesMap.put(mode, subtype);
satok16331c82010-12-20 23:48:46 +09002028 if (!containsKeyboardSubtype
2029 && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
2030 containsKeyboardSubtype = true;
2031 }
2032 }
2033 }
satok3da92232011-01-11 22:46:30 +09002034 ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2035 applicableModeAndSubtypesMap.values());
satok16331c82010-12-20 23:48:46 +09002036 if (!containsKeyboardSubtype) {
2037 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
satokdf31ae62011-01-15 06:19:44 +09002038 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
satok16331c82010-12-20 23:48:46 +09002039 if (lastResortKeyboardSubtype != null) {
2040 applicableSubtypes.add(lastResortKeyboardSubtype);
2041 }
2042 }
2043 return applicableSubtypes;
2044 }
2045
satok4e4569d2010-11-19 18:45:53 +09002046 /**
2047 * If there are no selected subtypes, tries finding the most applicable one according to the
2048 * given locale.
2049 * @param subtypes this function will search the most applicable subtype in subtypes
2050 * @param mode subtypes will be filtered by mode
2051 * @param locale subtypes will be filtered by locale
satok7599a7fb2010-12-22 13:45:23 +09002052 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
2053 * it will return the first subtype matched with mode
satok4e4569d2010-11-19 18:45:53 +09002054 * @return the most applicable subtypeId
2055 */
satokdf31ae62011-01-15 06:19:44 +09002056 private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
2057 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
satok7599a7fb2010-12-22 13:45:23 +09002058 boolean canIgnoreLocaleAsLastResort) {
satok8fbb1e82010-11-02 23:15:58 +09002059 if (subtypes == null || subtypes.size() == 0) {
satokcd7cd292010-11-20 15:46:23 +09002060 return null;
satok8fbb1e82010-11-02 23:15:58 +09002061 }
satok4e4569d2010-11-19 18:45:53 +09002062 if (TextUtils.isEmpty(locale)) {
satokdf31ae62011-01-15 06:19:44 +09002063 locale = res.getConfiguration().locale.toString();
satok4e4569d2010-11-19 18:45:53 +09002064 }
satok8fbb1e82010-11-02 23:15:58 +09002065 final String language = locale.substring(0, 2);
2066 boolean partialMatchFound = false;
satokcd7cd292010-11-20 15:46:23 +09002067 InputMethodSubtype applicableSubtype = null;
satok7599a7fb2010-12-22 13:45:23 +09002068 InputMethodSubtype firstMatchedModeSubtype = null;
satok16331c82010-12-20 23:48:46 +09002069 final int N = subtypes.size();
2070 for (int i = 0; i < N; ++i) {
satokcd7cd292010-11-20 15:46:23 +09002071 InputMethodSubtype subtype = subtypes.get(i);
2072 final String subtypeLocale = subtype.getLocale();
satokd8713432011-01-18 00:55:13 +09002073 // An applicable subtype should match "mode". If mode is null, mode will be ignored,
2074 // and all subtypes with all modes can be candidates.
2075 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
satok7599a7fb2010-12-22 13:45:23 +09002076 if (firstMatchedModeSubtype == null) {
2077 firstMatchedModeSubtype = subtype;
2078 }
satok9ef02832010-11-04 21:17:48 +09002079 if (locale.equals(subtypeLocale)) {
2080 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
satokcd7cd292010-11-20 15:46:23 +09002081 applicableSubtype = subtype;
satok9ef02832010-11-04 21:17:48 +09002082 break;
2083 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
2084 // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
satokcd7cd292010-11-20 15:46:23 +09002085 applicableSubtype = subtype;
satok9ef02832010-11-04 21:17:48 +09002086 partialMatchFound = true;
2087 }
satok8fbb1e82010-11-02 23:15:58 +09002088 }
2089 }
2090
satok7599a7fb2010-12-22 13:45:23 +09002091 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
2092 return firstMatchedModeSubtype;
satok16331c82010-12-20 23:48:46 +09002093 }
2094
satok8fbb1e82010-11-02 23:15:58 +09002095 // The first subtype applicable to the system locale will be defined as the most applicable
2096 // subtype.
2097 if (DEBUG) {
satok16331c82010-12-20 23:48:46 +09002098 if (applicableSubtype != null) {
2099 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
2100 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
2101 }
satok8fbb1e82010-11-02 23:15:58 +09002102 }
satokcd7cd292010-11-20 15:46:23 +09002103 return applicableSubtype;
satok8fbb1e82010-11-02 23:15:58 +09002104 }
2105
satok4e4569d2010-11-19 18:45:53 +09002106 // If there are no selected shortcuts, tries finding the most applicable ones.
2107 private Pair<InputMethodInfo, InputMethodSubtype>
2108 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2109 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2110 InputMethodInfo mostApplicableIMI = null;
satokcd7cd292010-11-20 15:46:23 +09002111 InputMethodSubtype mostApplicableSubtype = null;
satok4e4569d2010-11-19 18:45:53 +09002112 boolean foundInSystemIME = false;
2113
2114 // Search applicable subtype for each InputMethodInfo
2115 for (InputMethodInfo imi: imis) {
satok7599a7fb2010-12-22 13:45:23 +09002116 final String imiId = imi.getId();
2117 if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2118 continue;
2119 }
satokcd7cd292010-11-20 15:46:23 +09002120 InputMethodSubtype subtype = null;
satokdf31ae62011-01-15 06:19:44 +09002121 final List<InputMethodSubtype> enabledSubtypes =
2122 getEnabledInputMethodSubtypeList(imi, true);
2123 // 1. Search by the current subtype's locale from enabledSubtypes.
satok4e4569d2010-11-19 18:45:53 +09002124 if (mCurrentSubtype != null) {
satokcd7cd292010-11-20 15:46:23 +09002125 subtype = findLastResortApplicableSubtypeLocked(
satokdf31ae62011-01-15 06:19:44 +09002126 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
satok4e4569d2010-11-19 18:45:53 +09002127 }
satokdf31ae62011-01-15 06:19:44 +09002128 // 2. Search by the system locale from enabledSubtypes.
2129 // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
satokcd7cd292010-11-20 15:46:23 +09002130 if (subtype == null) {
2131 subtype = findLastResortApplicableSubtypeLocked(
satokdf31ae62011-01-15 06:19:44 +09002132 mRes, enabledSubtypes, mode, null, true);
satok4e4569d2010-11-19 18:45:53 +09002133 }
satok7599a7fb2010-12-22 13:45:23 +09002134 // 4. Search by the current subtype's locale from all subtypes.
2135 if (subtype == null && mCurrentSubtype != null) {
2136 subtype = findLastResortApplicableSubtypeLocked(
Ken Wakasa586f0512011-01-20 22:31:01 +09002137 mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
satok7599a7fb2010-12-22 13:45:23 +09002138 }
2139 // 5. Search by the system locale from all subtypes.
2140 // 6. Search the first enabled subtype matched with mode from all subtypes.
satokcd7cd292010-11-20 15:46:23 +09002141 if (subtype == null) {
satok7599a7fb2010-12-22 13:45:23 +09002142 subtype = findLastResortApplicableSubtypeLocked(
Ken Wakasa586f0512011-01-20 22:31:01 +09002143 mRes, getSubtypes(imi), mode, null, true);
satok4e4569d2010-11-19 18:45:53 +09002144 }
satokcd7cd292010-11-20 15:46:23 +09002145 if (subtype != null) {
satok7599a7fb2010-12-22 13:45:23 +09002146 if (imiId.equals(mCurMethodId)) {
satok4e4569d2010-11-19 18:45:53 +09002147 // The current input method is the most applicable IME.
2148 mostApplicableIMI = imi;
satokcd7cd292010-11-20 15:46:23 +09002149 mostApplicableSubtype = subtype;
satok4e4569d2010-11-19 18:45:53 +09002150 break;
satok7599a7fb2010-12-22 13:45:23 +09002151 } else if (!foundInSystemIME) {
satok4e4569d2010-11-19 18:45:53 +09002152 // The system input method is 2nd applicable IME.
2153 mostApplicableIMI = imi;
satokcd7cd292010-11-20 15:46:23 +09002154 mostApplicableSubtype = subtype;
satok7599a7fb2010-12-22 13:45:23 +09002155 if ((imi.getServiceInfo().applicationInfo.flags
2156 & ApplicationInfo.FLAG_SYSTEM) != 0) {
2157 foundInSystemIME = true;
2158 }
satok4e4569d2010-11-19 18:45:53 +09002159 }
2160 }
2161 }
2162 if (DEBUG) {
satokcd7cd292010-11-20 15:46:23 +09002163 if (mostApplicableIMI != null) {
2164 Slog.w(TAG, "Most applicable shortcut input method was:"
2165 + mostApplicableIMI.getId());
2166 if (mostApplicableSubtype != null) {
2167 Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2168 + "," + mostApplicableSubtype.getMode() + ","
2169 + mostApplicableSubtype.getLocale());
2170 }
2171 }
satok4e4569d2010-11-19 18:45:53 +09002172 }
satokcd7cd292010-11-20 15:46:23 +09002173 if (mostApplicableIMI != null) {
satok4e4569d2010-11-19 18:45:53 +09002174 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
satokcd7cd292010-11-20 15:46:23 +09002175 mostApplicableSubtype);
satok4e4569d2010-11-19 18:45:53 +09002176 } else {
2177 return null;
2178 }
2179 }
2180
satokab751aa2010-09-14 19:17:36 +09002181 /**
2182 * @return Return the current subtype of this input method.
2183 */
2184 public InputMethodSubtype getCurrentInputMethodSubtype() {
satok4e4569d2010-11-19 18:45:53 +09002185 boolean subtypeIsSelected = false;
2186 try {
2187 subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2188 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2189 } catch (SettingNotFoundException e) {
2190 }
satok3ef8b292010-11-23 06:06:29 +09002191 synchronized (mMethodMap) {
satok3ef8b292010-11-23 06:06:29 +09002192 if (!subtypeIsSelected || mCurrentSubtype == null) {
satok4e4569d2010-11-19 18:45:53 +09002193 String lastInputMethodId = Settings.Secure.getString(
2194 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
satok3ef8b292010-11-23 06:06:29 +09002195 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2196 if (subtypeId == NOT_A_SUBTYPE_ID) {
satok4e4569d2010-11-19 18:45:53 +09002197 InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2198 if (imi != null) {
2199 // If there are no selected subtypes, the framework will try to find
satokd8713432011-01-18 00:55:13 +09002200 // the most applicable subtype from explicitly or implicitly enabled
2201 // subtypes.
2202 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
2203 getEnabledInputMethodSubtypeList(imi, true);
2204 // If there is only one explicitly or implicitly enabled subtype,
2205 // just returns it.
2206 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
2207 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
2208 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
2209 mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2210 mRes, explicitlyOrImplicitlyEnabledSubtypes,
2211 SUBTYPE_MODE_KEYBOARD, null, true);
2212 if (mCurrentSubtype == null) {
2213 mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2214 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
2215 true);
2216 }
2217 }
satok4e4569d2010-11-19 18:45:53 +09002218 }
satokcd7cd292010-11-20 15:46:23 +09002219 } else {
satok3ef8b292010-11-23 06:06:29 +09002220 mCurrentSubtype =
Ken Wakasa586f0512011-01-20 22:31:01 +09002221 getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
satok3ef8b292010-11-23 06:06:29 +09002222 }
satok8fbb1e82010-11-02 23:15:58 +09002223 }
satok3ef8b292010-11-23 06:06:29 +09002224 return mCurrentSubtype;
satok8fbb1e82010-11-02 23:15:58 +09002225 }
satokab751aa2010-09-14 19:17:36 +09002226 }
2227
satokf3db1af2010-11-23 13:34:33 +09002228 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2229 InputMethodSubtype subtype) {
2230 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2231 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2232 } else {
2233 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2234 subtypes.add(subtype);
2235 mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2236 }
2237 }
2238
satok4e4569d2010-11-19 18:45:53 +09002239 // TODO: We should change the return type from List to List<Parcelable>
2240 public List getShortcutInputMethodsAndSubtypes() {
2241 synchronized (mMethodMap) {
satok3da92232011-01-11 22:46:30 +09002242 ArrayList<Object> ret = new ArrayList<Object>();
satokf3db1af2010-11-23 13:34:33 +09002243 if (mShortcutInputMethodsAndSubtypes.size() == 0) {
satok4e4569d2010-11-19 18:45:53 +09002244 // If there are no selected shortcut subtypes, the framework will try to find
2245 // the most applicable subtype from all subtypes whose mode is
2246 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
satokf3db1af2010-11-23 13:34:33 +09002247 Pair<InputMethodInfo, InputMethodSubtype> info =
2248 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2249 SUBTYPE_MODE_VOICE);
satok7599a7fb2010-12-22 13:45:23 +09002250 if (info != null) {
satok3da92232011-01-11 22:46:30 +09002251 ret.add(info.first);
2252 ret.add(info.second);
satok7599a7fb2010-12-22 13:45:23 +09002253 }
satok3da92232011-01-11 22:46:30 +09002254 return ret;
satokf3db1af2010-11-23 13:34:33 +09002255 }
satokf3db1af2010-11-23 13:34:33 +09002256 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2257 ret.add(imi);
2258 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2259 ret.add(subtype);
satok4e4569d2010-11-19 18:45:53 +09002260 }
2261 }
satokf3db1af2010-11-23 13:34:33 +09002262 return ret;
satok4e4569d2010-11-19 18:45:53 +09002263 }
2264 }
2265
satokb66d2872010-11-10 01:04:04 +09002266 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2267 synchronized (mMethodMap) {
2268 if (subtype != null && mCurMethodId != null) {
2269 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2270 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2271 if (subtypeId != NOT_A_SUBTYPE_ID) {
2272 setInputMethodLocked(mCurMethodId, subtypeId);
2273 return true;
2274 }
2275 }
2276 return false;
2277 }
2278 }
2279
satokd87c2592010-09-29 11:52:06 +09002280 /**
2281 * Utility class for putting and getting settings for InputMethod
2282 * TODO: Move all putters and getters of settings to this class.
2283 */
2284 private static class InputMethodSettings {
2285 // The string for enabled input method is saved as follows:
2286 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2287 private static final char INPUT_METHOD_SEPARATER = ':';
2288 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
satok723a27e2010-11-11 14:58:11 +09002289 private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
satokd87c2592010-09-29 11:52:06 +09002290 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2291
satok723a27e2010-11-11 14:58:11 +09002292 private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
satokd87c2592010-09-29 11:52:06 +09002293 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2294
satokdf31ae62011-01-15 06:19:44 +09002295 private final Resources mRes;
satokd87c2592010-09-29 11:52:06 +09002296 private final ContentResolver mResolver;
2297 private final HashMap<String, InputMethodInfo> mMethodMap;
2298 private final ArrayList<InputMethodInfo> mMethodList;
2299
2300 private String mEnabledInputMethodsStrCache;
2301
2302 private static void buildEnabledInputMethodsSettingString(
2303 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2304 String id = pair.first;
2305 ArrayList<String> subtypes = pair.second;
2306 builder.append(id);
satok57c767c2010-11-01 22:34:08 +09002307 // Inputmethod and subtypes are saved in the settings as follows:
2308 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2309 for (String subtypeId: subtypes) {
2310 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
satokd87c2592010-09-29 11:52:06 +09002311 }
2312 }
2313
2314 public InputMethodSettings(
satokdf31ae62011-01-15 06:19:44 +09002315 Resources res, ContentResolver resolver,
2316 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
2317 mRes = res;
satokd87c2592010-09-29 11:52:06 +09002318 mResolver = resolver;
2319 mMethodMap = methodMap;
2320 mMethodList = methodList;
2321 }
2322
2323 public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2324 return createEnabledInputMethodListLocked(
2325 getEnabledInputMethodsAndSubtypeListLocked());
2326 }
2327
satok7f35c8c2010-10-07 21:13:11 +09002328 public List<Pair<InputMethodInfo, ArrayList<String>>>
satok67ddf9c2010-11-17 09:45:54 +09002329 getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2330 return createEnabledInputMethodAndSubtypeHashCodeListLocked(
satok7f35c8c2010-10-07 21:13:11 +09002331 getEnabledInputMethodsAndSubtypeListLocked());
2332 }
2333
satok67ddf9c2010-11-17 09:45:54 +09002334 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2335 InputMethodInfo imi) {
2336 List<Pair<String, ArrayList<String>>> imsList =
2337 getEnabledInputMethodsAndSubtypeListLocked();
2338 ArrayList<InputMethodSubtype> enabledSubtypes =
2339 new ArrayList<InputMethodSubtype>();
satok884ef9a2010-11-18 10:39:46 +09002340 if (imi != null) {
2341 for (Pair<String, ArrayList<String>> imsPair : imsList) {
2342 InputMethodInfo info = mMethodMap.get(imsPair.first);
2343 if (info != null && info.getId().equals(imi.getId())) {
Ken Wakasa586f0512011-01-20 22:31:01 +09002344 final int subtypeCount = info.getSubtypeCount();
2345 for (int i = 0; i < subtypeCount; ++i) {
2346 InputMethodSubtype ims = info.getSubtypeAt(i);
satok884ef9a2010-11-18 10:39:46 +09002347 for (String s: imsPair.second) {
2348 if (String.valueOf(ims.hashCode()).equals(s)) {
2349 enabledSubtypes.add(ims);
2350 }
satok67ddf9c2010-11-17 09:45:54 +09002351 }
2352 }
satok884ef9a2010-11-18 10:39:46 +09002353 break;
satok67ddf9c2010-11-17 09:45:54 +09002354 }
satok67ddf9c2010-11-17 09:45:54 +09002355 }
2356 }
2357 return enabledSubtypes;
2358 }
2359
satokd87c2592010-09-29 11:52:06 +09002360 // At the initial boot, the settings for input methods are not set,
2361 // so we need to enable IME in that case.
2362 public void enableAllIMEsIfThereIsNoEnabledIME() {
2363 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2364 StringBuilder sb = new StringBuilder();
2365 final int N = mMethodList.size();
2366 for (int i = 0; i < N; i++) {
2367 InputMethodInfo imi = mMethodList.get(i);
2368 Slog.i(TAG, "Adding: " + imi.getId());
2369 if (i > 0) sb.append(':');
2370 sb.append(imi.getId());
2371 }
2372 putEnabledInputMethodsStr(sb.toString());
2373 }
2374 }
2375
satokbb4aa062011-01-19 21:40:27 +09002376 private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
satokd87c2592010-09-29 11:52:06 +09002377 ArrayList<Pair<String, ArrayList<String>>> imsList
2378 = new ArrayList<Pair<String, ArrayList<String>>>();
2379 final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2380 if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2381 return imsList;
2382 }
satok723a27e2010-11-11 14:58:11 +09002383 mInputMethodSplitter.setString(enabledInputMethodsStr);
2384 while (mInputMethodSplitter.hasNext()) {
2385 String nextImsStr = mInputMethodSplitter.next();
2386 mSubtypeSplitter.setString(nextImsStr);
2387 if (mSubtypeSplitter.hasNext()) {
satokd87c2592010-09-29 11:52:06 +09002388 ArrayList<String> subtypeHashes = new ArrayList<String>();
2389 // The first element is ime id.
satok723a27e2010-11-11 14:58:11 +09002390 String imeId = mSubtypeSplitter.next();
2391 while (mSubtypeSplitter.hasNext()) {
2392 subtypeHashes.add(mSubtypeSplitter.next());
satokd87c2592010-09-29 11:52:06 +09002393 }
2394 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2395 }
2396 }
2397 return imsList;
2398 }
2399
2400 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2401 if (reloadInputMethodStr) {
2402 getEnabledInputMethodsStr();
2403 }
2404 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2405 // Add in the newly enabled input method.
2406 putEnabledInputMethodsStr(id);
2407 } else {
2408 putEnabledInputMethodsStr(
2409 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2410 }
2411 }
2412
2413 /**
2414 * Build and put a string of EnabledInputMethods with removing specified Id.
2415 * @return the specified id was removed or not.
2416 */
2417 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2418 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2419 boolean isRemoved = false;
2420 boolean needsAppendSeparator = false;
2421 for (Pair<String, ArrayList<String>> ims: imsList) {
2422 String curId = ims.first;
2423 if (curId.equals(id)) {
2424 // We are disabling this input method, and it is
2425 // currently enabled. Skip it to remove from the
2426 // new list.
2427 isRemoved = true;
2428 } else {
2429 if (needsAppendSeparator) {
2430 builder.append(INPUT_METHOD_SEPARATER);
2431 } else {
2432 needsAppendSeparator = true;
2433 }
2434 buildEnabledInputMethodsSettingString(builder, ims);
2435 }
2436 }
2437 if (isRemoved) {
2438 // Update the setting with the new list of input methods.
2439 putEnabledInputMethodsStr(builder.toString());
2440 }
2441 return isRemoved;
2442 }
2443
2444 private List<InputMethodInfo> createEnabledInputMethodListLocked(
2445 List<Pair<String, ArrayList<String>>> imsList) {
2446 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2447 for (Pair<String, ArrayList<String>> ims: imsList) {
2448 InputMethodInfo info = mMethodMap.get(ims.first);
2449 if (info != null) {
2450 res.add(info);
2451 }
2452 }
2453 return res;
2454 }
2455
satok7f35c8c2010-10-07 21:13:11 +09002456 private List<Pair<InputMethodInfo, ArrayList<String>>>
satok67ddf9c2010-11-17 09:45:54 +09002457 createEnabledInputMethodAndSubtypeHashCodeListLocked(
satok7f35c8c2010-10-07 21:13:11 +09002458 List<Pair<String, ArrayList<String>>> imsList) {
2459 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2460 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2461 for (Pair<String, ArrayList<String>> ims : imsList) {
2462 InputMethodInfo info = mMethodMap.get(ims.first);
2463 if (info != null) {
2464 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2465 }
2466 }
2467 return res;
2468 }
2469
satokd87c2592010-09-29 11:52:06 +09002470 private void putEnabledInputMethodsStr(String str) {
2471 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2472 mEnabledInputMethodsStrCache = str;
2473 }
2474
2475 private String getEnabledInputMethodsStr() {
2476 mEnabledInputMethodsStrCache = Settings.Secure.getString(
2477 mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
satok723a27e2010-11-11 14:58:11 +09002478 if (DEBUG) {
2479 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2480 }
satokd87c2592010-09-29 11:52:06 +09002481 return mEnabledInputMethodsStrCache;
2482 }
satok723a27e2010-11-11 14:58:11 +09002483
2484 private void saveSubtypeHistory(
2485 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2486 StringBuilder builder = new StringBuilder();
2487 boolean isImeAdded = false;
2488 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2489 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2490 newSubtypeId);
2491 isImeAdded = true;
2492 }
2493 for (Pair<String, String> ime: savedImes) {
2494 String imeId = ime.first;
2495 String subtypeId = ime.second;
2496 if (TextUtils.isEmpty(subtypeId)) {
2497 subtypeId = NOT_A_SUBTYPE_ID_STR;
2498 }
2499 if (isImeAdded) {
2500 builder.append(INPUT_METHOD_SEPARATER);
2501 } else {
2502 isImeAdded = true;
2503 }
2504 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2505 subtypeId);
2506 }
2507 // Remove the last INPUT_METHOD_SEPARATER
2508 putSubtypeHistoryStr(builder.toString());
2509 }
2510
2511 public void addSubtypeToHistory(String imeId, String subtypeId) {
2512 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2513 for (Pair<String, String> ime: subtypeHistory) {
2514 if (ime.first.equals(imeId)) {
2515 if (DEBUG) {
satokbb4aa062011-01-19 21:40:27 +09002516 Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
satok723a27e2010-11-11 14:58:11 +09002517 + ime.second);
2518 }
2519 // We should break here
2520 subtypeHistory.remove(ime);
2521 break;
2522 }
2523 }
satokbb4aa062011-01-19 21:40:27 +09002524 if (DEBUG) {
2525 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
2526 }
satok723a27e2010-11-11 14:58:11 +09002527 saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2528 }
2529
2530 private void putSubtypeHistoryStr(String str) {
2531 if (DEBUG) {
2532 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2533 }
2534 Settings.Secure.putString(
2535 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2536 }
2537
2538 public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2539 // Gets the first one from the history
2540 return getLastSubtypeForInputMethodLockedInternal(null);
2541 }
2542
2543 public String getLastSubtypeForInputMethodLocked(String imeId) {
2544 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2545 if (ime != null) {
2546 return ime.second;
2547 } else {
2548 return null;
2549 }
2550 }
2551
2552 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
2553 List<Pair<String, ArrayList<String>>> enabledImes =
2554 getEnabledInputMethodsAndSubtypeListLocked();
2555 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2556 for (Pair<String, String> imeAndSubtype: subtypeHistory) {
2557 final String imeInTheHistory = imeAndSubtype.first;
2558 // If imeId is empty, returns the first IME and subtype in the history
2559 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
2560 final String subtypeInTheHistory = imeAndSubtype.second;
satokdf31ae62011-01-15 06:19:44 +09002561 final String subtypeHashCode =
2562 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
2563 enabledImes, imeInTheHistory, subtypeInTheHistory);
satok723a27e2010-11-11 14:58:11 +09002564 if (!TextUtils.isEmpty(subtypeHashCode)) {
2565 if (DEBUG) {
satokbb4aa062011-01-19 21:40:27 +09002566 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
satok723a27e2010-11-11 14:58:11 +09002567 }
2568 return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
2569 }
2570 }
2571 }
2572 if (DEBUG) {
2573 Slog.d(TAG, "No enabled IME found in the history");
2574 }
2575 return null;
2576 }
2577
satokdf31ae62011-01-15 06:19:44 +09002578 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
satok723a27e2010-11-11 14:58:11 +09002579 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
2580 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
2581 if (enabledIme.first.equals(imeId)) {
satokf6cafb62011-01-17 16:29:02 +09002582 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
2583 if (explicitlyEnabledSubtypes.size() == 0) {
2584 // If there are no explicitly enabled subtypes, applicable subtypes are
2585 // enabled implicitly.
satokdf31ae62011-01-15 06:19:44 +09002586 InputMethodInfo ime = mMethodMap.get(imeId);
2587 // If IME is enabled and no subtypes are enabled, applicable subtypes
2588 // are enabled implicitly, so needs to treat them to be enabled.
Ken Wakasa586f0512011-01-20 22:31:01 +09002589 if (ime != null && ime.getSubtypeCount() > 0) {
satokdf31ae62011-01-15 06:19:44 +09002590 List<InputMethodSubtype> implicitlySelectedSubtypes =
Ken Wakasa586f0512011-01-20 22:31:01 +09002591 getApplicableSubtypesLocked(mRes, getSubtypes(ime));
satokdf31ae62011-01-15 06:19:44 +09002592 if (implicitlySelectedSubtypes != null) {
2593 final int N = implicitlySelectedSubtypes.size();
2594 for (int i = 0; i < N; ++i) {
2595 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
2596 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
2597 return subtypeHashCode;
2598 }
2599 }
2600 }
2601 }
2602 } else {
satokf6cafb62011-01-17 16:29:02 +09002603 for (String s: explicitlyEnabledSubtypes) {
satokdf31ae62011-01-15 06:19:44 +09002604 if (s.equals(subtypeHashCode)) {
2605 // If both imeId and subtypeId are enabled, return subtypeId.
2606 return s;
2607 }
satok723a27e2010-11-11 14:58:11 +09002608 }
2609 }
2610 // If imeId was enabled but subtypeId was disabled.
2611 return NOT_A_SUBTYPE_ID_STR;
2612 }
2613 }
2614 // If both imeId and subtypeId are disabled, return null
2615 return null;
2616 }
2617
2618 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
2619 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
2620 final String subtypeHistoryStr = getSubtypeHistoryStr();
2621 if (TextUtils.isEmpty(subtypeHistoryStr)) {
2622 return imsList;
2623 }
2624 mInputMethodSplitter.setString(subtypeHistoryStr);
2625 while (mInputMethodSplitter.hasNext()) {
2626 String nextImsStr = mInputMethodSplitter.next();
2627 mSubtypeSplitter.setString(nextImsStr);
2628 if (mSubtypeSplitter.hasNext()) {
2629 String subtypeId = NOT_A_SUBTYPE_ID_STR;
2630 // The first element is ime id.
2631 String imeId = mSubtypeSplitter.next();
2632 while (mSubtypeSplitter.hasNext()) {
2633 subtypeId = mSubtypeSplitter.next();
2634 break;
2635 }
2636 imsList.add(new Pair<String, String>(imeId, subtypeId));
2637 }
2638 }
2639 return imsList;
2640 }
2641
2642 private String getSubtypeHistoryStr() {
2643 if (DEBUG) {
2644 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
2645 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
2646 }
2647 return Settings.Secure.getString(
2648 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
2649 }
2650
2651 public void putSelectedInputMethod(String imeId) {
2652 Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
2653 }
2654
2655 public void putSelectedSubtype(int subtypeId) {
2656 Settings.Secure.putInt(
2657 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
2658 }
satokd87c2592010-09-29 11:52:06 +09002659 }
2660
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002661 // ----------------------------------------------------------------------
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002663 @Override
2664 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2665 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2666 != PackageManager.PERMISSION_GRANTED) {
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002667
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002668 pw.println("Permission Denial: can't dump InputMethodManager from from pid="
2669 + Binder.getCallingPid()
2670 + ", uid=" + Binder.getCallingUid());
2671 return;
2672 }
2673
2674 IInputMethod method;
2675 ClientState client;
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002676
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002677 final Printer p = new PrintWriterPrinter(pw);
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002678
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002679 synchronized (mMethodMap) {
2680 p.println("Current Input Method Manager state:");
2681 int N = mMethodList.size();
2682 p.println(" Input Methods:");
2683 for (int i=0; i<N; i++) {
2684 InputMethodInfo info = mMethodList.get(i);
2685 p.println(" InputMethod #" + i + ":");
2686 info.dump(p, " ");
2687 }
2688 p.println(" Clients:");
2689 for (ClientState ci : mClients.values()) {
2690 p.println(" Client " + ci + ":");
2691 p.println(" client=" + ci.client);
2692 p.println(" inputContext=" + ci.inputContext);
2693 p.println(" sessionRequested=" + ci.sessionRequested);
2694 p.println(" curSession=" + ci.curSession);
2695 }
The Android Open Source Project10592532009-03-18 17:39:46 -07002696 p.println(" mCurMethodId=" + mCurMethodId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002697 client = mCurClient;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07002698 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
2699 p.println(" mCurFocusedWindow=" + mCurFocusedWindow);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002700 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
2701 + " mBoundToMethod=" + mBoundToMethod);
2702 p.println(" mCurToken=" + mCurToken);
2703 p.println(" mCurIntent=" + mCurIntent);
2704 method = mCurMethod;
2705 p.println(" mCurMethod=" + mCurMethod);
2706 p.println(" mEnabledSession=" + mEnabledSession);
2707 p.println(" mShowRequested=" + mShowRequested
2708 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
2709 + " mShowForced=" + mShowForced
2710 + " mInputShown=" + mInputShown);
Dianne Hackborncc278702009-09-02 23:07:23 -07002711 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002712 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002713
Jeff Brownb88102f2010-09-08 11:49:43 -07002714 p.println(" ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002715 if (client != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002716 pw.flush();
2717 try {
2718 client.client.asBinder().dump(fd, args);
2719 } catch (RemoteException e) {
2720 p.println("Input method client dead: " + e);
2721 }
Jeff Brownb88102f2010-09-08 11:49:43 -07002722 } else {
2723 p.println("No input method client.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002724 }
Doug Zongkerab5c49c2009-12-04 10:31:43 -08002725
Jeff Brownb88102f2010-09-08 11:49:43 -07002726 p.println(" ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002727 if (method != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002728 pw.flush();
2729 try {
2730 method.asBinder().dump(fd, args);
2731 } catch (RemoteException e) {
2732 p.println("Input method service dead: " + e);
2733 }
Jeff Brownb88102f2010-09-08 11:49:43 -07002734 } else {
2735 p.println("No input method service.");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002736 }
2737 }
2738}