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