blob: 139aaa3aaa3f0dba23c5d01715fdcec3edd70ed1 [file] [log] [blame]
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001/*
2 * Copyright (C) 2006-2008 The Android Open Source Project
3 *
4 * 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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, 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
19import com.android.internal.os.HandlerCaller;
20import com.android.internal.view.IInputContext;
21import com.android.internal.view.IInputMethod;
22import com.android.internal.view.IInputMethodCallback;
23import com.android.internal.view.IInputMethodClient;
24import com.android.internal.view.IInputMethodManager;
25import com.android.internal.view.IInputMethodSession;
26import com.android.internal.view.InputBindResult;
27
28import com.android.server.status.IconData;
29import com.android.server.status.StatusBarService;
30
31import org.xmlpull.v1.XmlPullParserException;
32
33import android.app.AlertDialog;
34import android.content.ComponentName;
35import android.content.ContentResolver;
36import android.content.Context;
37import android.content.DialogInterface;
38import android.content.IntentFilter;
39import android.content.DialogInterface.OnCancelListener;
40import android.content.Intent;
41import android.content.ServiceConnection;
42import android.content.pm.PackageManager;
43import android.content.pm.ResolveInfo;
44import android.content.pm.ServiceInfo;
45import android.content.res.Resources;
46import android.content.res.TypedArray;
47import android.database.ContentObserver;
48import android.net.Uri;
49import android.os.Binder;
50import android.os.Handler;
51import android.os.IBinder;
52import android.os.IInterface;
53import android.os.Message;
54import android.os.Parcel;
55import android.os.RemoteException;
56import android.os.ServiceManager;
57import android.provider.Settings;
58import android.text.TextUtils;
59import android.util.Log;
60import android.util.PrintWriterPrinter;
61import android.util.Printer;
62import android.view.IWindowManager;
63import android.view.WindowManager;
64import android.view.inputmethod.DefaultInputMethod;
65import android.view.inputmethod.InputBinding;
66import android.view.inputmethod.InputMethod;
67import android.view.inputmethod.InputMethodInfo;
68import android.view.inputmethod.InputMethodManager;
69import android.view.inputmethod.EditorInfo;
70
71import java.io.FileDescriptor;
72import java.io.IOException;
73import java.io.PrintWriter;
74import java.util.ArrayList;
75import java.util.HashMap;
76import java.util.List;
77
78/**
79 * This class provides a system service that manages input methods.
80 */
81public class InputMethodManagerService extends IInputMethodManager.Stub
82 implements ServiceConnection, Handler.Callback {
83 static final boolean DEBUG = false;
84 static final String TAG = "InputManagerService";
85
86 static final int MSG_SHOW_IM_PICKER = 1;
87
88 static final int MSG_UNBIND_INPUT = 1000;
89 static final int MSG_BIND_INPUT = 1010;
90 static final int MSG_SHOW_SOFT_INPUT = 1020;
91 static final int MSG_HIDE_SOFT_INPUT = 1030;
92 static final int MSG_ATTACH_TOKEN = 1040;
93 static final int MSG_CREATE_SESSION = 1050;
94
95 static final int MSG_START_INPUT = 2000;
96 static final int MSG_RESTART_INPUT = 2010;
97
98 static final int MSG_UNBIND_METHOD = 3000;
99 static final int MSG_BIND_METHOD = 3010;
100
101 final Context mContext;
102 final Handler mHandler;
103 final SettingsObserver mSettingsObserver;
104 final StatusBarService mStatusBar;
105 final IBinder mInputMethodIcon;
106 final IconData mInputMethodData;
107 final IWindowManager mIWindowManager;
108 final HandlerCaller mCaller;
109
110 final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
111
112 // All known input methods. mMethodMap also serves as the global
113 // lock for this class.
114 final ArrayList<InputMethodInfo> mMethodList
115 = new ArrayList<InputMethodInfo>();
116 final HashMap<String, InputMethodInfo> mMethodMap
117 = new HashMap<String, InputMethodInfo>();
118
119 final TextUtils.SimpleStringSplitter mStringColonSplitter
120 = new TextUtils.SimpleStringSplitter(':');
121
122 class SessionState {
123 final ClientState client;
124 final IInputMethod method;
125 final IInputMethodSession session;
126
127 @Override
128 public String toString() {
129 return "SessionState{uid " + client.uid + " pid " + client.pid
130 + " method " + Integer.toHexString(
131 System.identityHashCode(method))
132 + " session " + Integer.toHexString(
133 System.identityHashCode(session))
134 + "}";
135 }
136
137 SessionState(ClientState _client, IInputMethod _method,
138 IInputMethodSession _session) {
139 client = _client;
140 method = _method;
141 session = _session;
142 }
143 }
144
145 class ClientState {
146 final IInputMethodClient client;
147 final IInputContext inputContext;
148 final int uid;
149 final int pid;
150 final InputBinding binding;
151
152 boolean sessionRequested;
153 SessionState curSession;
154
155 @Override
156 public String toString() {
157 return "ClientState{" + Integer.toHexString(
158 System.identityHashCode(this)) + " uid " + uid
159 + " pid " + pid + "}";
160 }
161
162 ClientState(IInputMethodClient _client, IInputContext _inputContext,
163 int _uid, int _pid) {
164 client = _client;
165 inputContext = _inputContext;
166 uid = _uid;
167 pid = _pid;
168 binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
169 }
170 }
171
172 final HashMap<IBinder, ClientState> mClients
173 = new HashMap<IBinder, ClientState>();
174
175 /**
176 * Id of the currently selected input method.
177 */
178 String mCurMethodId;
179
180 /**
181 * The current binding sequence number, incremented every time there is
182 * a new bind performed.
183 */
184 int mCurSeq;
185
186 /**
187 * The client that is currently bound to an input method.
188 */
189 ClientState mCurClient;
190
191 /**
192 * The attributes last provided by the current client.
193 */
194 EditorInfo mCurAttribute;
195
196 /**
197 * The input method ID of the input method service that we are currently
198 * connected to or in the process of connecting to.
199 */
200 String mCurId;
201
202 /**
203 * Set to true if our ServiceConnection is currently actively bound to
204 * a service (whether or not we have gotten its IBinder back yet).
205 */
206 boolean mHaveConnection;
207
208 /**
209 * Set if the client has asked for the input method to be shown.
210 */
211 boolean mShowRequested;
212
213 /**
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800214 * Set if we were explicitly told to show the input method.
215 */
216 boolean mShowExplicitlyRequested;
217
218 /**
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800219 * Set if we last told the input method to show itself.
220 */
221 boolean mInputShown;
222
223 /**
224 * The Intent used to connect to the current input method.
225 */
226 Intent mCurIntent;
227
228 /**
229 * The token we have made for the currently active input method, to
230 * identify it in the future.
231 */
232 IBinder mCurToken;
233
234 /**
235 * If non-null, this is the input method service we are currently connected
236 * to.
237 */
238 IInputMethod mCurMethod;
239
240 /**
241 * Have we called mCurMethod.bindInput()?
242 */
243 boolean mBoundToMethod;
244
245 /**
246 * Currently enabled session. Only touched by service thread, not
247 * protected by a lock.
248 */
249 SessionState mEnabledSession;
250
251 /**
252 * True if the screen is on. The value is true initially.
253 */
254 boolean mScreenOn = true;
255
256 AlertDialog.Builder mDialogBuilder;
257 AlertDialog mSwitchingDialog;
258 InputMethodInfo[] mIms;
259 CharSequence[] mItems;
260
261 class SettingsObserver extends ContentObserver {
262 SettingsObserver(Handler handler) {
263 super(handler);
264 ContentResolver resolver = mContext.getContentResolver();
265 resolver.registerContentObserver(Settings.Secure.getUriFor(
266 Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
267 }
268
269 @Override public void onChange(boolean selfChange) {
270 synchronized (mMethodMap) {
271 updateFromSettingsLocked();
272 }
273 }
274 }
275
276 class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
277 @Override
278 public void onReceive(Context context, Intent intent) {
279 if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
280 mScreenOn = true;
281 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
282 mScreenOn = false;
283 } else {
284 Log.e(TAG, "Unexpected intent " + intent);
285 }
286
287 // Inform the current client of the change in active status
288 try {
289 if (mCurClient != null && mCurClient.client != null) {
290 mCurClient.client.setActive(mScreenOn);
291 }
292 } catch (RemoteException e) {
293 Log.e(TAG, "Got RemoteException sending 'screen on/off' notification", e);
294 }
295 }
296 }
297
298 class PackageReceiver extends android.content.BroadcastReceiver {
299 @Override
300 public void onReceive(Context context, Intent intent) {
301 synchronized (mMethodMap) {
302 buildInputMethodListLocked(mMethodList, mMethodMap);
303
304 InputMethodInfo curIm = null;
305 String curInputMethodId = Settings.Secure.getString(context
306 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
307 final int N = mMethodList.size();
308 if (curInputMethodId != null) {
309 for (int i=0; i<N; i++) {
310 if (mMethodList.get(i).getId().equals(curInputMethodId)) {
311 curIm = mMethodList.get(i);
312 }
313 }
314 }
315
316 boolean changed = false;
317
318 Uri uri = intent.getData();
319 String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
320 if (curIm != null && curIm.getPackageName().equals(pkg)) {
321 ServiceInfo si = null;
322 try {
323 si = mContext.getPackageManager().getServiceInfo(
324 curIm.getComponent(), 0);
325 } catch (PackageManager.NameNotFoundException ex) {
326 }
327 if (si == null) {
328 // Uh oh, current input method is no longer around!
329 // Pick another one...
330 Log.i(TAG, "Current input method removed: " + curInputMethodId);
331 List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
332 if (enabled != null && enabled.size() > 0) {
333 changed = true;
334 curIm = enabled.get(0);
335 curInputMethodId = curIm.getId();
336 Log.i(TAG, "Switching to: " + curInputMethodId);
337 Settings.Secure.putString(mContext.getContentResolver(),
338 Settings.Secure.DEFAULT_INPUT_METHOD,
339 curInputMethodId);
340 } else if (curIm != null) {
341 changed = true;
342 curIm = null;
343 curInputMethodId = "";
344 Log.i(TAG, "Unsetting current input method");
345 Settings.Secure.putString(mContext.getContentResolver(),
346 Settings.Secure.DEFAULT_INPUT_METHOD,
347 curInputMethodId);
348 }
349 }
350
351 } else if (curIm == null) {
352 // We currently don't have a default input method... is
353 // one now available?
354 List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
355 if (enabled != null && enabled.size() > 0) {
356 changed = true;
357 curIm = enabled.get(0);
358 curInputMethodId = curIm.getId();
359 Log.i(TAG, "New default input method: " + curInputMethodId);
360 Settings.Secure.putString(mContext.getContentResolver(),
361 Settings.Secure.DEFAULT_INPUT_METHOD,
362 curInputMethodId);
363 }
364 }
365
366 if (changed) {
367 updateFromSettingsLocked();
368 }
369 }
370 }
371 }
372
373 class MethodCallback extends IInputMethodCallback.Stub {
374 final IInputMethod mMethod;
375
376 MethodCallback(IInputMethod method) {
377 mMethod = method;
378 }
379
380 public void finishedEvent(int seq, boolean handled) throws RemoteException {
381 }
382
383 public void sessionCreated(IInputMethodSession session) throws RemoteException {
384 onSessionCreated(mMethod, session);
385 }
386 }
387
388 public InputMethodManagerService(Context context, StatusBarService statusBar) {
389 mContext = context;
390 mHandler = new Handler(this);
391 mIWindowManager = IWindowManager.Stub.asInterface(
392 ServiceManager.getService(Context.WINDOW_SERVICE));
393 mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
394 public void executeMessage(Message msg) {
395 handleMessage(msg);
396 }
397 });
398
399 IntentFilter packageFilt = new IntentFilter();
400 packageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
401 packageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
402 packageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
403 packageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
404 packageFilt.addDataScheme("package");
405 mContext.registerReceiver(new PackageReceiver(), packageFilt);
406
407 IntentFilter screenOnOffFilt = new IntentFilter();
408 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
409 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
410 mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
411
412 buildInputMethodListLocked(mMethodList, mMethodMap);
413
414 final String enabledStr = Settings.Secure.getString(
415 mContext.getContentResolver(),
416 Settings.Secure.ENABLED_INPUT_METHODS);
417 Log.i(TAG, "Enabled input methods: " + enabledStr);
418 if (enabledStr == null) {
419 Log.i(TAG, "Enabled input methods has not been set, enabling all");
420 InputMethodInfo defIm = null;
421 StringBuilder sb = new StringBuilder(256);
422 final int N = mMethodList.size();
423 for (int i=0; i<N; i++) {
424 InputMethodInfo imi = mMethodList.get(i);
425 Log.i(TAG, "Adding: " + imi.getId());
426 if (i > 0) sb.append(':');
427 sb.append(imi.getId());
428 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
429 try {
430 Resources res = mContext.createPackageContext(
431 imi.getPackageName(), 0).getResources();
432 if (res.getBoolean(imi.getIsDefaultResourceId())) {
433 defIm = imi;
434 Log.i(TAG, "Selected default: " + imi.getId());
435 }
436 } catch (PackageManager.NameNotFoundException ex) {
437 } catch (Resources.NotFoundException ex) {
438 }
439 }
440 }
441 if (defIm == null && N > 0) {
442 defIm = mMethodList.get(0);
443 Log.i(TAG, "No default found, using " + defIm.getId());
444 }
445 Settings.Secure.putString(mContext.getContentResolver(),
446 Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
447 if (defIm != null) {
448 Settings.Secure.putString(mContext.getContentResolver(),
449 Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
450 }
451 }
452
453 mStatusBar = statusBar;
454 mInputMethodData = IconData.makeIcon("ime", null, 0, 0, 0);
455 mInputMethodIcon = statusBar.addIcon(mInputMethodData, null);
456 statusBar.setIconVisibility(mInputMethodIcon, false);
457
458 mSettingsObserver = new SettingsObserver(mHandler);
459 updateFromSettingsLocked();
460 }
461
462 @Override
463 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
464 throws RemoteException {
465 try {
466 return super.onTransact(code, data, reply, flags);
467 } catch (RuntimeException e) {
468 // The input method manager only throws security exceptions, so let's
469 // log all others.
470 if (!(e instanceof SecurityException)) {
471 Log.e(TAG, "Input Method Manager Crash", e);
472 }
473 throw e;
474 }
475 }
476
477 public void systemReady() {
478
479 }
480
481 public List<InputMethodInfo> getInputMethodList() {
482 synchronized (mMethodMap) {
483 return new ArrayList<InputMethodInfo>(mMethodList);
484 }
485 }
486
487 public List<InputMethodInfo> getEnabledInputMethodList() {
488 synchronized (mMethodMap) {
489 return getEnabledInputMethodListLocked();
490 }
491 }
492
493 List<InputMethodInfo> getEnabledInputMethodListLocked() {
494 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
495
496 final String enabledStr = Settings.Secure.getString(
497 mContext.getContentResolver(),
498 Settings.Secure.ENABLED_INPUT_METHODS);
499 if (enabledStr != null) {
500 final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
501 splitter.setString(enabledStr);
502
503 while (splitter.hasNext()) {
504 InputMethodInfo info = mMethodMap.get(splitter.next());
505 if (info != null) {
506 res.add(info);
507 }
508 }
509 }
510
511 return res;
512 }
513
514 public void addClient(IInputMethodClient client,
515 IInputContext inputContext, int uid, int pid) {
516 synchronized (mMethodMap) {
517 mClients.put(client.asBinder(), new ClientState(client,
518 inputContext, uid, pid));
519 }
520 }
521
522 public void removeClient(IInputMethodClient client) {
523 synchronized (mMethodMap) {
524 mClients.remove(client.asBinder());
525 }
526 }
527
528 void executeOrSendMessage(IInterface target, Message msg) {
529 if (target.asBinder() instanceof Binder) {
530 mCaller.sendMessage(msg);
531 } else {
532 handleMessage(msg);
533 msg.recycle();
534 }
535 }
536
537 void unbindCurrentInputLocked() {
538 if (mCurClient != null) {
539 if (DEBUG) Log.v(TAG, "unbindCurrentInputLocked: client = "
540 + mCurClient.client.asBinder());
541 if (mBoundToMethod) {
542 mBoundToMethod = false;
543 if (mCurMethod != null) {
544 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
545 MSG_UNBIND_INPUT, mCurMethod));
546 }
547 }
548 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
549 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
550 mCurClient.sessionRequested = false;
551
552 // Call setActive(false) on the old client
553 try {
554 mCurClient.client.setActive(false);
555 } catch (RemoteException e) {
556 Log.e(TAG, "Got RemoteException sending setActive(false) notification", e);
557 }
558 mCurClient = null;
559 }
560 }
561
562 InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
563 if (!mBoundToMethod) {
564 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
565 MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
566 mBoundToMethod = true;
567 }
568 final SessionState session = mCurClient.curSession;
569 if (initial) {
570 executeOrSendMessage(session.method, mCaller.obtainMessageOO(
571 MSG_START_INPUT, session, mCurAttribute));
572 } else {
573 executeOrSendMessage(session.method, mCaller.obtainMessageOO(
574 MSG_RESTART_INPUT, session, mCurAttribute));
575 }
576 if (mShowRequested) {
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800577 if (DEBUG) Log.v(TAG, "Attach new input asks to show input");
578 showCurrentInputLocked(mShowExplicitlyRequested
579 ? 0 : InputMethodManager.SHOW_IMPLICIT);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800580 }
581 return needResult
582 ? new InputBindResult(session.session, mCurId, mCurSeq)
583 : null;
584 }
585
586 InputBindResult startInputLocked(IInputMethodClient client,
587 EditorInfo attribute, boolean initial, boolean needResult) {
588 // If no method is currently selected, do nothing.
589 if (mCurMethodId == null) {
590 return mNoBinding;
591 }
592
593 ClientState cs = mClients.get(client.asBinder());
594 if (cs == null) {
595 throw new IllegalArgumentException("unknown client "
596 + client.asBinder());
597 }
598
599 try {
600 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
601 // Check with the window manager to make sure this client actually
602 // has a window with focus. If not, reject. This is thread safe
603 // because if the focus changes some time before or after, the
604 // next client receiving focus that has any interest in input will
605 // be calling through here after that change happens.
606 Log.w(TAG, "Starting input on non-focused client " + cs.client
607 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
608 return null;
609 }
610 } catch (RemoteException e) {
611 }
612
613 if (mCurClient != cs) {
614 // If the client is changing, we need to switch over to the new
615 // one.
616 unbindCurrentInputLocked();
617 if (DEBUG) Log.v(TAG, "switching to client: client = "
618 + cs.client.asBinder());
619
620 // If the screen is on, inform the new client it is active
621 if (mScreenOn) {
622 try {
623 cs.client.setActive(mScreenOn);
624 } catch (RemoteException e) {
625 Log.e(TAG, "Got RemoteException sending setActive notification", e);
626 }
627 }
628 }
629
630 // Bump up the sequence for this client and attach it.
631 mCurSeq++;
632 if (mCurSeq <= 0) mCurSeq = 1;
633 mCurClient = cs;
634 mCurAttribute = attribute;
635
636 // Check if the input method is changing.
637 if (mCurId != null && mCurId.equals(mCurMethodId)) {
638 if (cs.curSession != null) {
639 // Fast case: if we are already connected to the input method,
640 // then just return it.
641 return attachNewInputLocked(initial, needResult);
642 }
643 if (mHaveConnection) {
644 if (mCurMethod != null && !cs.sessionRequested) {
645 cs.sessionRequested = true;
646 if (DEBUG) Log.v(TAG, "Creating new session for client " + cs);
647 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
648 MSG_CREATE_SESSION, mCurMethod,
649 new MethodCallback(mCurMethod)));
650 }
651 return new InputBindResult(null, mCurId, mCurSeq);
652 }
653 }
654
655 InputMethodInfo info = mMethodMap.get(mCurMethodId);
656 if (info == null) {
657 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
658 }
659
660 if (mCurToken != null) {
661 try {
662 mIWindowManager.removeWindowToken(mCurToken);
663 } catch (RemoteException e) {
664 }
665 mCurToken = null;
666 }
667
668 if (mHaveConnection) {
669 mContext.unbindService(this);
670 mHaveConnection = false;
671 }
672
673 clearCurMethod();
674
675 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
676 mCurIntent.setComponent(info.getComponent());
677 if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
678 mHaveConnection = true;
679 mCurId = info.getId();
680 mCurToken = new Binder();
681 try {
682 mIWindowManager.addWindowToken(mCurToken,
683 WindowManager.LayoutParams.TYPE_INPUT_METHOD);
684 } catch (RemoteException e) {
685 }
686 return new InputBindResult(null, mCurId, mCurSeq);
687 } else {
688 mCurIntent = null;
689 Log.e(TAG, "Failure connecting to input method service: "
690 + mCurIntent);
691 }
692 return null;
693 }
694
695 public InputBindResult startInput(IInputMethodClient client,
696 EditorInfo attribute, boolean initial, boolean needResult) {
697 synchronized (mMethodMap) {
698 final long ident = Binder.clearCallingIdentity();
699 try {
700 return startInputLocked(client, attribute, initial, needResult);
701 } finally {
702 Binder.restoreCallingIdentity(ident);
703 }
704 }
705 }
706
707 public void finishInput(IInputMethodClient client) {
708 }
709
710 public void onServiceConnected(ComponentName name, IBinder service) {
711 synchronized (mMethodMap) {
712 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
713 mCurMethod = IInputMethod.Stub.asInterface(service);
714 if (mCurClient != null) {
715 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
716 MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
717 if (mCurClient != null) {
718 if (DEBUG) Log.v(TAG, "Creating first session while with client "
719 + mCurClient);
720 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
721 MSG_CREATE_SESSION, mCurMethod,
722 new MethodCallback(mCurMethod)));
723 }
724 }
725 }
726 }
727 }
728
729 void onSessionCreated(IInputMethod method, IInputMethodSession session) {
730 synchronized (mMethodMap) {
731 if (mCurMethod == method) {
732 if (mCurClient != null) {
733 mCurClient.curSession = new SessionState(mCurClient,
734 method, session);
735 mCurClient.sessionRequested = false;
736 InputBindResult res = attachNewInputLocked(true, true);
737 if (res.method != null) {
738 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
739 MSG_BIND_METHOD, mCurClient.client, res));
740 }
741 }
742 }
743 }
744 }
745
746 void clearCurMethod() {
747 if (mCurMethod != null) {
748 for (ClientState cs : mClients.values()) {
749 cs.sessionRequested = false;
750 cs.curSession = null;
751 }
752 mCurMethod = null;
753 }
754 }
755
756 public void onServiceDisconnected(ComponentName name) {
757 synchronized (mMethodMap) {
758 if (DEBUG) Log.v(TAG, "Service disconnected: " + name
759 + " mCurIntent=" + mCurIntent);
760 if (mCurMethod != null && mCurIntent != null
761 && name.equals(mCurIntent.getComponent())) {
762 clearCurMethod();
763 mShowRequested = mInputShown;
764 mInputShown = false;
765 if (mCurClient != null) {
766 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
767 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
768 }
769 }
770 }
771 }
772
773 public void updateStatusIcon(int iconId, String iconPackage) {
774 if (iconId == 0) {
775 Log.d(TAG, "hide the small icon for the input method");
776 mStatusBar.setIconVisibility(mInputMethodIcon, false);
777 } else {
778 Log.d(TAG, "show a small icon for the input method");
779
780 if (iconPackage != null
781 && iconPackage
782 .equals(InputMethodManager.BUILDIN_INPUTMETHOD_PACKAGE)) {
783 iconPackage = null;
784 }
785
786 mInputMethodData.iconId = iconId;
787 mInputMethodData.iconPackage = iconPackage;
788 mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null);
789 mStatusBar.setIconVisibility(mInputMethodIcon, true);
790 }
791 }
792
793 void updateFromSettingsLocked() {
794 String id = Settings.Secure.getString(mContext.getContentResolver(),
795 Settings.Secure.DEFAULT_INPUT_METHOD);
796 if (id != null) {
797 try {
798 setInputMethodLocked(id);
799 } catch (IllegalArgumentException e) {
800 Log.w(TAG, "Unknown input method from prefs: " + id, e);
801 }
802 }
803 }
804
805 void setInputMethodLocked(String id) {
806 InputMethodInfo info = mMethodMap.get(id);
807 if (info == null) {
808 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
809 }
810
811 if (id.equals(mCurMethodId)) {
812 return;
813 }
814
815 final long ident = Binder.clearCallingIdentity();
816 try {
817 mCurMethodId = id;
818 Settings.Secure.putString(mContext.getContentResolver(),
819 Settings.Secure.DEFAULT_INPUT_METHOD, id);
820
821 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
822 intent.putExtra("input_method_id", id);
823 mContext.sendBroadcast(intent);
824 unbindCurrentInputLocked();
825 } finally {
826 Binder.restoreCallingIdentity(ident);
827 }
828 }
829
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800830 public void showSoftInput(IInputMethodClient client, int flags) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800831 synchronized (mMethodMap) {
832 if (mCurClient == null || client == null
833 || mCurClient.client.asBinder() != client.asBinder()) {
834 try {
835 // We need to check if this is the current client with
836 // focus in the window manager, to allow this call to
837 // be made before input is started in it.
838 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
839 Log.w(TAG, "Ignoring showSoftInput of: " + client);
840 return;
841 }
842 } catch (RemoteException e) {
843 }
844 }
845
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800846 if (DEBUG) Log.v(TAG, "Client requesting input be shown");
847 showCurrentInputLocked(flags);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800848 }
849 }
850
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800851 void showCurrentInputLocked(int flags) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800852 mShowRequested = true;
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800853 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
854 mShowExplicitlyRequested = true;
855 }
856 if (mCurMethod != null) {
857 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO(
858 MSG_SHOW_SOFT_INPUT, mShowExplicitlyRequested ? 1 : 0,
859 mCurMethod));
860 mInputShown = true;
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800861 }
862 }
863
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800864 public void hideSoftInput(IInputMethodClient client, int flags) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800865 synchronized (mMethodMap) {
866 if (mCurClient == null || client == null
867 || mCurClient.client.asBinder() != client.asBinder()) {
868 try {
869 // We need to check if this is the current client with
870 // focus in the window manager, to allow this call to
871 // be made before input is started in it.
872 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
873 Log.w(TAG, "Ignoring hideSoftInput of: " + client);
874 return;
875 }
876 } catch (RemoteException e) {
877 }
878 }
879
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800880 if (DEBUG) Log.v(TAG, "Client requesting input be hidden");
881 hideCurrentInputLocked(flags);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800882 }
883 }
884
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800885 void hideCurrentInputLocked(int flags) {
886 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
887 && mShowExplicitlyRequested) {
888 if (DEBUG) Log.v(TAG,
889 "Not hiding: explicit show not cancelled by non-explicit hide");
890 return;
891 }
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800892 if (mInputShown && mCurMethod != null) {
893 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
894 MSG_HIDE_SOFT_INPUT, mCurMethod));
895 }
896 mInputShown = false;
897 mShowRequested = false;
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800898 mShowExplicitlyRequested = false;
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800899 }
900
901 public void windowGainedFocus(IInputMethodClient client,
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800902 boolean viewHasFocus, boolean isTextEditor, int softInputMode,
903 boolean first, int windowFlags) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800904 synchronized (mMethodMap) {
905 if (DEBUG) Log.v(TAG, "windowGainedFocus: " + client.asBinder()
906 + " viewHasFocus=" + viewHasFocus
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800907 + " isTextEditor=" + isTextEditor
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800908 + " softInputMode=#" + Integer.toHexString(softInputMode)
909 + " first=" + first + " flags=#"
910 + Integer.toHexString(windowFlags));
911
912 if (mCurClient == null || client == null
913 || mCurClient.client.asBinder() != client.asBinder()) {
914 try {
915 // We need to check if this is the current client with
916 // focus in the window manager, to allow this call to
917 // be made before input is started in it.
918 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
919 Log.w(TAG, "Ignoring focus gain of: " + client);
920 return;
921 }
922 } catch (RemoteException e) {
923 }
924 }
925
926 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
927 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800928 if (!isTextEditor || (softInputMode &
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800929 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
930 != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800931 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800932 // There is no focus view, and this window will
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800933 // be behind any soft input window, so hide the
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800934 // soft input window if it is shown.
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800935 if (DEBUG) Log.v(TAG, "Unspecified window will hide input");
936 hideCurrentInputLocked(0);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800937 }
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800938 } else if (isTextEditor && (softInputMode &
939 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
940 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
941 && (softInputMode &
942 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
943 // There is a focus view, and we are navigating forward
944 // into the window, so show the input window for the user.
945 if (DEBUG) Log.v(TAG, "Unspecified window will show input");
946 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800947 }
948 break;
949 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
950 // Do nothing.
951 break;
952 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800953 if (DEBUG) Log.v(TAG, "Window asks to hide input");
954 hideCurrentInputLocked(0);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800955 break;
956 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800957 if ((softInputMode &
958 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
959 if (DEBUG) Log.v(TAG, "Window asks to show input going forward");
960 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800961 }
962 break;
The Android Open Source Project9266c5582009-01-15 16:12:10 -0800963 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
964 if (DEBUG) Log.v(TAG, "Window asks to always show input");
965 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT);
966 break;
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800967 }
968 }
969 }
970
971 public void showInputMethodPickerFromClient(IInputMethodClient client) {
972 synchronized (mMethodMap) {
973 if (mCurClient == null || client == null
974 || mCurClient.client.asBinder() != client.asBinder()) {
975 Log.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client);
976 }
977
978 mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
979 }
980 }
981
982 public void setInputMethod(IBinder token, String id) {
983 synchronized (mMethodMap) {
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800984 if (mCurToken == null) {
985 if (mContext.checkCallingOrSelfPermission(
986 android.Manifest.permission.WRITE_SECURE_SETTINGS)
987 != PackageManager.PERMISSION_GRANTED) {
988 throw new SecurityException(
989 "Using null token requires permission "
990 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
991 }
992 } else if (mCurToken != token) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800993 Log.w(TAG, "Ignoring setInputMethod of token: " + token);
994 }
995
996 setInputMethodLocked(id);
997 }
998 }
999
The Android Open Source Project9266c5582009-01-15 16:12:10 -08001000 public void hideMySoftInput(IBinder token, int flags) {
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001001 synchronized (mMethodMap) {
1002 if (mCurToken == null || mCurToken != token) {
1003 Log.w(TAG, "Ignoring hideInputMethod of token: " + token);
1004 }
1005
The Android Open Source Project9266c5582009-01-15 16:12:10 -08001006 hideCurrentInputLocked(flags);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001007 }
1008 }
1009
1010 void setEnabledSessionInMainThread(SessionState session) {
1011 if (mEnabledSession != session) {
1012 if (mEnabledSession != null) {
1013 try {
1014 if (DEBUG) Log.v(TAG, "Disabling: " + mEnabledSession);
1015 mEnabledSession.method.setSessionEnabled(
1016 mEnabledSession.session, false);
1017 } catch (RemoteException e) {
1018 }
1019 }
1020 mEnabledSession = session;
1021 try {
1022 if (DEBUG) Log.v(TAG, "Enabling: " + mEnabledSession);
1023 session.method.setSessionEnabled(
1024 session.session, true);
1025 } catch (RemoteException e) {
1026 }
1027 }
1028 }
1029
1030 public boolean handleMessage(Message msg) {
1031 HandlerCaller.SomeArgs args;
1032 switch (msg.what) {
1033 case MSG_SHOW_IM_PICKER:
1034 showInputMethodMenu();
1035 return true;
1036
1037 // ---------------------------------------------------------
1038
1039 case MSG_UNBIND_INPUT:
1040 try {
1041 ((IInputMethod)msg.obj).unbindInput();
1042 } catch (RemoteException e) {
1043 // There is nothing interesting about the method dying.
1044 }
1045 return true;
1046 case MSG_BIND_INPUT:
1047 args = (HandlerCaller.SomeArgs)msg.obj;
1048 try {
1049 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1050 } catch (RemoteException e) {
1051 }
1052 return true;
1053 case MSG_SHOW_SOFT_INPUT:
1054 try {
The Android Open Source Project9266c5582009-01-15 16:12:10 -08001055 ((IInputMethod)msg.obj).showSoftInput(msg.arg1 != 0);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001056 } catch (RemoteException e) {
1057 }
1058 return true;
1059 case MSG_HIDE_SOFT_INPUT:
1060 try {
1061 ((IInputMethod)msg.obj).hideSoftInput();
1062 } catch (RemoteException e) {
1063 }
1064 return true;
1065 case MSG_ATTACH_TOKEN:
1066 args = (HandlerCaller.SomeArgs)msg.obj;
1067 try {
1068 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1069 } catch (RemoteException e) {
1070 }
1071 return true;
1072 case MSG_CREATE_SESSION:
1073 args = (HandlerCaller.SomeArgs)msg.obj;
1074 try {
1075 ((IInputMethod)args.arg1).createSession(
1076 (IInputMethodCallback)args.arg2);
1077 } catch (RemoteException e) {
1078 }
1079 return true;
1080
1081 // ---------------------------------------------------------
1082
1083 case MSG_START_INPUT:
1084 args = (HandlerCaller.SomeArgs)msg.obj;
1085 try {
1086 SessionState session = (SessionState)args.arg1;
1087 setEnabledSessionInMainThread(session);
1088 session.method.startInput((EditorInfo)args.arg2);
1089 } catch (RemoteException e) {
1090 }
1091 return true;
1092 case MSG_RESTART_INPUT:
1093 args = (HandlerCaller.SomeArgs)msg.obj;
1094 try {
1095 SessionState session = (SessionState)args.arg1;
1096 setEnabledSessionInMainThread(session);
1097 session.method.restartInput((EditorInfo)args.arg2);
1098 } catch (RemoteException e) {
1099 }
1100 return true;
1101
1102 // ---------------------------------------------------------
1103
1104 case MSG_UNBIND_METHOD:
1105 try {
1106 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1107 } catch (RemoteException e) {
1108 // There is nothing interesting about the last client dying.
1109 }
1110 return true;
1111 case MSG_BIND_METHOD:
1112 args = (HandlerCaller.SomeArgs)msg.obj;
1113 try {
1114 ((IInputMethodClient)args.arg1).onBindMethod(
1115 (InputBindResult)args.arg2);
1116 } catch (RemoteException e) {
1117 Log.w(TAG, "Client died receiving input method " + args.arg2);
1118 }
1119 return true;
1120 }
1121 return false;
1122 }
1123
1124 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1125 HashMap<String, InputMethodInfo> map) {
1126 list.clear();
1127 map.clear();
1128
1129 PackageManager pm = mContext.getPackageManager();
1130
1131 Object[][] buildin = {{
1132 DefaultInputMethod.class.getName(),
1133 DefaultInputMethod.getMetaInfo()}};
1134
1135 List<ResolveInfo> services = pm.queryIntentServices(
1136 new Intent(InputMethod.SERVICE_INTERFACE),
1137 PackageManager.GET_META_DATA);
1138
1139 for (int i = 0; i < services.size(); ++i) {
1140 ResolveInfo ri = services.get(i);
1141 ServiceInfo si = ri.serviceInfo;
1142 ComponentName compName = new ComponentName(si.packageName, si.name);
1143 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1144 si.permission)) {
1145 Log.w(TAG, "Skipping input method " + compName
1146 + ": it does not require the permission "
1147 + android.Manifest.permission.BIND_INPUT_METHOD);
1148 continue;
1149 }
1150
1151 if (DEBUG) Log.d(TAG, "Checking " + compName);
1152
1153 /* Built-in input methods are not currently supported... this will
1154 * need to be reworked to bring them back (all input methods must
1155 * now be published in a manifest).
1156 */
1157 /*
1158 if (compName.getPackageName().equals(
1159 InputMethodManager.BUILDIN_INPUTMETHOD_PACKAGE)) {
1160 // System build-in input methods;
1161 String inputMethodName = null;
1162 int kbType = 0;
1163 String skbName = null;
1164
1165 for (int j = 0; j < buildin.length; ++j) {
1166 Object[] obj = buildin[j];
1167 if (compName.getClassName().equals(obj[0])) {
1168 InputMethodMetaInfo imp = (InputMethodMetaInfo) obj[1];
1169 inputMethodName = imp.inputMethodName;
1170 }
1171 }
1172
1173 InputMethodMetaInfo p = new InputMethodMetaInfo(compName,
1174 inputMethodName, "");
1175
1176 list.add(p);
1177
1178 if (DEBUG) {
1179 Log.d(TAG, "Found a build-in input method " + p);
1180 }
1181
1182 continue;
1183 }
1184 */
1185
1186 try {
1187 InputMethodInfo p = new InputMethodInfo(mContext, ri);
1188 list.add(p);
1189 map.put(p.getId(), p);
1190
1191 if (DEBUG) {
1192 Log.d(TAG, "Found a third-party input method " + p);
1193 }
1194
1195 } catch (XmlPullParserException e) {
1196 Log.w(TAG, "Unable to load input method " + compName, e);
1197 } catch (IOException e) {
1198 Log.w(TAG, "Unable to load input method " + compName, e);
1199 }
1200 }
1201 }
1202
1203 // ----------------------------------------------------------------------
1204
1205 public void showInputMethodMenu() {
1206 if (DEBUG) Log.v(TAG, "Show switching menu");
1207
1208 hideInputMethodMenu();
1209
1210 final Context context = mContext;
1211
1212 final PackageManager pm = context.getPackageManager();
1213
1214 String lastInputMethodId = Settings.Secure.getString(context
1215 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1216 if (DEBUG) Log.v(TAG, "Current IME: " + lastInputMethodId);
1217
1218 final List<InputMethodInfo> immis = getEnabledInputMethodList();
1219
1220 int N = (immis == null ? 0 : immis.size());
1221
1222 mItems = new CharSequence[N];
1223 mIms = new InputMethodInfo[N];
1224
1225 for (int i = 0; i < N; ++i) {
1226 InputMethodInfo property = immis.get(i);
1227 mItems[i] = property.loadLabel(pm);
1228 mIms[i] = property;
1229 }
1230
1231 int checkedItem = 0;
1232 for (int i = 0; i < N; ++i) {
1233 if (mIms[i].getId().equals(lastInputMethodId)) {
1234 checkedItem = i;
1235 break;
1236 }
1237 }
1238
1239 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1240 public void onClick(DialogInterface dialog, int which) {
1241 hideInputMethodMenu();
1242 }
1243 };
1244
1245 TypedArray a = context.obtainStyledAttributes(null,
1246 com.android.internal.R.styleable.DialogPreference,
1247 com.android.internal.R.attr.alertDialogStyle, 0);
1248 mDialogBuilder = new AlertDialog.Builder(context)
1249 .setTitle(com.android.internal.R.string.select_input_method)
1250 .setOnCancelListener(new OnCancelListener() {
1251 public void onCancel(DialogInterface dialog) {
1252 hideInputMethodMenu();
1253 }
1254 })
1255 .setIcon(a.getDrawable(
1256 com.android.internal.R.styleable.DialogPreference_dialogTitle));
1257 a.recycle();
1258
1259 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1260 new AlertDialog.OnClickListener() {
1261 public void onClick(DialogInterface dialog, int which) {
1262 synchronized (mMethodMap) {
1263 InputMethodInfo im = mIms[which];
1264 hideInputMethodMenu();
1265 setInputMethodLocked(im.getId());
1266 }
1267 }
1268 });
1269
1270 synchronized (mMethodMap) {
1271 mSwitchingDialog = mDialogBuilder.create();
1272 mSwitchingDialog.getWindow().setType(
1273 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1274 mSwitchingDialog.show();
1275 }
1276 }
1277
1278 void hideInputMethodMenu() {
1279 if (DEBUG) Log.v(TAG, "Hide switching menu");
1280
1281 synchronized (mMethodMap) {
1282 if (mSwitchingDialog != null) {
1283 mSwitchingDialog.dismiss();
1284 mSwitchingDialog = null;
1285 }
1286
1287 mDialogBuilder = null;
1288 mItems = null;
1289 mIms = null;
1290 }
1291 }
1292
The Android Open Source Projectb7986892009-01-09 17:51:23 -08001293 // ----------------------------------------------------------------------
1294
1295 public boolean setInputMethodEnabled(String id, boolean enabled) {
1296 synchronized (mMethodMap) {
1297 if (mContext.checkCallingOrSelfPermission(
1298 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1299 != PackageManager.PERMISSION_GRANTED) {
1300 throw new SecurityException(
1301 "Requires permission "
1302 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1303 }
1304
1305 // Make sure this is a valid input method.
1306 InputMethodInfo imm = mMethodMap.get(id);
1307 if (imm == null) {
1308 if (imm == null) {
1309 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1310 }
1311 }
1312
1313 StringBuilder builder = new StringBuilder(256);
1314
1315 boolean removed = false;
1316 String firstId = null;
1317
1318 // Look through the currently enabled input methods.
1319 String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
1320 Settings.Secure.ENABLED_INPUT_METHODS);
1321 if (enabledStr != null) {
1322 final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
1323 splitter.setString(enabledStr);
1324 while (splitter.hasNext()) {
1325 String curId = splitter.next();
1326 if (curId.equals(id)) {
1327 if (enabled) {
1328 // We are enabling this input method, but it is
1329 // already enabled. Nothing to do. The previous
1330 // state was enabled.
1331 return true;
1332 }
1333 // We are disabling this input method, and it is
1334 // currently enabled. Skip it to remove from the
1335 // new list.
1336 removed = true;
1337 } else if (!enabled) {
1338 // We are building a new list of input methods that
1339 // doesn't contain the given one.
1340 if (firstId == null) firstId = curId;
1341 if (builder.length() > 0) builder.append(':');
1342 builder.append(curId);
1343 }
1344 }
1345 }
1346
1347 if (!enabled) {
1348 if (!removed) {
1349 // We are disabling the input method but it is already
1350 // disabled. Nothing to do. The previous state was
1351 // disabled.
1352 return false;
1353 }
1354 // Update the setting with the new list of input methods.
1355 Settings.Secure.putString(mContext.getContentResolver(),
1356 Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
1357 // We the disabled input method is currently selected, switch
1358 // to another one.
1359 String selId = Settings.Secure.getString(mContext.getContentResolver(),
1360 Settings.Secure.DEFAULT_INPUT_METHOD);
1361 if (id.equals(selId)) {
1362 Settings.Secure.putString(mContext.getContentResolver(),
1363 Settings.Secure.DEFAULT_INPUT_METHOD,
1364 firstId != null ? firstId : "");
1365 }
1366 // Previous state was enabled.
1367 return true;
1368 }
1369
1370 // Add in the newly enabled input method.
1371 if (enabledStr == null || enabledStr.length() == 0) {
1372 enabledStr = id;
1373 } else {
1374 enabledStr = enabledStr + ':' + id;
1375 }
1376
1377 Settings.Secure.putString(mContext.getContentResolver(),
1378 Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
1379
1380 // Previous state was disabled.
1381 return false;
1382 }
1383 }
1384
1385 // ----------------------------------------------------------------------
1386
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001387 @Override
1388 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1389 if (mContext.checkCallingPermission("android.permission.DUMP")
1390 != PackageManager.PERMISSION_GRANTED) {
1391
1392 pw.println("Permission Denial: can't dump InputMethodManager from from pid="
1393 + Binder.getCallingPid()
1394 + ", uid=" + Binder.getCallingUid());
1395 return;
1396 }
1397
1398 synchronized (mMethodMap) {
1399 final Printer p = new PrintWriterPrinter(pw);
1400 p.println("Current Input Method Manager state:");
1401 int N = mMethodList.size();
1402 p.println(" Input Methods:");
1403 for (int i=0; i<N; i++) {
1404 InputMethodInfo info = mMethodList.get(i);
1405 p.println(" InputMethod #" + i + ":");
1406 info.dump(p, " ");
1407 }
1408 p.println(" Clients:");
1409 for (ClientState ci : mClients.values()) {
1410 p.println(" Client " + ci + ":");
1411 p.println(" client=" + ci.client);
1412 p.println(" inputContext=" + ci.inputContext);
1413 p.println(" sessionRequested=" + ci.sessionRequested);
1414 p.println(" curSession=" + ci.curSession);
1415 }
1416 p.println(" mInputMethodIcon=" + mInputMethodIcon);
1417 p.println(" mInputMethodData=" + mInputMethodData);
1418 p.println(" mCurrentMethod=" + mCurMethodId);
1419 p.println(" mCurSeq=" + mCurSeq + " mCurClient=" + mCurClient);
1420 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
1421 + " mBoundToMethod=" + mBoundToMethod);
1422 p.println(" mCurToken=" + mCurToken);
1423 p.println(" mCurIntent=" + mCurIntent);
1424 p.println(" mCurMethod=" + mCurMethod);
1425 p.println(" mEnabledSession=" + mEnabledSession);
1426 p.println(" mShowRequested=" + mShowRequested
The Android Open Source Project9266c5582009-01-15 16:12:10 -08001427 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -08001428 + " mInputShown=" + mInputShown);
1429 p.println(" mScreenOn=" + mScreenOn);
1430 }
1431 }
1432}