| /* |
| * Copyright (C) 2009 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.SearchManager; |
| import android.app.SearchableInfo; |
| import android.app.SearchableInfo.ActionKeyInfo; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ProviderInfo; |
| import android.content.pm.ResolveInfo; |
| import android.content.res.Resources; |
| import android.content.res.XmlResourceParser; |
| import android.os.RemoteException; |
| import com.android.server.search.Searchables; |
| import android.test.AndroidTestCase; |
| import android.test.MoreAsserts; |
| import android.test.mock.MockContext; |
| import android.test.mock.MockPackageManager; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.view.KeyEvent; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * To launch this test from the command line: |
| * |
| * adb shell am instrument -w \ |
| * -e class com.android.unit_tests.SearchablesTest \ |
| * com.android.unit_tests/android.test.InstrumentationTestRunner |
| */ |
| @SmallTest |
| public class SearchablesTest extends AndroidTestCase { |
| |
| /* |
| * SearchableInfo tests |
| * Mock the context so I can provide very specific input data |
| * Confirm OK with "zero" searchables |
| * Confirm "good" metadata read properly |
| * Confirm "bad" metadata skipped properly |
| * Confirm ordering of searchables |
| * Confirm "good" actionkeys |
| * confirm "bad" actionkeys are rejected |
| * confirm XML ordering enforced (will fail today - bug in SearchableInfo) |
| * findActionKey works |
| * getIcon works |
| */ |
| |
| /** |
| * Test that non-searchable activities return no searchable info (this would typically |
| * trigger the use of the default searchable e.g. contacts) |
| */ |
| public void testNonSearchable() { |
| // test basic array & hashmap |
| Searchables searchables = new Searchables(mContext, 0); |
| searchables.updateSearchableList(); |
| |
| // confirm that we return null for non-searchy activities |
| ComponentName nonActivity = new ComponentName( |
| "com.android.frameworks.coretests", |
| "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY"); |
| SearchableInfo si = searchables.getSearchableInfo(nonActivity); |
| assertNull(si); |
| } |
| |
| /** |
| * This is an attempt to run the searchable info list with a mocked context. Here are some |
| * things I'd like to test. |
| * |
| * Confirm OK with "zero" searchables |
| * Confirm "good" metadata read properly |
| * Confirm "bad" metadata skipped properly |
| * Confirm ordering of searchables |
| * Confirm "good" actionkeys |
| * confirm "bad" actionkeys are rejected |
| * confirm XML ordering enforced (will fail today - bug in SearchableInfo) |
| * findActionKey works |
| * getIcon works |
| |
| */ |
| public void testSearchablesListReal() { |
| MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager()); |
| MyMockContext mockContext = new MyMockContext(mContext, mockPM); |
| |
| // build item list with real-world source data |
| mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH); |
| Searchables searchables = new Searchables(mockContext, 0); |
| searchables.updateSearchableList(); |
| // tests with "real" searchables (deprecate, this should be a unit test) |
| ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList(); |
| int count = searchablesList.size(); |
| assertTrue(count >= 1); // this isn't really a unit test |
| checkSearchables(searchablesList); |
| ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList(); |
| checkSearchables(global); |
| } |
| |
| /** |
| * This round of tests confirms good operations with "zero" searchables found |
| */ |
| public void testSearchablesListEmpty() { |
| MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager()); |
| MyMockContext mockContext = new MyMockContext(mContext, mockPM); |
| |
| mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO); |
| Searchables searchables = new Searchables(mockContext, 0); |
| searchables.updateSearchableList(); |
| ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList(); |
| assertNotNull(searchablesList); |
| MoreAsserts.assertEmpty(searchablesList); |
| ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList(); |
| MoreAsserts.assertEmpty(global); |
| } |
| |
| /** |
| * Generic health checker for an array of searchables. |
| * |
| * This is designed to pass for any semi-legal searchable, without knowing much about |
| * the format of the underlying data. It's fairly easy for a non-compliant application |
| * to provide meta-data that will pass here (e.g. a non-existent suggestions authority). |
| * |
| * @param searchables The list of searchables to examine. |
| */ |
| private void checkSearchables(ArrayList<SearchableInfo> searchablesList) { |
| assertNotNull(searchablesList); |
| int count = searchablesList.size(); |
| for (int ii = 0; ii < count; ii++) { |
| SearchableInfo si = searchablesList.get(ii); |
| checkSearchable(si); |
| } |
| } |
| |
| private void checkSearchable(SearchableInfo si) { |
| assertNotNull(si); |
| assertTrue(si.getLabelId() != 0); // This must be a useable string |
| assertNotEmpty(si.getSearchActivity().getClassName()); |
| assertNotEmpty(si.getSearchActivity().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.getKeyCode(), KeyEvent.KEYCODE_CALL); |
| // one of these three fields must be non-null & non-empty |
| boolean m1 = (ai.getQueryActionMsg() != null) && (ai.getQueryActionMsg().length() > 0); |
| boolean m2 = (ai.getSuggestActionMsg() != null) && (ai.getSuggestActionMsg().length() > 0); |
| boolean m3 = (ai.getSuggestActionMsgColumn() != null) && |
| (ai.getSuggestActionMsgColumn().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" |
| */ |
| private void assertNotEmpty(final String s) { |
| assertNotNull(s); |
| MoreAsserts.assertNotEqual(s, ""); |
| } |
| |
| /** |
| * Combo assert for "string null or (not null and not empty)" |
| */ |
| private void assertNullOrNotEmpty(final String s) { |
| if (s != null) { |
| MoreAsserts.assertNotEqual(s, ""); |
| } |
| } |
| |
| /** |
| * This is a mock for context. Used to perform a true unit test on SearchableInfo. |
| * |
| */ |
| private class MyMockContext extends MockContext { |
| |
| protected Context mRealContext; |
| protected PackageManager mPackageManager; |
| |
| /** |
| * Constructor. |
| * |
| * @param realContext Please pass in a real context for some pass-throughs to function. |
| */ |
| MyMockContext(Context realContext, PackageManager packageManager) { |
| mRealContext = realContext; |
| mPackageManager = packageManager; |
| } |
| |
| /** |
| * Resources. Pass through for now. |
| */ |
| @Override |
| public Resources getResources() { |
| return mRealContext.getResources(); |
| } |
| |
| /** |
| * Package manager. Pass through for now. |
| */ |
| @Override |
| public PackageManager getPackageManager() { |
| return mPackageManager; |
| } |
| |
| /** |
| * Package manager. Pass through for now. |
| */ |
| @Override |
| public Context createPackageContext(String packageName, int flags) |
| throws PackageManager.NameNotFoundException { |
| return mRealContext.createPackageContext(packageName, flags); |
| } |
| |
| /** |
| * Message broadcast. Pass through for now. |
| */ |
| @Override |
| public void sendBroadcast(Intent intent) { |
| mRealContext.sendBroadcast(intent); |
| } |
| } |
| |
| /** |
| * This is a mock for package manager. Used to perform a true unit test on SearchableInfo. |
| * |
| */ |
| private class MyMockPackageManager extends MockPackageManager { |
| |
| public final static int SEARCHABLES_PASSTHROUGH = 0; |
| public final static int SEARCHABLES_MOCK_ZERO = 1; |
| public final static int SEARCHABLES_MOCK_ONEGOOD = 2; |
| public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3; |
| |
| protected PackageManager mRealPackageManager; |
| protected int mSearchablesMode; |
| |
| public MyMockPackageManager(PackageManager realPM) { |
| mRealPackageManager = realPM; |
| mSearchablesMode = SEARCHABLES_PASSTHROUGH; |
| } |
| |
| /** |
| * Set the mode for various tests. |
| */ |
| public void setSearchablesMode(int newMode) { |
| switch (newMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| case SEARCHABLES_MOCK_ZERO: |
| mSearchablesMode = newMode; |
| break; |
| |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** |
| * Find activities that support a given intent. |
| * |
| * Retrieve all activities that can be performed for the given intent. |
| * |
| * @param intent The desired intent as per resolveActivity(). |
| * @param flags Additional option flags. The most important is |
| * MATCH_DEFAULT_ONLY, to limit the resolution to only |
| * those activities that support the CATEGORY_DEFAULT. |
| * |
| * @return A List<ResolveInfo> containing one entry for each matching |
| * Activity. These are ordered from best to worst match -- that |
| * is, the first item in the list is what is returned by |
| * resolveActivity(). If there are no matching activities, an empty |
| * list is returned. |
| */ |
| @Override |
| public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { |
| assertNotNull(intent); |
| assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH) |
| || intent.getAction().equals(Intent.ACTION_WEB_SEARCH) |
| || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH)); |
| switch (mSearchablesMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| return mRealPackageManager.queryIntentActivities(intent, flags); |
| case SEARCHABLES_MOCK_ZERO: |
| return null; |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| @Override |
| public ResolveInfo resolveActivity(Intent intent, int flags) { |
| assertNotNull(intent); |
| assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH) |
| || intent.getAction().equals(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 XML meta data. |
| * |
| * @param packageName The name of the package that this xml is coming from. |
| * Can not be null. |
| * @param resid The resource identifier of the desired xml. Can not be 0. |
| * @param appInfo Overall information about <var>packageName</var>. This |
| * may be null, in which case the application information will be retrieved |
| * for you if needed; if you already have this information around, it can |
| * be much more efficient to supply it here. |
| * |
| * @return Returns an XmlPullParser allowing you to parse out the XML |
| * data. Returns null if the xml resource could not be found for any |
| * reason. |
| */ |
| @Override |
| public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) { |
| assertNotNull(packageName); |
| MoreAsserts.assertNotEqual(packageName, ""); |
| MoreAsserts.assertNotEqual(resid, 0); |
| switch (mSearchablesMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| return mRealPackageManager.getXml(packageName, resid, appInfo); |
| case SEARCHABLES_MOCK_ZERO: |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** |
| * Find a single content provider by its base path name. |
| * |
| * @param name The name of the provider to find. |
| * @param flags Additional option flags. Currently should always be 0. |
| * |
| * @return ContentProviderInfo Information about the provider, if found, |
| * else null. |
| */ |
| @Override |
| public ProviderInfo resolveContentProvider(String name, int flags) { |
| assertNotNull(name); |
| MoreAsserts.assertNotEqual(name, ""); |
| assertEquals(flags, 0); |
| switch (mSearchablesMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| return mRealPackageManager.resolveContentProvider(name, flags); |
| case SEARCHABLES_MOCK_ZERO: |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /** |
| * Get the activity information for a particular activity. |
| * |
| * @param name The name of the activity to find. |
| * @param flags Additional option flags. |
| * |
| * @return ActivityInfo Information about the activity, if found, else null. |
| */ |
| @Override |
| public ActivityInfo getActivityInfo(ComponentName name, int flags) |
| throws NameNotFoundException { |
| assertNotNull(name); |
| MoreAsserts.assertNotEqual(name, ""); |
| switch (mSearchablesMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| return mRealPackageManager.getActivityInfo(name, flags); |
| case SEARCHABLES_MOCK_ZERO: |
| throw new NameNotFoundException(); |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| @Override |
| public int checkPermission(String permName, String pkgName) { |
| assertNotNull(permName); |
| assertNotNull(pkgName); |
| switch (mSearchablesMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| return mRealPackageManager.checkPermission(permName, pkgName); |
| case SEARCHABLES_MOCK_ZERO: |
| return PackageManager.PERMISSION_DENIED; |
| default: |
| throw new UnsupportedOperationException(); |
| } |
| } |
| } |
| } |
| |