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