blob: f3b9b18e6c5e9b9cd8f343c763f2bc3fa898995d [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.search;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.statusbar.StatusBarManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* The search manager service handles the search UI, and maintains a registry of
* searchable activities.
*/
public class SearchManagerService extends ISearchManager.Stub {
private static final String TAG = "SearchManagerService";
final Handler mHandler;
public static class Lifecycle extends SystemService {
private SearchManagerService mService;
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
mService = new SearchManagerService(getContext());
publishBinderService(Context.SEARCH_SERVICE, mService);
}
@Override
public void onUnlockUser(final int userId) {
mService.mHandler.post(new Runnable() {
@Override
public void run() {
mService.onUnlockUser(userId);
}
});
}
@Override
public void onCleanupUser(int userHandle) {
mService.onCleanupUser(userHandle);
}
}
// Context that the service is running in.
private final Context mContext;
// This field is initialized lazily in getSearchables(), and then never modified.
@GuardedBy("mSearchables")
private final SparseArray<Searchables> mSearchables = new SparseArray<>();
/**
* Initializes the Search Manager service in the provided system context.
* Only one instance of this object should be created!
*
* @param context to use for accessing DB, window manager, etc.
*/
public SearchManagerService(Context context) {
mContext = context;
new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
new GlobalSearchProviderObserver(context.getContentResolver());
mHandler = BackgroundThread.getHandler();
}
private Searchables getSearchables(int userId) {
return getSearchables(userId, false);
}
private Searchables getSearchables(int userId, boolean forceUpdate) {
final long token = Binder.clearCallingIdentity();
try {
final UserManager um = mContext.getSystemService(UserManager.class);
if (um.getUserInfo(userId) == null) {
throw new IllegalStateException("User " + userId + " doesn't exist");
}
if (!um.isUserUnlockingOrUnlocked(userId)) {
throw new IllegalStateException("User " + userId + " isn't unlocked");
}
} finally {
Binder.restoreCallingIdentity(token);
}
synchronized (mSearchables) {
Searchables searchables = mSearchables.get(userId);
if (searchables == null) {
searchables = new Searchables(mContext, userId);
searchables.updateSearchableList();
mSearchables.append(userId, searchables);
} else if (forceUpdate) {
searchables.updateSearchableList();
}
return searchables;
}
}
private void onUnlockUser(int userId) {
try {
getSearchables(userId, true);
} catch (IllegalStateException ignored) {
// We're just trying to warm a cache, so we don't mind if the user
// was stopped or destroyed before we got here.
}
}
private void onCleanupUser(int userId) {
synchronized (mSearchables) {
mSearchables.remove(userId);
}
}
/**
* Refreshes the "searchables" list when packages are added/removed.
*/
class MyPackageMonitor extends PackageMonitor {
@Override
public void onSomePackagesChanged() {
updateSearchables();
}
@Override
public void onPackageModified(String pkg) {
updateSearchables();
}
private void updateSearchables() {
final int changingUserId = getChangingUserId();
synchronized (mSearchables) {
// Update list of searchable activities
for (int i = 0; i < mSearchables.size(); i++) {
if (changingUserId == mSearchables.keyAt(i)) {
mSearchables.valueAt(i).updateSearchableList();
break;
}
}
}
// Inform all listeners that the list of searchables has been updated.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
}
}
class GlobalSearchProviderObserver extends ContentObserver {
private final ContentResolver mResolver;
public GlobalSearchProviderObserver(ContentResolver resolver) {
super(null);
mResolver = resolver;
mResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY),
false /* notifyDescendants */,
this);
}
@Override
public void onChange(boolean selfChange) {
synchronized (mSearchables) {
for (int i = 0; i < mSearchables.size(); i++) {
mSearchables.valueAt(i).updateSearchableList();
}
}
Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
//
// Searchable activities API
//
/**
* Returns the SearchableInfo for a given activity.
*
* @param launchActivity The activity from which we're launching this search.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
@Override
public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
if (launchActivity == null) {
Log.e(TAG, "getSearchableInfo(), activity == null");
return null;
}
return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity);
}
/**
* Returns a list of the searchable activities that can be included in global search.
*/
@Override
public List<SearchableInfo> getSearchablesInGlobalSearch() {
return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
}
@Override
public List<ResolveInfo> getGlobalSearchActivities() {
return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
}
/**
* Gets the name of the global search activity.
*/
@Override
public ComponentName getGlobalSearchActivity() {
return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
}
/**
* Gets the name of the web search activity.
*/
@Override
public ComponentName getWebSearchActivity() {
return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
}
@Override
public void launchAssist(Bundle args) {
StatusBarManagerInternal statusBarManager =
LocalServices.getService(StatusBarManagerInternal.class);
if (statusBarManager != null) {
statusBarManager.startAssist(args);
}
}
private ComponentName getLegacyAssistComponent(int userHandle) {
try {
userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userHandle, true, false, "getLegacyAssistComponent", null);
IPackageManager pm = AppGlobals.getPackageManager();
Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
ResolveInfo info =
pm.resolveIntent(assistIntent,
assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
PackageManager.MATCH_DEFAULT_ONLY, userHandle);
if (info != null) {
return new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
}
} catch (RemoteException re) {
// Local call
Log.e(TAG, "RemoteException in getLegacyAssistComponent: " + re);
} catch (Exception e) {
Log.e(TAG, "Exception in getLegacyAssistComponent: " + e);
}
return null;
}
@Override
public boolean launchLegacyAssist(String hint, int userHandle, Bundle args) {
ComponentName comp = getLegacyAssistComponent(userHandle);
if (comp == null) {
return false;
}
long ident = Binder.clearCallingIdentity();
try {
Intent intent = new Intent(Intent.ACTION_ASSIST);
intent.setComponent(comp);
IActivityManager am = ActivityManagerNative.getDefault();
return am.launchAssistIntent(intent, ActivityManager.ASSIST_CONTEXT_BASIC, hint,
userHandle, args);
} catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(ident);
}
return true;
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
synchronized (mSearchables) {
for (int i = 0; i < mSearchables.size(); i++) {
ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i));
ipw.increaseIndent();
mSearchables.valueAt(i).dump(fd, ipw, args);
ipw.decreaseIndent();
}
}
}
}