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