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.ActivityManagerNative; 21 import android.app.AppGlobals; 22 import android.app.IActivityManager; 23 import android.app.ISearchManager; 24 import android.app.SearchManager; 25 import android.app.SearchableInfo; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.content.pm.IPackageManager; 33 import android.content.pm.PackageManager; 34 import android.content.pm.ResolveInfo; 35 import android.database.ContentObserver; 36 import android.os.Binder; 37 import android.os.Bundle; 38 import android.os.Process; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.os.UserManager; 42 import android.provider.Settings; 43 import android.util.Log; 44 import android.util.SparseArray; 45 46 import com.android.internal.content.PackageMonitor; 47 import com.android.internal.util.IndentingPrintWriter; 48 49 import java.io.FileDescriptor; 50 import java.io.PrintWriter; 51 import java.util.List; 52 53 /** 54 * The search manager service handles the search UI, and maintains a registry of searchable 55 * activities. 56 */ 57 public class SearchManagerService extends ISearchManager.Stub { 58 59 // general debugging support 60 private static final String TAG = "SearchManagerService"; 61 62 // Context that the service is running in. 63 private final Context mContext; 64 65 // This field is initialized lazily in getSearchables(), and then never modified. 66 private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>(); 67 68 /** 69 * Initializes the Search Manager service in the provided system context. 70 * Only one instance of this object should be created! 71 * 72 * @param context to use for accessing DB, window manager, etc. 73 */ 74 public SearchManagerService(Context context) { 75 mContext = context; 76 IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); 77 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 78 mContext.registerReceiver(new BootCompletedReceiver(), filter); 79 mContext.registerReceiver(new UserReceiver(), 80 new IntentFilter(Intent.ACTION_USER_REMOVED)); 81 new MyPackageMonitor().register(context, null, UserHandle.ALL, true); 82 } 83 84 private Searchables getSearchables(int userId) { 85 long origId = Binder.clearCallingIdentity(); 86 try { 87 boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)) 88 .getUserInfo(userId) != null; 89 if (!userExists) return null; 90 } finally { 91 Binder.restoreCallingIdentity(origId); 92 } 93 synchronized (mSearchables) { 94 Searchables searchables = mSearchables.get(userId); 95 96 if (searchables == null) { 97 //Log.i(TAG, "Building list of searchable activities for userId=" + userId); 98 searchables = new Searchables(mContext, userId); 99 searchables.buildSearchableList(); 100 mSearchables.append(userId, searchables); 101 } 102 return searchables; 103 } 104 } 105 106 private void onUserRemoved(int userId) { 107 if (userId != UserHandle.USER_OWNER) { 108 synchronized (mSearchables) { 109 mSearchables.remove(userId); 110 } 111 } 112 } 113 114 /** 115 * Creates the initial searchables list after boot. 116 */ 117 private final class BootCompletedReceiver extends BroadcastReceiver { 118 @Override 119 public void onReceive(Context context, Intent intent) { 120 new Thread() { 121 @Override 122 public void run() { 123 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 124 mContext.unregisterReceiver(BootCompletedReceiver.this); 125 getSearchables(0); 126 } 127 }.start(); 128 } 129 } 130 131 private final class UserReceiver extends BroadcastReceiver { 132 @Override 133 public void onReceive(Context context, Intent intent) { 134 onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER)); 135 } 136 } 137 138 /** 139 * Refreshes the "searchables" list when packages are added/removed. 140 */ 141 class MyPackageMonitor extends PackageMonitor { 142 143 @Override 144 public void onSomePackagesChanged() { 145 updateSearchables(); 146 } 147 148 @Override 149 public void onPackageModified(String pkg) { 150 updateSearchables(); 151 } 152 153 private void updateSearchables() { 154 final int changingUserId = getChangingUserId(); 155 synchronized (mSearchables) { 156 // Update list of searchable activities 157 for (int i = 0; i < mSearchables.size(); i++) { 158 if (changingUserId == mSearchables.keyAt(i)) { 159 getSearchables(mSearchables.keyAt(i)).buildSearchableList(); 160 break; 161 } 162 } 163 } 164 // Inform all listeners that the list of searchables has been updated. 165 Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); 166 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING 167 | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 168 mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId)); 169 } 170 } 171 172 class GlobalSearchProviderObserver extends ContentObserver { 173 private final ContentResolver mResolver; 174 175 public GlobalSearchProviderObserver(ContentResolver resolver) { 176 super(null); 177 mResolver = resolver; 178 mResolver.registerContentObserver( 179 Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY), 180 false /* notifyDescendants */, 181 this); 182 } 183 184 @Override 185 public void onChange(boolean selfChange) { 186 synchronized (mSearchables) { 187 for (int i = 0; i < mSearchables.size(); i++) { 188 getSearchables(mSearchables.keyAt(i)).buildSearchableList(); 189 } 190 } 191 Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED); 192 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 193 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 194 } 195 196 } 197 198 // 199 // Searchable activities API 200 // 201 202 /** 203 * Returns the SearchableInfo for a given activity. 204 * 205 * @param launchActivity The activity from which we're launching this search. 206 * @return Returns a SearchableInfo record describing the parameters of the search, 207 * or null if no searchable metadata was available. 208 */ 209 public SearchableInfo getSearchableInfo(final ComponentName launchActivity) { 210 if (launchActivity == null) { 211 Log.e(TAG, "getSearchableInfo(), activity == null"); 212 return null; 213 } 214 return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity); 215 } 216 217 /** 218 * Returns a list of the searchable activities that can be included in global search. 219 */ 220 public List<SearchableInfo> getSearchablesInGlobalSearch() { 221 return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList(); 222 } 223 224 public List<ResolveInfo> getGlobalSearchActivities() { 225 return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities(); 226 } 227 228 /** 229 * Gets the name of the global search activity. 230 */ 231 public ComponentName getGlobalSearchActivity() { 232 return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity(); 233 } 234 235 /** 236 * Gets the name of the web search activity. 237 */ 238 public ComponentName getWebSearchActivity() { 239 return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity(); 240 } 241 242 @Override 243 public ComponentName getAssistIntent(int userHandle) { 244 try { 245 userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(), 246 Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", null); 247 IPackageManager pm = AppGlobals.getPackageManager(); 248 Intent assistIntent = new Intent(Intent.ACTION_ASSIST); 249 ResolveInfo info = 250 pm.resolveIntent(assistIntent, 251 assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()), 252 PackageManager.MATCH_DEFAULT_ONLY, userHandle); 253 if (info != null) { 254 return new ComponentName( 255 info.activityInfo.applicationInfo.packageName, 256 info.activityInfo.name); 257 } 258 } catch (RemoteException re) { 259 // Local call 260 Log.e(TAG, "RemoteException in getAssistIntent: " + re); 261 } catch (Exception e) { 262 Log.e(TAG, "Exception in getAssistIntent: " + e); 263 } 264 return null; 265 } 266 267 @Override 268 public boolean launchAssistAction(int requestType, String hint, int userHandle) { 269 ComponentName comp = getAssistIntent(userHandle); 270 if (comp == null) { 271 return false; 272 } 273 long ident = Binder.clearCallingIdentity(); 274 try { 275 Intent intent = new Intent(Intent.ACTION_ASSIST); 276 intent.setComponent(comp); 277 IActivityManager am = ActivityManagerNative.getDefault(); 278 return am.launchAssistIntent(intent, requestType, hint, userHandle); 279 } catch (RemoteException e) { 280 } finally { 281 Binder.restoreCallingIdentity(ident); 282 } 283 return true; 284 } 285 286 @Override 287 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 288 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 289 290 IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 291 synchronized (mSearchables) { 292 for (int i = 0; i < mSearchables.size(); i++) { 293 ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i)); 294 ipw.increaseIndent(); 295 mSearchables.valueAt(i).dump(fd, ipw, args); 296 ipw.decreaseIndent(); 297 } 298 } 299 } 300 } 301