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