Home | History | Annotate | Download | only in search
      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