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