blob: 1da2b47d14465ab66e7f4596493e91c11bb76997 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -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.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;
44import android.content.pm.PackageManager;
45import android.content.pm.ResolveInfo;
46import android.content.pm.ServiceInfo;
47import android.content.res.Resources;
48import android.content.res.TypedArray;
49import android.database.ContentObserver;
50import android.net.Uri;
51import android.os.Binder;
52import android.os.Handler;
53import android.os.IBinder;
54import android.os.IInterface;
55import android.os.Message;
56import android.os.Parcel;
57import android.os.RemoteException;
The Android Open Source Project4df24232009-03-05 14:34:35 -080058import android.os.ResultReceiver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059import android.os.ServiceManager;
60import android.os.SystemClock;
61import android.provider.Settings;
62import android.text.TextUtils;
63import android.util.EventLog;
64import android.util.Log;
65import android.util.PrintWriterPrinter;
66import android.util.Printer;
67import android.view.IWindowManager;
68import android.view.WindowManager;
69import android.view.inputmethod.InputBinding;
70import android.view.inputmethod.InputMethod;
71import android.view.inputmethod.InputMethodInfo;
72import android.view.inputmethod.InputMethodManager;
73import android.view.inputmethod.EditorInfo;
74
75import java.io.FileDescriptor;
76import java.io.IOException;
77import java.io.PrintWriter;
78import java.util.ArrayList;
79import java.util.HashMap;
80import java.util.List;
81
82/**
83 * This class provides a system service that manages input methods.
84 */
85public class InputMethodManagerService extends IInputMethodManager.Stub
86 implements ServiceConnection, Handler.Callback {
87 static final boolean DEBUG = false;
88 static final String TAG = "InputManagerService";
89
90 static final int MSG_SHOW_IM_PICKER = 1;
91
92 static final int MSG_UNBIND_INPUT = 1000;
93 static final int MSG_BIND_INPUT = 1010;
94 static final int MSG_SHOW_SOFT_INPUT = 1020;
95 static final int MSG_HIDE_SOFT_INPUT = 1030;
96 static final int MSG_ATTACH_TOKEN = 1040;
97 static final int MSG_CREATE_SESSION = 1050;
98
99 static final int MSG_START_INPUT = 2000;
100 static final int MSG_RESTART_INPUT = 2010;
101
102 static final int MSG_UNBIND_METHOD = 3000;
103 static final int MSG_BIND_METHOD = 3010;
104
105 static final long TIME_TO_RECONNECT = 10*1000;
106
107 static final int LOG_IMF_FORCE_RECONNECT_IME = 32000;
108
109 final Context mContext;
110 final Handler mHandler;
111 final SettingsObserver mSettingsObserver;
112 final StatusBarService mStatusBar;
113 final IBinder mInputMethodIcon;
114 final IconData mInputMethodData;
115 final IWindowManager mIWindowManager;
116 final HandlerCaller mCaller;
117
118 final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
119
120 // All known input methods. mMethodMap also serves as the global
121 // lock for this class.
122 final ArrayList<InputMethodInfo> mMethodList
123 = new ArrayList<InputMethodInfo>();
124 final HashMap<String, InputMethodInfo> mMethodMap
125 = new HashMap<String, InputMethodInfo>();
126
127 final TextUtils.SimpleStringSplitter mStringColonSplitter
128 = new TextUtils.SimpleStringSplitter(':');
129
130 class SessionState {
131 final ClientState client;
132 final IInputMethod method;
133 final IInputMethodSession session;
134
135 @Override
136 public String toString() {
137 return "SessionState{uid " + client.uid + " pid " + client.pid
138 + " method " + Integer.toHexString(
139 System.identityHashCode(method))
140 + " session " + Integer.toHexString(
141 System.identityHashCode(session))
142 + "}";
143 }
144
145 SessionState(ClientState _client, IInputMethod _method,
146 IInputMethodSession _session) {
147 client = _client;
148 method = _method;
149 session = _session;
150 }
151 }
152
153 class ClientState {
154 final IInputMethodClient client;
155 final IInputContext inputContext;
156 final int uid;
157 final int pid;
158 final InputBinding binding;
159
160 boolean sessionRequested;
161 SessionState curSession;
162
163 @Override
164 public String toString() {
165 return "ClientState{" + Integer.toHexString(
166 System.identityHashCode(this)) + " uid " + uid
167 + " pid " + pid + "}";
168 }
169
170 ClientState(IInputMethodClient _client, IInputContext _inputContext,
171 int _uid, int _pid) {
172 client = _client;
173 inputContext = _inputContext;
174 uid = _uid;
175 pid = _pid;
176 binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
177 }
178 }
179
180 final HashMap<IBinder, ClientState> mClients
181 = new HashMap<IBinder, ClientState>();
182
183 /**
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700184 * Set once the system is ready to run third party code.
185 */
186 boolean mSystemReady;
187
188 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 * Id of the currently selected input method.
190 */
191 String mCurMethodId;
192
193 /**
194 * The current binding sequence number, incremented every time there is
195 * a new bind performed.
196 */
197 int mCurSeq;
198
199 /**
200 * The client that is currently bound to an input method.
201 */
202 ClientState mCurClient;
203
204 /**
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700205 * The last window token that gained focus.
206 */
207 IBinder mCurFocusedWindow;
208
209 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 * The input context last provided by the current client.
211 */
212 IInputContext mCurInputContext;
213
214 /**
215 * The attributes last provided by the current client.
216 */
217 EditorInfo mCurAttribute;
218
219 /**
220 * The input method ID of the input method service that we are currently
221 * connected to or in the process of connecting to.
222 */
223 String mCurId;
224
225 /**
226 * Set to true if our ServiceConnection is currently actively bound to
227 * a service (whether or not we have gotten its IBinder back yet).
228 */
229 boolean mHaveConnection;
230
231 /**
232 * Set if the client has asked for the input method to be shown.
233 */
234 boolean mShowRequested;
235
236 /**
237 * Set if we were explicitly told to show the input method.
238 */
239 boolean mShowExplicitlyRequested;
240
241 /**
242 * Set if we were forced to be shown.
243 */
244 boolean mShowForced;
245
246 /**
247 * Set if we last told the input method to show itself.
248 */
249 boolean mInputShown;
250
251 /**
252 * The Intent used to connect to the current input method.
253 */
254 Intent mCurIntent;
255
256 /**
257 * The token we have made for the currently active input method, to
258 * identify it in the future.
259 */
260 IBinder mCurToken;
261
262 /**
263 * If non-null, this is the input method service we are currently connected
264 * to.
265 */
266 IInputMethod mCurMethod;
267
268 /**
269 * Time that we last initiated a bind to the input method, to determine
270 * if we should try to disconnect and reconnect to it.
271 */
272 long mLastBindTime;
273
274 /**
275 * Have we called mCurMethod.bindInput()?
276 */
277 boolean mBoundToMethod;
278
279 /**
280 * Currently enabled session. Only touched by service thread, not
281 * protected by a lock.
282 */
283 SessionState mEnabledSession;
284
285 /**
286 * True if the screen is on. The value is true initially.
287 */
288 boolean mScreenOn = true;
289
290 AlertDialog.Builder mDialogBuilder;
291 AlertDialog mSwitchingDialog;
292 InputMethodInfo[] mIms;
293 CharSequence[] mItems;
294
295 class SettingsObserver extends ContentObserver {
296 SettingsObserver(Handler handler) {
297 super(handler);
298 ContentResolver resolver = mContext.getContentResolver();
299 resolver.registerContentObserver(Settings.Secure.getUriFor(
300 Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
301 }
302
303 @Override public void onChange(boolean selfChange) {
304 synchronized (mMethodMap) {
305 updateFromSettingsLocked();
306 }
307 }
308 }
309
310 class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
311 @Override
312 public void onReceive(Context context, Intent intent) {
313 if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
314 mScreenOn = true;
315 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
316 mScreenOn = false;
The Android Open Source Project10592532009-03-18 17:39:46 -0700317 } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
318 hideInputMethodMenu();
319 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 } else {
321 Log.w(TAG, "Unexpected intent " + intent);
322 }
323
324 // Inform the current client of the change in active status
325 try {
326 if (mCurClient != null && mCurClient.client != null) {
327 mCurClient.client.setActive(mScreenOn);
328 }
329 } catch (RemoteException e) {
330 Log.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
331 + mCurClient.pid + " uid " + mCurClient.uid);
332 }
333 }
334 }
335
336 class PackageReceiver extends android.content.BroadcastReceiver {
337 @Override
338 public void onReceive(Context context, Intent intent) {
339 synchronized (mMethodMap) {
340 buildInputMethodListLocked(mMethodList, mMethodMap);
341
342 InputMethodInfo curIm = null;
343 String curInputMethodId = Settings.Secure.getString(context
344 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
345 final int N = mMethodList.size();
346 if (curInputMethodId != null) {
347 for (int i=0; i<N; i++) {
348 if (mMethodList.get(i).getId().equals(curInputMethodId)) {
349 curIm = mMethodList.get(i);
350 }
351 }
352 }
353
354 boolean changed = false;
355
356 Uri uri = intent.getData();
357 String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
358 if (curIm != null && curIm.getPackageName().equals(pkg)) {
359 ServiceInfo si = null;
360 try {
361 si = mContext.getPackageManager().getServiceInfo(
362 curIm.getComponent(), 0);
363 } catch (PackageManager.NameNotFoundException ex) {
364 }
365 if (si == null) {
366 // Uh oh, current input method is no longer around!
367 // Pick another one...
368 Log.i(TAG, "Current input method removed: " + curInputMethodId);
369 List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
370 if (enabled != null && enabled.size() > 0) {
371 changed = true;
372 curIm = enabled.get(0);
373 curInputMethodId = curIm.getId();
374 Log.i(TAG, "Switching to: " + curInputMethodId);
375 Settings.Secure.putString(mContext.getContentResolver(),
376 Settings.Secure.DEFAULT_INPUT_METHOD,
377 curInputMethodId);
378 } else if (curIm != null) {
379 changed = true;
380 curIm = null;
381 curInputMethodId = "";
382 Log.i(TAG, "Unsetting current input method");
383 Settings.Secure.putString(mContext.getContentResolver(),
384 Settings.Secure.DEFAULT_INPUT_METHOD,
385 curInputMethodId);
386 }
387 }
388
389 } else if (curIm == null) {
390 // We currently don't have a default input method... is
391 // one now available?
392 List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
393 if (enabled != null && enabled.size() > 0) {
394 changed = true;
395 curIm = enabled.get(0);
396 curInputMethodId = curIm.getId();
397 Log.i(TAG, "New default input method: " + curInputMethodId);
398 Settings.Secure.putString(mContext.getContentResolver(),
399 Settings.Secure.DEFAULT_INPUT_METHOD,
400 curInputMethodId);
401 }
402 }
403
404 if (changed) {
405 updateFromSettingsLocked();
406 }
407 }
408 }
409 }
410
411 class MethodCallback extends IInputMethodCallback.Stub {
412 final IInputMethod mMethod;
413
414 MethodCallback(IInputMethod method) {
415 mMethod = method;
416 }
417
418 public void finishedEvent(int seq, boolean handled) throws RemoteException {
419 }
420
421 public void sessionCreated(IInputMethodSession session) throws RemoteException {
422 onSessionCreated(mMethod, session);
423 }
424 }
425
426 public InputMethodManagerService(Context context, StatusBarService statusBar) {
427 mContext = context;
428 mHandler = new Handler(this);
429 mIWindowManager = IWindowManager.Stub.asInterface(
430 ServiceManager.getService(Context.WINDOW_SERVICE));
431 mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
432 public void executeMessage(Message msg) {
433 handleMessage(msg);
434 }
435 });
436
437 IntentFilter packageFilt = new IntentFilter();
438 packageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
439 packageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
440 packageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
441 packageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
442 packageFilt.addDataScheme("package");
443 mContext.registerReceiver(new PackageReceiver(), packageFilt);
444
445 IntentFilter screenOnOffFilt = new IntentFilter();
446 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
447 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
The Android Open Source Project10592532009-03-18 17:39:46 -0700448 screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
450
451 buildInputMethodListLocked(mMethodList, mMethodMap);
452
453 final String enabledStr = Settings.Secure.getString(
454 mContext.getContentResolver(),
455 Settings.Secure.ENABLED_INPUT_METHODS);
456 Log.i(TAG, "Enabled input methods: " + enabledStr);
457 if (enabledStr == null) {
458 Log.i(TAG, "Enabled input methods has not been set, enabling all");
459 InputMethodInfo defIm = null;
460 StringBuilder sb = new StringBuilder(256);
461 final int N = mMethodList.size();
462 for (int i=0; i<N; i++) {
463 InputMethodInfo imi = mMethodList.get(i);
464 Log.i(TAG, "Adding: " + imi.getId());
465 if (i > 0) sb.append(':');
466 sb.append(imi.getId());
467 if (defIm == null && imi.getIsDefaultResourceId() != 0) {
468 try {
469 Resources res = mContext.createPackageContext(
470 imi.getPackageName(), 0).getResources();
471 if (res.getBoolean(imi.getIsDefaultResourceId())) {
472 defIm = imi;
473 Log.i(TAG, "Selected default: " + imi.getId());
474 }
475 } catch (PackageManager.NameNotFoundException ex) {
476 } catch (Resources.NotFoundException ex) {
477 }
478 }
479 }
480 if (defIm == null && N > 0) {
481 defIm = mMethodList.get(0);
482 Log.i(TAG, "No default found, using " + defIm.getId());
483 }
484 Settings.Secure.putString(mContext.getContentResolver(),
485 Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
486 if (defIm != null) {
487 Settings.Secure.putString(mContext.getContentResolver(),
488 Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
489 }
490 }
491
492 mStatusBar = statusBar;
493 mInputMethodData = IconData.makeIcon("ime", null, 0, 0, 0);
494 mInputMethodIcon = statusBar.addIcon(mInputMethodData, null);
495 statusBar.setIconVisibility(mInputMethodIcon, false);
496
497 mSettingsObserver = new SettingsObserver(mHandler);
498 updateFromSettingsLocked();
499 }
500
501 @Override
502 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
503 throws RemoteException {
504 try {
505 return super.onTransact(code, data, reply, flags);
506 } catch (RuntimeException e) {
507 // The input method manager only throws security exceptions, so let's
508 // log all others.
509 if (!(e instanceof SecurityException)) {
510 Log.e(TAG, "Input Method Manager Crash", e);
511 }
512 throw e;
513 }
514 }
515
516 public void systemReady() {
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700517 synchronized (mMethodMap) {
518 if (!mSystemReady) {
519 mSystemReady = true;
Dianne Hackborncc278702009-09-02 23:07:23 -0700520 try {
521 startInputInnerLocked();
522 } catch (RuntimeException e) {
523 Log.w(TAG, "Unexpected exception", e);
524 }
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700525 }
526 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 }
528
529 public List<InputMethodInfo> getInputMethodList() {
530 synchronized (mMethodMap) {
531 return new ArrayList<InputMethodInfo>(mMethodList);
532 }
533 }
534
535 public List<InputMethodInfo> getEnabledInputMethodList() {
536 synchronized (mMethodMap) {
537 return getEnabledInputMethodListLocked();
538 }
539 }
540
541 List<InputMethodInfo> getEnabledInputMethodListLocked() {
542 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
543
544 final String enabledStr = Settings.Secure.getString(
545 mContext.getContentResolver(),
546 Settings.Secure.ENABLED_INPUT_METHODS);
547 if (enabledStr != null) {
548 final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
549 splitter.setString(enabledStr);
550
551 while (splitter.hasNext()) {
552 InputMethodInfo info = mMethodMap.get(splitter.next());
553 if (info != null) {
554 res.add(info);
555 }
556 }
557 }
558
559 return res;
560 }
561
562 public void addClient(IInputMethodClient client,
563 IInputContext inputContext, int uid, int pid) {
564 synchronized (mMethodMap) {
565 mClients.put(client.asBinder(), new ClientState(client,
566 inputContext, uid, pid));
567 }
568 }
569
570 public void removeClient(IInputMethodClient client) {
571 synchronized (mMethodMap) {
572 mClients.remove(client.asBinder());
573 }
574 }
575
576 void executeOrSendMessage(IInterface target, Message msg) {
577 if (target.asBinder() instanceof Binder) {
578 mCaller.sendMessage(msg);
579 } else {
580 handleMessage(msg);
581 msg.recycle();
582 }
583 }
584
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700585 void unbindCurrentClientLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 if (mCurClient != null) {
587 if (DEBUG) Log.v(TAG, "unbindCurrentInputLocked: client = "
588 + mCurClient.client.asBinder());
589 if (mBoundToMethod) {
590 mBoundToMethod = false;
591 if (mCurMethod != null) {
592 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
593 MSG_UNBIND_INPUT, mCurMethod));
594 }
595 }
596 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
597 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
598 mCurClient.sessionRequested = false;
599
600 // Call setActive(false) on the old client
601 try {
602 mCurClient.client.setActive(false);
603 } catch (RemoteException e) {
604 Log.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
605 + mCurClient.pid + " uid " + mCurClient.uid);
606 }
607 mCurClient = null;
The Android Open Source Project10592532009-03-18 17:39:46 -0700608
609 hideInputMethodMenuLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800610 }
611 }
612
613 private int getImeShowFlags() {
614 int flags = 0;
615 if (mShowForced) {
616 flags |= InputMethod.SHOW_FORCED
617 | InputMethod.SHOW_EXPLICIT;
618 } else if (mShowExplicitlyRequested) {
619 flags |= InputMethod.SHOW_EXPLICIT;
620 }
621 return flags;
622 }
623
624 private int getAppShowFlags() {
625 int flags = 0;
626 if (mShowForced) {
627 flags |= InputMethodManager.SHOW_FORCED;
628 } else if (!mShowExplicitlyRequested) {
629 flags |= InputMethodManager.SHOW_IMPLICIT;
630 }
631 return flags;
632 }
633
634 InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
635 if (!mBoundToMethod) {
636 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
637 MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
638 mBoundToMethod = true;
639 }
640 final SessionState session = mCurClient.curSession;
641 if (initial) {
642 executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
643 MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
644 } else {
645 executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
646 MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
647 }
648 if (mShowRequested) {
649 if (DEBUG) Log.v(TAG, "Attach new input asks to show input");
The Android Open Source Project4df24232009-03-05 14:34:35 -0800650 showCurrentInputLocked(getAppShowFlags(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800651 }
652 return needResult
653 ? new InputBindResult(session.session, mCurId, mCurSeq)
654 : null;
655 }
656
657 InputBindResult startInputLocked(IInputMethodClient client,
658 IInputContext inputContext, EditorInfo attribute,
659 boolean initial, boolean needResult) {
660 // If no method is currently selected, do nothing.
661 if (mCurMethodId == null) {
662 return mNoBinding;
663 }
664
665 ClientState cs = mClients.get(client.asBinder());
666 if (cs == null) {
667 throw new IllegalArgumentException("unknown client "
668 + client.asBinder());
669 }
670
671 try {
672 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
673 // Check with the window manager to make sure this client actually
674 // has a window with focus. If not, reject. This is thread safe
675 // because if the focus changes some time before or after, the
676 // next client receiving focus that has any interest in input will
677 // be calling through here after that change happens.
678 Log.w(TAG, "Starting input on non-focused client " + cs.client
679 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
680 return null;
681 }
682 } catch (RemoteException e) {
683 }
684
685 if (mCurClient != cs) {
686 // If the client is changing, we need to switch over to the new
687 // one.
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700688 unbindCurrentClientLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800689 if (DEBUG) Log.v(TAG, "switching to client: client = "
690 + cs.client.asBinder());
691
692 // If the screen is on, inform the new client it is active
693 if (mScreenOn) {
694 try {
695 cs.client.setActive(mScreenOn);
696 } catch (RemoteException e) {
697 Log.w(TAG, "Got RemoteException sending setActive notification to pid "
698 + cs.pid + " uid " + cs.uid);
699 }
700 }
701 }
702
703 // Bump up the sequence for this client and attach it.
704 mCurSeq++;
705 if (mCurSeq <= 0) mCurSeq = 1;
706 mCurClient = cs;
707 mCurInputContext = inputContext;
708 mCurAttribute = attribute;
709
710 // Check if the input method is changing.
711 if (mCurId != null && mCurId.equals(mCurMethodId)) {
712 if (cs.curSession != null) {
713 // Fast case: if we are already connected to the input method,
714 // then just return it.
715 return attachNewInputLocked(initial, needResult);
716 }
717 if (mHaveConnection) {
718 if (mCurMethod != null) {
719 if (!cs.sessionRequested) {
720 cs.sessionRequested = true;
721 if (DEBUG) Log.v(TAG, "Creating new session for client " + cs);
722 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
723 MSG_CREATE_SESSION, mCurMethod,
724 new MethodCallback(mCurMethod)));
725 }
726 // Return to client, and we will get back with it when
727 // we have had a session made for it.
728 return new InputBindResult(null, mCurId, mCurSeq);
729 } else if (SystemClock.uptimeMillis()
730 < (mLastBindTime+TIME_TO_RECONNECT)) {
731 // In this case we have connected to the service, but
732 // don't yet have its interface. If it hasn't been too
733 // long since we did the connection, we'll return to
734 // the client and wait to get the service interface so
735 // we can report back. If it has been too long, we want
736 // to fall through so we can try a disconnect/reconnect
737 // to see if we can get back in touch with the service.
738 return new InputBindResult(null, mCurId, mCurSeq);
739 } else {
740 EventLog.writeEvent(LOG_IMF_FORCE_RECONNECT_IME, mCurMethodId,
741 SystemClock.uptimeMillis()-mLastBindTime, 0);
742 }
743 }
744 }
745
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700746 return startInputInnerLocked();
747 }
748
749 InputBindResult startInputInnerLocked() {
750 if (mCurMethodId == null) {
751 return mNoBinding;
752 }
753
754 if (!mSystemReady) {
755 // If the system is not yet ready, we shouldn't be running third
756 // party code.
Dianne Hackborncc278702009-09-02 23:07:23 -0700757 return new InputBindResult(null, mCurMethodId, mCurSeq);
Dianne Hackborna34f1ad2009-09-02 13:26:28 -0700758 }
759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 InputMethodInfo info = mMethodMap.get(mCurMethodId);
761 if (info == null) {
762 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
763 }
764
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700765 unbindCurrentMethodLocked(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800766
767 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
768 mCurIntent.setComponent(info.getComponent());
Dianne Hackborndd9b82c2009-09-03 00:18:47 -0700769 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
770 com.android.internal.R.string.input_method_binding_label);
771 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
772 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800773 if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
774 mLastBindTime = SystemClock.uptimeMillis();
775 mHaveConnection = true;
776 mCurId = info.getId();
777 mCurToken = new Binder();
778 try {
779 if (DEBUG) Log.v(TAG, "Adding window token: " + mCurToken);
780 mIWindowManager.addWindowToken(mCurToken,
781 WindowManager.LayoutParams.TYPE_INPUT_METHOD);
782 } catch (RemoteException e) {
783 }
784 return new InputBindResult(null, mCurId, mCurSeq);
785 } else {
786 mCurIntent = null;
787 Log.w(TAG, "Failure connecting to input method service: "
788 + mCurIntent);
789 }
790 return null;
791 }
792
793 public InputBindResult startInput(IInputMethodClient client,
794 IInputContext inputContext, EditorInfo attribute,
795 boolean initial, boolean needResult) {
796 synchronized (mMethodMap) {
797 final long ident = Binder.clearCallingIdentity();
798 try {
799 return startInputLocked(client, inputContext, attribute,
800 initial, needResult);
801 } finally {
802 Binder.restoreCallingIdentity(ident);
803 }
804 }
805 }
806
807 public void finishInput(IInputMethodClient client) {
808 }
809
810 public void onServiceConnected(ComponentName name, IBinder service) {
811 synchronized (mMethodMap) {
812 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
813 mCurMethod = IInputMethod.Stub.asInterface(service);
Dianne Hackborncc278702009-09-02 23:07:23 -0700814 if (mCurToken == null) {
815 Log.w(TAG, "Service connected without a token!");
816 unbindCurrentMethodLocked(false);
817 return;
818 }
819 if (DEBUG) Log.v(TAG, "Initiating attach with token: " + mCurToken);
820 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
821 MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 if (mCurClient != null) {
Dianne Hackborncc278702009-09-02 23:07:23 -0700823 if (DEBUG) Log.v(TAG, "Creating first session while with client "
824 + mCurClient);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
Dianne Hackborncc278702009-09-02 23:07:23 -0700826 MSG_CREATE_SESSION, mCurMethod,
827 new MethodCallback(mCurMethod)));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800828 }
829 }
830 }
831 }
832
833 void onSessionCreated(IInputMethod method, IInputMethodSession session) {
834 synchronized (mMethodMap) {
835 if (mCurMethod != null && method != null
836 && mCurMethod.asBinder() == method.asBinder()) {
837 if (mCurClient != null) {
838 mCurClient.curSession = new SessionState(mCurClient,
839 method, session);
840 mCurClient.sessionRequested = false;
841 InputBindResult res = attachNewInputLocked(true, true);
842 if (res.method != null) {
843 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
844 MSG_BIND_METHOD, mCurClient.client, res));
845 }
846 }
847 }
848 }
849 }
850
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700851 void unbindCurrentMethodLocked(boolean reportToClient) {
852 if (mHaveConnection) {
853 mContext.unbindService(this);
854 mHaveConnection = false;
855 }
856
857 if (mCurToken != null) {
858 try {
859 if (DEBUG) Log.v(TAG, "Removing window token: " + mCurToken);
860 mIWindowManager.removeWindowToken(mCurToken);
861 } catch (RemoteException e) {
862 }
863 mCurToken = null;
864 }
865
The Android Open Source Project10592532009-03-18 17:39:46 -0700866 mCurId = null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700867 clearCurMethodLocked();
868
869 if (reportToClient && mCurClient != null) {
870 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
871 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
872 }
873 }
874
875 void clearCurMethodLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800876 if (mCurMethod != null) {
877 for (ClientState cs : mClients.values()) {
878 cs.sessionRequested = false;
879 cs.curSession = null;
880 }
881 mCurMethod = null;
882 }
883 mStatusBar.setIconVisibility(mInputMethodIcon, false);
884 }
885
886 public void onServiceDisconnected(ComponentName name) {
887 synchronized (mMethodMap) {
888 if (DEBUG) Log.v(TAG, "Service disconnected: " + name
889 + " mCurIntent=" + mCurIntent);
890 if (mCurMethod != null && mCurIntent != null
891 && name.equals(mCurIntent.getComponent())) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700892 clearCurMethodLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800893 // We consider this to be a new bind attempt, since the system
894 // should now try to restart the service for us.
895 mLastBindTime = SystemClock.uptimeMillis();
896 mShowRequested = mInputShown;
897 mInputShown = false;
898 if (mCurClient != null) {
899 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
900 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
901 }
902 }
903 }
904 }
905
906 public void updateStatusIcon(IBinder token, String packageName, int iconId) {
907 long ident = Binder.clearCallingIdentity();
908 try {
909 if (token == null || mCurToken != token) {
910 Log.w(TAG, "Ignoring setInputMethod of token: " + token);
911 return;
912 }
913
914 synchronized (mMethodMap) {
915 if (iconId == 0) {
916 if (DEBUG) Log.d(TAG, "hide the small icon for the input method");
917 mStatusBar.setIconVisibility(mInputMethodIcon, false);
918 } else if (packageName != null) {
919 if (DEBUG) Log.d(TAG, "show a small icon for the input method");
920 mInputMethodData.iconId = iconId;
921 mInputMethodData.iconPackage = packageName;
922 mStatusBar.updateIcon(mInputMethodIcon, mInputMethodData, null);
923 mStatusBar.setIconVisibility(mInputMethodIcon, true);
924 }
925 }
926 } finally {
927 Binder.restoreCallingIdentity(ident);
928 }
929 }
930
931 void updateFromSettingsLocked() {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700932 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
933 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
934 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
935 // enabled.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936 String id = Settings.Secure.getString(mContext.getContentResolver(),
937 Settings.Secure.DEFAULT_INPUT_METHOD);
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700938 if (id != null && id.length() > 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800939 try {
940 setInputMethodLocked(id);
941 } catch (IllegalArgumentException e) {
942 Log.w(TAG, "Unknown input method from prefs: " + id, e);
The Android Open Source Project10592532009-03-18 17:39:46 -0700943 mCurMethodId = null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700944 unbindCurrentMethodLocked(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700946 } else {
947 // There is no longer an input method set, so stop any current one.
The Android Open Source Project10592532009-03-18 17:39:46 -0700948 mCurMethodId = null;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700949 unbindCurrentMethodLocked(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800950 }
951 }
952
953 void setInputMethodLocked(String id) {
954 InputMethodInfo info = mMethodMap.get(id);
955 if (info == null) {
956 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
957 }
958
959 if (id.equals(mCurMethodId)) {
960 return;
961 }
962
963 final long ident = Binder.clearCallingIdentity();
964 try {
965 mCurMethodId = id;
966 Settings.Secure.putString(mContext.getContentResolver(),
967 Settings.Secure.DEFAULT_INPUT_METHOD, id);
968
969 if (ActivityManagerNative.isSystemReady()) {
970 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
971 intent.putExtra("input_method_id", id);
972 mContext.sendBroadcast(intent);
973 }
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -0700974 unbindCurrentClientLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 } finally {
976 Binder.restoreCallingIdentity(ident);
977 }
978 }
979
The Android Open Source Project4df24232009-03-05 14:34:35 -0800980 public boolean showSoftInput(IInputMethodClient client, int flags,
981 ResultReceiver resultReceiver) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800982 long ident = Binder.clearCallingIdentity();
983 try {
984 synchronized (mMethodMap) {
985 if (mCurClient == null || client == null
986 || mCurClient.client.asBinder() != client.asBinder()) {
987 try {
988 // We need to check if this is the current client with
989 // focus in the window manager, to allow this call to
990 // be made before input is started in it.
991 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
992 Log.w(TAG, "Ignoring showSoftInput of: " + client);
The Android Open Source Project4df24232009-03-05 14:34:35 -0800993 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 }
995 } catch (RemoteException e) {
The Android Open Source Project4df24232009-03-05 14:34:35 -0800996 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800997 }
998 }
999
1000 if (DEBUG) Log.v(TAG, "Client requesting input be shown");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001001 return showCurrentInputLocked(flags, resultReceiver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001002 }
1003 } finally {
1004 Binder.restoreCallingIdentity(ident);
1005 }
1006 }
1007
The Android Open Source Project4df24232009-03-05 14:34:35 -08001008 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 mShowRequested = true;
1010 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1011 mShowExplicitlyRequested = true;
1012 }
1013 if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1014 mShowExplicitlyRequested = true;
1015 mShowForced = true;
1016 }
Dianne Hackborncc278702009-09-02 23:07:23 -07001017
1018 if (!mSystemReady) {
1019 return false;
1020 }
1021
The Android Open Source Project4df24232009-03-05 14:34:35 -08001022 boolean res = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001023 if (mCurMethod != null) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001024 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1025 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1026 resultReceiver));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 mInputShown = true;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001028 res = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001029 } else if (mHaveConnection && SystemClock.uptimeMillis()
1030 < (mLastBindTime+TIME_TO_RECONNECT)) {
1031 // The client has asked to have the input method shown, but
1032 // we have been sitting here too long with a connection to the
1033 // service and no interface received, so let's disconnect/connect
1034 // to try to prod things along.
1035 EventLog.writeEvent(LOG_IMF_FORCE_RECONNECT_IME, mCurMethodId,
1036 SystemClock.uptimeMillis()-mLastBindTime,1);
1037 mContext.unbindService(this);
1038 mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1039 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001040
1041 return res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 }
1043
The Android Open Source Project4df24232009-03-05 14:34:35 -08001044 public boolean hideSoftInput(IInputMethodClient client, int flags,
1045 ResultReceiver resultReceiver) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046 long ident = Binder.clearCallingIdentity();
1047 try {
1048 synchronized (mMethodMap) {
1049 if (mCurClient == null || client == null
1050 || mCurClient.client.asBinder() != client.asBinder()) {
1051 try {
1052 // We need to check if this is the current client with
1053 // focus in the window manager, to allow this call to
1054 // be made before input is started in it.
1055 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1056 Log.w(TAG, "Ignoring hideSoftInput of: " + client);
The Android Open Source Project4df24232009-03-05 14:34:35 -08001057 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001058 }
1059 } catch (RemoteException e) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001060 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001061 }
1062 }
1063
1064 if (DEBUG) Log.v(TAG, "Client requesting input be hidden");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001065 return hideCurrentInputLocked(flags, resultReceiver);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001066 }
1067 } finally {
1068 Binder.restoreCallingIdentity(ident);
1069 }
1070 }
1071
The Android Open Source Project4df24232009-03-05 14:34:35 -08001072 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001073 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1074 && (mShowExplicitlyRequested || mShowForced)) {
1075 if (DEBUG) Log.v(TAG,
1076 "Not hiding: explicit show not cancelled by non-explicit hide");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001077 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001078 }
1079 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1080 if (DEBUG) Log.v(TAG,
1081 "Not hiding: forced show not cancelled by not-always hide");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001082 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001083 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001084 boolean res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 if (mInputShown && mCurMethod != null) {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001086 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1087 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1088 res = true;
1089 } else {
1090 res = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001091 }
1092 mInputShown = false;
1093 mShowRequested = false;
1094 mShowExplicitlyRequested = false;
1095 mShowForced = false;
The Android Open Source Project4df24232009-03-05 14:34:35 -08001096 return res;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001097 }
1098
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001099 public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001100 boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1101 boolean first, int windowFlags) {
1102 long ident = Binder.clearCallingIdentity();
1103 try {
1104 synchronized (mMethodMap) {
1105 if (DEBUG) Log.v(TAG, "windowGainedFocus: " + client.asBinder()
1106 + " viewHasFocus=" + viewHasFocus
1107 + " isTextEditor=" + isTextEditor
1108 + " softInputMode=#" + Integer.toHexString(softInputMode)
1109 + " first=" + first + " flags=#"
1110 + Integer.toHexString(windowFlags));
1111
1112 if (mCurClient == null || client == null
1113 || mCurClient.client.asBinder() != client.asBinder()) {
1114 try {
1115 // We need to check if this is the current client with
1116 // focus in the window manager, to allow this call to
1117 // be made before input is started in it.
1118 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001119 Log.w(TAG, "Client not active, ignoring focus gain of: " + client);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 return;
1121 }
1122 } catch (RemoteException e) {
1123 }
1124 }
1125
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001126 if (mCurFocusedWindow == windowToken) {
1127 Log.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1128 return;
1129 }
1130 mCurFocusedWindow = windowToken;
1131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1133 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1134 if (!isTextEditor || (softInputMode &
1135 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1136 != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
1137 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1138 // There is no focus view, and this window will
1139 // be behind any soft input window, so hide the
1140 // soft input window if it is shown.
1141 if (DEBUG) Log.v(TAG, "Unspecified window will hide input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001142 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001143 }
1144 } else if (isTextEditor && (softInputMode &
1145 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1146 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1147 && (softInputMode &
1148 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1149 // There is a focus view, and we are navigating forward
1150 // into the window, so show the input window for the user.
1151 if (DEBUG) Log.v(TAG, "Unspecified window will show input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001152 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153 }
1154 break;
1155 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1156 // Do nothing.
1157 break;
1158 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1159 if ((softInputMode &
1160 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1161 if (DEBUG) Log.v(TAG, "Window asks to hide input going forward");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001162 hideCurrentInputLocked(0, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001163 }
1164 break;
1165 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1166 if (DEBUG) Log.v(TAG, "Window asks to hide input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001167 hideCurrentInputLocked(0, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001168 break;
1169 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1170 if ((softInputMode &
1171 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1172 if (DEBUG) Log.v(TAG, "Window asks to show input going forward");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001173 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001174 }
1175 break;
1176 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1177 if (DEBUG) Log.v(TAG, "Window asks to always show input");
The Android Open Source Project4df24232009-03-05 14:34:35 -08001178 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 break;
1180 }
1181 }
1182 } finally {
1183 Binder.restoreCallingIdentity(ident);
1184 }
1185 }
1186
1187 public void showInputMethodPickerFromClient(IInputMethodClient client) {
1188 synchronized (mMethodMap) {
1189 if (mCurClient == null || client == null
1190 || mCurClient.client.asBinder() != client.asBinder()) {
1191 Log.w(TAG, "Ignoring showInputMethodDialogFromClient of: " + client);
1192 }
1193
1194 mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
1195 }
1196 }
1197
1198 public void setInputMethod(IBinder token, String id) {
1199 synchronized (mMethodMap) {
1200 if (token == null) {
1201 if (mContext.checkCallingOrSelfPermission(
1202 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1203 != PackageManager.PERMISSION_GRANTED) {
1204 throw new SecurityException(
1205 "Using null token requires permission "
1206 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1207 }
1208 } else if (mCurToken != token) {
1209 Log.w(TAG, "Ignoring setInputMethod of token: " + token);
1210 return;
1211 }
1212
1213 long ident = Binder.clearCallingIdentity();
1214 try {
1215 setInputMethodLocked(id);
1216 } finally {
1217 Binder.restoreCallingIdentity(ident);
1218 }
1219 }
1220 }
1221
1222 public void hideMySoftInput(IBinder token, int flags) {
1223 synchronized (mMethodMap) {
1224 if (token == null || mCurToken != token) {
1225 Log.w(TAG, "Ignoring hideInputMethod of token: " + token);
1226 return;
1227 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 long ident = Binder.clearCallingIdentity();
1229 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001230 hideCurrentInputLocked(flags, null);
1231 } finally {
1232 Binder.restoreCallingIdentity(ident);
1233 }
1234 }
1235 }
1236
1237 public void showMySoftInput(IBinder token, int flags) {
1238 synchronized (mMethodMap) {
1239 if (token == null || mCurToken != token) {
1240 Log.w(TAG, "Ignoring hideInputMethod of token: " + token);
1241 return;
1242 }
1243 long ident = Binder.clearCallingIdentity();
1244 try {
1245 showCurrentInputLocked(flags, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001246 } finally {
1247 Binder.restoreCallingIdentity(ident);
1248 }
1249 }
1250 }
1251
1252 void setEnabledSessionInMainThread(SessionState session) {
1253 if (mEnabledSession != session) {
1254 if (mEnabledSession != null) {
1255 try {
1256 if (DEBUG) Log.v(TAG, "Disabling: " + mEnabledSession);
1257 mEnabledSession.method.setSessionEnabled(
1258 mEnabledSession.session, false);
1259 } catch (RemoteException e) {
1260 }
1261 }
1262 mEnabledSession = session;
1263 try {
1264 if (DEBUG) Log.v(TAG, "Enabling: " + mEnabledSession);
1265 session.method.setSessionEnabled(
1266 session.session, true);
1267 } catch (RemoteException e) {
1268 }
1269 }
1270 }
1271
1272 public boolean handleMessage(Message msg) {
1273 HandlerCaller.SomeArgs args;
1274 switch (msg.what) {
1275 case MSG_SHOW_IM_PICKER:
1276 showInputMethodMenu();
1277 return true;
1278
1279 // ---------------------------------------------------------
1280
1281 case MSG_UNBIND_INPUT:
1282 try {
1283 ((IInputMethod)msg.obj).unbindInput();
1284 } catch (RemoteException e) {
1285 // There is nothing interesting about the method dying.
1286 }
1287 return true;
1288 case MSG_BIND_INPUT:
1289 args = (HandlerCaller.SomeArgs)msg.obj;
1290 try {
1291 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1292 } catch (RemoteException e) {
1293 }
1294 return true;
1295 case MSG_SHOW_SOFT_INPUT:
The Android Open Source Project4df24232009-03-05 14:34:35 -08001296 args = (HandlerCaller.SomeArgs)msg.obj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001297 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001298 ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1299 (ResultReceiver)args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001300 } catch (RemoteException e) {
1301 }
1302 return true;
1303 case MSG_HIDE_SOFT_INPUT:
The Android Open Source Project4df24232009-03-05 14:34:35 -08001304 args = (HandlerCaller.SomeArgs)msg.obj;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 try {
The Android Open Source Project4df24232009-03-05 14:34:35 -08001306 ((IInputMethod)args.arg1).hideSoftInput(0,
1307 (ResultReceiver)args.arg2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001308 } catch (RemoteException e) {
1309 }
1310 return true;
1311 case MSG_ATTACH_TOKEN:
1312 args = (HandlerCaller.SomeArgs)msg.obj;
1313 try {
1314 if (DEBUG) Log.v(TAG, "Sending attach of token: " + args.arg2);
1315 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1316 } catch (RemoteException e) {
1317 }
1318 return true;
1319 case MSG_CREATE_SESSION:
1320 args = (HandlerCaller.SomeArgs)msg.obj;
1321 try {
1322 ((IInputMethod)args.arg1).createSession(
1323 (IInputMethodCallback)args.arg2);
1324 } catch (RemoteException e) {
1325 }
1326 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001327 // ---------------------------------------------------------
1328
1329 case MSG_START_INPUT:
1330 args = (HandlerCaller.SomeArgs)msg.obj;
1331 try {
1332 SessionState session = (SessionState)args.arg1;
1333 setEnabledSessionInMainThread(session);
1334 session.method.startInput((IInputContext)args.arg2,
1335 (EditorInfo)args.arg3);
1336 } catch (RemoteException e) {
1337 }
1338 return true;
1339 case MSG_RESTART_INPUT:
1340 args = (HandlerCaller.SomeArgs)msg.obj;
1341 try {
1342 SessionState session = (SessionState)args.arg1;
1343 setEnabledSessionInMainThread(session);
1344 session.method.restartInput((IInputContext)args.arg2,
1345 (EditorInfo)args.arg3);
1346 } catch (RemoteException e) {
1347 }
1348 return true;
1349
1350 // ---------------------------------------------------------
1351
1352 case MSG_UNBIND_METHOD:
1353 try {
1354 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1355 } catch (RemoteException e) {
1356 // There is nothing interesting about the last client dying.
1357 }
1358 return true;
1359 case MSG_BIND_METHOD:
1360 args = (HandlerCaller.SomeArgs)msg.obj;
1361 try {
1362 ((IInputMethodClient)args.arg1).onBindMethod(
1363 (InputBindResult)args.arg2);
1364 } catch (RemoteException e) {
1365 Log.w(TAG, "Client died receiving input method " + args.arg2);
1366 }
1367 return true;
1368 }
1369 return false;
1370 }
1371
1372 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1373 HashMap<String, InputMethodInfo> map) {
1374 list.clear();
1375 map.clear();
1376
1377 PackageManager pm = mContext.getPackageManager();
1378
1379 List<ResolveInfo> services = pm.queryIntentServices(
1380 new Intent(InputMethod.SERVICE_INTERFACE),
1381 PackageManager.GET_META_DATA);
1382
1383 for (int i = 0; i < services.size(); ++i) {
1384 ResolveInfo ri = services.get(i);
1385 ServiceInfo si = ri.serviceInfo;
1386 ComponentName compName = new ComponentName(si.packageName, si.name);
1387 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1388 si.permission)) {
1389 Log.w(TAG, "Skipping input method " + compName
1390 + ": it does not require the permission "
1391 + android.Manifest.permission.BIND_INPUT_METHOD);
1392 continue;
1393 }
1394
1395 if (DEBUG) Log.d(TAG, "Checking " + compName);
1396
1397 try {
1398 InputMethodInfo p = new InputMethodInfo(mContext, ri);
1399 list.add(p);
1400 map.put(p.getId(), p);
1401
1402 if (DEBUG) {
1403 Log.d(TAG, "Found a third-party input method " + p);
1404 }
1405
1406 } catch (XmlPullParserException e) {
1407 Log.w(TAG, "Unable to load input method " + compName, e);
1408 } catch (IOException e) {
1409 Log.w(TAG, "Unable to load input method " + compName, e);
1410 }
1411 }
1412 }
1413
1414 // ----------------------------------------------------------------------
1415
1416 void showInputMethodMenu() {
1417 if (DEBUG) Log.v(TAG, "Show switching menu");
1418
1419 hideInputMethodMenu();
1420
1421 final Context context = mContext;
1422
1423 final PackageManager pm = context.getPackageManager();
1424
1425 String lastInputMethodId = Settings.Secure.getString(context
1426 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1427 if (DEBUG) Log.v(TAG, "Current IME: " + lastInputMethodId);
1428
1429 final List<InputMethodInfo> immis = getEnabledInputMethodList();
1430
1431 int N = (immis == null ? 0 : immis.size());
1432
1433 mItems = new CharSequence[N];
1434 mIms = new InputMethodInfo[N];
1435
1436 for (int i = 0; i < N; ++i) {
1437 InputMethodInfo property = immis.get(i);
1438 mItems[i] = property.loadLabel(pm);
1439 mIms[i] = property;
1440 }
1441
1442 int checkedItem = 0;
1443 for (int i = 0; i < N; ++i) {
1444 if (mIms[i].getId().equals(lastInputMethodId)) {
1445 checkedItem = i;
1446 break;
1447 }
1448 }
1449
1450 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1451 public void onClick(DialogInterface dialog, int which) {
1452 hideInputMethodMenu();
1453 }
1454 };
1455
1456 TypedArray a = context.obtainStyledAttributes(null,
1457 com.android.internal.R.styleable.DialogPreference,
1458 com.android.internal.R.attr.alertDialogStyle, 0);
1459 mDialogBuilder = new AlertDialog.Builder(context)
1460 .setTitle(com.android.internal.R.string.select_input_method)
1461 .setOnCancelListener(new OnCancelListener() {
1462 public void onCancel(DialogInterface dialog) {
1463 hideInputMethodMenu();
1464 }
1465 })
1466 .setIcon(a.getDrawable(
1467 com.android.internal.R.styleable.DialogPreference_dialogTitle));
1468 a.recycle();
1469
1470 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1471 new AlertDialog.OnClickListener() {
1472 public void onClick(DialogInterface dialog, int which) {
1473 synchronized (mMethodMap) {
1474 InputMethodInfo im = mIms[which];
1475 hideInputMethodMenu();
1476 setInputMethodLocked(im.getId());
1477 }
1478 }
1479 });
1480
1481 synchronized (mMethodMap) {
1482 mSwitchingDialog = mDialogBuilder.create();
1483 mSwitchingDialog.getWindow().setType(
1484 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1485 mSwitchingDialog.show();
1486 }
1487 }
1488
1489 void hideInputMethodMenu() {
The Android Open Source Project10592532009-03-18 17:39:46 -07001490 synchronized (mMethodMap) {
1491 hideInputMethodMenuLocked();
1492 }
1493 }
1494
1495 void hideInputMethodMenuLocked() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001496 if (DEBUG) Log.v(TAG, "Hide switching menu");
1497
The Android Open Source Project10592532009-03-18 17:39:46 -07001498 if (mSwitchingDialog != null) {
1499 mSwitchingDialog.dismiss();
1500 mSwitchingDialog = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001501 }
The Android Open Source Project10592532009-03-18 17:39:46 -07001502
1503 mDialogBuilder = null;
1504 mItems = null;
1505 mIms = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001506 }
1507
1508 // ----------------------------------------------------------------------
1509
1510 public boolean setInputMethodEnabled(String id, boolean enabled) {
1511 synchronized (mMethodMap) {
1512 if (mContext.checkCallingOrSelfPermission(
1513 android.Manifest.permission.WRITE_SECURE_SETTINGS)
1514 != PackageManager.PERMISSION_GRANTED) {
1515 throw new SecurityException(
1516 "Requires permission "
1517 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1518 }
1519
1520 long ident = Binder.clearCallingIdentity();
1521 try {
1522 // Make sure this is a valid input method.
1523 InputMethodInfo imm = mMethodMap.get(id);
1524 if (imm == null) {
1525 if (imm == null) {
1526 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1527 }
1528 }
1529
1530 StringBuilder builder = new StringBuilder(256);
1531
1532 boolean removed = false;
1533 String firstId = null;
1534
1535 // Look through the currently enabled input methods.
1536 String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
1537 Settings.Secure.ENABLED_INPUT_METHODS);
1538 if (enabledStr != null) {
1539 final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
1540 splitter.setString(enabledStr);
1541 while (splitter.hasNext()) {
1542 String curId = splitter.next();
1543 if (curId.equals(id)) {
1544 if (enabled) {
1545 // We are enabling this input method, but it is
1546 // already enabled. Nothing to do. The previous
1547 // state was enabled.
1548 return true;
1549 }
1550 // We are disabling this input method, and it is
1551 // currently enabled. Skip it to remove from the
1552 // new list.
1553 removed = true;
1554 } else if (!enabled) {
1555 // We are building a new list of input methods that
1556 // doesn't contain the given one.
1557 if (firstId == null) firstId = curId;
1558 if (builder.length() > 0) builder.append(':');
1559 builder.append(curId);
1560 }
1561 }
1562 }
1563
1564 if (!enabled) {
1565 if (!removed) {
1566 // We are disabling the input method but it is already
1567 // disabled. Nothing to do. The previous state was
1568 // disabled.
1569 return false;
1570 }
1571 // Update the setting with the new list of input methods.
1572 Settings.Secure.putString(mContext.getContentResolver(),
1573 Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
1574 // We the disabled input method is currently selected, switch
1575 // to another one.
1576 String selId = Settings.Secure.getString(mContext.getContentResolver(),
1577 Settings.Secure.DEFAULT_INPUT_METHOD);
1578 if (id.equals(selId)) {
1579 Settings.Secure.putString(mContext.getContentResolver(),
1580 Settings.Secure.DEFAULT_INPUT_METHOD,
1581 firstId != null ? firstId : "");
1582 }
1583 // Previous state was enabled.
1584 return true;
1585 }
1586
1587 // Add in the newly enabled input method.
1588 if (enabledStr == null || enabledStr.length() == 0) {
1589 enabledStr = id;
1590 } else {
1591 enabledStr = enabledStr + ':' + id;
1592 }
1593
1594 Settings.Secure.putString(mContext.getContentResolver(),
1595 Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
1596
1597 // Previous state was disabled.
1598 return false;
1599 } finally {
1600 Binder.restoreCallingIdentity(ident);
1601 }
1602 }
1603 }
The Android Open Source Project4df24232009-03-05 14:34:35 -08001604
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001605 // ----------------------------------------------------------------------
1606
1607 @Override
1608 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1609 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
1610 != PackageManager.PERMISSION_GRANTED) {
1611
1612 pw.println("Permission Denial: can't dump InputMethodManager from from pid="
1613 + Binder.getCallingPid()
1614 + ", uid=" + Binder.getCallingUid());
1615 return;
1616 }
1617
1618 IInputMethod method;
1619 ClientState client;
1620
1621 final Printer p = new PrintWriterPrinter(pw);
1622
1623 synchronized (mMethodMap) {
1624 p.println("Current Input Method Manager state:");
1625 int N = mMethodList.size();
1626 p.println(" Input Methods:");
1627 for (int i=0; i<N; i++) {
1628 InputMethodInfo info = mMethodList.get(i);
1629 p.println(" InputMethod #" + i + ":");
1630 info.dump(p, " ");
1631 }
1632 p.println(" Clients:");
1633 for (ClientState ci : mClients.values()) {
1634 p.println(" Client " + ci + ":");
1635 p.println(" client=" + ci.client);
1636 p.println(" inputContext=" + ci.inputContext);
1637 p.println(" sessionRequested=" + ci.sessionRequested);
1638 p.println(" curSession=" + ci.curSession);
1639 }
1640 p.println(" mInputMethodIcon=" + mInputMethodIcon);
1641 p.println(" mInputMethodData=" + mInputMethodData);
The Android Open Source Project10592532009-03-18 17:39:46 -07001642 p.println(" mCurMethodId=" + mCurMethodId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001643 client = mCurClient;
The Android Open Source Projectb2a3dd82009-03-09 11:52:12 -07001644 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
1645 p.println(" mCurFocusedWindow=" + mCurFocusedWindow);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001646 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
1647 + " mBoundToMethod=" + mBoundToMethod);
1648 p.println(" mCurToken=" + mCurToken);
1649 p.println(" mCurIntent=" + mCurIntent);
1650 method = mCurMethod;
1651 p.println(" mCurMethod=" + mCurMethod);
1652 p.println(" mEnabledSession=" + mEnabledSession);
1653 p.println(" mShowRequested=" + mShowRequested
1654 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
1655 + " mShowForced=" + mShowForced
1656 + " mInputShown=" + mInputShown);
Dianne Hackborncc278702009-09-02 23:07:23 -07001657 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001658 }
1659
1660 if (client != null) {
1661 p.println(" ");
1662 pw.flush();
1663 try {
1664 client.client.asBinder().dump(fd, args);
1665 } catch (RemoteException e) {
1666 p.println("Input method client dead: " + e);
1667 }
1668 }
1669
1670 if (method != null) {
1671 p.println(" ");
1672 pw.flush();
1673 try {
1674 method.asBinder().dump(fd, args);
1675 } catch (RemoteException e) {
1676 p.println("Input method service dead: " + e);
1677 }
1678 }
1679 }
1680}