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