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