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