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.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 {
    120         public MockAccountManager(Context conteact) {
    121             super(context, null, null);
    122         }
    123 
    124         @Override
    125         public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener,
    126                 Handler handler, boolean updateImmediately) {
    127             // do nothing
    128         }
    129 
    130         @Override
    131         public Account[] getAccounts() {
    132             return mAccounts;
    133         }
    134 
    135         @Override
    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
    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 {
    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 
    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. */
    180         public void setUsers(UserInfo... users) {
    181             mUsers.clear();
    182             for (UserInfo ui : users) {
    183                 mUsers.add(ui);
    184             }
    185         }
    186 
    187         @Override
    188         public int getUserHandle() {
    189             return myUser;
    190         }
    191 
    192         @Override
    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
    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
    217         public List<UserInfo> getUsers() {
    218             return mUsers;
    219         }
    220 
    221         @Override
    222         public Bundle getUserRestrictions(UserHandle userHandle) {
    223             return new Bundle();
    224         }
    225 
    226         @Override
    227         public boolean hasUserRestriction(String restrictionKey) {
    228             return false;
    229         }
    230 
    231         @Override
    232         public boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
    233             return false;
    234         }
    235 
    236         @Override
    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 
    251         public AlteringUserContext(Context base, int userId) {
    252             super(base);
    253             mUserId = userId;
    254         }
    255 
    256         @Override
    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      */
    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 
    363     public Context getProviderContext() {
    364         return mProviderContext;
    365     }
    366 
    367     public <T extends ContentProvider> T addProvider(Class<T> providerClass,
    368             String authority) throws Exception {
    369         return addProvider(providerClass, authority, mProviderContext);
    370     }
    371 
    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      */
    394     private String stripOutUserIdFromAuthority(String authority) {
    395         final int pos = authority.indexOf('@');
    396         return pos < 0 ? authority : authority.substring(pos + 1);
    397     }
    398 
    399     public void addPermissions(String... permissions) {
    400         mGrantedPermissions.addAll(Arrays.asList(permissions));
    401     }
    402 
    403     public void removePermissions(String... permissions) {
    404         mGrantedPermissions.removeAll(Arrays.asList(permissions));
    405     }
    406 
    407     public void addUriPermissions(Uri... uris) {
    408         mGrantedUriPermissions.addAll(Arrays.asList(uris));
    409     }
    410 
    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          */
    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
    460         public String getPackageName() {
    461             return mReportedPackageName;
    462         }
    463 
    464         @Override
    465         public PackageManager getPackageManager() {
    466             return mPackageManager;
    467         }
    468 
    469         @Override
    470         public Resources getResources() {
    471             return mRes;
    472         }
    473 
    474         @Override
    475         public ContentResolver getContentResolver() {
    476             return mResolver;
    477         }
    478 
    479         @Override
    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
    489         public int checkPermission(String permission, int pid, int uid) {
    490             return checkCallingPermission(permission);
    491         }
    492 
    493         @Override
    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
    503         public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
    504             return checkCallingUriPermission(uri, modeFlags);
    505         }
    506 
    507         @Override
    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
    517         public int checkCallingOrSelfPermission(String permission) {
    518             return checkCallingPermission(permission);
    519         }
    520 
    521         @Override
    522         public void enforcePermission(String permission, int pid, int uid, String message) {
    523             enforceCallingPermission(permission, message);
    524         }
    525 
    526         @Override
    527         public void enforceCallingPermission(String permission, String message) {
    528             if (!mGrantedPermissions.contains(permission)) {
    529                 throw new SecurityException(message);
    530             }
    531         }
    532 
    533         @Override
    534         public void enforceCallingOrSelfPermission(String permission, String message) {
    535             enforceCallingPermission(permission, message);
    536         }
    537 
    538         @Override
    539         public void sendBroadcast(Intent intent) {
    540             mOverallContext.sendBroadcast(intent);
    541         }
    542 
    543         @Override
    544         public void sendBroadcast(Intent intent, String receiverPermission) {
    545             mOverallContext.sendBroadcast(intent, receiverPermission);
    546         }
    547     }
    548 
    549     static String sCallingPackage = null;
    550 
    551     void ensureCallingPackage() {
    552         sCallingPackage = this.packageName;
    553     }
    554 
    555     public long createRawContact(String name) {
    556         ensureCallingPackage();
    557         long rawContactId = createRawContact();
    558         createName(rawContactId, name);
    559         return rawContactId;
    560     }
    561 
    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 
    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 
    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 
    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 
    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 
    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 
    632     public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
    633         throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
    634     }
    635 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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