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