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