1 /* 2 * Copyright (C) 2007 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 17 package com.android.server.search; 18 19 import android.app.ActivityManager; 20 import android.app.IActivityManager; 21 import android.app.ISearchManager; 22 import android.app.SearchManager; 23 import android.app.SearchableInfo; 24 import android.content.ComponentName; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.database.ContentObserver; 31 import android.os.Binder; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.RemoteException; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.provider.Settings; 38 import android.service.voice.VoiceInteractionService; 39 import android.util.Log; 40 import android.util.SparseArray; 41 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.content.PackageMonitor; 44 import com.android.internal.os.BackgroundThread; 45 import com.android.internal.util.DumpUtils; 46 import com.android.internal.util.IndentingPrintWriter; 47 import com.android.server.LocalServices; 48 import com.android.server.SystemService; 49 import com.android.server.statusbar.StatusBarManagerInternal; 50 51 import java.io.FileDescriptor; 52 import java.io.PrintWriter; 53 import java.util.List; 54 55 /** 56 * The search manager service handles the search UI, and maintains a registry of 57 * searchable activities. 58 */ 59 public class SearchManagerService extends ISearchManager.Stub { 60 private static final String TAG = "SearchManagerService"; 61 final Handler mHandler; 62 63 public static class Lifecycle extends SystemService { 64 private SearchManagerService mService; 65 66 public Lifecycle(Context context) { 67 super(context); 68 } 69 70 @Override 71 public void onStart() { 72 mService = new SearchManagerService(getContext()); 73 publishBinderService(Context.SEARCH_SERVICE, mService); 74 } 75 76 @Override 77 public void onUnlockUser(final int userId) { 78 mService.mHandler.post(new Runnable() { 79 @Override 80 public void run() { 81 mService.onUnlockUser(userId); 82 } 83 }); 84 } 85 86 @Override 87 public void onCleanupUser(int userHandle) { 88 mService.onCleanupUser(userHandle); 89 } 90 } 91 92 // Context that the service is running in. 93 private final Context mContext; 94 95 // This field is initialized lazily in getSearchables(), and then never modified. 96 @GuardedBy("mSearchables") 97 private final SparseArray<Searchables> mSearchables = new SparseArray<>(); 98 99 /** 100 * Initializes the Search Manager service in the provided system context. 101 * Only one instance of this object should be created! 102 * 103 * @param context to use for accessing DB, window manager, etc. 104 */ 105 public SearchManagerService(Context context) { 106 mContext = context; 107 new MyPackageMonitor().register(context, null, UserHandle.ALL, true); 108 new GlobalSearchProviderObserver(context.getContentResolver()); 109 mHandler = BackgroundThread.getHandler(); 110 } 111 112 private Searchables getSearchables(int userId) { 113 return getSearchables(userId, false); 114 } 115 116 private Searchables getSearchables(int userId, boolean forceUpdate) { 117 final long token = Binder.clearCallingIdentity(); 118 try { 119 final UserManager um = mContext.getSystemService(UserManager.class); 120 if (um.getUserInfo(userId) == null) { 121 throw new IllegalStateException("User " + userId + " doesn't exist"); 122 } 123 if (!um.isUserUnlockingOrUnlocked(userId)) { 124 throw new IllegalStateException("User " + userId + " isn't unlocked"); 125 } 126 } finally { 127 Binder.restoreCallingIdentity(token); 128 } 129 synchronized (mSearchables) { 130 Searchables searchables = mSearchables.get(userId); 131 if (searchables == null) { 132 searchables = new Searchables(mContext, userId); 133 searchables.updateSearchableList(); 134 mSearchables.append(userId, searchables); 135 } else if (forceUpdate) { 136 searchables.updateSearchableList(); 137 } 138 return searchables; 139 } 140 } 141 142 private void onUnlockUser(int userId) { 143 try { 144 getSearchables(userId, true); 145 } catch (IllegalStateException ignored) { 146 // We're just trying to warm a cache, so we don't mind if the user 147 // was stopped or destroyed before we got here. 148 } 149 } 150 151 private void onCleanupUser(int userId) { 152 synchronized (mSearchables) { 153 mSearchables.remove(userId); 154 } 155 } 156 157 /** 158 * Refreshes the "searchables" list when packages are added/removed. 159 */ 160 class MyPackageMonitor extends PackageMonitor { 161 162 @Override 163 public void onSomePackagesChanged() { 164 updateSearchables(); 165 } 166 167 @Override 168 public void onPackageModified(String pkg) { 169 updateSearchables(); 170 } 171 172 private void updateSearchables() { 173 final int changingUserId = getChangingUserId(); 174 synchronized (mSearchables) { 175 // Update list of searchable activities 176 for (int i = 0; i < mSearchables.size(); i++) { 177 if (changingUserId == mSearchables.keyAt(i)) { 178 mSearchables.valueAt(i).updateSearchableList(); 179 break; 180 } 181 } 182 } 183 // Inform all listeners that the list of searchables has been updated. 184 Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); 185 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING 186 | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 187 mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId)); 188 } 189 } 190 191 class GlobalSearchProviderObserver extends ContentObserver { 192 private final ContentResolver mResolver; 193 194 public GlobalSearchProviderObserver(ContentResolver resolver) { 195 super(null); 196 mResolver = resolver; 197 mResolver.registerContentObserver( 198 Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY), 199 false /* notifyDescendants */, 200 this); 201 } 202 203 @Override 204 public void onChange(boolean selfChange) { 205 synchronized (mSearchables) { 206 for (int i = 0; i < mSearchables.size(); i++) { 207 mSearchables.valueAt(i).updateSearchableList(); 208 } 209 } 210 Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); 211 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 212 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 213 } 214 } 215 216 // 217 // Searchable activities API 218 // 219 220 /** 221 * Returns the SearchableInfo for a given activity. 222 * 223 * @param launchActivity The activity from which we're launching this search. 224 * @return Returns a SearchableInfo record describing the parameters of the search, 225 * or null if no searchable metadata was available. 226 */ 227 @Override 228 public SearchableInfo getSearchableInfo(final ComponentName launchActivity) { 229 if (launchActivity == null) { 230 Log.e(TAG, "getSearchableInfo(), activity == null"); 231 return null; 232 } 233 return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity); 234 } 235 236 /** 237 * Returns a list of the searchable activities that can be included in global search. 238 */ 239 @Override 240 public List<SearchableInfo> getSearchablesInGlobalSearch() { 241 return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList(); 242 } 243 244 @Override 245 public List<ResolveInfo> getGlobalSearchActivities() { 246 return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities(); 247 } 248 249 /** 250 * Gets the name of the global search activity. 251 */ 252 @Override 253 public ComponentName getGlobalSearchActivity() { 254 return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity(); 255 } 256 257 /** 258 * Gets the name of the web search activity. 259 */ 260 @Override 261 public ComponentName getWebSearchActivity() { 262 return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity(); 263 } 264 265 @Override 266 public void launchAssist(Bundle args) { 267 StatusBarManagerInternal statusBarManager = 268 LocalServices.getService(StatusBarManagerInternal.class); 269 if (statusBarManager != null) { 270 statusBarManager.startAssist(args); 271 } 272 } 273 274 // Check and return VIS component 275 private ComponentName getLegacyAssistComponent(int userHandle) { 276 try { 277 userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(), 278 Binder.getCallingUid(), userHandle, true, false, "getLegacyAssistComponent", 279 null); 280 PackageManager pm = mContext.getPackageManager(); 281 Intent intentAssistProbe = new Intent(VoiceInteractionService.SERVICE_INTERFACE); 282 List<ResolveInfo> infoListVis = pm.queryIntentServicesAsUser(intentAssistProbe, 283 PackageManager.MATCH_SYSTEM_ONLY, userHandle); 284 if (infoListVis == null || infoListVis.isEmpty()) { 285 return null; 286 } else { 287 ResolveInfo rInfo = infoListVis.get(0); 288 return new ComponentName( 289 rInfo.serviceInfo.applicationInfo.packageName, 290 rInfo.serviceInfo.name); 291 292 } 293 } catch (Exception e) { 294 Log.e(TAG, "Exception in getLegacyAssistComponent: " + e); 295 } 296 return null; 297 } 298 299 @Override 300 public boolean launchLegacyAssist(String hint, int userHandle, Bundle args) { 301 ComponentName comp = getLegacyAssistComponent(userHandle); 302 if (comp == null) { 303 return false; 304 } 305 long ident = Binder.clearCallingIdentity(); 306 try { 307 Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); 308 intent.setComponent(comp); 309 310 IActivityManager am = ActivityManager.getService(); 311 if (args != null) { 312 args.putInt(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.KEYCODE_ASSIST); 313 } 314 intent.putExtras(args); 315 316 return am.launchAssistIntent(intent, ActivityManager.ASSIST_CONTEXT_BASIC, hint, 317 userHandle, args); 318 } catch (RemoteException e) { 319 } finally { 320 Binder.restoreCallingIdentity(ident); 321 } 322 return true; 323 } 324 325 @Override 326 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 327 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 328 329 IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 330 synchronized (mSearchables) { 331 for (int i = 0; i < mSearchables.size(); i++) { 332 ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i)); 333 ipw.increaseIndent(); 334 mSearchables.valueAt(i).dump(fd, ipw, args); 335 ipw.decreaseIndent(); 336 } 337 } 338 } 339 } 340