| /* |
| * Copyright (C) 2006 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.unit_tests; |
| |
| import com.android.unit_tests.activity.LocalActivity; |
| |
| import android.app.Activity; |
| import android.app.ISearchManager; |
| import android.app.SearchManager; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| 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.ServiceManager; |
| import android.server.search.SearchableInfo; |
| import android.server.search.SearchableInfo.ActionKeyInfo; |
| import android.test.ActivityInstrumentationTestCase; |
| import android.test.MoreAsserts; |
| import android.test.mock.MockContext; |
| import android.test.mock.MockPackageManager; |
| import android.test.suitebuilder.annotation.LargeTest; |
| import android.test.suitebuilder.annotation.MediumTest; |
| import android.util.AndroidRuntimeException; |
| 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.SearchManagerTest \ |
| * com.android.unit_tests/android.test.InstrumentationTestRunner |
| */ |
| public class SearchManagerTest extends ActivityInstrumentationTestCase<LocalActivity> { |
| |
| // If non-zero, enable a set of tests that start and stop the search manager. |
| // This is currently disabled because it's causing an unwanted jump from the unit test |
| // activity into the contacts activity. We'll put this back after we disable that jump. |
| private static final int TEST_SEARCH_START = 0; |
| |
| /* |
| * Bug list of test ideas. |
| * |
| * testSearchManagerInterfaceAvailable() |
| * Exercise the interface obtained |
| * |
| * testSearchManagerAvailable() |
| * Exercise the interface obtained |
| * |
| * testSearchManagerInvocations() |
| * FIX - make it work again |
| * stress test with a very long string |
| * |
| * 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 |
| * |
| * SearchManager tests |
| * confirm proper identification of "default" activity based on policy, not hardcoded contacts |
| * |
| * SearchBar tests |
| * Maybe have to do with framework / unittest runner - need instrumented activity? |
| * How can we unit test the suggestions content providers? |
| * Should I write unit tests for any of them? |
| * Test scenarios: |
| * type-BACK (cancel) |
| * type-GO (send) |
| * type-navigate-click (suggestion) |
| * type-action |
| * type-navigate-action (suggestion) |
| */ |
| |
| /** |
| * Local copy of activity context |
| */ |
| Context mContext; |
| |
| public SearchManagerTest() { |
| super("com.android.unit_tests", LocalActivity.class); |
| } |
| |
| /** |
| * Setup any common data for the upcoming tests. |
| */ |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| Activity testActivity = getActivity(); |
| mContext = (Context)testActivity; |
| } |
| |
| /** |
| * The goal of this test is to confirm that we can obtain |
| * a search manager interface. |
| */ |
| @MediumTest |
| public void testSearchManagerInterfaceAvailable() { |
| ISearchManager searchManager1 = ISearchManager.Stub.asInterface( |
| ServiceManager.getService(Context.SEARCH_SERVICE)); |
| assertNotNull(searchManager1); |
| } |
| |
| /** |
| * The goal of this test is to confirm that we can *only* obtain a search manager |
| * interface from an Activity context. |
| */ |
| @MediumTest |
| public void testSearchManagerContextRestrictions() { |
| SearchManager searchManager1 = (SearchManager) |
| mContext.getSystemService(Context.SEARCH_SERVICE); |
| assertNotNull(searchManager1); |
| |
| Context applicationContext = mContext.getApplicationContext(); |
| // this should fail, because you can't get a SearchManager from a non-Activity context |
| try { |
| applicationContext.getSystemService(Context.SEARCH_SERVICE); |
| assertFalse("Shouldn't retrieve SearchManager from a non-Activity context", true); |
| } catch (AndroidRuntimeException e) { |
| // happy here - we should catch this. |
| } |
| } |
| |
| /** |
| * The goal of this test is to confirm that we can obtain |
| * a search manager at any time, and that for any given context, |
| * it is a singleton. |
| */ |
| @LargeTest |
| public void testSearchManagerAvailable() { |
| SearchManager searchManager1 = (SearchManager) |
| mContext.getSystemService(Context.SEARCH_SERVICE); |
| assertNotNull(searchManager1); |
| SearchManager searchManager2 = (SearchManager) |
| mContext.getSystemService(Context.SEARCH_SERVICE); |
| assertNotNull(searchManager2); |
| assertSame( searchManager1, searchManager2 ); |
| } |
| |
| /** |
| * The goal of this test is to confirm that we can start and then |
| * stop a simple search. |
| */ |
| |
| @MediumTest |
| public void testSearchManagerInvocations() { |
| SearchManager searchManager = (SearchManager) |
| mContext.getSystemService(Context.SEARCH_SERVICE); |
| assertNotNull(searchManager); |
| |
| // TODO: make a real component name, or remove this need |
| final ComponentName cn = new ComponentName("", ""); |
| |
| if (TEST_SEARCH_START != 0) { |
| // These tests should simply run to completion w/o exceptions |
| searchManager.startSearch(null, false, cn, null, false); |
| searchManager.stopSearch(); |
| |
| searchManager.startSearch("", false, cn, null, false); |
| searchManager.stopSearch(); |
| |
| searchManager.startSearch("test search string", false, cn, null, false); |
| searchManager.stopSearch(); |
| |
| searchManager.startSearch("test search string", true, cn, null, false); |
| searchManager.stopSearch(); |
| } |
| } |
| |
| /** |
| * The goal of this test is to confirm proper operation of the |
| * SearchableInfo helper class. |
| * |
| * TODO: The metadata source needs to be mocked out because adding |
| * searchability metadata via this test is causing it to leak into the |
| * real system. So for now I'm just going to test for existence of the |
| * GoogleSearch app (which is searchable). |
| */ |
| @LargeTest |
| public void testSearchableGoogleSearch() { |
| // test basic array & hashmap |
| SearchableInfo.buildSearchableList(mContext); |
| |
| // test linkage from another activity |
| // TODO inject this via mocking into the package manager. |
| // TODO for now, just check for searchable GoogleSearch app (this isn't really a unit test) |
| ComponentName thisActivity = new ComponentName( |
| "com.android.googlesearch", |
| "com.android.googlesearch.GoogleSearch"); |
| |
| SearchableInfo si = SearchableInfo.getSearchableInfo(mContext, thisActivity); |
| assertNotNull(si); |
| assertTrue(si.mSearchable); |
| assertEquals(thisActivity, si.mSearchActivity); |
| |
| Context appContext = si.getActivityContext(mContext); |
| assertNotNull(appContext); |
| MoreAsserts.assertNotEqual(appContext, mContext); |
| assertEquals("Google Search", appContext.getString(si.getHintId())); |
| assertEquals("Google", appContext.getString(si.getLabelId())); |
| } |
| |
| /** |
| * Test that non-searchable activities return no searchable info (this would typically |
| * trigger the use of the default searchable e.g. contacts) |
| */ |
| @LargeTest |
| public void testNonSearchable() { |
| // test basic array & hashmap |
| SearchableInfo.buildSearchableList(mContext); |
| |
| // confirm that we return null for non-searchy activities |
| ComponentName nonActivity = new ComponentName( |
| "com.android.unit_tests", |
| "com.android.unit_tests.NO_SEARCH_ACTIVITY"); |
| SearchableInfo si = SearchableInfo.getSearchableInfo(mContext, 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 |
| |
| */ |
| @LargeTest |
| public void testSearchableMocked() { |
| MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager()); |
| MyMockContext mockContext = new MyMockContext(mContext, mockPM); |
| ArrayList<SearchableInfo> searchables; |
| int count; |
| |
| // build item list with real-world source data |
| mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH); |
| SearchableInfo.buildSearchableList(mockContext); |
| // tests with "real" searchables (deprecate, this should be a unit test) |
| searchables = SearchableInfo.getSearchablesList(); |
| count = searchables.size(); |
| assertTrue(count >= 1); // this isn't really a unit test |
| checkSearchables(searchables); |
| |
| // build item list with mocked search data |
| // this round of tests confirms good operations with "zero" searchables found |
| // This should return either a null pointer or an empty list |
| mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO); |
| SearchableInfo.buildSearchableList(mockContext); |
| searchables = SearchableInfo.getSearchablesList(); |
| if (searchables != null) { |
| count = searchables.size(); |
| assertTrue(count == 0); |
| } |
| } |
| |
| /** |
| * 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); |
| 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" |
| */ |
| 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); |
| } |
| } |
| |
| /** |
| * 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); |
| assertEquals(intent.getAction(), Intent.ACTION_SEARCH); |
| switch (mSearchablesMode) { |
| case SEARCHABLES_PASSTHROUGH: |
| return mRealPackageManager.queryIntentActivities(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(); |
| } |
| } |
| } |
| } |
| |