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