Merge change 640 into donut
* changes: Add GLOBAL_SEARCH intent for finding global search provider.
This commit is contained in:
@ -1335,6 +1335,15 @@ public class SearchManager
|
|||||||
public final static String INTENT_ACTION_CURSOR_RESPOND
|
public final static String INTENT_ACTION_CURSOR_RESPOND
|
||||||
= "android.search.action.CURSOR_RESPOND";
|
= "android.search.action.CURSOR_RESPOND";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intent action for finding the global search activity.
|
||||||
|
* The global search provider should handle this intent.
|
||||||
|
*
|
||||||
|
* @hide Pending API council approval.
|
||||||
|
*/
|
||||||
|
public final static String INTENT_ACTION_GLOBAL_SEARCH
|
||||||
|
= "android.search.action.GLOBAL_SEARCH";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intent action for starting the global search settings activity.
|
* Intent action for starting the global search settings activity.
|
||||||
* The global search provider should handle this intent.
|
* The global search provider should handle this intent.
|
||||||
|
@ -22,7 +22,6 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,23 +115,6 @@ public class SearchManagerService extends ISearchManager.Stub
|
|||||||
private void updateSearchables() {
|
private void updateSearchables() {
|
||||||
mSearchables.buildSearchableList();
|
mSearchables.buildSearchableList();
|
||||||
mSearchablesDirty = false;
|
mSearchablesDirty = false;
|
||||||
|
|
||||||
// TODO SearchableInfo should be the source of truth about whether a searchable exists.
|
|
||||||
// As it stands, if the package exists but is misconfigured in some way, then this
|
|
||||||
// would fail, and needs to be fixed.
|
|
||||||
ComponentName defaultSearch = new ComponentName(
|
|
||||||
"com.android.globalsearch",
|
|
||||||
"com.android.globalsearch.GlobalSearch");
|
|
||||||
|
|
||||||
try {
|
|
||||||
mContext.getPackageManager().getActivityInfo(defaultSearch, 0);
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
defaultSearch = new ComponentName(
|
|
||||||
"com.android.googlesearch",
|
|
||||||
"com.android.googlesearch.GoogleSearch");
|
|
||||||
}
|
|
||||||
|
|
||||||
mSearchables.setDefaultSearchable(defaultSearch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package android.server.search;
|
package android.server.search;
|
||||||
|
|
||||||
|
import android.app.SearchManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -147,22 +148,6 @@ public class Searchables {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the default searchable activity (when none is specified).
|
|
||||||
*/
|
|
||||||
public synchronized void setDefaultSearchable(ComponentName activity) {
|
|
||||||
SearchableInfo si = null;
|
|
||||||
if (activity != null) {
|
|
||||||
si = getSearchableInfo(activity);
|
|
||||||
if (si != null) {
|
|
||||||
// move to front of list
|
|
||||||
mSearchablesList.remove(si);
|
|
||||||
mSearchablesList.add(0, si);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mDefaultSearchable = si;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the system-default search activity, which you can use
|
* Provides the system-default search activity, which you can use
|
||||||
* whenever getSearchableInfo() returns null;
|
* whenever getSearchableInfo() returns null;
|
||||||
@ -199,14 +184,15 @@ public class Searchables {
|
|||||||
*/
|
*/
|
||||||
public void buildSearchableList() {
|
public void buildSearchableList() {
|
||||||
|
|
||||||
// create empty hash & list
|
// These will become the new values at the end of the method
|
||||||
HashMap<ComponentName, SearchableInfo> newSearchablesMap
|
HashMap<ComponentName, SearchableInfo> newSearchablesMap
|
||||||
= new HashMap<ComponentName, SearchableInfo>();
|
= new HashMap<ComponentName, SearchableInfo>();
|
||||||
ArrayList<SearchableInfo> newSearchablesList
|
ArrayList<SearchableInfo> newSearchablesList
|
||||||
= new ArrayList<SearchableInfo>();
|
= new ArrayList<SearchableInfo>();
|
||||||
|
|
||||||
// use intent resolver to generate list of ACTION_SEARCH receivers
|
|
||||||
final PackageManager pm = mContext.getPackageManager();
|
final PackageManager pm = mContext.getPackageManager();
|
||||||
|
|
||||||
|
// use intent resolver to generate list of ACTION_SEARCH receivers
|
||||||
List<ResolveInfo> infoList;
|
List<ResolveInfo> infoList;
|
||||||
final Intent intent = new Intent(Intent.ACTION_SEARCH);
|
final Intent intent = new Intent(Intent.ACTION_SEARCH);
|
||||||
infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
|
infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
|
||||||
@ -226,10 +212,16 @@ public class Searchables {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// record the final values as a coherent pair
|
// Find the global search provider
|
||||||
|
Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
|
||||||
|
ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
|
||||||
|
SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
|
||||||
|
|
||||||
|
// Store a consistent set of new values
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
mSearchablesList = newSearchablesList;
|
mSearchablesList = newSearchablesList;
|
||||||
mSearchablesMap = newSearchablesMap;
|
mSearchablesMap = newSearchablesMap;
|
||||||
|
mDefaultSearchable = newDefaultSearchable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.unit_tests;
|
package com.android.unit_tests;
|
||||||
|
|
||||||
|
import android.app.SearchManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -25,6 +26,7 @@ import android.content.pm.ProviderInfo;
|
|||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.server.search.SearchableInfo;
|
import android.server.search.SearchableInfo;
|
||||||
import android.server.search.Searchables;
|
import android.server.search.Searchables;
|
||||||
import android.server.search.SearchableInfo.ActionKeyInfo;
|
import android.server.search.SearchableInfo.ActionKeyInfo;
|
||||||
@ -112,6 +114,17 @@ public class SearchablesTest extends AndroidTestCase {
|
|||||||
assertNull(si);
|
assertNull(si);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that there is a default searchable (aka global search provider).
|
||||||
|
*/
|
||||||
|
public void testDefaultSearchable() {
|
||||||
|
Searchables searchables = new Searchables(mContext);
|
||||||
|
searchables.buildSearchableList();
|
||||||
|
SearchableInfo si = searchables.getDefaultSearchable();
|
||||||
|
checkSearchable(si);
|
||||||
|
assertTrue(searchables.isDefaultSearchable(si));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an attempt to run the searchable info list with a mocked context. Here are some
|
* This is an attempt to run the searchable info list with a mocked context. Here are some
|
||||||
* things I'd like to test.
|
* things I'd like to test.
|
||||||
@ -172,62 +185,66 @@ public class SearchablesTest extends AndroidTestCase {
|
|||||||
int count = searchablesList.size();
|
int count = searchablesList.size();
|
||||||
for (int ii = 0; ii < count; ii++) {
|
for (int ii = 0; ii < count; ii++) {
|
||||||
SearchableInfo si = searchablesList.get(ii);
|
SearchableInfo si = searchablesList.get(ii);
|
||||||
assertNotNull(si);
|
checkSearchable(si);
|
||||||
assertTrue(si.mSearchable);
|
|
||||||
assertTrue(si.getLabelId() != 0); // This must be a useable string
|
|
||||||
assertNotEmpty(si.mSearchActivity.getClassName());
|
|
||||||
assertNotEmpty(si.mSearchActivity.getPackageName());
|
|
||||||
if (si.getSuggestAuthority() != null) {
|
|
||||||
// The suggestion fields are largely optional, so we'll just confirm basic health
|
|
||||||
assertNotEmpty(si.getSuggestAuthority());
|
|
||||||
assertNullOrNotEmpty(si.getSuggestPath());
|
|
||||||
assertNullOrNotEmpty(si.getSuggestSelection());
|
|
||||||
assertNullOrNotEmpty(si.getSuggestIntentAction());
|
|
||||||
assertNullOrNotEmpty(si.getSuggestIntentData());
|
|
||||||
}
|
|
||||||
/* Add a way to get the entire action key list, then explicitly test its elements */
|
|
||||||
/* For now, test the most common action key (CALL) */
|
|
||||||
ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
|
|
||||||
if (ai != null) {
|
|
||||||
assertEquals(ai.mKeyCode, KeyEvent.KEYCODE_CALL);
|
|
||||||
// one of these three fields must be non-null & non-empty
|
|
||||||
boolean m1 = (ai.mQueryActionMsg != null) && (ai.mQueryActionMsg.length() > 0);
|
|
||||||
boolean m2 = (ai.mSuggestActionMsg != null) && (ai.mSuggestActionMsg.length() > 0);
|
|
||||||
boolean m3 = (ai.mSuggestActionMsgColumn != null) &&
|
|
||||||
(ai.mSuggestActionMsgColumn.length() > 0);
|
|
||||||
assertTrue(m1 || m2 || m3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Find ways to test these:
|
|
||||||
*
|
|
||||||
* private int mSearchMode
|
|
||||||
* private Drawable mIcon
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Explicitly not tested here:
|
|
||||||
*
|
|
||||||
* Can be null, so not much to see:
|
|
||||||
* public String mSearchHint
|
|
||||||
* private String mZeroQueryBanner
|
|
||||||
*
|
|
||||||
* To be deprecated/removed, so don't bother:
|
|
||||||
* public boolean mFilterMode
|
|
||||||
* public boolean mQuickStart
|
|
||||||
* private boolean mIconResized
|
|
||||||
* private int mIconResizeWidth
|
|
||||||
* private int mIconResizeHeight
|
|
||||||
*
|
|
||||||
* All of these are "internal" working variables, not part of any contract
|
|
||||||
* private ActivityInfo mActivityInfo
|
|
||||||
* private Rect mTempRect
|
|
||||||
* private String mSuggestProviderPackage
|
|
||||||
* private String mCacheActivityContext
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkSearchable(SearchableInfo si) {
|
||||||
|
assertNotNull(si);
|
||||||
|
assertTrue(si.mSearchable);
|
||||||
|
assertTrue(si.getLabelId() != 0); // This must be a useable string
|
||||||
|
assertNotEmpty(si.mSearchActivity.getClassName());
|
||||||
|
assertNotEmpty(si.mSearchActivity.getPackageName());
|
||||||
|
if (si.getSuggestAuthority() != null) {
|
||||||
|
// The suggestion fields are largely optional, so we'll just confirm basic health
|
||||||
|
assertNotEmpty(si.getSuggestAuthority());
|
||||||
|
assertNullOrNotEmpty(si.getSuggestPath());
|
||||||
|
assertNullOrNotEmpty(si.getSuggestSelection());
|
||||||
|
assertNullOrNotEmpty(si.getSuggestIntentAction());
|
||||||
|
assertNullOrNotEmpty(si.getSuggestIntentData());
|
||||||
|
}
|
||||||
|
/* Add a way to get the entire action key list, then explicitly test its elements */
|
||||||
|
/* For now, test the most common action key (CALL) */
|
||||||
|
ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
|
||||||
|
if (ai != null) {
|
||||||
|
assertEquals(ai.mKeyCode, KeyEvent.KEYCODE_CALL);
|
||||||
|
// one of these three fields must be non-null & non-empty
|
||||||
|
boolean m1 = (ai.mQueryActionMsg != null) && (ai.mQueryActionMsg.length() > 0);
|
||||||
|
boolean m2 = (ai.mSuggestActionMsg != null) && (ai.mSuggestActionMsg.length() > 0);
|
||||||
|
boolean m3 = (ai.mSuggestActionMsgColumn != null) &&
|
||||||
|
(ai.mSuggestActionMsgColumn.length() > 0);
|
||||||
|
assertTrue(m1 || m2 || m3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find ways to test these:
|
||||||
|
*
|
||||||
|
* private int mSearchMode
|
||||||
|
* private Drawable mIcon
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Explicitly not tested here:
|
||||||
|
*
|
||||||
|
* Can be null, so not much to see:
|
||||||
|
* public String mSearchHint
|
||||||
|
* private String mZeroQueryBanner
|
||||||
|
*
|
||||||
|
* To be deprecated/removed, so don't bother:
|
||||||
|
* public boolean mFilterMode
|
||||||
|
* public boolean mQuickStart
|
||||||
|
* private boolean mIconResized
|
||||||
|
* private int mIconResizeWidth
|
||||||
|
* private int mIconResizeHeight
|
||||||
|
*
|
||||||
|
* All of these are "internal" working variables, not part of any contract
|
||||||
|
* private ActivityInfo mActivityInfo
|
||||||
|
* private Rect mTempRect
|
||||||
|
* private String mSuggestProviderPackage
|
||||||
|
* private String mCacheActivityContext
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combo assert for "string not null and not empty"
|
* Combo assert for "string not null and not empty"
|
||||||
*/
|
*/
|
||||||
@ -354,6 +371,20 @@ public class SearchablesTest extends AndroidTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResolveInfo resolveActivity(Intent intent, int flags) {
|
||||||
|
assertNotNull(intent);
|
||||||
|
assertEquals(intent.getAction(), SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
|
||||||
|
switch (mSearchablesMode) {
|
||||||
|
case SEARCHABLES_PASSTHROUGH:
|
||||||
|
return mRealPackageManager.resolveActivity(intent, flags);
|
||||||
|
case SEARCHABLES_MOCK_ZERO:
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an XML file from a package. This is a low-level API used to
|
* Retrieve an XML file from a package. This is a low-level API used to
|
||||||
* retrieve XML meta data.
|
* retrieve XML meta data.
|
||||||
|
Reference in New Issue
Block a user