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