1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.contacts;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.accounts.AccountManagerCallback;
22 import android.accounts.AccountManagerFuture;
23 import android.accounts.AuthenticatorException;
24 import android.accounts.OnAccountsUpdateListener;
25 import android.accounts.OperationCanceledException;
26 import android.app.admin.DevicePolicyManager;
27 import android.content.ContentProvider;
28 import android.content.ContentResolver;
29 import android.content.ContentUris;
30 import android.content.ContentValues;
31 import android.content.Context;
32 import android.content.ContextWrapper;
33 import android.content.Intent;
34 import android.content.SharedPreferences;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageManager;
37 import android.content.pm.ProviderInfo;
38 import android.content.pm.UserInfo;
39 import android.content.res.Configuration;
40 import android.content.res.Resources;
41 import android.database.Cursor;
42 import android.location.Country;
43 import android.location.CountryDetector;
44 import android.location.CountryListener;
45 import android.net.Uri;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.IUserManager;
49 import android.os.Looper;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.provider.BaseColumns;
53 import android.provider.ContactsContract;
54 import android.provider.ContactsContract.AggregationExceptions;
55 import android.provider.ContactsContract.CommonDataKinds;
56 import android.provider.ContactsContract.CommonDataKinds.Email;
57 import android.provider.ContactsContract.CommonDataKinds.Phone;
58 import android.provider.ContactsContract.Contacts;
59 import android.provider.ContactsContract.Data;
60 import android.provider.ContactsContract.RawContacts;
61 import android.provider.ContactsContract.StatusUpdates;
62 import android.test.IsolatedContext;
63 import android.test.RenamingDelegatingContext;
64 import android.test.mock.MockContentResolver;
65 import android.test.mock.MockContext;
66 import android.util.Log;
67 
68 import com.android.providers.contacts.util.ContactsPermissions;
69 import com.android.providers.contacts.util.MockSharedPreferences;
70 
71 import com.google.android.collect.Sets;
72 
73 import java.io.File;
74 import java.io.IOException;
75 import java.util.ArrayList;
76 import java.util.Arrays;
77 import java.util.List;
78 import java.util.Locale;
79 import java.util.Set;
80 
81 /**
82  * Helper class that encapsulates an "actor" which is owned by a specific
83  * package name. It correctly maintains a wrapped {@link Context} and an
84  * attached {@link MockContentResolver}. Multiple actors can be used to test
85  * security scenarios between multiple packages.
86  */
87 public class ContactsActor {
88     private static final String FILENAME_PREFIX = "test.";
89 
90     public static final String PACKAGE_GREY = "edu.example.grey";
91     public static final String PACKAGE_RED = "net.example.red";
92     public static final String PACKAGE_GREEN = "com.example.green";
93     public static final String PACKAGE_BLUE = "org.example.blue";
94 
95     public Context context;
96     public String packageName;
97     public MockContentResolver resolver;
98     public ContentProvider provider;
99     private Country mMockCountry = new Country("us", 0);
100 
101     private Account[] mAccounts = new Account[0];
102 
103     private Set<String> mGrantedPermissions = Sets.newHashSet();
104     private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet();
105 
106     private CountryDetector mMockCountryDetector = new CountryDetector(null){
107         @Override
108         public Country detectCountry() {
109             return mMockCountry;
110         }
111 
112         @Override
113         public void addCountryListener(CountryListener listener, Looper looper) {
114         }
115     };
116 
117     private AccountManager mMockAccountManager;
118 
119     private class MockAccountManager extends AccountManager {
MockAccountManager(Context conteact)120         public MockAccountManager(Context conteact) {
121             super(context, null, null);
122         }
123 
124         @Override
addOnAccountsUpdatedListener(OnAccountsUpdateListener listener, Handler handler, boolean updateImmediately)125         public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener,
126                 Handler handler, boolean updateImmediately) {
127             // do nothing
128         }
129 
130         @Override
getAccounts()131         public Account[] getAccounts() {
132             return mAccounts;
133         }
134 
135         @Override
getAccountsByTypeAndFeatures( final String type, final String[] features, AccountManagerCallback<Account[]> callback, Handler handler)136         public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
137                 final String type, final String[] features,
138                 AccountManagerCallback<Account[]> callback, Handler handler) {
139             return null;
140         }
141 
142         @Override
blockingGetAuthToken(Account account, String authTokenType, boolean notifyAuthFailure)143         public String blockingGetAuthToken(Account account, String authTokenType,
144                 boolean notifyAuthFailure)
145                 throws OperationCanceledException, IOException, AuthenticatorException {
146             return null;
147         }
148     }
149 
150     public MockUserManager mockUserManager;
151 
152     public static class MockUserManager extends UserManager {
createUserInfo(String name, int id, int groupId, int flags)153         public static UserInfo createUserInfo(String name, int id, int groupId, int flags) {
154             final UserInfo ui = new UserInfo();
155             ui.name = name;
156             ui.id = id;
157             ui.profileGroupId = groupId;
158             ui.flags = flags | UserInfo.FLAG_INITIALIZED;
159             return ui;
160         }
161 
162         public static final UserInfo PRIMARY_USER = createUserInfo("primary", 0, 0,
163                 UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
164         public static final UserInfo CORP_USER = createUserInfo("corp", 10, 0,
165                 UserInfo.FLAG_MANAGED_PROFILE);
166         public static final UserInfo SECONDARY_USER = createUserInfo("2nd", 11, 11, 0);
167 
168         /** "My" user.  Set it to change the current user. */
169         public int myUser = 0;
170 
171         private ArrayList<UserInfo> mUsers = new ArrayList<>();
172 
MockUserManager(Context context)173         public MockUserManager(Context context) {
174             super(context, /* IUserManager */ null);
175 
176             mUsers.add(PRIMARY_USER); // Add the primary user.
177         }
178 
179         /** Replaces users. */
setUsers(UserInfo... users)180         public void setUsers(UserInfo... users) {
181             mUsers.clear();
182             for (UserInfo ui : users) {
183                 mUsers.add(ui);
184             }
185         }
186 
187         @Override
getUserHandle()188         public int getUserHandle() {
189             return myUser;
190         }
191 
192         @Override
getUserInfo(int userHandle)193         public UserInfo getUserInfo(int userHandle) {
194             for (UserInfo ui : mUsers) {
195                 if (ui.id == userHandle) {
196                     return ui;
197                 }
198             }
199             return null;
200         }
201 
202         @Override
getProfileParent(int userHandle)203         public UserInfo getProfileParent(int userHandle) {
204             final UserInfo child = getUserInfo(userHandle);
205             if (child == null) {
206                 return null;
207             }
208             for (UserInfo ui : mUsers) {
209                 if (ui.id != userHandle && ui.id == child.profileGroupId) {
210                     return ui;
211                 }
212             }
213             return null;
214         }
215 
216         @Override
getUsers()217         public List<UserInfo> getUsers() {
218             return mUsers;
219         }
220 
221         @Override
getUserRestrictions(UserHandle userHandle)222         public Bundle getUserRestrictions(UserHandle userHandle) {
223             return new Bundle();
224         }
225 
226         @Override
hasUserRestriction(String restrictionKey)227         public boolean hasUserRestriction(String restrictionKey) {
228             return false;
229         }
230 
231         @Override
hasUserRestriction(String restrictionKey, UserHandle userHandle)232         public boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
233             return false;
234         }
235 
236         @Override
isSameProfileGroup(int userId, int otherUserId)237         public boolean isSameProfileGroup(int userId, int otherUserId) {
238             return getUserInfo(userId).profileGroupId == getUserInfo(otherUserId).profileGroupId;
239         }
240     }
241 
242     /**
243      * A context wrapper that reports a different user id.
244      *
245      * TODO This should override getSystemService() and returns a UserManager that returns the
246      * same, altered user ID too.
247      */
248     public static class AlteringUserContext extends ContextWrapper {
249         private final int mUserId;
250 
AlteringUserContext(Context base, int userId)251         public AlteringUserContext(Context base, int userId) {
252             super(base);
253             mUserId = userId;
254         }
255 
256         @Override
getUserId()257         public int getUserId() {
258             return mUserId;
259         }
260     }
261 
262     private IsolatedContext mProviderContext;
263 
264     /**
265      * Create an "actor" using the given parent {@link Context} and the specific
266      * package name. Internally, all {@link Context} method calls are passed to
267      * a new instance of {@link RestrictionMockContext}, which stubs out the
268      * security infrastructure.
269      */
ContactsActor(final Context overallContext, String packageName, Class<? extends ContentProvider> providerClass, String authority)270     public ContactsActor(final Context overallContext, String packageName,
271             Class<? extends ContentProvider> providerClass, String authority) throws Exception {
272 
273         // Force permission check even when called by self.
274         ContactsPermissions.ALLOW_SELF_CALL = false;
275 
276         resolver = new MockContentResolver();
277         context = new RestrictionMockContext(overallContext, packageName, resolver,
278                 mGrantedPermissions, mGrantedUriPermissions) {
279             @Override
280             public Object getSystemService(String name) {
281                 if (Context.COUNTRY_DETECTOR.equals(name)) {
282                     return mMockCountryDetector;
283                 }
284                 if (Context.ACCOUNT_SERVICE.equals(name)) {
285                     return mMockAccountManager;
286                 }
287                 if (Context.USER_SERVICE.equals(name)) {
288                     return mockUserManager;
289                 }
290                 // Use overallContext here; super.getSystemService() somehow won't return
291                 // DevicePolicyManager.
292                 return overallContext.getSystemService(name);
293             }
294 
295             @Override
296             public String getSystemServiceName(Class<?> serviceClass) {
297                 return overallContext.getSystemServiceName(serviceClass);
298             }
299         };
300         this.packageName = packageName;
301 
302         // Let the Secure class initialize the settings provider, which is done when we first
303         // tries to get any setting.  Because our mock context/content resolver doesn't have the
304         // settings provider, we need to do this with an actual context, before other classes
305         // try to do this with a mock context.
306         // (Otherwise ContactsProvider2.initialzie() will crash trying to get a setting with
307         // a mock context.)
308         android.provider.Settings.Secure.getString(overallContext.getContentResolver(), "dummy");
309 
310         RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
311                 overallContext, FILENAME_PREFIX);
312         mProviderContext = new IsolatedContext(resolver, targetContextWrapper) {
313             private final MockSharedPreferences mPrefs = new MockSharedPreferences();
314 
315             @Override
316             public File getFilesDir() {
317                 // TODO: Need to figure out something more graceful than this.
318                 return new File("/data/data/com.android.providers.contacts.tests/files");
319             }
320 
321             @Override
322             public Object getSystemService(String name) {
323                 if (Context.COUNTRY_DETECTOR.equals(name)) {
324                     return mMockCountryDetector;
325                 }
326                 if (Context.ACCOUNT_SERVICE.equals(name)) {
327                     return mMockAccountManager;
328                 }
329                 if (Context.USER_SERVICE.equals(name)) {
330                     return mockUserManager;
331                 }
332                 // Use overallContext here; super.getSystemService() somehow won't return
333                 // DevicePolicyManager.
334                 return overallContext.getSystemService(name);
335             }
336 
337             @Override
338             public String getSystemServiceName(Class<?> serviceClass) {
339                 return overallContext.getSystemServiceName(serviceClass);
340             }
341 
342             @Override
343             public SharedPreferences getSharedPreferences(String name, int mode) {
344                 return mPrefs;
345             }
346 
347             @Override
348             public int getUserId() {
349                 return mockUserManager.getUserHandle();
350             }
351 
352             @Override
353             public void sendBroadcast(Intent intent, String receiverPermission) {
354                 // Ignore.
355             }
356         };
357 
358         mMockAccountManager = new MockAccountManager(mProviderContext);
359         mockUserManager = new MockUserManager(mProviderContext);
360         provider = addProvider(providerClass, authority);
361     }
362 
getProviderContext()363     public Context getProviderContext() {
364         return mProviderContext;
365     }
366 
addProvider(Class<T> providerClass, String authority)367     public <T extends ContentProvider> T addProvider(Class<T> providerClass,
368             String authority) throws Exception {
369         return addProvider(providerClass, authority, mProviderContext);
370     }
371 
addProvider(Class<T> providerClass, String authority, Context providerContext)372     public <T extends ContentProvider> T addProvider(Class<T> providerClass,
373             String authority, Context providerContext) throws Exception {
374         T provider = providerClass.newInstance();
375         ProviderInfo info = new ProviderInfo();
376 
377         // Here, authority can have "user-id@".  We want to use it for addProvider, but provider
378         // info shouldn't have it.
379         info.authority = stripOutUserIdFromAuthority(authority);
380         provider.attachInfoForTesting(providerContext, info);
381 
382         // In case of LegacyTest, "authority" here is actually multiple authorities.
383         // Register all authority here.
384         for (String a : authority.split(";")) {
385             resolver.addProvider(a, provider);
386             resolver.addProvider("0@" + a, provider);
387         }
388         return provider;
389     }
390 
391     /**
392      * Takes an provider authority. If it has "userid@", then remove it.
393      */
stripOutUserIdFromAuthority(String authority)394     private String stripOutUserIdFromAuthority(String authority) {
395         final int pos = authority.indexOf('@');
396         return pos < 0 ? authority : authority.substring(pos + 1);
397     }
398 
addPermissions(String... permissions)399     public void addPermissions(String... permissions) {
400         mGrantedPermissions.addAll(Arrays.asList(permissions));
401     }
402 
removePermissions(String... permissions)403     public void removePermissions(String... permissions) {
404         mGrantedPermissions.removeAll(Arrays.asList(permissions));
405     }
406 
addUriPermissions(Uri... uris)407     public void addUriPermissions(Uri... uris) {
408         mGrantedUriPermissions.addAll(Arrays.asList(uris));
409     }
410 
removeUriPermissions(Uri... uris)411     public void removeUriPermissions(Uri... uris) {
412         mGrantedUriPermissions.removeAll(Arrays.asList(uris));
413     }
414 
415     /**
416      * Mock {@link Context} that reports specific well-known values for testing
417      * data protection. The creator can override the owner package name, and
418      * force the {@link PackageManager} to always return a well-known package
419      * list for any call to {@link PackageManager#getPackagesForUid(int)}.
420      * <p>
421      * For example, the creator could request that the {@link Context} lives in
422      * package name "com.example.red", and also cause the {@link PackageManager}
423      * to report that no UID contains that package name.
424      */
425     private static class RestrictionMockContext extends MockContext {
426         private final Context mOverallContext;
427         private final String mReportedPackageName;
428         private final ContactsMockPackageManager mPackageManager;
429         private final ContentResolver mResolver;
430         private final Resources mRes;
431         private final Set<String> mGrantedPermissions;
432         private final Set<Uri> mGrantedUriPermissions;
433 
434         /**
435          * Create a {@link Context} under the given package name.
436          */
RestrictionMockContext(Context overallContext, String reportedPackageName, ContentResolver resolver, Set<String> grantedPermissions, Set<Uri> grantedUriPermissions)437         public RestrictionMockContext(Context overallContext, String reportedPackageName,
438                 ContentResolver resolver, Set<String> grantedPermissions,
439                 Set<Uri> grantedUriPermissions) {
440             mOverallContext = overallContext;
441             mReportedPackageName = reportedPackageName;
442             mResolver = resolver;
443             mGrantedPermissions = grantedPermissions;
444             mGrantedUriPermissions = grantedUriPermissions;
445 
446             mPackageManager = new ContactsMockPackageManager();
447             mPackageManager.addPackage(1000, PACKAGE_GREY);
448             mPackageManager.addPackage(2000, PACKAGE_RED);
449             mPackageManager.addPackage(3000, PACKAGE_GREEN);
450             mPackageManager.addPackage(4000, PACKAGE_BLUE);
451 
452             Resources resources = overallContext.getResources();
453             Configuration configuration = new Configuration(resources.getConfiguration());
454             configuration.locale = Locale.US;
455             resources.updateConfiguration(configuration, resources.getDisplayMetrics());
456             mRes = resources;
457         }
458 
459         @Override
getPackageName()460         public String getPackageName() {
461             return mReportedPackageName;
462         }
463 
464         @Override
getPackageManager()465         public PackageManager getPackageManager() {
466             return mPackageManager;
467         }
468 
469         @Override
getResources()470         public Resources getResources() {
471             return mRes;
472         }
473 
474         @Override
getContentResolver()475         public ContentResolver getContentResolver() {
476             return mResolver;
477         }
478 
479         @Override
getApplicationInfo()480         public ApplicationInfo getApplicationInfo() {
481             ApplicationInfo ai = new ApplicationInfo();
482             ai.packageName = "contactsTestPackage";
483             return ai;
484         }
485 
486         // All permission checks are implemented to simply check against the granted permission set.
487 
488         @Override
checkPermission(String permission, int pid, int uid)489         public int checkPermission(String permission, int pid, int uid) {
490             return checkCallingPermission(permission);
491         }
492 
493         @Override
checkCallingPermission(String permission)494         public int checkCallingPermission(String permission) {
495             if (mGrantedPermissions.contains(permission)) {
496                 return PackageManager.PERMISSION_GRANTED;
497             } else {
498                 return PackageManager.PERMISSION_DENIED;
499             }
500         }
501 
502         @Override
checkUriPermission(Uri uri, int pid, int uid, int modeFlags)503         public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
504             return checkCallingUriPermission(uri, modeFlags);
505         }
506 
507         @Override
checkCallingUriPermission(Uri uri, int modeFlags)508         public int checkCallingUriPermission(Uri uri, int modeFlags) {
509             if (mGrantedUriPermissions.contains(uri)) {
510                 return PackageManager.PERMISSION_GRANTED;
511             } else {
512                 return PackageManager.PERMISSION_DENIED;
513             }
514         }
515 
516         @Override
checkCallingOrSelfPermission(String permission)517         public int checkCallingOrSelfPermission(String permission) {
518             return checkCallingPermission(permission);
519         }
520 
521         @Override
enforcePermission(String permission, int pid, int uid, String message)522         public void enforcePermission(String permission, int pid, int uid, String message) {
523             enforceCallingPermission(permission, message);
524         }
525 
526         @Override
enforceCallingPermission(String permission, String message)527         public void enforceCallingPermission(String permission, String message) {
528             if (!mGrantedPermissions.contains(permission)) {
529                 throw new SecurityException(message);
530             }
531         }
532 
533         @Override
enforceCallingOrSelfPermission(String permission, String message)534         public void enforceCallingOrSelfPermission(String permission, String message) {
535             enforceCallingPermission(permission, message);
536         }
537 
538         @Override
sendBroadcast(Intent intent)539         public void sendBroadcast(Intent intent) {
540             mOverallContext.sendBroadcast(intent);
541         }
542 
543         @Override
sendBroadcast(Intent intent, String receiverPermission)544         public void sendBroadcast(Intent intent, String receiverPermission) {
545             mOverallContext.sendBroadcast(intent, receiverPermission);
546         }
547     }
548 
549     static String sCallingPackage = null;
550 
ensureCallingPackage()551     void ensureCallingPackage() {
552         sCallingPackage = this.packageName;
553     }
554 
createRawContact(String name)555     public long createRawContact(String name) {
556         ensureCallingPackage();
557         long rawContactId = createRawContact();
558         createName(rawContactId, name);
559         return rawContactId;
560     }
561 
createRawContact()562     public long createRawContact() {
563         ensureCallingPackage();
564         final ContentValues values = new ContentValues();
565 
566         Uri rawContactUri = resolver.insert(RawContacts.CONTENT_URI, values);
567         return ContentUris.parseId(rawContactUri);
568     }
569 
createRawContactWithStatus(String name, String address, String status)570     public long createRawContactWithStatus(String name, String address,
571             String status) {
572         final long rawContactId = createRawContact(name);
573         final long dataId = createEmail(rawContactId, address);
574         createStatus(dataId, status);
575         return rawContactId;
576     }
577 
createName(long contactId, String name)578     public long createName(long contactId, String name) {
579         ensureCallingPackage();
580         final ContentValues values = new ContentValues();
581         values.put(Data.RAW_CONTACT_ID, contactId);
582         values.put(Data.IS_PRIMARY, 1);
583         values.put(Data.IS_SUPER_PRIMARY, 1);
584         values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
585         values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name);
586         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
587                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
588         Uri dataUri = resolver.insert(insertUri, values);
589         return ContentUris.parseId(dataUri);
590     }
591 
createPhone(long contactId, String phoneNumber)592     public long createPhone(long contactId, String phoneNumber) {
593         ensureCallingPackage();
594         final ContentValues values = new ContentValues();
595         values.put(Data.RAW_CONTACT_ID, contactId);
596         values.put(Data.IS_PRIMARY, 1);
597         values.put(Data.IS_SUPER_PRIMARY, 1);
598         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
599         values.put(ContactsContract.CommonDataKinds.Phone.TYPE,
600                 ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
601         values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
602         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
603                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
604         Uri dataUri = resolver.insert(insertUri, values);
605         return ContentUris.parseId(dataUri);
606     }
607 
createEmail(long contactId, String address)608     public long createEmail(long contactId, String address) {
609         ensureCallingPackage();
610         final ContentValues values = new ContentValues();
611         values.put(Data.RAW_CONTACT_ID, contactId);
612         values.put(Data.IS_PRIMARY, 1);
613         values.put(Data.IS_SUPER_PRIMARY, 1);
614         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
615         values.put(Email.TYPE, Email.TYPE_HOME);
616         values.put(Email.DATA, address);
617         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
618                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
619         Uri dataUri = resolver.insert(insertUri, values);
620         return ContentUris.parseId(dataUri);
621     }
622 
createStatus(long dataId, String status)623     public long createStatus(long dataId, String status) {
624         ensureCallingPackage();
625         final ContentValues values = new ContentValues();
626         values.put(StatusUpdates.DATA_ID, dataId);
627         values.put(StatusUpdates.STATUS, status);
628         Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values);
629         return ContentUris.parseId(dataUri);
630     }
631 
updateException(String packageProvider, String packageClient, boolean allowAccess)632     public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
633         throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
634     }
635 
getContactForRawContact(long rawContactId)636     public long getContactForRawContact(long rawContactId) {
637         ensureCallingPackage();
638         Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
639         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null,
640                 null, null);
641         if (!cursor.moveToFirst()) {
642             cursor.close();
643             throw new RuntimeException("Contact didn't have an aggregate");
644         }
645         final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID);
646         cursor.close();
647         return aggId;
648     }
649 
getDataCountForContact(long contactId)650     public int getDataCountForContact(long contactId) {
651         ensureCallingPackage();
652         Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
653                 contactId), Contacts.Data.CONTENT_DIRECTORY);
654         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
655                 null);
656         final int count = cursor.getCount();
657         cursor.close();
658         return count;
659     }
660 
getDataCountForRawContact(long rawContactId)661     public int getDataCountForRawContact(long rawContactId) {
662         ensureCallingPackage();
663         Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
664                 rawContactId), Contacts.Data.CONTENT_DIRECTORY);
665         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
666                 null);
667         final int count = cursor.getCount();
668         cursor.close();
669         return count;
670     }
671 
setSuperPrimaryPhone(long dataId)672     public void setSuperPrimaryPhone(long dataId) {
673         ensureCallingPackage();
674         final ContentValues values = new ContentValues();
675         values.put(Data.IS_PRIMARY, 1);
676         values.put(Data.IS_SUPER_PRIMARY, 1);
677         Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
678         resolver.update(updateUri, values, null, null);
679     }
680 
createGroup(String groupName)681     public long createGroup(String groupName) {
682         ensureCallingPackage();
683         final ContentValues values = new ContentValues();
684         values.put(ContactsContract.Groups.RES_PACKAGE, packageName);
685         values.put(ContactsContract.Groups.TITLE, groupName);
686         Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values);
687         return ContentUris.parseId(groupUri);
688     }
689 
createGroupMembership(long rawContactId, long groupId)690     public long createGroupMembership(long rawContactId, long groupId) {
691         ensureCallingPackage();
692         final ContentValues values = new ContentValues();
693         values.put(Data.RAW_CONTACT_ID, rawContactId);
694         values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
695         values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
696         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
697                 rawContactId), RawContacts.Data.CONTENT_DIRECTORY);
698         Uri dataUri = resolver.insert(insertUri, values);
699         return ContentUris.parseId(dataUri);
700     }
701 
setAggregationException(int type, long rawContactId1, long rawContactId2)702     protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
703         ContentValues values = new ContentValues();
704         values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
705         values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
706         values.put(AggregationExceptions.TYPE, type);
707         resolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
708     }
709 
setAccounts(Account[] accounts)710     public void setAccounts(Account[] accounts) {
711         mAccounts = accounts;
712     }
713 
714     /**
715      * Various internal database projections.
716      */
717     private interface Projections {
718         static final String[] PROJ_ID = new String[] {
719                 BaseColumns._ID,
720         };
721 
722         static final int COL_ID = 0;
723 
724         static final String[] PROJ_RAW_CONTACTS = new String[] {
725                 RawContacts.CONTACT_ID
726         };
727 
728         static final int COL_CONTACTS_ID = 0;
729     }
730 }
731