TextServicesManagerService.java revision 5357806980269d846a15c845a6fcc0384fb18860
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server; 18 19import com.android.internal.content.PackageMonitor; 20import com.android.internal.textservice.ISpellCheckerService; 21import com.android.internal.textservice.ISpellCheckerSession; 22import com.android.internal.textservice.ISpellCheckerSessionListener; 23import com.android.internal.textservice.ITextServicesManager; 24import com.android.internal.textservice.ITextServicesSessionListener; 25 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.Intent; 29import android.content.ServiceConnection; 30import android.content.pm.PackageManager; 31import android.content.pm.ResolveInfo; 32import android.content.pm.ServiceInfo; 33import android.os.Binder; 34import android.os.Bundle; 35import android.os.IBinder; 36import android.os.RemoteException; 37import android.provider.Settings; 38import android.service.textservice.SpellCheckerService; 39import android.text.TextUtils; 40import android.util.Slog; 41import android.view.textservice.SpellCheckerInfo; 42 43import java.util.ArrayList; 44import java.util.HashMap; 45import java.util.List; 46 47public class TextServicesManagerService extends ITextServicesManager.Stub { 48 private static final String TAG = TextServicesManagerService.class.getSimpleName(); 49 private static final boolean DBG = false; 50 51 private final Context mContext; 52 private boolean mSystemReady; 53 private final TextServicesMonitor mMonitor; 54 private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap = 55 new HashMap<String, SpellCheckerInfo>(); 56 private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>(); 57 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups = 58 new HashMap<String, SpellCheckerBindGroup>(); 59 60 public void systemReady() { 61 if (!mSystemReady) { 62 mSystemReady = true; 63 } 64 } 65 66 public TextServicesManagerService(Context context) { 67 mSystemReady = false; 68 mContext = context; 69 mMonitor = new TextServicesMonitor(); 70 mMonitor.register(context, true); 71 synchronized (mSpellCheckerMap) { 72 buildSpellCheckerMapLocked(context, mSpellCheckerList, mSpellCheckerMap); 73 } 74 SpellCheckerInfo sci = getCurrentSpellChecker(null); 75 if (sci == null) { 76 sci = findAvailSpellCheckerLocked(null, null); 77 if (sci != null) { 78 // Set the current spell checker if there is one or more spell checkers 79 // available. In this case, "sci" is the first one in the available spell 80 // checkers. 81 setCurrentSpellCheckerLocked(sci.getId()); 82 } 83 } 84 } 85 86 private class TextServicesMonitor extends PackageMonitor { 87 @Override 88 public void onSomePackagesChanged() { 89 synchronized (mSpellCheckerMap) { 90 buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap); 91 // TODO: Update for each locale 92 SpellCheckerInfo sci = getCurrentSpellChecker(null); 93 if (sci == null) return; 94 final String packageName = sci.getPackageName(); 95 final int change = isPackageDisappearing(packageName); 96 if (// Package disappearing 97 change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE 98 // Package modified 99 || isPackageModified(packageName)) { 100 sci = findAvailSpellCheckerLocked(null, packageName); 101 if (sci != null) { 102 setCurrentSpellCheckerLocked(sci.getId()); 103 } 104 } 105 } 106 } 107 } 108 109 private static void buildSpellCheckerMapLocked(Context context, 110 ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map) { 111 list.clear(); 112 map.clear(); 113 final PackageManager pm = context.getPackageManager(); 114 List<ResolveInfo> services = pm.queryIntentServices( 115 new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA); 116 final int N = services.size(); 117 for (int i = 0; i < N; ++i) { 118 final ResolveInfo ri = services.get(i); 119 final ServiceInfo si = ri.serviceInfo; 120 final ComponentName compName = new ComponentName(si.packageName, si.name); 121 if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) { 122 Slog.w(TAG, "Skipping text service " + compName 123 + ": it does not require the permission " 124 + android.Manifest.permission.BIND_TEXT_SERVICE); 125 continue; 126 } 127 if (DBG) Slog.d(TAG, "Add: " + compName); 128 final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri); 129 list.add(sci); 130 map.put(sci.getId(), sci); 131 } 132 if (DBG) { 133 Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size()); 134 } 135 } 136 137 // TODO: find an appropriate spell checker for specified locale 138 private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) { 139 final int spellCheckersCount = mSpellCheckerList.size(); 140 if (spellCheckersCount == 0) { 141 Slog.w(TAG, "no available spell checker services found"); 142 return null; 143 } 144 if (prefPackage != null) { 145 for (int i = 0; i < spellCheckersCount; ++i) { 146 final SpellCheckerInfo sci = mSpellCheckerList.get(i); 147 if (prefPackage.equals(sci.getPackageName())) { 148 if (DBG) { 149 Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName()); 150 } 151 return sci; 152 } 153 } 154 } 155 if (spellCheckersCount > 1) { 156 Slog.w(TAG, "more than one spell checker service found, picking first"); 157 } 158 return mSpellCheckerList.get(0); 159 } 160 161 // TODO: Save SpellCheckerService by supported languages. Currently only one spell 162 // checker is saved. 163 @Override 164 public SpellCheckerInfo getCurrentSpellChecker(String locale) { 165 synchronized (mSpellCheckerMap) { 166 String curSpellCheckerId = 167 Settings.Secure.getString(mContext.getContentResolver(), 168 Settings.Secure.SPELL_CHECKER_SERVICE); 169 if (DBG) { 170 Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId); 171 } 172 if (TextUtils.isEmpty(curSpellCheckerId)) { 173 return null; 174 } 175 return mSpellCheckerMap.get(curSpellCheckerId); 176 } 177 } 178 179 @Override 180 public void getSpellCheckerService(String sciId, String locale, 181 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener, 182 Bundle bundle) { 183 if (!mSystemReady) { 184 return; 185 } 186 if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) { 187 Slog.e(TAG, "getSpellCheckerService: Invalid input."); 188 return; 189 } 190 synchronized(mSpellCheckerMap) { 191 if (!mSpellCheckerMap.containsKey(sciId)) { 192 return; 193 } 194 final SpellCheckerInfo sci = mSpellCheckerMap.get(sciId); 195 final int uid = Binder.getCallingUid(); 196 if (mSpellCheckerBindGroups.containsKey(sciId)) { 197 final SpellCheckerBindGroup bindGroup = mSpellCheckerBindGroups.get(sciId); 198 if (bindGroup != null) { 199 final InternalDeathRecipient recipient = 200 mSpellCheckerBindGroups.get(sciId).addListener( 201 tsListener, locale, scListener, uid, bundle); 202 if (recipient == null) { 203 if (DBG) { 204 Slog.w(TAG, "Didn't create a death recipient."); 205 } 206 return; 207 } 208 if (bindGroup.mSpellChecker == null & bindGroup.mConnected) { 209 Slog.e(TAG, "The state of the spell checker bind group is illegal."); 210 bindGroup.removeAll(); 211 } else if (bindGroup.mSpellChecker != null) { 212 if (DBG) { 213 Slog.w(TAG, "Existing bind found. Return a spell checker session now. " 214 + "Listeners count = " + bindGroup.mListeners.size()); 215 } 216 try { 217 final ISpellCheckerSession session = 218 bindGroup.mSpellChecker.getISpellCheckerSession( 219 recipient.mScLocale, recipient.mScListener, bundle); 220 if (session != null) { 221 tsListener.onServiceConnected(session); 222 return; 223 } else { 224 if (DBG) { 225 Slog.w(TAG, "Existing bind already expired. "); 226 } 227 bindGroup.removeAll(); 228 } 229 } catch (RemoteException e) { 230 Slog.e(TAG, "Exception in getting spell checker session: " + e); 231 bindGroup.removeAll(); 232 } 233 } 234 } 235 } 236 final long ident = Binder.clearCallingIdentity(); 237 try { 238 startSpellCheckerServiceInnerLocked( 239 sci, locale, tsListener, scListener, uid, bundle); 240 } finally { 241 Binder.restoreCallingIdentity(ident); 242 } 243 } 244 return; 245 } 246 247 private void startSpellCheckerServiceInnerLocked(SpellCheckerInfo info, String locale, 248 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener, 249 int uid, Bundle bundle) { 250 if (DBG) { 251 Slog.w(TAG, "Start spell checker session inner locked."); 252 } 253 final String sciId = info.getId(); 254 final InternalServiceConnection connection = new InternalServiceConnection( 255 sciId, locale, scListener, bundle); 256 final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE); 257 serviceIntent.setComponent(info.getComponent()); 258 if (DBG) { 259 Slog.w(TAG, "bind service: " + info.getId()); 260 } 261 if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) { 262 Slog.e(TAG, "Failed to get a spell checker service."); 263 return; 264 } 265 final SpellCheckerBindGroup group = new SpellCheckerBindGroup( 266 connection, tsListener, locale, scListener, uid, bundle); 267 mSpellCheckerBindGroups.put(sciId, group); 268 } 269 270 @Override 271 public SpellCheckerInfo[] getEnabledSpellCheckers() { 272 if (DBG) { 273 Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size()); 274 for (int i = 0; i < mSpellCheckerList.size(); ++i) { 275 Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName()); 276 } 277 } 278 return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]); 279 } 280 281 @Override 282 public void finishSpellCheckerService(ISpellCheckerSessionListener listener) { 283 if (DBG) { 284 Slog.d(TAG, "FinishSpellCheckerService"); 285 } 286 synchronized(mSpellCheckerMap) { 287 for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) { 288 if (group == null) continue; 289 group.removeListener(listener); 290 } 291 } 292 } 293 294 @Override 295 public void setCurrentSpellChecker(String sciId) { 296 synchronized(mSpellCheckerMap) { 297 if (mContext.checkCallingOrSelfPermission( 298 android.Manifest.permission.WRITE_SECURE_SETTINGS) 299 != PackageManager.PERMISSION_GRANTED) { 300 throw new SecurityException( 301 "Requires permission " 302 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 303 } 304 setCurrentSpellCheckerLocked(sciId); 305 } 306 } 307 308 private void setCurrentSpellCheckerLocked(String sciId) { 309 if (DBG) { 310 Slog.w(TAG, "setCurrentSpellChecker: " + sciId); 311 } 312 if (TextUtils.isEmpty(sciId) || !mSpellCheckerMap.containsKey(sciId)) return; 313 final long ident = Binder.clearCallingIdentity(); 314 try { 315 Settings.Secure.putString(mContext.getContentResolver(), 316 Settings.Secure.SPELL_CHECKER_SERVICE, sciId); 317 } finally { 318 Binder.restoreCallingIdentity(ident); 319 } 320 } 321 322 // SpellCheckerBindGroup contains active text service session listeners. 323 // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from 324 // mSpellCheckerBindGroups 325 private class SpellCheckerBindGroup { 326 private final String TAG = SpellCheckerBindGroup.class.getSimpleName(); 327 private final InternalServiceConnection mInternalConnection; 328 private final ArrayList<InternalDeathRecipient> mListeners = 329 new ArrayList<InternalDeathRecipient>(); 330 public ISpellCheckerService mSpellChecker; 331 public boolean mConnected; 332 333 public SpellCheckerBindGroup(InternalServiceConnection connection, 334 ITextServicesSessionListener listener, String locale, 335 ISpellCheckerSessionListener scListener, int uid, Bundle bundle) { 336 mInternalConnection = connection; 337 mConnected = false; 338 addListener(listener, locale, scListener, uid, bundle); 339 } 340 341 public void onServiceConnected(ISpellCheckerService spellChecker) { 342 if (DBG) { 343 Slog.d(TAG, "onServiceConnected"); 344 } 345 synchronized(mSpellCheckerMap) { 346 for (InternalDeathRecipient listener : mListeners) { 347 try { 348 final ISpellCheckerSession session = spellChecker.getISpellCheckerSession( 349 listener.mScLocale, listener.mScListener, listener.mBundle); 350 listener.mTsListener.onServiceConnected(session); 351 } catch (RemoteException e) { 352 Slog.e(TAG, "Exception in getting the spell checker session: " + e); 353 removeAll(); 354 return; 355 } 356 } 357 mSpellChecker = spellChecker; 358 mConnected = true; 359 } 360 } 361 362 public InternalDeathRecipient addListener(ITextServicesSessionListener tsListener, 363 String locale, ISpellCheckerSessionListener scListener, int uid, Bundle bundle) { 364 if (DBG) { 365 Slog.d(TAG, "addListener: " + locale); 366 } 367 InternalDeathRecipient recipient = null; 368 synchronized(mSpellCheckerMap) { 369 try { 370 final int size = mListeners.size(); 371 for (int i = 0; i < size; ++i) { 372 if (mListeners.get(i).hasSpellCheckerListener(scListener)) { 373 // do not add the lister if the group already contains this. 374 return null; 375 } 376 } 377 recipient = new InternalDeathRecipient( 378 this, tsListener, locale, scListener, uid, bundle); 379 scListener.asBinder().linkToDeath(recipient, 0); 380 mListeners.add(recipient); 381 } catch(RemoteException e) { 382 // do nothing 383 } 384 cleanLocked(); 385 } 386 return recipient; 387 } 388 389 public void removeListener(ISpellCheckerSessionListener listener) { 390 if (DBG) { 391 Slog.w(TAG, "remove listener: " + listener.hashCode()); 392 } 393 synchronized(mSpellCheckerMap) { 394 final int size = mListeners.size(); 395 final ArrayList<InternalDeathRecipient> removeList = 396 new ArrayList<InternalDeathRecipient>(); 397 for (int i = 0; i < size; ++i) { 398 final InternalDeathRecipient tempRecipient = mListeners.get(i); 399 if(tempRecipient.hasSpellCheckerListener(listener)) { 400 if (DBG) { 401 Slog.w(TAG, "found existing listener."); 402 } 403 removeList.add(tempRecipient); 404 } 405 } 406 final int removeSize = removeList.size(); 407 for (int i = 0; i < removeSize; ++i) { 408 if (DBG) { 409 Slog.w(TAG, "Remove " + removeList.get(i)); 410 } 411 mListeners.remove(removeList.get(i)); 412 } 413 cleanLocked(); 414 } 415 } 416 417 private void cleanLocked() { 418 if (DBG) { 419 Slog.d(TAG, "cleanLocked"); 420 } 421 if (mListeners.isEmpty()) { 422 if (mSpellCheckerBindGroups.containsKey(this)) { 423 mSpellCheckerBindGroups.remove(this); 424 } 425 // Unbind service when there is no active clients. 426 mContext.unbindService(mInternalConnection); 427 } 428 } 429 430 public void removeAll() { 431 Slog.e(TAG, "Remove the spell checker bind unexpectedly."); 432 synchronized(mSpellCheckerMap) { 433 mListeners.clear(); 434 cleanLocked(); 435 } 436 } 437 } 438 439 private class InternalServiceConnection implements ServiceConnection { 440 private final ISpellCheckerSessionListener mListener; 441 private final String mSciId; 442 private final String mLocale; 443 private final Bundle mBundle; 444 public InternalServiceConnection( 445 String id, String locale, ISpellCheckerSessionListener listener, Bundle bundle) { 446 mSciId = id; 447 mLocale = locale; 448 mListener = listener; 449 mBundle = bundle; 450 } 451 452 @Override 453 public void onServiceConnected(ComponentName name, IBinder service) { 454 synchronized(mSpellCheckerMap) { 455 if (DBG) { 456 Slog.w(TAG, "onServiceConnected: " + name); 457 } 458 ISpellCheckerService spellChecker = ISpellCheckerService.Stub.asInterface(service); 459 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId); 460 if (group != null) { 461 group.onServiceConnected(spellChecker); 462 } 463 } 464 } 465 466 @Override 467 public void onServiceDisconnected(ComponentName name) { 468 mSpellCheckerBindGroups.remove(mSciId); 469 } 470 } 471 472 private class InternalDeathRecipient implements IBinder.DeathRecipient { 473 public final ITextServicesSessionListener mTsListener; 474 public final ISpellCheckerSessionListener mScListener; 475 public final String mScLocale; 476 private final SpellCheckerBindGroup mGroup; 477 public final int mUid; 478 public final Bundle mBundle; 479 public InternalDeathRecipient(SpellCheckerBindGroup group, 480 ITextServicesSessionListener tsListener, String scLocale, 481 ISpellCheckerSessionListener scListener, int uid, Bundle bundle) { 482 mTsListener = tsListener; 483 mScListener = scListener; 484 mScLocale = scLocale; 485 mGroup = group; 486 mUid = uid; 487 mBundle = bundle; 488 } 489 490 public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) { 491 return listener.asBinder().equals(mScListener.asBinder()); 492 } 493 494 @Override 495 public void binderDied() { 496 mGroup.removeListener(mScListener); 497 } 498 } 499} 500