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