1 /*
2  * Copyright (C) 2010 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.providers.contacts;
18 
19 import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
20 
21 import android.accounts.Account;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.PackageManager.NameNotFoundException;
27 import android.content.pm.ProviderInfo;
28 import android.database.Cursor;
29 import android.database.MatrixCursor;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.provider.ContactsContract;
33 import android.provider.ContactsContract.AggregationExceptions;
34 import android.provider.ContactsContract.Contacts;
35 import android.provider.ContactsContract.Directory;
36 import android.provider.ContactsContract.RawContacts;
37 import android.test.mock.MockContentProvider;
38 import android.test.suitebuilder.annotation.MediumTest;
39 import android.util.Log;
40 
41 import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns;
42 
43 import com.google.android.collect.Lists;
44 
45 /**
46  * Unit tests for {@link ContactDirectoryManager}. Run the test like this:
47  *
48     adb shell am instrument -e class com.android.providers.contacts.ContactDirectoryManagerTest \
49         -w com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
50  *
51  */
52 @MediumTest
53 public class ContactDirectoryManagerTest extends BaseContactsProvider2Test {
54     private static final String TAG = "ContactDirectoryManagerTest";
55 
56     private ContactsMockPackageManager mPackageManager;
57 
58     private ContactsProvider2 mProvider;
59 
60     private ContactDirectoryManager mDirectoryManager;
61 
62     public static class MockContactDirectoryProvider extends MockContentProvider {
63 
64         private String mAuthority;
65 
66         private MatrixCursor mResponse;
67 
68         @Override
attachInfoForTesting(Context context, ProviderInfo info)69         public void attachInfoForTesting(Context context, ProviderInfo info) {
70             mAuthority = info.authority;
71         }
72 
createResponseCursor()73         public MatrixCursor createResponseCursor() {
74             mResponse = new MatrixCursor(
75                     new String[] { Directory.ACCOUNT_NAME, Directory.ACCOUNT_TYPE,
76                             Directory.DISPLAY_NAME, Directory.TYPE_RESOURCE_ID,
77                             Directory.EXPORT_SUPPORT, Directory.SHORTCUT_SUPPORT,
78                             Directory.PHOTO_SUPPORT });
79 
80             return mResponse;
81         }
82 
83         @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)84         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
85                 String sortOrder) {
86 
87             if (uri.toString().equals("content://" + mAuthority + "/directories")) {
88                 // Should tolerate multiple queries.
89                 mResponse.moveToPosition(-1);
90                 return mResponse;
91             } else if (uri.toString().startsWith("content://" + mAuthority + "/contacts")) {
92                 MatrixCursor cursor = new MatrixCursor(
93                         new String[] { "projection", "selection", "selectionArgs", "sortOrder",
94                                 "accountName", "accountType"});
95                 cursor.addRow(new Object[] {
96                     Lists.newArrayList(projection).toString(),
97                     selection,
98                     Lists.newArrayList(selectionArgs).toString(),
99                     sortOrder,
100                     uri.getQueryParameter(RawContacts.ACCOUNT_NAME),
101                     uri.getQueryParameter(RawContacts.ACCOUNT_TYPE),
102                 });
103                 return cursor;
104             } else if (uri.toString().startsWith(
105                     "content://" + mAuthority + "/aggregation_exceptions")) {
106                 return new MatrixCursor(projection);
107             }
108 
109             fail("Unexpected uri: " + uri);
110             return null;
111         }
112     }
113 
114     @Override
setUp()115     public void setUp() throws Exception {
116         super.setUp();
117 
118         mProvider = (ContactsProvider2) getProvider();
119         mDirectoryManager = mProvider.getContactDirectoryManagerForTest();
120 
121         mPackageManager = (ContactsMockPackageManager) getProvider()
122                 .getContext().getPackageManager();
123     }
124 
125     @Override
getContextPackageName()126     protected String getContextPackageName() {
127         // In this test, we need to use the real package name, because that'll be recorded in the
128         // directory table, and if it's wrong, the tests would get confused.
129         return getContext().getPackageName();
130     }
131 
testIsDirectoryProvider()132     public void testIsDirectoryProvider() {
133         ProviderInfo provider = new ProviderInfo();
134 
135         // Null -- just return false.
136         assertFalse(ContactDirectoryManager.isDirectoryProvider(null));
137 
138         // No metadata
139         assertFalse(ContactDirectoryManager.isDirectoryProvider(provider));
140 
141         // No CONTACT_DIRECTORY_META_DATA
142         provider.metaData = new Bundle();
143         assertFalse(ContactDirectoryManager.isDirectoryProvider(provider));
144 
145         // CONTACT_DIRECTORY_META_DATA is a string
146         provider.metaData.putString("android.content.ContactDirectory", "");
147         assertFalse(ContactDirectoryManager.isDirectoryProvider(provider));
148 
149         // CONTACT_DIRECTORY_META_DATA is false
150         provider.metaData.putBoolean("android.content.ContactDirectory", false);
151         assertFalse(ContactDirectoryManager.isDirectoryProvider(provider));
152 
153         // CONTACT_DIRECTORY_META_DATA is true
154         provider.metaData.putBoolean("android.content.ContactDirectory", true);
155         assertTrue(ContactDirectoryManager.isDirectoryProvider(provider));
156     }
157 
testScanAllProviders()158     public void testScanAllProviders() throws Exception {
159         mPackageManager.setInstalledPackages(
160                 Lists.newArrayList(
161                         createProviderPackage("test.package1", "authority1"),
162                         createProviderPackage("test.package2", "authority2"),
163                         createPackage("test.packageX", "authorityX", false)));
164 
165         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
166                 MockContactDirectoryProvider.class, "authority1");
167 
168         MatrixCursor response1 = provider1.createResponseCursor();
169         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
170                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
171                 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
172         addDirectoryRow(response1, "account-name2", "account-type2", "display-name2", 2,
173                 Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY,
174                 Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY);
175 
176         MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
177                 MockContactDirectoryProvider.class, "authority2");
178 
179         MatrixCursor response2 = provider2.createResponseCursor();
180         addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
181                 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
182                 Directory.PHOTO_SUPPORT_FULL);
183 
184         assertEquals(3, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
185 
186         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null,
187                 /* order by=*/ Directory.DIRECTORY_AUTHORITY + "," + Directory.ACCOUNT_NAME +
188                 "," + Directory._ID );
189 
190         TestUtils.dumpCursor(cursor);
191         assertEquals(5, cursor.getCount());
192 
193         assertTrue(cursor.moveToPosition(0));
194         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
195                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
196                 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
197 
198         assertTrue(cursor.moveToPosition(1));
199         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2",
200                 "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT,
201                 Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY, Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY);
202 
203         assertTrue(cursor.moveToPosition(2));
204         assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3",
205                 "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY,
206                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
207 
208         assertTrue(cursor.moveToPosition(3));
209         assertDirectoryRow(cursor, getContext().getPackageName(),
210                 "com.android.contacts", null, null,
211                 null, -1 /* =any */, Directory.EXPORT_SUPPORT_NONE,
212                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
213 
214         assertTrue(cursor.moveToPosition(4));
215         assertDirectoryRow(cursor, getContext().getPackageName(),
216                 "com.android.contacts", null, null,
217                 null, -1 /* =any */, Directory.EXPORT_SUPPORT_NONE,
218                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
219 
220         cursor.close();
221     }
222 
testScanAllProviders_scanCondition()223     public void testScanAllProviders_scanCondition() throws Exception {
224         testScanAllProviders();
225 
226         // Nothing has changed, so no scanning.
227         assertEquals(0, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
228 
229         // rescan = true, so a full-scan should happen.
230         assertEquals(3, mDirectoryManager.scanAllPackages(/* rescan=*/ true));
231 
232         // Change GAL packages, a scan should happen.
233         mPackageManager.setInstalledPackages(
234                 Lists.newArrayList(
235                         createProviderPackage("test.package2", "authority2"),
236                         createPackage("test.packageX", "authorityX", false)));
237         assertEquals(1, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
238 
239         // Remove the non-GAL package, no scan should happen.
240         mPackageManager.setInstalledPackages(
241                 Lists.newArrayList(
242                         createProviderPackage("test.package2", "authority2")));
243         assertEquals(0, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
244 
245         // Remove GAL package 2 and add 1, a scan should happen.
246         mPackageManager.setInstalledPackages(
247                 Lists.newArrayList(
248                         createProviderPackage("test.package1", "authority1"),
249                         createPackage("test.packageX", "authorityX", false)));
250         assertEquals(2, mDirectoryManager.scanAllPackages(/* rescan=*/ false));
251 
252     }
253 
testPackageInstalled()254     public void testPackageInstalled() throws Exception {
255         mPackageManager.setInstalledPackages(
256                 Lists.newArrayList(createProviderPackage("test.package1", "authority1"),
257                         createPackage("test.packageX", "authorityX", false)));
258 
259         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
260                 MockContactDirectoryProvider.class, "authority1");
261 
262         MatrixCursor response1 = provider1.createResponseCursor();
263         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
264                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
265                 Directory.PHOTO_SUPPORT_FULL);
266 
267         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
268 
269         // At this point the manager has discovered a single directory (plus two
270         // standard ones).
271         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
272         assertEquals(3, cursor.getCount());
273         cursor.close();
274 
275         // Pretend to install another package
276         MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
277                 MockContactDirectoryProvider.class, "authority2");
278 
279         MatrixCursor response2 = provider2.createResponseCursor();
280         addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
281                 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
282                 Directory.PHOTO_SUPPORT_FULL);
283 
284         mPackageManager.getInstalledPackages(0).add(
285                 createProviderPackage("test.package2", "authority2"));
286 
287         mProvider.onPackageChanged("test.package2");
288 
289         cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
290         assertEquals(4, cursor.getCount());
291 
292         cursor.moveToPosition(2);
293         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
294                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
295                 Directory.PHOTO_SUPPORT_FULL);
296 
297         cursor.moveToNext();
298         assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3",
299                 "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY,
300                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
301 
302         cursor.close();
303     }
304 
testPackageUninstalled()305     public void testPackageUninstalled() throws Exception {
306         mPackageManager.setInstalledPackages(
307                 Lists.newArrayList(
308                         createProviderPackage("test.package1", "authority1"),
309                         createProviderPackage("test.package2", "authority2"),
310                         createPackage("test.packageX", "authorityX", false)));
311 
312         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
313                 MockContactDirectoryProvider.class, "authority1");
314 
315         MatrixCursor response1 = provider1.createResponseCursor();
316         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
317                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
318                 Directory.PHOTO_SUPPORT_NONE);
319 
320         MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
321                 MockContactDirectoryProvider.class, "authority2");
322 
323         MatrixCursor response2 = provider2.createResponseCursor();
324         addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
325                 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
326                 Directory.PHOTO_SUPPORT_FULL);
327 
328         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
329 
330         // At this point the manager has discovered two custom directories.
331         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
332         assertEquals(4, cursor.getCount());
333         cursor.close();
334 
335         // Pretend to uninstall one of the packages
336         mPackageManager.getInstalledPackages(0).remove(1);
337 
338         mProvider.onPackageChanged("test.package2");
339 
340         cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
341         assertEquals(3, cursor.getCount());
342 
343         cursor.moveToPosition(2);
344         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
345                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
346                 Directory.PHOTO_SUPPORT_NONE);
347 
348         cursor.close();
349     }
350 
testPackageReplaced()351     public void testPackageReplaced() throws Exception {
352         mPackageManager.setInstalledPackages(
353                 Lists.newArrayList(
354                         createProviderPackage("test.package1", "authority1"),
355                         createProviderPackage("test.package2", "authority2"),
356                         createPackage("test.packageX", "authorityX", false)));
357 
358         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
359                 MockContactDirectoryProvider.class, "authority1");
360 
361         MatrixCursor response1 = provider1.createResponseCursor();
362         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
363                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
364                 Directory.PHOTO_SUPPORT_NONE);
365 
366         MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
367                 MockContactDirectoryProvider.class, "authority2");
368 
369         MatrixCursor response2 = provider2.createResponseCursor();
370         addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3,
371                 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
372                 Directory.PHOTO_SUPPORT_FULL);
373 
374         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
375 
376         // At this point the manager has discovered two custom directories.
377         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
378         assertEquals(4, cursor.getCount());
379         cursor.close();
380 
381         // Pretend to replace the package with a different provider inside
382         MatrixCursor response3 = provider2.createResponseCursor();
383         addDirectoryRow(response3, "account-name4", "account-type4", "display-name4", 4,
384                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
385                 Directory.PHOTO_SUPPORT_NONE);
386 
387         mProvider.onPackageChanged("test.package2");
388 
389         cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
390         assertEquals(4, cursor.getCount());
391 
392         cursor.moveToPosition(2);
393         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
394                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
395                 Directory.PHOTO_SUPPORT_NONE);
396 
397         cursor.moveToNext();
398         assertDirectoryRow(cursor, "test.package2", "authority2", "account-name4", "account-type4",
399                 "display-name4", 4, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
400                 Directory.PHOTO_SUPPORT_NONE);
401 
402         cursor.close();
403     }
404 
405     /**
406      * Tests if the manager works correctly when the package name for a directory is changed
407      * (on system update).
408      */
testPackageRenamed()409     public void testPackageRenamed() throws Exception {
410         mPackageManager.setInstalledPackages(
411                 Lists.newArrayList(
412                         createProviderPackage("test.package1", "authority1"),
413                         createProviderPackage("test.package2", "authority2"),
414                         createPackage("test.packageX", "authorityX", false)));
415 
416         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
417                 MockContactDirectoryProvider.class, "authority1");
418 
419         MatrixCursor response1 = provider1.createResponseCursor();
420         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
421                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
422                 Directory.PHOTO_SUPPORT_NONE);
423 
424         MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider(
425                 MockContactDirectoryProvider.class, "authority2");
426 
427         MatrixCursor response2 = provider2.createResponseCursor();
428         addDirectoryRow(response2, "account-name2", "account-type2", "display-name2", 2,
429                 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
430                 Directory.PHOTO_SUPPORT_FULL);
431 
432         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
433 
434         // At this point the manager has discovered two custom directories.
435         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
436         assertEquals(4, cursor.getCount());
437         cursor.close();
438 
439         // Pretend to rename the package name of a package on system update.
440         PackageInfo info = mPackageManager.getInstalledPackages(0).get(1);
441         info.packageName              = "test.package3";
442         info.providers[0].packageName = "test.package3";
443         MatrixCursor response3 = provider2.createResponseCursor();
444         // Change resource id.
445         addDirectoryRow(response3, "account-name2", "account-type2", "display-name2", 3,
446                 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL,
447                 Directory.PHOTO_SUPPORT_FULL);
448 
449         // When this happens on reboot, scanAllPackages() is called instead of onPackageChanged()
450         // (as part of ContactsProvider2 initialization).
451         // Accounts won't be affected so false should be passed here.
452         mDirectoryManager.scanAllPackages(false);
453 
454         cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
455         // We should have columns for default, local_invisible, test.package1, and test.package3.
456         // The row for test.package2 should not remain.
457         assertEquals(4, cursor.getCount());
458 
459         cursor.moveToPosition(2);
460         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
461                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
462                 Directory.PHOTO_SUPPORT_NONE);
463 
464         cursor.moveToNext();
465         assertDirectoryRow(cursor, "test.package3", "authority2", "account-name2", "account-type2",
466                 "display-name2", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY,
467                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
468 
469         cursor.close();
470     }
471 
testAccountRemoval()472     public void testAccountRemoval() throws Exception {
473         mPackageManager.setInstalledPackages(
474                 Lists.newArrayList(
475                         createProviderPackage("test.package1", "authority1"),
476                         createProviderPackage("test.package2", "authority2"),
477                         createPackage("test.packageX", "authorityX", false)));
478 
479         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
480                 MockContactDirectoryProvider.class, "authority1");
481 
482         Account[] accounts = new Account[]{
483                 new Account("account-name1", "account-type1"),
484                 new Account("account-name2", "account-type2")};
485         mActor.setAccounts(accounts);
486         ((ContactsProvider2)getProvider()).onAccountsUpdated(accounts);
487 
488         MatrixCursor response1 = provider1.createResponseCursor();
489         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
490                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
491                 Directory.PHOTO_SUPPORT_NONE);
492         addDirectoryRow(response1, "account-name2", "account-type2", "display-name2", 2,
493                 Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY,
494                 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY);
495 
496         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
497 
498         accounts = new Account[]{new Account("account-name1", "account-type1")};
499         mActor.setAccounts(accounts);
500         ((ContactsProvider2)getProvider()).onAccountsUpdated(accounts);
501 
502         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
503         assertEquals(3, cursor.getCount());
504 
505         cursor.moveToPosition(2);
506         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1",
507                 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
508                 Directory.PHOTO_SUPPORT_NONE);
509 
510         cursor.close();
511     }
512 
testNotifyDirectoryChange()513     public void testNotifyDirectoryChange() throws Exception {
514         mPackageManager.setInstalledPackages(
515                 Lists.newArrayList(createProviderPackage("test.package1", "authority1"),
516                         createPackage("test.packageX", "authorityX", false)));
517 
518         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
519                 MockContactDirectoryProvider.class, "authority1");
520 
521         MatrixCursor response1 = provider1.createResponseCursor();
522         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
523                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
524                 Directory.PHOTO_SUPPORT_NONE);
525 
526         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
527 
528         // Pretend to replace the package with a different provider inside
529         MatrixCursor response2 = provider1.createResponseCursor();
530         addDirectoryRow(response2, "account-name2", "account-type2", "display-name2", 2,
531                 Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_FULL,
532                 Directory.PHOTO_SUPPORT_FULL);
533 
534         ContactsContract.Directory.notifyDirectoryChange(mResolver);
535 
536         Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null);
537         assertEquals(3, cursor.getCount());
538 
539         cursor.moveToPosition(2);
540         assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2",
541                 "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT,
542                 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL);
543 
544         cursor.close();
545     }
546 
testForwardingToDirectoryProvider()547     public void testForwardingToDirectoryProvider() throws Exception {
548         mPackageManager.setInstalledPackages(
549                 Lists.newArrayList(createProviderPackage("test.package1", "authority1"),
550                         createPackage("test.packageX", "authorityX", false)));
551 
552         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
553                 MockContactDirectoryProvider.class, "authority1");
554 
555         MatrixCursor response1 = provider1.createResponseCursor();
556         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
557                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
558                 Directory.PHOTO_SUPPORT_NONE);
559 
560         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
561 
562         Cursor cursor = mResolver.query(
563                 Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null);
564         cursor.moveToPosition(2);
565         long directoryId = cursor.getLong(0);
566         cursor.close();
567 
568         Uri contentUri = Contacts.CONTENT_URI.buildUpon().appendQueryParameter(
569                 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
570 
571         // The request should be forwarded to TestProvider, which will simply
572         // package arguments and return them to us for verification
573         cursor = mResolver.query(contentUri,
574                 new String[]{"f1", "f2"}, "query", new String[]{"s1", "s2"}, "so");
575         assertNotNull(cursor);
576         assertEquals(1, cursor.getCount());
577         cursor.moveToFirst();
578         assertEquals("[f1, f2]", cursor.getString(cursor.getColumnIndex("projection")));
579         assertEquals("query", cursor.getString(cursor.getColumnIndex("selection")));
580         assertEquals("[s1, s2]", cursor.getString(cursor.getColumnIndex("selectionArgs")));
581         assertEquals("so", cursor.getString(cursor.getColumnIndex("sortOrder")));
582         assertEquals("account-name1", cursor.getString(cursor.getColumnIndex("accountName")));
583         assertEquals("account-type1", cursor.getString(cursor.getColumnIndex("accountType")));
584         cursor.close();
585     }
586 
testProjectionPopulated()587     public void testProjectionPopulated() throws Exception {
588         mPackageManager.setInstalledPackages(
589                 Lists.newArrayList(createProviderPackage("test.package1", "authority1"),
590                         createPackage("test.packageX", "authorityX", false)));
591 
592         MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider(
593                 MockContactDirectoryProvider.class, "authority1");
594 
595         MatrixCursor response1 = provider1.createResponseCursor();
596         addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1,
597                 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE,
598                 Directory.PHOTO_SUPPORT_NONE);
599 
600         mDirectoryManager.scanAllPackages(/* rescan=*/ false);
601 
602         Cursor cursor = mResolver.query(
603                 Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null);
604         cursor.moveToPosition(2);
605         long directoryId = cursor.getLong(0);
606         cursor.close();
607 
608         Uri contentUri = AggregationExceptions.CONTENT_URI.buildUpon().appendQueryParameter(
609                 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
610 
611         // The request should be forwarded to TestProvider, which will return an empty cursor
612         // but the projection should be correctly populated by ContactProvider
613         assertProjection(contentUri, new String[]{
614                 AggregationExceptionColumns._ID,
615                 AggregationExceptions.TYPE,
616                 AggregationExceptions.RAW_CONTACT_ID1,
617                 AggregationExceptions.RAW_CONTACT_ID2,
618         });
619     }
620 
621     /**
622      * Test {@link ContactDirectoryManager#getDirectoryProviderPackages} with the actual
623      * package manager, and see if it returns the google sync package.
624      */
testGetDirectoryProviderPackages()625     public void testGetDirectoryProviderPackages() {
626         final PackageManager pm = getContext().getPackageManager();
627         final String googleSync = "com.google.android.syncadapters.contacts";
628 
629         // Skip if the package is not installed.
630         try {
631             pm.getPackageInfo(googleSync, 0);
632         } catch (NameNotFoundException e) {
633             Log.w(TAG, googleSync + " not installed.  Skipping...");
634             return;
635         }
636 
637         // If installed, getDirectoryProviderPackages() should return it.
638         assertTrue(ContactDirectoryManager.getDirectoryProviderPackages(pm).contains(googleSync));
639     }
640 
createProviderPackage(String packageName, String authority)641     protected PackageInfo createProviderPackage(String packageName, String authority) {
642         return createPackage(packageName, authority, true);
643     }
644 
createPackage(String packageName, String authority, boolean isDirectoryProvider)645     protected PackageInfo createPackage(String packageName, String authority,
646             boolean isDirectoryProvider) {
647         PackageInfo providerPackage = new PackageInfo();
648         providerPackage.packageName = packageName;
649         ProviderInfo providerInfo = new ProviderInfo();
650         providerInfo.packageName = providerPackage.packageName;
651         providerInfo.authority = authority;
652         if (isDirectoryProvider) {
653             providerInfo.metaData = new Bundle();
654             providerInfo.metaData.putBoolean("android.content.ContactDirectory", true);
655         }
656         providerPackage.providers = new ProviderInfo[] { providerInfo };
657         return providerPackage;
658     }
659 
addDirectoryRow(MatrixCursor cursor, String accountName, String accountType, String displayName, int typeResourceId, int exportSupport, int shortcutSupport, int photoSupport)660     protected void addDirectoryRow(MatrixCursor cursor, String accountName, String accountType,
661             String displayName, int typeResourceId, int exportSupport, int shortcutSupport,
662             int photoSupport) {
663         Object[] row = new Object[cursor.getColumnCount()];
664         row[cursor.getColumnIndex(Directory.ACCOUNT_NAME)] = accountName;
665         row[cursor.getColumnIndex(Directory.ACCOUNT_TYPE)] = accountType;
666         row[cursor.getColumnIndex(Directory.DISPLAY_NAME)] = displayName;
667         row[cursor.getColumnIndex(Directory.TYPE_RESOURCE_ID)] = typeResourceId;
668         row[cursor.getColumnIndex(Directory.EXPORT_SUPPORT)] = exportSupport;
669         row[cursor.getColumnIndex(Directory.SHORTCUT_SUPPORT)] = shortcutSupport;
670         row[cursor.getColumnIndex(Directory.PHOTO_SUPPORT)] = photoSupport;
671         cursor.addRow(row);
672     }
673 
assertDirectoryRow(Cursor cursor, String packageName, String authority, String accountName, String accountType, String displayName, int typeResourceId, int exportSupport, int shortcutSupport, int photoSupport)674     protected void assertDirectoryRow(Cursor cursor, String packageName, String authority,
675             String accountName, String accountType, String displayName, int typeResourceId,
676             int exportSupport, int shortcutSupport, int photoSupport) {
677         ContentValues values = new ContentValues();
678         values.put(Directory.PACKAGE_NAME, packageName);
679         values.put(Directory.DIRECTORY_AUTHORITY, authority);
680         values.put(Directory.ACCOUNT_NAME, accountName);
681         values.put(Directory.ACCOUNT_TYPE, accountType);
682         values.put(Directory.DISPLAY_NAME, displayName);
683         if (typeResourceId >= 0) {
684             values.put(Directory.TYPE_RESOURCE_ID, typeResourceId);
685         }
686         values.put(Directory.EXPORT_SUPPORT, exportSupport);
687         values.put(Directory.SHORTCUT_SUPPORT, shortcutSupport);
688         values.put(Directory.PHOTO_SUPPORT, photoSupport);
689 
690         assertCursorValues(cursor, values);
691     }
692 }
693