Home | History | Annotate | Download | only in search
      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 
     17 package com.android.server.search;
     18 
     19 import android.app.SearchManager;
     20 import android.app.SearchableInfo;
     21 import android.app.SearchableInfo.ActionKeyInfo;
     22 import android.content.ComponentName;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.ActivityInfo;
     26 import android.content.pm.ApplicationInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ProviderInfo;
     29 import android.content.pm.ResolveInfo;
     30 import android.content.res.Resources;
     31 import android.content.res.XmlResourceParser;
     32 import android.os.RemoteException;
     33 import com.android.server.search.Searchables;
     34 import android.test.AndroidTestCase;
     35 import android.test.MoreAsserts;
     36 import android.test.mock.MockContext;
     37 import android.test.mock.MockPackageManager;
     38 import android.test.suitebuilder.annotation.SmallTest;
     39 import android.view.KeyEvent;
     40 
     41 import java.util.ArrayList;
     42 import 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
     52 public 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.updateSearchableList();
     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.updateSearchableList();
    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.updateSearchableList();
    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