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