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