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.app.admin.DevicePolicyManager;
27import android.content.ContentProvider;
28import android.content.ContentResolver;
29import android.content.ContentUris;
30import android.content.ContentValues;
31import android.content.Context;
32import android.content.ContextWrapper;
33import android.content.Intent;
34import android.content.SharedPreferences;
35import android.content.pm.ApplicationInfo;
36import android.content.pm.PackageManager;
37import android.content.pm.ProviderInfo;
38import android.content.pm.UserInfo;
39import android.content.res.Configuration;
40import android.content.res.Resources;
41import android.database.Cursor;
42import android.location.Country;
43import android.location.CountryDetector;
44import android.location.CountryListener;
45import android.net.Uri;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.IUserManager;
49import android.os.Looper;
50import android.os.UserHandle;
51import android.os.UserManager;
52import android.provider.BaseColumns;
53import android.provider.ContactsContract;
54import android.provider.ContactsContract.AggregationExceptions;
55import android.provider.ContactsContract.CommonDataKinds;
56import android.provider.ContactsContract.CommonDataKinds.Email;
57import android.provider.ContactsContract.CommonDataKinds.Phone;
58import android.provider.ContactsContract.Contacts;
59import android.provider.ContactsContract.Data;
60import android.provider.ContactsContract.RawContacts;
61import android.provider.ContactsContract.StatusUpdates;
62import android.test.IsolatedContext;
63import android.test.RenamingDelegatingContext;
64import android.test.mock.MockContentResolver;
65import android.test.mock.MockContext;
66import android.util.Log;
67
68import com.android.providers.contacts.util.ContactsPermissions;
69import com.android.providers.contacts.util.MockSharedPreferences;
70
71import com.google.android.collect.Sets;
72
73import java.io.File;
74import java.io.IOException;
75import java.util.ArrayList;
76import java.util.Arrays;
77import java.util.List;
78import java.util.Locale;
79import 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 */
87public 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        @Override
242        public boolean isUserUnlocked(int userId) {
243            return true; // Just make it always unlocked for now.
244        }
245    }
246
247    /**
248     * A context wrapper that reports a different user id.
249     *
250     * TODO This should override getSystemService() and returns a UserManager that returns the
251     * same, altered user ID too.
252     */
253    public static class AlteringUserContext extends ContextWrapper {
254        private final int mUserId;
255
256        public AlteringUserContext(Context base, int userId) {
257            super(base);
258            mUserId = userId;
259        }
260
261        @Override
262        public int getUserId() {
263            return mUserId;
264        }
265    }
266
267    private IsolatedContext mProviderContext;
268
269    /**
270     * Create an "actor" using the given parent {@link Context} and the specific
271     * package name. Internally, all {@link Context} method calls are passed to
272     * a new instance of {@link RestrictionMockContext}, which stubs out the
273     * security infrastructure.
274     */
275    public ContactsActor(final Context overallContext, String packageName,
276            Class<? extends ContentProvider> providerClass, String authority) throws Exception {
277
278        // Force permission check even when called by self.
279        ContactsPermissions.ALLOW_SELF_CALL = false;
280
281        resolver = new MockContentResolver();
282        context = new RestrictionMockContext(overallContext, packageName, resolver,
283                mGrantedPermissions, mGrantedUriPermissions) {
284            @Override
285            public Object getSystemService(String name) {
286                if (Context.COUNTRY_DETECTOR.equals(name)) {
287                    return mMockCountryDetector;
288                }
289                if (Context.ACCOUNT_SERVICE.equals(name)) {
290                    return mMockAccountManager;
291                }
292                if (Context.USER_SERVICE.equals(name)) {
293                    return mockUserManager;
294                }
295                // Use overallContext here; super.getSystemService() somehow won't return
296                // DevicePolicyManager.
297                return overallContext.getSystemService(name);
298            }
299
300
301
302            @Override
303            public String getSystemServiceName(Class<?> serviceClass) {
304                return overallContext.getSystemServiceName(serviceClass);
305            }
306        };
307        this.packageName = packageName;
308
309        // Let the Secure class initialize the settings provider, which is done when we first
310        // tries to get any setting.  Because our mock context/content resolver doesn't have the
311        // settings provider, we need to do this with an actual context, before other classes
312        // try to do this with a mock context.
313        // (Otherwise ContactsProvider2.initialzie() will crash trying to get a setting with
314        // a mock context.)
315        android.provider.Settings.Secure.getString(overallContext.getContentResolver(), "dummy");
316
317        RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
318                overallContext, FILENAME_PREFIX);
319        mProviderContext = new IsolatedContext(resolver, targetContextWrapper) {
320            private final MockSharedPreferences mPrefs = new MockSharedPreferences();
321
322            @Override
323            public File getFilesDir() {
324                // TODO: Need to figure out something more graceful than this.
325                return new File("/data/data/com.android.providers.contacts.tests/files");
326            }
327
328            @Override
329            public Object getSystemService(String name) {
330                if (Context.COUNTRY_DETECTOR.equals(name)) {
331                    return mMockCountryDetector;
332                }
333                if (Context.ACCOUNT_SERVICE.equals(name)) {
334                    return mMockAccountManager;
335                }
336                if (Context.USER_SERVICE.equals(name)) {
337                    return mockUserManager;
338                }
339                // Use overallContext here; super.getSystemService() somehow won't return
340                // DevicePolicyManager.
341                return overallContext.getSystemService(name);
342            }
343
344            @Override
345            public String getSystemServiceName(Class<?> serviceClass) {
346                return overallContext.getSystemServiceName(serviceClass);
347            }
348
349            @Override
350            public SharedPreferences getSharedPreferences(String name, int mode) {
351                return mPrefs;
352            }
353
354            @Override
355            public int getUserId() {
356                return mockUserManager.getUserHandle();
357            }
358
359            @Override
360            public void sendBroadcast(Intent intent, String receiverPermission) {
361                // Ignore.
362            }
363        };
364
365        mMockAccountManager = new MockAccountManager(mProviderContext);
366        mockUserManager = new MockUserManager(mProviderContext);
367        provider = addProvider(providerClass, authority);
368    }
369
370    public Context getProviderContext() {
371        return mProviderContext;
372    }
373
374    public <T extends ContentProvider> T addProvider(Class<T> providerClass,
375            String authority) throws Exception {
376        return addProvider(providerClass, authority, mProviderContext);
377    }
378
379    public <T extends ContentProvider> T addProvider(Class<T> providerClass,
380            String authority, Context providerContext) throws Exception {
381        T provider = providerClass.newInstance();
382        ProviderInfo info = new ProviderInfo();
383
384        // Here, authority can have "user-id@".  We want to use it for addProvider, but provider
385        // info shouldn't have it.
386        info.authority = stripOutUserIdFromAuthority(authority);
387        provider.attachInfoForTesting(providerContext, info);
388
389        // In case of LegacyTest, "authority" here is actually multiple authorities.
390        // Register all authority here.
391        for (String a : authority.split(";")) {
392            resolver.addProvider(a, provider);
393            resolver.addProvider("0@" + a, provider);
394        }
395        return provider;
396    }
397
398    /**
399     * Takes an provider authority. If it has "userid@", then remove it.
400     */
401    private String stripOutUserIdFromAuthority(String authority) {
402        final int pos = authority.indexOf('@');
403        return pos < 0 ? authority : authority.substring(pos + 1);
404    }
405
406    public void addPermissions(String... permissions) {
407        mGrantedPermissions.addAll(Arrays.asList(permissions));
408    }
409
410    public void removePermissions(String... permissions) {
411        mGrantedPermissions.removeAll(Arrays.asList(permissions));
412    }
413
414    public void addUriPermissions(Uri... uris) {
415        mGrantedUriPermissions.addAll(Arrays.asList(uris));
416    }
417
418    public void removeUriPermissions(Uri... uris) {
419        mGrantedUriPermissions.removeAll(Arrays.asList(uris));
420    }
421
422    /**
423     * Mock {@link Context} that reports specific well-known values for testing
424     * data protection. The creator can override the owner package name, and
425     * force the {@link PackageManager} to always return a well-known package
426     * list for any call to {@link PackageManager#getPackagesForUid(int)}.
427     * <p>
428     * For example, the creator could request that the {@link Context} lives in
429     * package name "com.example.red", and also cause the {@link PackageManager}
430     * to report that no UID contains that package name.
431     */
432    private static class RestrictionMockContext extends MockContext {
433        private final Context mOverallContext;
434        private final String mReportedPackageName;
435        private final ContactsMockPackageManager mPackageManager;
436        private final ContentResolver mResolver;
437        private final Resources mRes;
438        private final Set<String> mGrantedPermissions;
439        private final Set<Uri> mGrantedUriPermissions;
440
441        /**
442         * Create a {@link Context} under the given package name.
443         */
444        public RestrictionMockContext(Context overallContext, String reportedPackageName,
445                ContentResolver resolver, Set<String> grantedPermissions,
446                Set<Uri> grantedUriPermissions) {
447            mOverallContext = overallContext;
448            mReportedPackageName = reportedPackageName;
449            mResolver = resolver;
450            mGrantedPermissions = grantedPermissions;
451            mGrantedUriPermissions = grantedUriPermissions;
452
453            mPackageManager = new ContactsMockPackageManager();
454            mPackageManager.addPackage(1000, PACKAGE_GREY);
455            mPackageManager.addPackage(2000, PACKAGE_RED);
456            mPackageManager.addPackage(3000, PACKAGE_GREEN);
457            mPackageManager.addPackage(4000, PACKAGE_BLUE);
458
459            Resources resources = overallContext.getResources();
460            Configuration configuration = new Configuration(resources.getConfiguration());
461            configuration.locale = Locale.US;
462            resources.updateConfiguration(configuration, resources.getDisplayMetrics());
463            mRes = resources;
464        }
465
466        @Override
467        public String getPackageName() {
468            return mReportedPackageName;
469        }
470
471        @Override
472        public PackageManager getPackageManager() {
473            return mPackageManager;
474        }
475
476        @Override
477        public Resources getResources() {
478            return mRes;
479        }
480
481        @Override
482        public ContentResolver getContentResolver() {
483            return mResolver;
484        }
485
486        @Override
487        public ApplicationInfo getApplicationInfo() {
488            ApplicationInfo ai = new ApplicationInfo();
489            ai.packageName = "contactsTestPackage";
490            return ai;
491        }
492
493        // All permission checks are implemented to simply check against the granted permission set.
494
495        @Override
496        public int checkPermission(String permission, int pid, int uid) {
497            return checkCallingPermission(permission);
498        }
499
500        @Override
501        public int checkCallingPermission(String permission) {
502            if (mGrantedPermissions.contains(permission)) {
503                return PackageManager.PERMISSION_GRANTED;
504            } else {
505                return PackageManager.PERMISSION_DENIED;
506            }
507        }
508
509        @Override
510        public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
511            return checkCallingUriPermission(uri, modeFlags);
512        }
513
514        @Override
515        public int checkCallingUriPermission(Uri uri, int modeFlags) {
516            if (mGrantedUriPermissions.contains(uri)) {
517                return PackageManager.PERMISSION_GRANTED;
518            } else {
519                return PackageManager.PERMISSION_DENIED;
520            }
521        }
522
523        @Override
524        public int checkCallingOrSelfPermission(String permission) {
525            return checkCallingPermission(permission);
526        }
527
528        @Override
529        public void enforcePermission(String permission, int pid, int uid, String message) {
530            enforceCallingPermission(permission, message);
531        }
532
533        @Override
534        public void enforceCallingPermission(String permission, String message) {
535            if (!mGrantedPermissions.contains(permission)) {
536                throw new SecurityException(message);
537            }
538        }
539
540        @Override
541        public void enforceCallingOrSelfPermission(String permission, String message) {
542            enforceCallingPermission(permission, message);
543        }
544
545        @Override
546        public void sendBroadcast(Intent intent) {
547            mOverallContext.sendBroadcast(intent);
548        }
549
550        @Override
551        public void sendBroadcast(Intent intent, String receiverPermission) {
552            mOverallContext.sendBroadcast(intent, receiverPermission);
553        }
554    }
555
556    static String sCallingPackage = null;
557
558    void ensureCallingPackage() {
559        sCallingPackage = this.packageName;
560    }
561
562    public long createRawContact(String name) {
563        ensureCallingPackage();
564        long rawContactId = createRawContact();
565        createName(rawContactId, name);
566        return rawContactId;
567    }
568
569    public long createRawContact() {
570        ensureCallingPackage();
571        final ContentValues values = new ContentValues();
572
573        Uri rawContactUri = resolver.insert(RawContacts.CONTENT_URI, values);
574        return ContentUris.parseId(rawContactUri);
575    }
576
577    public long createRawContactWithStatus(String name, String address,
578            String status) {
579        final long rawContactId = createRawContact(name);
580        final long dataId = createEmail(rawContactId, address);
581        createStatus(dataId, status);
582        return rawContactId;
583    }
584
585    public long createName(long contactId, String name) {
586        ensureCallingPackage();
587        final ContentValues values = new ContentValues();
588        values.put(Data.RAW_CONTACT_ID, contactId);
589        values.put(Data.IS_PRIMARY, 1);
590        values.put(Data.IS_SUPER_PRIMARY, 1);
591        values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
592        values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name);
593        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
594                contactId), RawContacts.Data.CONTENT_DIRECTORY);
595        Uri dataUri = resolver.insert(insertUri, values);
596        return ContentUris.parseId(dataUri);
597    }
598
599    public long createPhone(long contactId, String phoneNumber) {
600        ensureCallingPackage();
601        final ContentValues values = new ContentValues();
602        values.put(Data.RAW_CONTACT_ID, contactId);
603        values.put(Data.IS_PRIMARY, 1);
604        values.put(Data.IS_SUPER_PRIMARY, 1);
605        values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
606        values.put(ContactsContract.CommonDataKinds.Phone.TYPE,
607                ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
608        values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
609        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
610                contactId), RawContacts.Data.CONTENT_DIRECTORY);
611        Uri dataUri = resolver.insert(insertUri, values);
612        return ContentUris.parseId(dataUri);
613    }
614
615    public long createEmail(long contactId, String address) {
616        ensureCallingPackage();
617        final ContentValues values = new ContentValues();
618        values.put(Data.RAW_CONTACT_ID, contactId);
619        values.put(Data.IS_PRIMARY, 1);
620        values.put(Data.IS_SUPER_PRIMARY, 1);
621        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
622        values.put(Email.TYPE, Email.TYPE_HOME);
623        values.put(Email.DATA, address);
624        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
625                contactId), RawContacts.Data.CONTENT_DIRECTORY);
626        Uri dataUri = resolver.insert(insertUri, values);
627        return ContentUris.parseId(dataUri);
628    }
629
630    public long createStatus(long dataId, String status) {
631        ensureCallingPackage();
632        final ContentValues values = new ContentValues();
633        values.put(StatusUpdates.DATA_ID, dataId);
634        values.put(StatusUpdates.STATUS, status);
635        Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values);
636        return ContentUris.parseId(dataUri);
637    }
638
639    public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
640        throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
641    }
642
643    public long getContactForRawContact(long rawContactId) {
644        ensureCallingPackage();
645        Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
646        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null,
647                null, null);
648        if (!cursor.moveToFirst()) {
649            cursor.close();
650            throw new RuntimeException("Contact didn't have an aggregate");
651        }
652        final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID);
653        cursor.close();
654        return aggId;
655    }
656
657    public int getDataCountForContact(long contactId) {
658        ensureCallingPackage();
659        Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
660                contactId), Contacts.Data.CONTENT_DIRECTORY);
661        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
662                null);
663        final int count = cursor.getCount();
664        cursor.close();
665        return count;
666    }
667
668    public int getDataCountForRawContact(long rawContactId) {
669        ensureCallingPackage();
670        Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
671                rawContactId), Contacts.Data.CONTENT_DIRECTORY);
672        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
673                null);
674        final int count = cursor.getCount();
675        cursor.close();
676        return count;
677    }
678
679    public void setSuperPrimaryPhone(long dataId) {
680        ensureCallingPackage();
681        final ContentValues values = new ContentValues();
682        values.put(Data.IS_PRIMARY, 1);
683        values.put(Data.IS_SUPER_PRIMARY, 1);
684        Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
685        resolver.update(updateUri, values, null, null);
686    }
687
688    public long createGroup(String groupName) {
689        ensureCallingPackage();
690        final ContentValues values = new ContentValues();
691        values.put(ContactsContract.Groups.RES_PACKAGE, packageName);
692        values.put(ContactsContract.Groups.TITLE, groupName);
693        Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values);
694        return ContentUris.parseId(groupUri);
695    }
696
697    public long createGroupMembership(long rawContactId, long groupId) {
698        ensureCallingPackage();
699        final ContentValues values = new ContentValues();
700        values.put(Data.RAW_CONTACT_ID, rawContactId);
701        values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
702        values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
703        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
704                rawContactId), RawContacts.Data.CONTENT_DIRECTORY);
705        Uri dataUri = resolver.insert(insertUri, values);
706        return ContentUris.parseId(dataUri);
707    }
708
709    protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
710        ContentValues values = new ContentValues();
711        values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
712        values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
713        values.put(AggregationExceptions.TYPE, type);
714        resolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
715    }
716
717    public void setAccounts(Account[] accounts) {
718        mAccounts = accounts;
719    }
720
721    /**
722     * Various internal database projections.
723     */
724    private interface Projections {
725        static final String[] PROJ_ID = new String[] {
726                BaseColumns._ID,
727        };
728
729        static final int COL_ID = 0;
730
731        static final String[] PROJ_RAW_CONTACTS = new String[] {
732                RawContacts.CONTACT_ID
733        };
734
735        static final int COL_CONTACTS_ID = 0;
736    }
737}
738