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.StatusBarManagerService; 30 31 import org.xmlpull.v1.XmlPullParserException; 32 33 import android.app.ActivityManagerNative; 34 import android.app.AlertDialog; 35 import android.app.PendingIntent; 36 import android.content.ComponentName; 37 import android.content.ContentResolver; 38 import android.content.Context; 39 import android.content.DialogInterface; 40 import android.content.IntentFilter; 41 import android.content.DialogInterface.OnCancelListener; 42 import android.content.Intent; 43 import android.content.ServiceConnection; 44 import android.content.pm.ApplicationInfo; 45 import android.content.pm.PackageManager; 46 import android.content.pm.ResolveInfo; 47 import android.content.pm.ServiceInfo; 48 import android.content.res.Configuration; 49 import android.content.res.Resources; 50 import android.content.res.TypedArray; 51 import android.database.ContentObserver; 52 import android.os.Binder; 53 import android.os.Handler; 54 import android.os.IBinder; 55 import android.os.IInterface; 56 import android.os.Message; 57 import android.os.Parcel; 58 import android.os.RemoteException; 59 import android.os.ResultReceiver; 60 import android.os.ServiceManager; 61 import android.os.SystemClock; 62 import android.provider.Settings; 63 import android.provider.Settings.Secure; 64 import android.text.TextUtils; 65 import android.util.EventLog; 66 import android.util.Slog; 67 import android.util.PrintWriterPrinter; 68 import android.util.Printer; 69 import android.view.IWindowManager; 70 import android.view.WindowManager; 71 import android.view.inputmethod.InputBinding; 72 import android.view.inputmethod.InputMethod; 73 import android.view.inputmethod.InputMethodInfo; 74 import android.view.inputmethod.InputMethodManager; 75 import android.view.inputmethod.EditorInfo; 76 77 import java.io.FileDescriptor; 78 import java.io.IOException; 79 import java.io.PrintWriter; 80 import java.text.Collator; 81 import java.util.ArrayList; 82 import java.util.HashMap; 83 import java.util.List; 84 import java.util.Map; 85 import java.util.TreeMap; 86 87 /** 88 * This class provides a system service that manages input methods. 89 */ 90 public class InputMethodManagerService extends IInputMethodManager.Stub 91 implements ServiceConnection, Handler.Callback { 92 static final boolean DEBUG = false; 93 static final String TAG = "InputManagerService"; 94 95 static final int MSG_SHOW_IM_PICKER = 1; 96 97 static final int MSG_UNBIND_INPUT = 1000; 98 static final int MSG_BIND_INPUT = 1010; 99 static final int MSG_SHOW_SOFT_INPUT = 1020; 100 static final int MSG_HIDE_SOFT_INPUT = 1030; 101 static final int MSG_ATTACH_TOKEN = 1040; 102 static final int MSG_CREATE_SESSION = 1050; 103 104 static final int MSG_START_INPUT = 2000; 105 static final int MSG_RESTART_INPUT = 2010; 106 107 static final int MSG_UNBIND_METHOD = 3000; 108 static final int MSG_BIND_METHOD = 3010; 109 110 static final long TIME_TO_RECONNECT = 10*1000; 111 112 final Context mContext; 113 final Handler mHandler; 114 final SettingsObserver mSettingsObserver; 115 final StatusBarManagerService mStatusBar; 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, StatusBarManagerService 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 mStatusBar = statusBar; 470 statusBar.setIconVisibility("ime", false); 471 472 buildInputMethodListLocked(mMethodList, mMethodMap); 473 474 final String enabledStr = Settings.Secure.getString( 475 mContext.getContentResolver(), 476 Settings.Secure.ENABLED_INPUT_METHODS); 477 Slog.i(TAG, "Enabled input methods: " + enabledStr); 478 final String defaultIme = Settings.Secure.getString(mContext 479 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 480 if (enabledStr == null || TextUtils.isEmpty(defaultIme)) { 481 Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all"); 482 InputMethodInfo defIm = null; 483 StringBuilder sb = new StringBuilder(256); 484 final int N = mMethodList.size(); 485 for (int i=0; i<N; i++) { 486 InputMethodInfo imi = mMethodList.get(i); 487 Slog.i(TAG, "Adding: " + imi.getId()); 488 if (i > 0) sb.append(':'); 489 sb.append(imi.getId()); 490 if (defIm == null && imi.getIsDefaultResourceId() != 0) { 491 try { 492 Resources res = mContext.createPackageContext( 493 imi.getPackageName(), 0).getResources(); 494 if (res.getBoolean(imi.getIsDefaultResourceId())) { 495 defIm = imi; 496 Slog.i(TAG, "Selected default: " + imi.getId()); 497 } 498 } catch (PackageManager.NameNotFoundException ex) { 499 } catch (Resources.NotFoundException ex) { 500 } 501 } 502 } 503 if (defIm == null && N > 0) { 504 defIm = mMethodList.get(0); 505 Slog.i(TAG, "No default found, using " + defIm.getId()); 506 } 507 Settings.Secure.putString(mContext.getContentResolver(), 508 Settings.Secure.ENABLED_INPUT_METHODS, sb.toString()); 509 if (defIm != null) { 510 Settings.Secure.putString(mContext.getContentResolver(), 511 Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId()); 512 } 513 } 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 private void finishSession(SessionState sessionState) { 894 if (sessionState != null && sessionState.session != null) { 895 try { 896 sessionState.session.finishSession(); 897 } catch (RemoteException e) { 898 Slog.w(TAG, "Session failed to close due to remote exception", e); 899 } 900 } 901 } 902 903 void clearCurMethodLocked() { 904 if (mCurMethod != null) { 905 for (ClientState cs : mClients.values()) { 906 cs.sessionRequested = false; 907 finishSession(cs.curSession); 908 cs.curSession = null; 909 } 910 911 finishSession(mEnabledSession); 912 mEnabledSession = null; 913 mCurMethod = null; 914 } 915 mStatusBar.setIconVisibility("ime", false); 916 } 917 918 public void onServiceDisconnected(ComponentName name) { 919 synchronized (mMethodMap) { 920 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name 921 + " mCurIntent=" + mCurIntent); 922 if (mCurMethod != null && mCurIntent != null 923 && name.equals(mCurIntent.getComponent())) { 924 clearCurMethodLocked(); 925 // We consider this to be a new bind attempt, since the system 926 // should now try to restart the service for us. 927 mLastBindTime = SystemClock.uptimeMillis(); 928 mShowRequested = mInputShown; 929 mInputShown = false; 930 if (mCurClient != null) { 931 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 932 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 933 } 934 } 935 } 936 } 937 938 public void updateStatusIcon(IBinder token, String packageName, int iconId) { 939 int uid = Binder.getCallingUid(); 940 long ident = Binder.clearCallingIdentity(); 941 try { 942 if (token == null || mCurToken != token) { 943 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token); 944 return; 945 } 946 947 synchronized (mMethodMap) { 948 if (iconId == 0) { 949 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); 950 mStatusBar.setIconVisibility("ime", false); 951 } else if (packageName != null) { 952 if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); 953 mStatusBar.setIcon("ime", packageName, iconId, 0); 954 mStatusBar.setIconVisibility("ime", true); 955 } 956 } 957 } finally { 958 Binder.restoreCallingIdentity(ident); 959 } 960 } 961 962 void updateFromSettingsLocked() { 963 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 964 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 965 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 966 // enabled. 967 String id = Settings.Secure.getString(mContext.getContentResolver(), 968 Settings.Secure.DEFAULT_INPUT_METHOD); 969 if (id != null && id.length() > 0) { 970 try { 971 setInputMethodLocked(id); 972 } catch (IllegalArgumentException e) { 973 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 974 mCurMethodId = null; 975 unbindCurrentMethodLocked(true); 976 } 977 } else { 978 // There is no longer an input method set, so stop any current one. 979 mCurMethodId = null; 980 unbindCurrentMethodLocked(true); 981 } 982 } 983 984 void setInputMethodLocked(String id) { 985 InputMethodInfo info = mMethodMap.get(id); 986 if (info == null) { 987 throw new IllegalArgumentException("Unknown id: " + id); 988 } 989 990 if (id.equals(mCurMethodId)) { 991 return; 992 } 993 994 final long ident = Binder.clearCallingIdentity(); 995 try { 996 mCurMethodId = id; 997 Settings.Secure.putString(mContext.getContentResolver(), 998 Settings.Secure.DEFAULT_INPUT_METHOD, id); 999 1000 if (ActivityManagerNative.isSystemReady()) { 1001 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 1002 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1003 intent.putExtra("input_method_id", id); 1004 mContext.sendBroadcast(intent); 1005 } 1006 unbindCurrentClientLocked(); 1007 } finally { 1008 Binder.restoreCallingIdentity(ident); 1009 } 1010 } 1011 1012 public boolean showSoftInput(IInputMethodClient client, int flags, 1013 ResultReceiver resultReceiver) { 1014 int uid = Binder.getCallingUid(); 1015 long ident = Binder.clearCallingIdentity(); 1016 try { 1017 synchronized (mMethodMap) { 1018 if (mCurClient == null || client == null 1019 || mCurClient.client.asBinder() != client.asBinder()) { 1020 try { 1021 // We need to check if this is the current client with 1022 // focus in the window manager, to allow this call to 1023 // be made before input is started in it. 1024 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1025 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); 1026 return false; 1027 } 1028 } catch (RemoteException e) { 1029 return false; 1030 } 1031 } 1032 1033 if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); 1034 return showCurrentInputLocked(flags, resultReceiver); 1035 } 1036 } finally { 1037 Binder.restoreCallingIdentity(ident); 1038 } 1039 } 1040 1041 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1042 mShowRequested = true; 1043 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { 1044 mShowExplicitlyRequested = true; 1045 } 1046 if ((flags&InputMethodManager.SHOW_FORCED) != 0) { 1047 mShowExplicitlyRequested = true; 1048 mShowForced = true; 1049 } 1050 1051 if (!mSystemReady) { 1052 return false; 1053 } 1054 1055 boolean res = false; 1056 if (mCurMethod != null) { 1057 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( 1058 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, 1059 resultReceiver)); 1060 mInputShown = true; 1061 res = true; 1062 } else if (mHaveConnection && SystemClock.uptimeMillis() 1063 < (mLastBindTime+TIME_TO_RECONNECT)) { 1064 // The client has asked to have the input method shown, but 1065 // we have been sitting here too long with a connection to the 1066 // service and no interface received, so let's disconnect/connect 1067 // to try to prod things along. 1068 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId, 1069 SystemClock.uptimeMillis()-mLastBindTime,1); 1070 mContext.unbindService(this); 1071 mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE); 1072 } 1073 1074 return res; 1075 } 1076 1077 public boolean hideSoftInput(IInputMethodClient client, int flags, 1078 ResultReceiver resultReceiver) { 1079 int uid = Binder.getCallingUid(); 1080 long ident = Binder.clearCallingIdentity(); 1081 try { 1082 synchronized (mMethodMap) { 1083 if (mCurClient == null || client == null 1084 || mCurClient.client.asBinder() != client.asBinder()) { 1085 try { 1086 // We need to check if this is the current client with 1087 // focus in the window manager, to allow this call to 1088 // be made before input is started in it. 1089 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1090 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " 1091 + uid + ": " + client); 1092 return false; 1093 } 1094 } catch (RemoteException e) { 1095 return false; 1096 } 1097 } 1098 1099 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); 1100 return hideCurrentInputLocked(flags, resultReceiver); 1101 } 1102 } finally { 1103 Binder.restoreCallingIdentity(ident); 1104 } 1105 } 1106 1107 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1108 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 1109 && (mShowExplicitlyRequested || mShowForced)) { 1110 if (DEBUG) Slog.v(TAG, 1111 "Not hiding: explicit show not cancelled by non-explicit hide"); 1112 return false; 1113 } 1114 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 1115 if (DEBUG) Slog.v(TAG, 1116 "Not hiding: forced show not cancelled by not-always hide"); 1117 return false; 1118 } 1119 boolean res; 1120 if (mInputShown && mCurMethod != null) { 1121 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1122 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver)); 1123 res = true; 1124 } else { 1125 res = false; 1126 } 1127 mInputShown = false; 1128 mShowRequested = false; 1129 mShowExplicitlyRequested = false; 1130 mShowForced = false; 1131 return res; 1132 } 1133 1134 public void windowGainedFocus(IInputMethodClient client, IBinder windowToken, 1135 boolean viewHasFocus, boolean isTextEditor, int softInputMode, 1136 boolean first, int windowFlags) { 1137 long ident = Binder.clearCallingIdentity(); 1138 try { 1139 synchronized (mMethodMap) { 1140 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder() 1141 + " viewHasFocus=" + viewHasFocus 1142 + " isTextEditor=" + isTextEditor 1143 + " softInputMode=#" + Integer.toHexString(softInputMode) 1144 + " first=" + first + " flags=#" 1145 + Integer.toHexString(windowFlags)); 1146 1147 if (mCurClient == null || client == null 1148 || mCurClient.client.asBinder() != client.asBinder()) { 1149 try { 1150 // We need to check if this is the current client with 1151 // focus in the window manager, to allow this call to 1152 // be made before input is started in it. 1153 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1154 Slog.w(TAG, "Client not active, ignoring focus gain of: " + client); 1155 return; 1156 } 1157 } catch (RemoteException e) { 1158 } 1159 } 1160 1161 if (mCurFocusedWindow == windowToken) { 1162 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client); 1163 return; 1164 } 1165 mCurFocusedWindow = windowToken; 1166 1167 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { 1168 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 1169 if (!isTextEditor || (softInputMode & 1170 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 1171 != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { 1172 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { 1173 // There is no focus view, and this window will 1174 // be behind any soft input window, so hide the 1175 // soft input window if it is shown. 1176 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 1177 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); 1178 } 1179 } else if (isTextEditor && (softInputMode & 1180 WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 1181 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 1182 && (softInputMode & 1183 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1184 // There is a focus view, and we are navigating forward 1185 // into the window, so show the input window for the user. 1186 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 1187 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1188 } 1189 break; 1190 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 1191 // Do nothing. 1192 break; 1193 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 1194 if ((softInputMode & 1195 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1196 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 1197 hideCurrentInputLocked(0, null); 1198 } 1199 break; 1200 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 1201 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 1202 hideCurrentInputLocked(0, null); 1203 break; 1204 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 1205 if ((softInputMode & 1206 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1207 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 1208 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1209 } 1210 break; 1211 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 1212 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 1213 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1214 break; 1215 } 1216 } 1217 } finally { 1218 Binder.restoreCallingIdentity(ident); 1219 } 1220 } 1221 1222 public void showInputMethodPickerFromClient(IInputMethodClient client) { 1223 synchronized (mMethodMap) { 1224 if (mCurClient == null || client == null 1225 || mCurClient.client.asBinder() != client.asBinder()) { 1226 Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid " 1227 + Binder.getCallingUid() + ": " + client); 1228 } 1229 1230 mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER); 1231 } 1232 } 1233 1234 public void setInputMethod(IBinder token, String id) { 1235 synchronized (mMethodMap) { 1236 if (token == null) { 1237 if (mContext.checkCallingOrSelfPermission( 1238 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1239 != PackageManager.PERMISSION_GRANTED) { 1240 throw new SecurityException( 1241 "Using null token requires permission " 1242 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1243 } 1244 } else if (mCurToken != token) { 1245 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() 1246 + " token: " + token); 1247 return; 1248 } 1249 1250 long ident = Binder.clearCallingIdentity(); 1251 try { 1252 setInputMethodLocked(id); 1253 } finally { 1254 Binder.restoreCallingIdentity(ident); 1255 } 1256 } 1257 } 1258 1259 public void hideMySoftInput(IBinder token, int flags) { 1260 synchronized (mMethodMap) { 1261 if (token == null || mCurToken != token) { 1262 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid " 1263 + Binder.getCallingUid() + " token: " + token); 1264 return; 1265 } 1266 long ident = Binder.clearCallingIdentity(); 1267 try { 1268 hideCurrentInputLocked(flags, null); 1269 } finally { 1270 Binder.restoreCallingIdentity(ident); 1271 } 1272 } 1273 } 1274 1275 public void showMySoftInput(IBinder token, int flags) { 1276 synchronized (mMethodMap) { 1277 if (token == null || mCurToken != token) { 1278 Slog.w(TAG, "Ignoring showMySoftInput of uid " 1279 + Binder.getCallingUid() + " token: " + token); 1280 return; 1281 } 1282 long ident = Binder.clearCallingIdentity(); 1283 try { 1284 showCurrentInputLocked(flags, null); 1285 } finally { 1286 Binder.restoreCallingIdentity(ident); 1287 } 1288 } 1289 } 1290 1291 void setEnabledSessionInMainThread(SessionState session) { 1292 if (mEnabledSession != session) { 1293 if (mEnabledSession != null) { 1294 try { 1295 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); 1296 mEnabledSession.method.setSessionEnabled( 1297 mEnabledSession.session, false); 1298 } catch (RemoteException e) { 1299 } 1300 } 1301 mEnabledSession = session; 1302 try { 1303 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); 1304 session.method.setSessionEnabled( 1305 session.session, true); 1306 } catch (RemoteException e) { 1307 } 1308 } 1309 } 1310 1311 public boolean handleMessage(Message msg) { 1312 HandlerCaller.SomeArgs args; 1313 switch (msg.what) { 1314 case MSG_SHOW_IM_PICKER: 1315 showInputMethodMenu(); 1316 return true; 1317 1318 // --------------------------------------------------------- 1319 1320 case MSG_UNBIND_INPUT: 1321 try { 1322 ((IInputMethod)msg.obj).unbindInput(); 1323 } catch (RemoteException e) { 1324 // There is nothing interesting about the method dying. 1325 } 1326 return true; 1327 case MSG_BIND_INPUT: 1328 args = (HandlerCaller.SomeArgs)msg.obj; 1329 try { 1330 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); 1331 } catch (RemoteException e) { 1332 } 1333 return true; 1334 case MSG_SHOW_SOFT_INPUT: 1335 args = (HandlerCaller.SomeArgs)msg.obj; 1336 try { 1337 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, 1338 (ResultReceiver)args.arg2); 1339 } catch (RemoteException e) { 1340 } 1341 return true; 1342 case MSG_HIDE_SOFT_INPUT: 1343 args = (HandlerCaller.SomeArgs)msg.obj; 1344 try { 1345 ((IInputMethod)args.arg1).hideSoftInput(0, 1346 (ResultReceiver)args.arg2); 1347 } catch (RemoteException e) { 1348 } 1349 return true; 1350 case MSG_ATTACH_TOKEN: 1351 args = (HandlerCaller.SomeArgs)msg.obj; 1352 try { 1353 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); 1354 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); 1355 } catch (RemoteException e) { 1356 } 1357 return true; 1358 case MSG_CREATE_SESSION: 1359 args = (HandlerCaller.SomeArgs)msg.obj; 1360 try { 1361 ((IInputMethod)args.arg1).createSession( 1362 (IInputMethodCallback)args.arg2); 1363 } catch (RemoteException e) { 1364 } 1365 return true; 1366 // --------------------------------------------------------- 1367 1368 case MSG_START_INPUT: 1369 args = (HandlerCaller.SomeArgs)msg.obj; 1370 try { 1371 SessionState session = (SessionState)args.arg1; 1372 setEnabledSessionInMainThread(session); 1373 session.method.startInput((IInputContext)args.arg2, 1374 (EditorInfo)args.arg3); 1375 } catch (RemoteException e) { 1376 } 1377 return true; 1378 case MSG_RESTART_INPUT: 1379 args = (HandlerCaller.SomeArgs)msg.obj; 1380 try { 1381 SessionState session = (SessionState)args.arg1; 1382 setEnabledSessionInMainThread(session); 1383 session.method.restartInput((IInputContext)args.arg2, 1384 (EditorInfo)args.arg3); 1385 } catch (RemoteException e) { 1386 } 1387 return true; 1388 1389 // --------------------------------------------------------- 1390 1391 case MSG_UNBIND_METHOD: 1392 try { 1393 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1); 1394 } catch (RemoteException e) { 1395 // There is nothing interesting about the last client dying. 1396 } 1397 return true; 1398 case MSG_BIND_METHOD: 1399 args = (HandlerCaller.SomeArgs)msg.obj; 1400 try { 1401 ((IInputMethodClient)args.arg1).onBindMethod( 1402 (InputBindResult)args.arg2); 1403 } catch (RemoteException e) { 1404 Slog.w(TAG, "Client died receiving input method " + args.arg2); 1405 } 1406 return true; 1407 } 1408 return false; 1409 } 1410 1411 private boolean isSystemIme(InputMethodInfo inputMethod) { 1412 return (inputMethod.getServiceInfo().applicationInfo.flags 1413 & ApplicationInfo.FLAG_SYSTEM) != 0; 1414 } 1415 1416 private boolean chooseNewDefaultIMELocked() { 1417 List<InputMethodInfo> enabled = getEnabledInputMethodListLocked(); 1418 if (enabled != null && enabled.size() > 0) { 1419 // We'd prefer to fall back on a system IME, since that is safer. 1420 int i=enabled.size(); 1421 while (i > 0) { 1422 i--; 1423 if ((enabled.get(i).getServiceInfo().applicationInfo.flags 1424 & ApplicationInfo.FLAG_SYSTEM) != 0) { 1425 break; 1426 } 1427 } 1428 Settings.Secure.putString(mContext.getContentResolver(), 1429 Settings.Secure.DEFAULT_INPUT_METHOD, 1430 enabled.get(i).getId()); 1431 return true; 1432 } 1433 1434 return false; 1435 } 1436 1437 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list, 1438 HashMap<String, InputMethodInfo> map) { 1439 list.clear(); 1440 map.clear(); 1441 1442 PackageManager pm = mContext.getPackageManager(); 1443 final Configuration config = mContext.getResources().getConfiguration(); 1444 final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY; 1445 String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(), 1446 Secure.DISABLED_SYSTEM_INPUT_METHODS); 1447 if (disabledSysImes == null) disabledSysImes = ""; 1448 1449 List<ResolveInfo> services = pm.queryIntentServices( 1450 new Intent(InputMethod.SERVICE_INTERFACE), 1451 PackageManager.GET_META_DATA); 1452 1453 for (int i = 0; i < services.size(); ++i) { 1454 ResolveInfo ri = services.get(i); 1455 ServiceInfo si = ri.serviceInfo; 1456 ComponentName compName = new ComponentName(si.packageName, si.name); 1457 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals( 1458 si.permission)) { 1459 Slog.w(TAG, "Skipping input method " + compName 1460 + ": it does not require the permission " 1461 + android.Manifest.permission.BIND_INPUT_METHOD); 1462 continue; 1463 } 1464 1465 if (DEBUG) Slog.d(TAG, "Checking " + compName); 1466 1467 try { 1468 InputMethodInfo p = new InputMethodInfo(mContext, ri); 1469 list.add(p); 1470 final String id = p.getId(); 1471 map.put(id, p); 1472 1473 // System IMEs are enabled by default, unless there's a hard keyboard 1474 // and the system IME was explicitly disabled 1475 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) { 1476 setInputMethodEnabledLocked(id, true); 1477 } 1478 1479 if (DEBUG) { 1480 Slog.d(TAG, "Found a third-party input method " + p); 1481 } 1482 1483 } catch (XmlPullParserException e) { 1484 Slog.w(TAG, "Unable to load input method " + compName, e); 1485 } catch (IOException e) { 1486 Slog.w(TAG, "Unable to load input method " + compName, e); 1487 } 1488 } 1489 1490 String defaultIme = Settings.Secure.getString(mContext 1491 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 1492 if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) { 1493 if (chooseNewDefaultIMELocked()) { 1494 updateFromSettingsLocked(); 1495 } 1496 } 1497 } 1498 1499 // ---------------------------------------------------------------------- 1500 1501 void showInputMethodMenu() { 1502 if (DEBUG) Slog.v(TAG, "Show switching menu"); 1503 1504 final Context context = mContext; 1505 1506 final PackageManager pm = context.getPackageManager(); 1507 1508 String lastInputMethodId = Settings.Secure.getString(context 1509 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 1510 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); 1511 1512 final List<InputMethodInfo> immis = getEnabledInputMethodList(); 1513 1514 if (immis == null) { 1515 return; 1516 } 1517 1518 synchronized (mMethodMap) { 1519 hideInputMethodMenuLocked(); 1520 1521 int N = immis.size(); 1522 1523 final Map<CharSequence, InputMethodInfo> imMap = 1524 new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance()); 1525 1526 for (int i = 0; i < N; ++i) { 1527 InputMethodInfo property = immis.get(i); 1528 if (property == null) { 1529 continue; 1530 } 1531 imMap.put(property.loadLabel(pm), property); 1532 } 1533 1534 N = imMap.size(); 1535 mItems = imMap.keySet().toArray(new CharSequence[N]); 1536 mIms = imMap.values().toArray(new InputMethodInfo[N]); 1537 1538 int checkedItem = 0; 1539 for (int i = 0; i < N; ++i) { 1540 if (mIms[i].getId().equals(lastInputMethodId)) { 1541 checkedItem = i; 1542 break; 1543 } 1544 } 1545 1546 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { 1547 public void onClick(DialogInterface dialog, int which) { 1548 hideInputMethodMenu(); 1549 } 1550 }; 1551 1552 TypedArray a = context.obtainStyledAttributes(null, 1553 com.android.internal.R.styleable.DialogPreference, 1554 com.android.internal.R.attr.alertDialogStyle, 0); 1555 mDialogBuilder = new AlertDialog.Builder(context) 1556 .setTitle(com.android.internal.R.string.select_input_method) 1557 .setOnCancelListener(new OnCancelListener() { 1558 public void onCancel(DialogInterface dialog) { 1559 hideInputMethodMenu(); 1560 } 1561 }) 1562 .setIcon(a.getDrawable( 1563 com.android.internal.R.styleable.DialogPreference_dialogTitle)); 1564 a.recycle(); 1565 1566 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem, 1567 new AlertDialog.OnClickListener() { 1568 public void onClick(DialogInterface dialog, int which) { 1569 synchronized (mMethodMap) { 1570 if (mIms == null || mIms.length <= which) { 1571 return; 1572 } 1573 InputMethodInfo im = mIms[which]; 1574 hideInputMethodMenu(); 1575 if (im != null) { 1576 setInputMethodLocked(im.getId()); 1577 } 1578 } 1579 } 1580 }); 1581 1582 mSwitchingDialog = mDialogBuilder.create(); 1583 mSwitchingDialog.getWindow().setType( 1584 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 1585 mSwitchingDialog.show(); 1586 } 1587 } 1588 1589 void hideInputMethodMenu() { 1590 synchronized (mMethodMap) { 1591 hideInputMethodMenuLocked(); 1592 } 1593 } 1594 1595 void hideInputMethodMenuLocked() { 1596 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 1597 1598 if (mSwitchingDialog != null) { 1599 mSwitchingDialog.dismiss(); 1600 mSwitchingDialog = null; 1601 } 1602 1603 mDialogBuilder = null; 1604 mItems = null; 1605 mIms = null; 1606 } 1607 1608 // ---------------------------------------------------------------------- 1609 1610 public boolean setInputMethodEnabled(String id, boolean enabled) { 1611 synchronized (mMethodMap) { 1612 if (mContext.checkCallingOrSelfPermission( 1613 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1614 != PackageManager.PERMISSION_GRANTED) { 1615 throw new SecurityException( 1616 "Requires permission " 1617 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1618 } 1619 1620 long ident = Binder.clearCallingIdentity(); 1621 try { 1622 return setInputMethodEnabledLocked(id, enabled); 1623 } finally { 1624 Binder.restoreCallingIdentity(ident); 1625 } 1626 } 1627 } 1628 1629 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 1630 // Make sure this is a valid input method. 1631 InputMethodInfo imm = mMethodMap.get(id); 1632 if (imm == null) { 1633 if (imm == null) { 1634 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 1635 } 1636 } 1637 1638 StringBuilder builder = new StringBuilder(256); 1639 1640 boolean removed = false; 1641 String firstId = null; 1642 1643 // Look through the currently enabled input methods. 1644 String enabledStr = Settings.Secure.getString(mContext.getContentResolver(), 1645 Settings.Secure.ENABLED_INPUT_METHODS); 1646 if (enabledStr != null) { 1647 final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; 1648 splitter.setString(enabledStr); 1649 while (splitter.hasNext()) { 1650 String curId = splitter.next(); 1651 if (curId.equals(id)) { 1652 if (enabled) { 1653 // We are enabling this input method, but it is 1654 // already enabled. Nothing to do. The previous 1655 // state was enabled. 1656 return true; 1657 } 1658 // We are disabling this input method, and it is 1659 // currently enabled. Skip it to remove from the 1660 // new list. 1661 removed = true; 1662 } else if (!enabled) { 1663 // We are building a new list of input methods that 1664 // doesn't contain the given one. 1665 if (firstId == null) firstId = curId; 1666 if (builder.length() > 0) builder.append(':'); 1667 builder.append(curId); 1668 } 1669 } 1670 } 1671 1672 if (!enabled) { 1673 if (!removed) { 1674 // We are disabling the input method but it is already 1675 // disabled. Nothing to do. The previous state was 1676 // disabled. 1677 return false; 1678 } 1679 // Update the setting with the new list of input methods. 1680 Settings.Secure.putString(mContext.getContentResolver(), 1681 Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); 1682 // We the disabled input method is currently selected, switch 1683 // to another one. 1684 String selId = Settings.Secure.getString(mContext.getContentResolver(), 1685 Settings.Secure.DEFAULT_INPUT_METHOD); 1686 if (id.equals(selId)) { 1687 Settings.Secure.putString(mContext.getContentResolver(), 1688 Settings.Secure.DEFAULT_INPUT_METHOD, 1689 firstId != null ? firstId : ""); 1690 } 1691 // Previous state was enabled. 1692 return true; 1693 } 1694 1695 // Add in the newly enabled input method. 1696 if (enabledStr == null || enabledStr.length() == 0) { 1697 enabledStr = id; 1698 } else { 1699 enabledStr = enabledStr + ':' + id; 1700 } 1701 1702 Settings.Secure.putString(mContext.getContentResolver(), 1703 Settings.Secure.ENABLED_INPUT_METHODS, enabledStr); 1704 1705 // Previous state was disabled. 1706 return false; 1707 } 1708 1709 // ---------------------------------------------------------------------- 1710 1711 @Override 1712 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1713 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 1714 != PackageManager.PERMISSION_GRANTED) { 1715 1716 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 1717 + Binder.getCallingPid() 1718 + ", uid=" + Binder.getCallingUid()); 1719 return; 1720 } 1721 1722 IInputMethod method; 1723 ClientState client; 1724 1725 final Printer p = new PrintWriterPrinter(pw); 1726 1727 synchronized (mMethodMap) { 1728 p.println("Current Input Method Manager state:"); 1729 int N = mMethodList.size(); 1730 p.println(" Input Methods:"); 1731 for (int i=0; i<N; i++) { 1732 InputMethodInfo info = mMethodList.get(i); 1733 p.println(" InputMethod #" + i + ":"); 1734 info.dump(p, " "); 1735 } 1736 p.println(" Clients:"); 1737 for (ClientState ci : mClients.values()) { 1738 p.println(" Client " + ci + ":"); 1739 p.println(" client=" + ci.client); 1740 p.println(" inputContext=" + ci.inputContext); 1741 p.println(" sessionRequested=" + ci.sessionRequested); 1742 p.println(" curSession=" + ci.curSession); 1743 } 1744 p.println(" mCurMethodId=" + mCurMethodId); 1745 client = mCurClient; 1746 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 1747 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 1748 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 1749 + " mBoundToMethod=" + mBoundToMethod); 1750 p.println(" mCurToken=" + mCurToken); 1751 p.println(" mCurIntent=" + mCurIntent); 1752 method = mCurMethod; 1753 p.println(" mCurMethod=" + mCurMethod); 1754 p.println(" mEnabledSession=" + mEnabledSession); 1755 p.println(" mShowRequested=" + mShowRequested 1756 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 1757 + " mShowForced=" + mShowForced 1758 + " mInputShown=" + mInputShown); 1759 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 1760 } 1761 1762 p.println(" "); 1763 if (client != null) { 1764 pw.flush(); 1765 try { 1766 client.client.asBinder().dump(fd, args); 1767 } catch (RemoteException e) { 1768 p.println("Input method client dead: " + e); 1769 } 1770 } else { 1771 p.println("No input method client."); 1772 } 1773 1774 p.println(" "); 1775 if (method != null) { 1776 pw.flush(); 1777 try { 1778 method.asBinder().dump(fd, args); 1779 } catch (RemoteException e) { 1780 p.println("Input method service dead: " + e); 1781 } 1782 } else { 1783 p.println("No input method service."); 1784 } 1785 } 1786 } 1787