/*
|
* 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.ActivityTaskManager;
|
import android.app.IActivityManager;
|
import android.app.IActivityTaskManager;
|
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.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.service.voice.VoiceInteractionService;
|
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.DumpUtils;
|
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);
|
}
|
}
|
|
// Check and return VIS component
|
private ComponentName getLegacyAssistComponent(int userHandle) {
|
try {
|
userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
|
Binder.getCallingUid(), userHandle, true, false, "getLegacyAssistComponent",
|
null);
|
PackageManager pm = mContext.getPackageManager();
|
Intent intentAssistProbe = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
|
List<ResolveInfo> infoListVis = pm.queryIntentServicesAsUser(intentAssistProbe,
|
PackageManager.MATCH_SYSTEM_ONLY, userHandle);
|
if (infoListVis == null || infoListVis.isEmpty()) {
|
return null;
|
} else {
|
ResolveInfo rInfo = infoListVis.get(0);
|
return new ComponentName(
|
rInfo.serviceInfo.applicationInfo.packageName,
|
rInfo.serviceInfo.name);
|
|
}
|
} 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(VoiceInteractionService.SERVICE_INTERFACE);
|
intent.setComponent(comp);
|
|
IActivityTaskManager am = ActivityTaskManager.getService();
|
if (args != null) {
|
args.putInt(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.KEYCODE_ASSIST);
|
}
|
intent.putExtras(args);
|
|
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) {
|
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
|
|
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();
|
}
|
}
|
}
|
}
|