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