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;
69import com.google.android.collect.Sets;
70
71import java.io.File;
72import java.io.IOException;
73import java.util.ArrayList;
74import java.util.Arrays;
75import java.util.List;
76import java.util.Locale;
77import java.util.Set;
78
79/**
80 * Helper class that encapsulates an "actor" which is owned by a specific
81 * package name. It correctly maintains a wrapped {@link Context} and an
82 * attached {@link MockContentResolver}. Multiple actors can be used to test
83 * security scenarios between multiple packages.
84 */
85public class ContactsActor {
86    private static final String FILENAME_PREFIX = "test.";
87
88    public static final String PACKAGE_GREY = "edu.example.grey";
89    public static final String PACKAGE_RED = "net.example.red";
90    public static final String PACKAGE_GREEN = "com.example.green";
91    public static final String PACKAGE_BLUE = "org.example.blue";
92
93    public Context context;
94    public String packageName;
95    public MockContentResolver resolver;
96    public ContentProvider provider;
97    private Country mMockCountry = new Country("us", 0);
98
99    private Account[] mAccounts = new Account[0];
100
101    private Set<String> mGrantedPermissions = Sets.newHashSet();
102    private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet();
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
225    /**
226     * A context wrapper that reports a different user id.
227     *
228     * TODO This should override getSystemService() and returns a UserManager that returns the
229     * same, altered user ID too.
230     */
231    public static class AlteringUserContext extends ContextWrapper {
232        private final int mUserId;
233
234        public AlteringUserContext(Context base, int userId) {
235            super(base);
236            mUserId = userId;
237        }
238
239        @Override
240        public int getUserId() {
241            return mUserId;
242        }
243    }
244
245    private IsolatedContext mProviderContext;
246
247    /**
248     * Create an "actor" using the given parent {@link Context} and the specific
249     * package name. Internally, all {@link Context} method calls are passed to
250     * a new instance of {@link RestrictionMockContext}, which stubs out the
251     * security infrastructure.
252     */
253    public ContactsActor(final Context overallContext, String packageName,
254            Class<? extends ContentProvider> providerClass, String authority) throws Exception {
255        resolver = new MockContentResolver();
256        context = new RestrictionMockContext(overallContext, packageName, resolver,
257                mGrantedPermissions, mGrantedUriPermissions);
258        this.packageName = packageName;
259
260        // Let the Secure class initialize the settings provider, which is done when we first
261        // tries to get any setting.  Because our mock context/content resolver doesn't have the
262        // settings provider, we need to do this with an actual context, before other classes
263        // try to do this with a mock context.
264        // (Otherwise ContactsProvider2.initialzie() will crash trying to get a setting with
265        // a mock context.)
266        android.provider.Settings.Secure.getString(overallContext.getContentResolver(), "dummy");
267
268        RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
269                overallContext, FILENAME_PREFIX);
270        mProviderContext = new IsolatedContext(resolver, targetContextWrapper) {
271            private final MockSharedPreferences mPrefs = new MockSharedPreferences();
272
273            @Override
274            public File getFilesDir() {
275                // TODO: Need to figure out something more graceful than this.
276                return new File("/data/data/com.android.providers.contacts.tests/files");
277            }
278
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 SharedPreferences getSharedPreferences(String name, int mode) {
297                return mPrefs;
298            }
299
300            @Override
301            public int getUserId() {
302                return mockUserManager.getUserHandle();
303            }
304        };
305
306        mMockAccountManager = new MockAccountManager(mProviderContext);
307        mockUserManager = new MockUserManager(mProviderContext);
308        provider = addProvider(providerClass, authority);
309    }
310
311    public Context getProviderContext() {
312        return mProviderContext;
313    }
314
315    public void addAuthority(String authority) {
316        resolver.addProvider(authority, provider);
317    }
318
319    public <T extends ContentProvider> T addProvider(Class<T> providerClass,
320            String authority) throws Exception {
321        return addProvider(providerClass, authority, mProviderContext);
322    }
323
324    public <T extends ContentProvider> T addProvider(Class<T> providerClass,
325            String authority, Context providerContext) throws Exception {
326        T provider = providerClass.newInstance();
327        ProviderInfo info = new ProviderInfo();
328
329        // Here, authority can have "user-id@".  We want to use it for addProvider, but provider
330        // info shouldn't have it.
331        info.authority = stripOutUserIdFromAuthority(authority);
332        provider.attachInfoForTesting(providerContext, info);
333        resolver.addProvider(authority, provider);
334        return provider;
335    }
336
337    /**
338     * Takes an provider authority. If it has "userid@", then remove it.
339     */
340    private String stripOutUserIdFromAuthority(String authority) {
341        final int pos = authority.indexOf('@');
342        return pos < 0 ? authority : authority.substring(pos + 1);
343    }
344
345    public void addPermissions(String... permissions) {
346        mGrantedPermissions.addAll(Arrays.asList(permissions));
347    }
348
349    public void removePermissions(String... permissions) {
350        mGrantedPermissions.removeAll(Arrays.asList(permissions));
351    }
352
353    public void addUriPermissions(Uri... uris) {
354        mGrantedUriPermissions.addAll(Arrays.asList(uris));
355    }
356
357    public void removeUriPermissions(Uri... uris) {
358        mGrantedUriPermissions.removeAll(Arrays.asList(uris));
359    }
360
361    /**
362     * Mock {@link Context} that reports specific well-known values for testing
363     * data protection. The creator can override the owner package name, and
364     * force the {@link PackageManager} to always return a well-known package
365     * list for any call to {@link PackageManager#getPackagesForUid(int)}.
366     * <p>
367     * For example, the creator could request that the {@link Context} lives in
368     * package name "com.example.red", and also cause the {@link PackageManager}
369     * to report that no UID contains that package name.
370     */
371    private static class RestrictionMockContext extends MockContext {
372        private final Context mOverallContext;
373        private final String mReportedPackageName;
374        private final ContactsMockPackageManager mPackageManager;
375        private final ContentResolver mResolver;
376        private final Resources mRes;
377        private final Set<String> mGrantedPermissions;
378        private final Set<Uri> mGrantedUriPermissions;
379
380        /**
381         * Create a {@link Context} under the given package name.
382         */
383        public RestrictionMockContext(Context overallContext, String reportedPackageName,
384                ContentResolver resolver, Set<String> grantedPermissions,
385                Set<Uri> grantedUriPermissions) {
386            mOverallContext = overallContext;
387            mReportedPackageName = reportedPackageName;
388            mResolver = resolver;
389            mGrantedPermissions = grantedPermissions;
390            mGrantedUriPermissions = grantedUriPermissions;
391
392            mPackageManager = new ContactsMockPackageManager();
393            mPackageManager.addPackage(1000, PACKAGE_GREY);
394            mPackageManager.addPackage(2000, PACKAGE_RED);
395            mPackageManager.addPackage(3000, PACKAGE_GREEN);
396            mPackageManager.addPackage(4000, PACKAGE_BLUE);
397
398            Resources resources = overallContext.getResources();
399            Configuration configuration = new Configuration(resources.getConfiguration());
400            configuration.locale = Locale.US;
401            resources.updateConfiguration(configuration, resources.getDisplayMetrics());
402            mRes = resources;
403        }
404
405        @Override
406        public String getPackageName() {
407            return mReportedPackageName;
408        }
409
410        @Override
411        public PackageManager getPackageManager() {
412            return mPackageManager;
413        }
414
415        @Override
416        public Resources getResources() {
417            return mRes;
418        }
419
420        @Override
421        public ContentResolver getContentResolver() {
422            return mResolver;
423        }
424
425        @Override
426        public ApplicationInfo getApplicationInfo() {
427            ApplicationInfo ai = new ApplicationInfo();
428            ai.packageName = "contactsTestPackage";
429            return ai;
430        }
431
432        // All permission checks are implemented to simply check against the granted permission set.
433
434        @Override
435        public int checkPermission(String permission, int pid, int uid) {
436            return checkCallingPermission(permission);
437        }
438
439        @Override
440        public int checkCallingPermission(String permission) {
441            if (mGrantedPermissions.contains(permission)) {
442                return PackageManager.PERMISSION_GRANTED;
443            } else {
444                return PackageManager.PERMISSION_DENIED;
445            }
446        }
447
448        @Override
449        public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
450            return checkCallingUriPermission(uri, modeFlags);
451        }
452
453        @Override
454        public int checkCallingUriPermission(Uri uri, int modeFlags) {
455            if (mGrantedUriPermissions.contains(uri)) {
456                return PackageManager.PERMISSION_GRANTED;
457            } else {
458                return PackageManager.PERMISSION_DENIED;
459            }
460        }
461
462        @Override
463        public int checkCallingOrSelfPermission(String permission) {
464            return checkCallingPermission(permission);
465        }
466
467        @Override
468        public void enforcePermission(String permission, int pid, int uid, String message) {
469            enforceCallingPermission(permission, message);
470        }
471
472        @Override
473        public void enforceCallingPermission(String permission, String message) {
474            if (!mGrantedPermissions.contains(permission)) {
475                throw new SecurityException(message);
476            }
477        }
478
479        @Override
480        public void enforceCallingOrSelfPermission(String permission, String message) {
481            enforceCallingPermission(permission, message);
482        }
483
484        @Override
485        public void sendBroadcast(Intent intent) {
486            mOverallContext.sendBroadcast(intent);
487        }
488
489        @Override
490        public void sendBroadcast(Intent intent, String receiverPermission) {
491            mOverallContext.sendBroadcast(intent, receiverPermission);
492        }
493    }
494
495    static String sCallingPackage = null;
496
497    void ensureCallingPackage() {
498        sCallingPackage = this.packageName;
499    }
500
501    public long createRawContact(String name) {
502        ensureCallingPackage();
503        long rawContactId = createRawContact();
504        createName(rawContactId, name);
505        return rawContactId;
506    }
507
508    public long createRawContact() {
509        ensureCallingPackage();
510        final ContentValues values = new ContentValues();
511
512        Uri rawContactUri = resolver.insert(RawContacts.CONTENT_URI, values);
513        return ContentUris.parseId(rawContactUri);
514    }
515
516    public long createRawContactWithStatus(String name, String address,
517            String status) {
518        final long rawContactId = createRawContact(name);
519        final long dataId = createEmail(rawContactId, address);
520        createStatus(dataId, status);
521        return rawContactId;
522    }
523
524    public long createName(long contactId, String name) {
525        ensureCallingPackage();
526        final ContentValues values = new ContentValues();
527        values.put(Data.RAW_CONTACT_ID, contactId);
528        values.put(Data.IS_PRIMARY, 1);
529        values.put(Data.IS_SUPER_PRIMARY, 1);
530        values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
531        values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name);
532        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
533                contactId), RawContacts.Data.CONTENT_DIRECTORY);
534        Uri dataUri = resolver.insert(insertUri, values);
535        return ContentUris.parseId(dataUri);
536    }
537
538    public long createPhone(long contactId, String phoneNumber) {
539        ensureCallingPackage();
540        final ContentValues values = new ContentValues();
541        values.put(Data.RAW_CONTACT_ID, contactId);
542        values.put(Data.IS_PRIMARY, 1);
543        values.put(Data.IS_SUPER_PRIMARY, 1);
544        values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
545        values.put(ContactsContract.CommonDataKinds.Phone.TYPE,
546                ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
547        values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
548        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
549                contactId), RawContacts.Data.CONTENT_DIRECTORY);
550        Uri dataUri = resolver.insert(insertUri, values);
551        return ContentUris.parseId(dataUri);
552    }
553
554    public long createEmail(long contactId, String address) {
555        ensureCallingPackage();
556        final ContentValues values = new ContentValues();
557        values.put(Data.RAW_CONTACT_ID, contactId);
558        values.put(Data.IS_PRIMARY, 1);
559        values.put(Data.IS_SUPER_PRIMARY, 1);
560        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
561        values.put(Email.TYPE, Email.TYPE_HOME);
562        values.put(Email.DATA, address);
563        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
564                contactId), RawContacts.Data.CONTENT_DIRECTORY);
565        Uri dataUri = resolver.insert(insertUri, values);
566        return ContentUris.parseId(dataUri);
567    }
568
569    public long createStatus(long dataId, String status) {
570        ensureCallingPackage();
571        final ContentValues values = new ContentValues();
572        values.put(StatusUpdates.DATA_ID, dataId);
573        values.put(StatusUpdates.STATUS, status);
574        Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values);
575        return ContentUris.parseId(dataUri);
576    }
577
578    public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
579        throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
580    }
581
582    public long getContactForRawContact(long rawContactId) {
583        ensureCallingPackage();
584        Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
585        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null,
586                null, null);
587        if (!cursor.moveToFirst()) {
588            cursor.close();
589            throw new RuntimeException("Contact didn't have an aggregate");
590        }
591        final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID);
592        cursor.close();
593        return aggId;
594    }
595
596    public int getDataCountForContact(long contactId) {
597        ensureCallingPackage();
598        Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
599                contactId), Contacts.Data.CONTENT_DIRECTORY);
600        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
601                null);
602        final int count = cursor.getCount();
603        cursor.close();
604        return count;
605    }
606
607    public int getDataCountForRawContact(long rawContactId) {
608        ensureCallingPackage();
609        Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
610                rawContactId), 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 void setSuperPrimaryPhone(long dataId) {
619        ensureCallingPackage();
620        final ContentValues values = new ContentValues();
621        values.put(Data.IS_PRIMARY, 1);
622        values.put(Data.IS_SUPER_PRIMARY, 1);
623        Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
624        resolver.update(updateUri, values, null, null);
625    }
626
627    public long createGroup(String groupName) {
628        ensureCallingPackage();
629        final ContentValues values = new ContentValues();
630        values.put(ContactsContract.Groups.RES_PACKAGE, packageName);
631        values.put(ContactsContract.Groups.TITLE, groupName);
632        Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values);
633        return ContentUris.parseId(groupUri);
634    }
635
636    public long createGroupMembership(long rawContactId, long groupId) {
637        ensureCallingPackage();
638        final ContentValues values = new ContentValues();
639        values.put(Data.RAW_CONTACT_ID, rawContactId);
640        values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
641        values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
642        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
643                rawContactId), RawContacts.Data.CONTENT_DIRECTORY);
644        Uri dataUri = resolver.insert(insertUri, values);
645        return ContentUris.parseId(dataUri);
646    }
647
648    protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
649        ContentValues values = new ContentValues();
650        values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
651        values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
652        values.put(AggregationExceptions.TYPE, type);
653        resolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
654    }
655
656    public void setAccounts(Account[] accounts) {
657        mAccounts = accounts;
658    }
659
660    /**
661     * Various internal database projections.
662     */
663    private interface Projections {
664        static final String[] PROJ_ID = new String[] {
665                BaseColumns._ID,
666        };
667
668        static final int COL_ID = 0;
669
670        static final String[] PROJ_RAW_CONTACTS = new String[] {
671                RawContacts.CONTACT_ID
672        };
673
674        static final int COL_CONTACTS_ID = 0;
675    }
676}
677