SearchablesTest.java revision 1a44d5dcabc18cd5ef111f732ccff91683a1a093
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app;
18
19import android.app.SearchManager;
20import android.app.SearchableInfo;
21import android.app.SearchableInfo.ActionKeyInfo;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ActivityInfo;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.ProviderInfo;
29import android.content.pm.ResolveInfo;
30import android.content.res.Resources;
31import android.content.res.XmlResourceParser;
32import android.os.RemoteException;
33import android.server.search.Searchables;
34import android.test.AndroidTestCase;
35import android.test.MoreAsserts;
36import android.test.mock.MockContext;
37import android.test.mock.MockPackageManager;
38import android.test.suitebuilder.annotation.SmallTest;
39import android.view.KeyEvent;
40
41import java.util.ArrayList;
42import java.util.List;
43
44/**
45 * To launch this test from the command line:
46 *
47 * adb shell am instrument -w \
48 *   -e class com.android.unit_tests.SearchablesTest \
49 *   com.android.unit_tests/android.test.InstrumentationTestRunner
50 */
51@SmallTest
52public class SearchablesTest extends AndroidTestCase {
53
54    /*
55     * SearchableInfo tests
56     *  Mock the context so I can provide very specific input data
57     *  Confirm OK with "zero" searchables
58     *  Confirm "good" metadata read properly
59     *  Confirm "bad" metadata skipped properly
60     *  Confirm ordering of searchables
61     *  Confirm "good" actionkeys
62     *  confirm "bad" actionkeys are rejected
63     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
64     *  findActionKey works
65     *  getIcon works
66     */
67
68    /**
69     * The goal of this test is to confirm proper operation of the
70     * SearchableInfo helper class.
71     *
72     * TODO:  The metadata source needs to be mocked out because adding
73     * searchability metadata via this test is causing it to leak into the
74     * real system.  So for now I'm just going to test for existence of the
75     * GlobalSearch app (which is searchable).
76     */
77    public void testSearchableGlobalSearch() {
78        // test basic array & hashmap
79        Searchables searchables = new Searchables(mContext);
80        searchables.buildSearchableList();
81
82        // test linkage from another activity
83        // TODO inject this via mocking into the package manager.
84        // TODO for now, just check for searchable GlobalSearch app (this isn't really a unit test)
85        ComponentName thisActivity = new ComponentName(
86                "com.android.globalsearch",
87                "com.android.globalsearch.GlobalSearch");
88
89        SearchableInfo si = searchables.getSearchableInfo(thisActivity);
90        assertNotNull(si);
91        assertEquals(thisActivity, si.getSearchActivity());
92
93        Context appContext = si.getActivityContext(mContext);
94        assertNotNull(appContext);
95        MoreAsserts.assertNotEqual(appContext, mContext);
96        assertEquals("Quick Search Box", appContext.getString(si.getHintId()));
97        assertEquals("Quick Search Box", appContext.getString(si.getLabelId()));
98    }
99
100    /**
101     * Test that non-searchable activities return no searchable info (this would typically
102     * trigger the use of the default searchable e.g. contacts)
103     */
104    public void testNonSearchable() {
105        // test basic array & hashmap
106        Searchables searchables = new Searchables(mContext);
107        searchables.buildSearchableList();
108
109        // confirm that we return null for non-searchy activities
110        ComponentName nonActivity = new ComponentName(
111                            "com.android.frameworks.coretests",
112                            "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY");
113        SearchableInfo si = searchables.getSearchableInfo(nonActivity);
114        assertNull(si);
115    }
116
117    /**
118     * Test that there is a default searchable (aka global search provider).
119     */
120    public void testDefaultSearchable() {
121        Searchables searchables = new Searchables(mContext);
122        searchables.buildSearchableList();
123        SearchableInfo si = searchables.getDefaultSearchable();
124        checkSearchable(si);
125        assertTrue(searchables.isDefaultSearchable(si));
126    }
127
128    /**
129     * This is an attempt to run the searchable info list with a mocked context.  Here are some
130     * things I'd like to test.
131     *
132     *  Confirm OK with "zero" searchables
133     *  Confirm "good" metadata read properly
134     *  Confirm "bad" metadata skipped properly
135     *  Confirm ordering of searchables
136     *  Confirm "good" actionkeys
137     *  confirm "bad" actionkeys are rejected
138     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
139     *  findActionKey works
140     *  getIcon works
141
142     */
143    public void testSearchablesListReal() {
144        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
145        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
146
147        // build item list with real-world source data
148        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
149        Searchables searchables = new Searchables(mockContext);
150        searchables.buildSearchableList();
151        // tests with "real" searchables (deprecate, this should be a unit test)
152        ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
153        int count = searchablesList.size();
154        assertTrue(count >= 1);         // this isn't really a unit test
155        checkSearchables(searchablesList);
156        ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
157        checkSearchables(global);
158    }
159
160    /**
161     * This round of tests confirms good operations with "zero" searchables found
162     */
163    public void testSearchablesListEmpty() {
164        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
165        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
166
167        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
168        Searchables searchables = new Searchables(mockContext);
169        searchables.buildSearchableList();
170        ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
171        assertNotNull(searchablesList);
172        MoreAsserts.assertEmpty(searchablesList);
173        ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
174        MoreAsserts.assertEmpty(global);
175    }
176
177    /**
178     * Generic health checker for an array of searchables.
179     *
180     * This is designed to pass for any semi-legal searchable, without knowing much about
181     * the format of the underlying data.  It's fairly easy for a non-compliant application
182     * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
183     *
184     * @param searchables The list of searchables to examine.
185     */
186    private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
187        assertNotNull(searchablesList);
188        int count = searchablesList.size();
189        for (int ii = 0; ii < count; ii++) {
190            SearchableInfo si = searchablesList.get(ii);
191            checkSearchable(si);
192        }
193    }
194
195    private void checkSearchable(SearchableInfo si) {
196        assertNotNull(si);
197        assertTrue(si.getLabelId() != 0);        // This must be a useable string
198        assertNotEmpty(si.getSearchActivity().getClassName());
199        assertNotEmpty(si.getSearchActivity().getPackageName());
200        if (si.getSuggestAuthority() != null) {
201            // The suggestion fields are largely optional, so we'll just confirm basic health
202            assertNotEmpty(si.getSuggestAuthority());
203            assertNullOrNotEmpty(si.getSuggestPath());
204            assertNullOrNotEmpty(si.getSuggestSelection());
205            assertNullOrNotEmpty(si.getSuggestIntentAction());
206            assertNullOrNotEmpty(si.getSuggestIntentData());
207        }
208        /* Add a way to get the entire action key list, then explicitly test its elements */
209        /* For now, test the most common action key (CALL) */
210        ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
211        if (ai != null) {
212            assertEquals(ai.getKeyCode(), KeyEvent.KEYCODE_CALL);
213            // one of these three fields must be non-null & non-empty
214            boolean m1 = (ai.getQueryActionMsg() != null) && (ai.getQueryActionMsg().length() > 0);
215            boolean m2 = (ai.getSuggestActionMsg() != null) && (ai.getSuggestActionMsg().length() > 0);
216            boolean m3 = (ai.getSuggestActionMsgColumn() != null) &&
217                            (ai.getSuggestActionMsgColumn().length() > 0);
218            assertTrue(m1 || m2 || m3);
219        }
220
221        /*
222         * Find ways to test these:
223         *
224         * private int mSearchMode
225         * private Drawable mIcon
226         */
227
228        /*
229         * Explicitly not tested here:
230         *
231         * Can be null, so not much to see:
232         * public String mSearchHint
233         * private String mZeroQueryBanner
234         *
235         * To be deprecated/removed, so don't bother:
236         * public boolean mFilterMode
237         * public boolean mQuickStart
238         * private boolean mIconResized
239         * private int mIconResizeWidth
240         * private int mIconResizeHeight
241         *
242         * All of these are "internal" working variables, not part of any contract
243         * private ActivityInfo mActivityInfo
244         * private Rect mTempRect
245         * private String mSuggestProviderPackage
246         * private String mCacheActivityContext
247         */
248    }
249
250    /**
251     * Combo assert for "string not null and not empty"
252     */
253    private void assertNotEmpty(final String s) {
254        assertNotNull(s);
255        MoreAsserts.assertNotEqual(s, "");
256    }
257
258    /**
259     * Combo assert for "string null or (not null and not empty)"
260     */
261    private void assertNullOrNotEmpty(final String s) {
262        if (s != null) {
263            MoreAsserts.assertNotEqual(s, "");
264        }
265    }
266
267    /**
268     * This is a mock for context.  Used to perform a true unit test on SearchableInfo.
269     *
270     */
271    private class MyMockContext extends MockContext {
272
273        protected Context mRealContext;
274        protected PackageManager mPackageManager;
275
276        /**
277         * Constructor.
278         *
279         * @param realContext Please pass in a real context for some pass-throughs to function.
280         */
281        MyMockContext(Context realContext, PackageManager packageManager) {
282            mRealContext = realContext;
283            mPackageManager = packageManager;
284        }
285
286        /**
287         * Resources.  Pass through for now.
288         */
289        @Override
290        public Resources getResources() {
291            return mRealContext.getResources();
292        }
293
294        /**
295         * Package manager.  Pass through for now.
296         */
297        @Override
298        public PackageManager getPackageManager() {
299            return mPackageManager;
300        }
301
302        /**
303         * Package manager.  Pass through for now.
304         */
305        @Override
306        public Context createPackageContext(String packageName, int flags)
307                throws PackageManager.NameNotFoundException {
308            return mRealContext.createPackageContext(packageName, flags);
309        }
310
311        /**
312         * Message broadcast.  Pass through for now.
313         */
314        @Override
315        public void sendBroadcast(Intent intent) {
316            mRealContext.sendBroadcast(intent);
317        }
318    }
319
320/**
321 * This is a mock for package manager.  Used to perform a true unit test on SearchableInfo.
322 *
323 */
324    private class MyMockPackageManager extends MockPackageManager {
325
326        public final static int SEARCHABLES_PASSTHROUGH = 0;
327        public final static int SEARCHABLES_MOCK_ZERO = 1;
328        public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
329        public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
330
331        protected PackageManager mRealPackageManager;
332        protected int mSearchablesMode;
333
334        public MyMockPackageManager(PackageManager realPM) {
335            mRealPackageManager = realPM;
336            mSearchablesMode = SEARCHABLES_PASSTHROUGH;
337        }
338
339        /**
340         * Set the mode for various tests.
341         */
342        public void setSearchablesMode(int newMode) {
343            switch (newMode) {
344            case SEARCHABLES_PASSTHROUGH:
345            case SEARCHABLES_MOCK_ZERO:
346                mSearchablesMode = newMode;
347                break;
348
349            default:
350                throw new UnsupportedOperationException();
351            }
352        }
353
354        /**
355         * Find activities that support a given intent.
356         *
357         * Retrieve all activities that can be performed for the given intent.
358         *
359         * @param intent The desired intent as per resolveActivity().
360         * @param flags Additional option flags.  The most important is
361         *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
362         *                    those activities that support the CATEGORY_DEFAULT.
363         *
364         * @return A List<ResolveInfo> containing one entry for each matching
365         *         Activity. These are ordered from best to worst match -- that
366         *         is, the first item in the list is what is returned by
367         *         resolveActivity().  If there are no matching activities, an empty
368         *         list is returned.
369         */
370        @Override
371        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
372            assertNotNull(intent);
373            assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
374                    || intent.getAction().equals(Intent.ACTION_WEB_SEARCH));
375            switch (mSearchablesMode) {
376            case SEARCHABLES_PASSTHROUGH:
377                return mRealPackageManager.queryIntentActivities(intent, flags);
378            case SEARCHABLES_MOCK_ZERO:
379                return null;
380            default:
381                throw new UnsupportedOperationException();
382            }
383        }
384
385        @Override
386        public ResolveInfo resolveActivity(Intent intent, int flags) {
387            assertNotNull(intent);
388            assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
389                    || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
390            switch (mSearchablesMode) {
391            case SEARCHABLES_PASSTHROUGH:
392                return mRealPackageManager.resolveActivity(intent, flags);
393            case SEARCHABLES_MOCK_ZERO:
394                return null;
395            default:
396                throw new UnsupportedOperationException();
397            }
398        }
399
400        /**
401         * Retrieve an XML file from a package.  This is a low-level API used to
402         * retrieve XML meta data.
403         *
404         * @param packageName The name of the package that this xml is coming from.
405         * Can not be null.
406         * @param resid The resource identifier of the desired xml.  Can not be 0.
407         * @param appInfo Overall information about <var>packageName</var>.  This
408         * may be null, in which case the application information will be retrieved
409         * for you if needed; if you already have this information around, it can
410         * be much more efficient to supply it here.
411         *
412         * @return Returns an XmlPullParser allowing you to parse out the XML
413         * data.  Returns null if the xml resource could not be found for any
414         * reason.
415         */
416        @Override
417        public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
418            assertNotNull(packageName);
419            MoreAsserts.assertNotEqual(packageName, "");
420            MoreAsserts.assertNotEqual(resid, 0);
421            switch (mSearchablesMode) {
422            case SEARCHABLES_PASSTHROUGH:
423                return mRealPackageManager.getXml(packageName, resid, appInfo);
424            case SEARCHABLES_MOCK_ZERO:
425            default:
426                throw new UnsupportedOperationException();
427            }
428        }
429
430        /**
431         * Find a single content provider by its base path name.
432         *
433         * @param name The name of the provider to find.
434         * @param flags Additional option flags.  Currently should always be 0.
435         *
436         * @return ContentProviderInfo Information about the provider, if found,
437         *         else null.
438         */
439        @Override
440        public ProviderInfo resolveContentProvider(String name, int flags) {
441            assertNotNull(name);
442            MoreAsserts.assertNotEqual(name, "");
443            assertEquals(flags, 0);
444            switch (mSearchablesMode) {
445            case SEARCHABLES_PASSTHROUGH:
446                return mRealPackageManager.resolveContentProvider(name, flags);
447            case SEARCHABLES_MOCK_ZERO:
448            default:
449                throw new UnsupportedOperationException();
450            }
451        }
452
453        /**
454         * Get the activity information for a particular activity.
455         *
456         * @param name The name of the activity to find.
457         * @param flags Additional option flags.
458         *
459         * @return ActivityInfo Information about the activity, if found, else null.
460         */
461        @Override
462        public ActivityInfo getActivityInfo(ComponentName name, int flags)
463                throws NameNotFoundException {
464            assertNotNull(name);
465            MoreAsserts.assertNotEqual(name, "");
466            switch (mSearchablesMode) {
467            case SEARCHABLES_PASSTHROUGH:
468                return mRealPackageManager.getActivityInfo(name, flags);
469            case SEARCHABLES_MOCK_ZERO:
470                throw new NameNotFoundException();
471            default:
472                throw new UnsupportedOperationException();
473            }
474        }
475    }
476}
477
478