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