SearchablesTest.java revision 814a8d35ae11b70027e0e476ca1c0bb589106b4d
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 com.android.server.search;
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 com.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     * Test that non-searchable activities return no searchable info (this would typically
70     * trigger the use of the default searchable e.g. contacts)
71     */
72    public void testNonSearchable() {
73        // test basic array & hashmap
74        Searchables searchables = new Searchables(mContext, 0);
75        searchables.buildSearchableList();
76
77        // confirm that we return null for non-searchy activities
78        ComponentName nonActivity = new ComponentName(
79                            "com.android.frameworks.coretests",
80                            "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY");
81        SearchableInfo si = searchables.getSearchableInfo(nonActivity);
82        assertNull(si);
83    }
84
85    /**
86     * This is an attempt to run the searchable info list with a mocked context.  Here are some
87     * things I'd like to test.
88     *
89     *  Confirm OK with "zero" searchables
90     *  Confirm "good" metadata read properly
91     *  Confirm "bad" metadata skipped properly
92     *  Confirm ordering of searchables
93     *  Confirm "good" actionkeys
94     *  confirm "bad" actionkeys are rejected
95     *  confirm XML ordering enforced (will fail today - bug in SearchableInfo)
96     *  findActionKey works
97     *  getIcon works
98
99     */
100    public void testSearchablesListReal() {
101        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
102        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
103
104        // build item list with real-world source data
105        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
106        Searchables searchables = new Searchables(mockContext, 0);
107        searchables.buildSearchableList();
108        // tests with "real" searchables (deprecate, this should be a unit test)
109        ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
110        int count = searchablesList.size();
111        assertTrue(count >= 1);         // this isn't really a unit test
112        checkSearchables(searchablesList);
113        ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
114        checkSearchables(global);
115    }
116
117    /**
118     * This round of tests confirms good operations with "zero" searchables found
119     */
120    public void testSearchablesListEmpty() {
121        MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
122        MyMockContext mockContext = new MyMockContext(mContext, mockPM);
123
124        mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
125        Searchables searchables = new Searchables(mockContext, 0);
126        searchables.buildSearchableList();
127        ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
128        assertNotNull(searchablesList);
129        MoreAsserts.assertEmpty(searchablesList);
130        ArrayList<SearchableInfo> global = searchables.getSearchablesInGlobalSearchList();
131        MoreAsserts.assertEmpty(global);
132    }
133
134    /**
135     * Generic health checker for an array of searchables.
136     *
137     * This is designed to pass for any semi-legal searchable, without knowing much about
138     * the format of the underlying data.  It's fairly easy for a non-compliant application
139     * to provide meta-data that will pass here (e.g. a non-existent suggestions authority).
140     *
141     * @param searchables The list of searchables to examine.
142     */
143    private void checkSearchables(ArrayList<SearchableInfo> searchablesList) {
144        assertNotNull(searchablesList);
145        int count = searchablesList.size();
146        for (int ii = 0; ii < count; ii++) {
147            SearchableInfo si = searchablesList.get(ii);
148            checkSearchable(si);
149        }
150    }
151
152    private void checkSearchable(SearchableInfo si) {
153        assertNotNull(si);
154        assertTrue(si.getLabelId() != 0);        // This must be a useable string
155        assertNotEmpty(si.getSearchActivity().getClassName());
156        assertNotEmpty(si.getSearchActivity().getPackageName());
157        if (si.getSuggestAuthority() != null) {
158            // The suggestion fields are largely optional, so we'll just confirm basic health
159            assertNotEmpty(si.getSuggestAuthority());
160            assertNullOrNotEmpty(si.getSuggestPath());
161            assertNullOrNotEmpty(si.getSuggestSelection());
162            assertNullOrNotEmpty(si.getSuggestIntentAction());
163            assertNullOrNotEmpty(si.getSuggestIntentData());
164        }
165        /* Add a way to get the entire action key list, then explicitly test its elements */
166        /* For now, test the most common action key (CALL) */
167        ActionKeyInfo ai = si.findActionKey(KeyEvent.KEYCODE_CALL);
168        if (ai != null) {
169            assertEquals(ai.getKeyCode(), KeyEvent.KEYCODE_CALL);
170            // one of these three fields must be non-null & non-empty
171            boolean m1 = (ai.getQueryActionMsg() != null) && (ai.getQueryActionMsg().length() > 0);
172            boolean m2 = (ai.getSuggestActionMsg() != null) && (ai.getSuggestActionMsg().length() > 0);
173            boolean m3 = (ai.getSuggestActionMsgColumn() != null) &&
174                            (ai.getSuggestActionMsgColumn().length() > 0);
175            assertTrue(m1 || m2 || m3);
176        }
177
178        /*
179         * Find ways to test these:
180         *
181         * private int mSearchMode
182         * private Drawable mIcon
183         */
184
185        /*
186         * Explicitly not tested here:
187         *
188         * Can be null, so not much to see:
189         * public String mSearchHint
190         * private String mZeroQueryBanner
191         *
192         * To be deprecated/removed, so don't bother:
193         * public boolean mFilterMode
194         * public boolean mQuickStart
195         * private boolean mIconResized
196         * private int mIconResizeWidth
197         * private int mIconResizeHeight
198         *
199         * All of these are "internal" working variables, not part of any contract
200         * private ActivityInfo mActivityInfo
201         * private Rect mTempRect
202         * private String mSuggestProviderPackage
203         * private String mCacheActivityContext
204         */
205    }
206
207    /**
208     * Combo assert for "string not null and not empty"
209     */
210    private void assertNotEmpty(final String s) {
211        assertNotNull(s);
212        MoreAsserts.assertNotEqual(s, "");
213    }
214
215    /**
216     * Combo assert for "string null or (not null and not empty)"
217     */
218    private void assertNullOrNotEmpty(final String s) {
219        if (s != null) {
220            MoreAsserts.assertNotEqual(s, "");
221        }
222    }
223
224    /**
225     * This is a mock for context.  Used to perform a true unit test on SearchableInfo.
226     *
227     */
228    private class MyMockContext extends MockContext {
229
230        protected Context mRealContext;
231        protected PackageManager mPackageManager;
232
233        /**
234         * Constructor.
235         *
236         * @param realContext Please pass in a real context for some pass-throughs to function.
237         */
238        MyMockContext(Context realContext, PackageManager packageManager) {
239            mRealContext = realContext;
240            mPackageManager = packageManager;
241        }
242
243        /**
244         * Resources.  Pass through for now.
245         */
246        @Override
247        public Resources getResources() {
248            return mRealContext.getResources();
249        }
250
251        /**
252         * Package manager.  Pass through for now.
253         */
254        @Override
255        public PackageManager getPackageManager() {
256            return mPackageManager;
257        }
258
259        /**
260         * Package manager.  Pass through for now.
261         */
262        @Override
263        public Context createPackageContext(String packageName, int flags)
264                throws PackageManager.NameNotFoundException {
265            return mRealContext.createPackageContext(packageName, flags);
266        }
267
268        /**
269         * Message broadcast.  Pass through for now.
270         */
271        @Override
272        public void sendBroadcast(Intent intent) {
273            mRealContext.sendBroadcast(intent);
274        }
275    }
276
277/**
278 * This is a mock for package manager.  Used to perform a true unit test on SearchableInfo.
279 *
280 */
281    private class MyMockPackageManager extends MockPackageManager {
282
283        public final static int SEARCHABLES_PASSTHROUGH = 0;
284        public final static int SEARCHABLES_MOCK_ZERO = 1;
285        public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
286        public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
287
288        protected PackageManager mRealPackageManager;
289        protected int mSearchablesMode;
290
291        public MyMockPackageManager(PackageManager realPM) {
292            mRealPackageManager = realPM;
293            mSearchablesMode = SEARCHABLES_PASSTHROUGH;
294        }
295
296        /**
297         * Set the mode for various tests.
298         */
299        public void setSearchablesMode(int newMode) {
300            switch (newMode) {
301            case SEARCHABLES_PASSTHROUGH:
302            case SEARCHABLES_MOCK_ZERO:
303                mSearchablesMode = newMode;
304                break;
305
306            default:
307                throw new UnsupportedOperationException();
308            }
309        }
310
311        /**
312         * Find activities that support a given intent.
313         *
314         * Retrieve all activities that can be performed for the given intent.
315         *
316         * @param intent The desired intent as per resolveActivity().
317         * @param flags Additional option flags.  The most important is
318         *                    MATCH_DEFAULT_ONLY, to limit the resolution to only
319         *                    those activities that support the CATEGORY_DEFAULT.
320         *
321         * @return A List<ResolveInfo> containing one entry for each matching
322         *         Activity. These are ordered from best to worst match -- that
323         *         is, the first item in the list is what is returned by
324         *         resolveActivity().  If there are no matching activities, an empty
325         *         list is returned.
326         */
327        @Override
328        public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
329            assertNotNull(intent);
330            assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
331                    || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
332                    || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
333            switch (mSearchablesMode) {
334            case SEARCHABLES_PASSTHROUGH:
335                return mRealPackageManager.queryIntentActivities(intent, flags);
336            case SEARCHABLES_MOCK_ZERO:
337                return null;
338            default:
339                throw new UnsupportedOperationException();
340            }
341        }
342
343        @Override
344        public ResolveInfo resolveActivity(Intent intent, int flags) {
345            assertNotNull(intent);
346            assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
347                    || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
348            switch (mSearchablesMode) {
349            case SEARCHABLES_PASSTHROUGH:
350                return mRealPackageManager.resolveActivity(intent, flags);
351            case SEARCHABLES_MOCK_ZERO:
352                return null;
353            default:
354                throw new UnsupportedOperationException();
355            }
356        }
357
358        /**
359         * Retrieve an XML file from a package.  This is a low-level API used to
360         * retrieve XML meta data.
361         *
362         * @param packageName The name of the package that this xml is coming from.
363         * Can not be null.
364         * @param resid The resource identifier of the desired xml.  Can not be 0.
365         * @param appInfo Overall information about <var>packageName</var>.  This
366         * may be null, in which case the application information will be retrieved
367         * for you if needed; if you already have this information around, it can
368         * be much more efficient to supply it here.
369         *
370         * @return Returns an XmlPullParser allowing you to parse out the XML
371         * data.  Returns null if the xml resource could not be found for any
372         * reason.
373         */
374        @Override
375        public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
376            assertNotNull(packageName);
377            MoreAsserts.assertNotEqual(packageName, "");
378            MoreAsserts.assertNotEqual(resid, 0);
379            switch (mSearchablesMode) {
380            case SEARCHABLES_PASSTHROUGH:
381                return mRealPackageManager.getXml(packageName, resid, appInfo);
382            case SEARCHABLES_MOCK_ZERO:
383            default:
384                throw new UnsupportedOperationException();
385            }
386        }
387
388        /**
389         * Find a single content provider by its base path name.
390         *
391         * @param name The name of the provider to find.
392         * @param flags Additional option flags.  Currently should always be 0.
393         *
394         * @return ContentProviderInfo Information about the provider, if found,
395         *         else null.
396         */
397        @Override
398        public ProviderInfo resolveContentProvider(String name, int flags) {
399            assertNotNull(name);
400            MoreAsserts.assertNotEqual(name, "");
401            assertEquals(flags, 0);
402            switch (mSearchablesMode) {
403            case SEARCHABLES_PASSTHROUGH:
404                return mRealPackageManager.resolveContentProvider(name, flags);
405            case SEARCHABLES_MOCK_ZERO:
406            default:
407                throw new UnsupportedOperationException();
408            }
409        }
410
411        /**
412         * Get the activity information for a particular activity.
413         *
414         * @param name The name of the activity to find.
415         * @param flags Additional option flags.
416         *
417         * @return ActivityInfo Information about the activity, if found, else null.
418         */
419        @Override
420        public ActivityInfo getActivityInfo(ComponentName name, int flags)
421                throws NameNotFoundException {
422            assertNotNull(name);
423            MoreAsserts.assertNotEqual(name, "");
424            switch (mSearchablesMode) {
425            case SEARCHABLES_PASSTHROUGH:
426                return mRealPackageManager.getActivityInfo(name, flags);
427            case SEARCHABLES_MOCK_ZERO:
428                throw new NameNotFoundException();
429            default:
430                throw new UnsupportedOperationException();
431            }
432        }
433
434        @Override
435        public int checkPermission(String permName, String pkgName) {
436            assertNotNull(permName);
437            assertNotNull(pkgName);
438            switch (mSearchablesMode) {
439                case SEARCHABLES_PASSTHROUGH:
440                    return mRealPackageManager.checkPermission(permName, pkgName);
441                case SEARCHABLES_MOCK_ZERO:
442                    return PackageManager.PERMISSION_DENIED;
443                default:
444                    throw new UnsupportedOperationException();
445                }
446        }
447    }
448}
449
450