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 */ testNonSearchable()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 */ testSearchablesListReal()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 */ testSearchablesListEmpty()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 */ checkSearchables(ArrayList<SearchableInfo> searchablesList)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 checkSearchable(SearchableInfo si)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 */ assertNotEmpty(final String s)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 */ assertNullOrNotEmpty(final String s)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 */ MyMockContext(Context realContext, PackageManager packageManager)238 MyMockContext(Context realContext, PackageManager packageManager) { 239 mRealContext = realContext; 240 mPackageManager = packageManager; 241 } 242 243 /** 244 * Resources. Pass through for now. 245 */ 246 @Override getResources()247 public Resources getResources() { 248 return mRealContext.getResources(); 249 } 250 251 /** 252 * Package manager. Pass through for now. 253 */ 254 @Override getPackageManager()255 public PackageManager getPackageManager() { 256 return mPackageManager; 257 } 258 259 /** 260 * Package manager. Pass through for now. 261 */ 262 @Override createPackageContext(String packageName, int flags)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 sendBroadcast(Intent intent)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 MyMockPackageManager(PackageManager realPM)291 public MyMockPackageManager(PackageManager realPM) { 292 mRealPackageManager = realPM; 293 mSearchablesMode = SEARCHABLES_PASSTHROUGH; 294 } 295 296 /** 297 * Set the mode for various tests. 298 */ setSearchablesMode(int newMode)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 queryIntentActivities(Intent intent, int flags)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 resolveActivity(Intent intent, int flags)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 TypedXmlPullParser 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 getXml(String packageName, int resid, ApplicationInfo appInfo)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 resolveContentProvider(String name, int flags)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 getActivityInfo(ComponentName name, int flags)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 checkPermission(String permName, String pkgName)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