Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.providers.contacts;
     18 
     19 import com.google.android.collect.Sets;
     20 
     21 import android.accounts.Account;
     22 import android.accounts.AccountManager;
     23 import android.accounts.AccountManagerCallback;
     24 import android.accounts.AccountManagerFuture;
     25 import android.accounts.AuthenticatorException;
     26 import android.accounts.OnAccountsUpdateListener;
     27 import android.accounts.OperationCanceledException;
     28 import android.content.ContentProvider;
     29 import android.content.ContentResolver;
     30 import android.content.ContentUris;
     31 import android.content.ContentValues;
     32 import android.content.Context;
     33 import android.content.pm.ApplicationInfo;
     34 import android.content.pm.PackageManager;
     35 import android.content.pm.ProviderInfo;
     36 import android.content.res.Configuration;
     37 import android.content.res.Resources;
     38 import android.database.Cursor;
     39 import android.location.Country;
     40 import android.location.CountryDetector;
     41 import android.location.CountryListener;
     42 import android.net.Uri;
     43 import android.os.Handler;
     44 import android.os.Looper;
     45 import android.provider.BaseColumns;
     46 import android.provider.ContactsContract;
     47 import android.provider.ContactsContract.AggregationExceptions;
     48 import android.provider.ContactsContract.CommonDataKinds;
     49 import android.provider.ContactsContract.CommonDataKinds.Email;
     50 import android.provider.ContactsContract.CommonDataKinds.Phone;
     51 import android.provider.ContactsContract.Contacts;
     52 import android.provider.ContactsContract.Data;
     53 import android.provider.ContactsContract.RawContacts;
     54 import android.provider.ContactsContract.StatusUpdates;
     55 import android.test.IsolatedContext;
     56 import android.test.RenamingDelegatingContext;
     57 import android.test.mock.MockContentResolver;
     58 import android.test.mock.MockContext;
     59 
     60 import java.io.File;
     61 import java.io.IOException;
     62 import java.util.Arrays;
     63 import java.util.Locale;
     64 import java.util.Set;
     65 
     66 /**
     67  * Helper class that encapsulates an "actor" which is owned by a specific
     68  * package name. It correctly maintains a wrapped {@link Context} and an
     69  * attached {@link MockContentResolver}. Multiple actors can be used to test
     70  * security scenarios between multiple packages.
     71  */
     72 public class ContactsActor {
     73     private static final String FILENAME_PREFIX = "test.";
     74 
     75     public static final String PACKAGE_GREY = "edu.example.grey";
     76     public static final String PACKAGE_RED = "net.example.red";
     77     public static final String PACKAGE_GREEN = "com.example.green";
     78     public static final String PACKAGE_BLUE = "org.example.blue";
     79 
     80     public Context context;
     81     public String packageName;
     82     public MockContentResolver resolver;
     83     public ContentProvider provider;
     84     private Country mMockCountry = new Country("us", 0);
     85 
     86     private Account[] mAccounts = new Account[0];
     87 
     88     private Set<String> mGrantedPermissions = Sets.newHashSet();
     89     private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet();
     90 
     91     private CountryDetector mMockCountryDetector = new CountryDetector(null){
     92         @Override
     93         public Country detectCountry() {
     94             return mMockCountry;
     95         }
     96 
     97         @Override
     98         public void addCountryListener(CountryListener listener, Looper looper) {
     99         }
    100     };
    101 
    102     private AccountManager mMockAccountManager;
    103 
    104     private class MockAccountManager extends AccountManager {
    105         public MockAccountManager(Context conteact) {
    106             super(context, null, null);
    107         }
    108 
    109         @Override
    110         public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener,
    111                 Handler handler, boolean updateImmediately) {
    112             // do nothing
    113         }
    114 
    115         @Override
    116         public Account[] getAccounts() {
    117             return mAccounts;
    118         }
    119 
    120         @Override
    121         public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
    122                 final String type, final String[] features,
    123                 AccountManagerCallback<Account[]> callback, Handler handler) {
    124             return null;
    125         }
    126 
    127         @Override
    128         public String blockingGetAuthToken(Account account, String authTokenType,
    129                 boolean notifyAuthFailure)
    130                 throws OperationCanceledException, IOException, AuthenticatorException {
    131             return null;
    132         }
    133     }
    134 
    135     private IsolatedContext mProviderContext;
    136 
    137     /**
    138      * Create an "actor" using the given parent {@link Context} and the specific
    139      * package name. Internally, all {@link Context} method calls are passed to
    140      * a new instance of {@link RestrictionMockContext}, which stubs out the
    141      * security infrastructure.
    142      */
    143     public ContactsActor(Context overallContext, String packageName,
    144             Class<? extends ContentProvider> providerClass, String authority) throws Exception {
    145         resolver = new MockContentResolver();
    146         context = new RestrictionMockContext(overallContext, packageName, resolver,
    147                 mGrantedPermissions, mGrantedUriPermissions);
    148         this.packageName = packageName;
    149 
    150         // Let the Secure class initialize the settings provider, which is done when we first
    151         // tries to get any setting.  Because our mock context/content resolver doesn't have the
    152         // settings provider, we need to do this with an actual context, before other classes
    153         // try to do this with a mock context.
    154         // (Otherwise ContactsProvider2.initialzie() will crash trying to get a setting with
    155         // a mock context.)
    156         android.provider.Settings.Secure.getString(overallContext.getContentResolver(), "dummy");
    157 
    158         RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
    159                 overallContext, FILENAME_PREFIX);
    160         mProviderContext = new IsolatedContext(resolver, targetContextWrapper){
    161 
    162             @Override
    163             public File getFilesDir() {
    164                 // TODO: Need to figure out something more graceful than this.
    165                 return new File("/data/data/com.android.providers.contacts.tests/files");
    166             }
    167 
    168             @Override
    169             public Object getSystemService(String name) {
    170                 if (Context.COUNTRY_DETECTOR.equals(name)) {
    171                     return mMockCountryDetector;
    172                 }
    173                 if (Context.ACCOUNT_SERVICE.equals(name)) {
    174                     return mMockAccountManager;
    175                 }
    176                 return super.getSystemService(name);
    177             }
    178         };
    179 
    180         mMockAccountManager = new MockAccountManager(mProviderContext);
    181         provider = addProvider(providerClass, authority);
    182     }
    183 
    184     public void addAuthority(String authority) {
    185         resolver.addProvider(authority, provider);
    186     }
    187 
    188     public ContentProvider addProvider(Class<? extends ContentProvider> providerClass,
    189             String authority) throws Exception {
    190         ContentProvider provider = providerClass.newInstance();
    191         ProviderInfo info = new ProviderInfo();
    192         info.authority = authority;
    193         provider.attachInfo(mProviderContext, info);
    194         resolver.addProvider(authority, provider);
    195         return provider;
    196     }
    197 
    198     public void addPermissions(String... permissions) {
    199         mGrantedPermissions.addAll(Arrays.asList(permissions));
    200     }
    201 
    202     public void removePermissions(String... permissions) {
    203         mGrantedPermissions.removeAll(Arrays.asList(permissions));
    204     }
    205 
    206     public void addUriPermissions(Uri... uris) {
    207         mGrantedUriPermissions.addAll(Arrays.asList(uris));
    208     }
    209 
    210     public void removeUriPermissions(Uri... uris) {
    211         mGrantedUriPermissions.removeAll(Arrays.asList(uris));
    212     }
    213 
    214     /**
    215      * Mock {@link Context} that reports specific well-known values for testing
    216      * data protection. The creator can override the owner package name, and
    217      * force the {@link PackageManager} to always return a well-known package
    218      * list for any call to {@link PackageManager#getPackagesForUid(int)}.
    219      * <p>
    220      * For example, the creator could request that the {@link Context} lives in
    221      * package name "com.example.red", and also cause the {@link PackageManager}
    222      * to report that no UID contains that package name.
    223      */
    224     private static class RestrictionMockContext extends MockContext {
    225         private final Context mOverallContext;
    226         private final String mReportedPackageName;
    227         private final ContactsMockPackageManager mPackageManager;
    228         private final ContentResolver mResolver;
    229         private final Resources mRes;
    230         private final Set<String> mGrantedPermissions;
    231         private final Set<Uri> mGrantedUriPermissions;
    232 
    233         /**
    234          * Create a {@link Context} under the given package name.
    235          */
    236         public RestrictionMockContext(Context overallContext, String reportedPackageName,
    237                 ContentResolver resolver, Set<String> grantedPermissions,
    238                 Set<Uri> grantedUriPermissions) {
    239             mOverallContext = overallContext;
    240             mReportedPackageName = reportedPackageName;
    241             mResolver = resolver;
    242             mGrantedPermissions = grantedPermissions;
    243             mGrantedUriPermissions = grantedUriPermissions;
    244 
    245             mPackageManager = new ContactsMockPackageManager();
    246             mPackageManager.addPackage(1000, PACKAGE_GREY);
    247             mPackageManager.addPackage(2000, PACKAGE_RED);
    248             mPackageManager.addPackage(3000, PACKAGE_GREEN);
    249             mPackageManager.addPackage(4000, PACKAGE_BLUE);
    250 
    251             Resources resources = overallContext.getResources();
    252             Configuration configuration = new Configuration(resources.getConfiguration());
    253             configuration.locale = Locale.US;
    254             resources.updateConfiguration(configuration, resources.getDisplayMetrics());
    255             mRes = resources;
    256         }
    257 
    258         @Override
    259         public String getPackageName() {
    260             return mReportedPackageName;
    261         }
    262 
    263         @Override
    264         public PackageManager getPackageManager() {
    265             return mPackageManager;
    266         }
    267 
    268         @Override
    269         public Resources getResources() {
    270             return mRes;
    271         }
    272 
    273         @Override
    274         public ContentResolver getContentResolver() {
    275             return mResolver;
    276         }
    277 
    278         @Override
    279         public ApplicationInfo getApplicationInfo() {
    280             ApplicationInfo ai = new ApplicationInfo();
    281             ai.packageName = "contactsTestPackage";
    282             return ai;
    283         }
    284 
    285         // All permission checks are implemented to simply check against the granted permission set.
    286 
    287         @Override
    288         public int checkPermission(String permission, int pid, int uid) {
    289             return checkCallingPermission(permission);
    290         }
    291 
    292         @Override
    293         public int checkCallingPermission(String permission) {
    294             if (mGrantedPermissions.contains(permission)) {
    295                 return PackageManager.PERMISSION_GRANTED;
    296             } else {
    297                 return PackageManager.PERMISSION_DENIED;
    298             }
    299         }
    300 
    301         @Override
    302         public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
    303             return checkCallingUriPermission(uri, modeFlags);
    304         }
    305 
    306         @Override
    307         public int checkCallingUriPermission(Uri uri, int modeFlags) {
    308             if (mGrantedUriPermissions.contains(uri)) {
    309                 return PackageManager.PERMISSION_GRANTED;
    310             } else {
    311                 return PackageManager.PERMISSION_DENIED;
    312             }
    313         }
    314 
    315         @Override
    316         public int checkCallingOrSelfPermission(String permission) {
    317             return checkCallingPermission(permission);
    318         }
    319 
    320         @Override
    321         public void enforcePermission(String permission, int pid, int uid, String message) {
    322             enforceCallingPermission(permission, message);
    323         }
    324 
    325         @Override
    326         public void enforceCallingPermission(String permission, String message) {
    327             if (!mGrantedPermissions.contains(permission)) {
    328                 throw new SecurityException(message);
    329             }
    330         }
    331 
    332         @Override
    333         public void enforceCallingOrSelfPermission(String permission, String message) {
    334             enforceCallingPermission(permission, message);
    335         }
    336     }
    337 
    338     static String sCallingPackage = null;
    339 
    340     void ensureCallingPackage() {
    341         sCallingPackage = this.packageName;
    342     }
    343 
    344     public long createRawContact(String name) {
    345         ensureCallingPackage();
    346         long rawContactId = createRawContact();
    347         createName(rawContactId, name);
    348         return rawContactId;
    349     }
    350 
    351     public long createRawContact() {
    352         ensureCallingPackage();
    353         final ContentValues values = new ContentValues();
    354 
    355         Uri rawContactUri = resolver.insert(RawContacts.CONTENT_URI, values);
    356         return ContentUris.parseId(rawContactUri);
    357     }
    358 
    359     public long createRawContactWithStatus(String name, String address,
    360             String status) {
    361         final long rawContactId = createRawContact(name);
    362         final long dataId = createEmail(rawContactId, address);
    363         createStatus(dataId, status);
    364         return rawContactId;
    365     }
    366 
    367     public long createName(long contactId, String name) {
    368         ensureCallingPackage();
    369         final ContentValues values = new ContentValues();
    370         values.put(Data.RAW_CONTACT_ID, contactId);
    371         values.put(Data.IS_PRIMARY, 1);
    372         values.put(Data.IS_SUPER_PRIMARY, 1);
    373         values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
    374         values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name);
    375         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
    376                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
    377         Uri dataUri = resolver.insert(insertUri, values);
    378         return ContentUris.parseId(dataUri);
    379     }
    380 
    381     public long createPhone(long contactId, String phoneNumber) {
    382         ensureCallingPackage();
    383         final ContentValues values = new ContentValues();
    384         values.put(Data.RAW_CONTACT_ID, contactId);
    385         values.put(Data.IS_PRIMARY, 1);
    386         values.put(Data.IS_SUPER_PRIMARY, 1);
    387         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    388         values.put(ContactsContract.CommonDataKinds.Phone.TYPE,
    389                 ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
    390         values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
    391         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
    392                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
    393         Uri dataUri = resolver.insert(insertUri, values);
    394         return ContentUris.parseId(dataUri);
    395     }
    396 
    397     public long createEmail(long contactId, String address) {
    398         ensureCallingPackage();
    399         final ContentValues values = new ContentValues();
    400         values.put(Data.RAW_CONTACT_ID, contactId);
    401         values.put(Data.IS_PRIMARY, 1);
    402         values.put(Data.IS_SUPER_PRIMARY, 1);
    403         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
    404         values.put(Email.TYPE, Email.TYPE_HOME);
    405         values.put(Email.DATA, address);
    406         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
    407                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
    408         Uri dataUri = resolver.insert(insertUri, values);
    409         return ContentUris.parseId(dataUri);
    410     }
    411 
    412     public long createStatus(long dataId, String status) {
    413         ensureCallingPackage();
    414         final ContentValues values = new ContentValues();
    415         values.put(StatusUpdates.DATA_ID, dataId);
    416         values.put(StatusUpdates.STATUS, status);
    417         Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values);
    418         return ContentUris.parseId(dataUri);
    419     }
    420 
    421     public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
    422         throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
    423     }
    424 
    425     public long getContactForRawContact(long rawContactId) {
    426         ensureCallingPackage();
    427         Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
    428         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null,
    429                 null, null);
    430         if (!cursor.moveToFirst()) {
    431             cursor.close();
    432             throw new RuntimeException("Contact didn't have an aggregate");
    433         }
    434         final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID);
    435         cursor.close();
    436         return aggId;
    437     }
    438 
    439     public int getDataCountForContact(long contactId) {
    440         ensureCallingPackage();
    441         Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
    442                 contactId), Contacts.Data.CONTENT_DIRECTORY);
    443         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
    444                 null);
    445         final int count = cursor.getCount();
    446         cursor.close();
    447         return count;
    448     }
    449 
    450     public int getDataCountForRawContact(long rawContactId) {
    451         ensureCallingPackage();
    452         Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
    453                 rawContactId), Contacts.Data.CONTENT_DIRECTORY);
    454         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
    455                 null);
    456         final int count = cursor.getCount();
    457         cursor.close();
    458         return count;
    459     }
    460 
    461     public void setSuperPrimaryPhone(long dataId) {
    462         ensureCallingPackage();
    463         final ContentValues values = new ContentValues();
    464         values.put(Data.IS_PRIMARY, 1);
    465         values.put(Data.IS_SUPER_PRIMARY, 1);
    466         Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
    467         resolver.update(updateUri, values, null, null);
    468     }
    469 
    470     public long createGroup(String groupName) {
    471         ensureCallingPackage();
    472         final ContentValues values = new ContentValues();
    473         values.put(ContactsContract.Groups.RES_PACKAGE, packageName);
    474         values.put(ContactsContract.Groups.TITLE, groupName);
    475         Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values);
    476         return ContentUris.parseId(groupUri);
    477     }
    478 
    479     public long createGroupMembership(long rawContactId, long groupId) {
    480         ensureCallingPackage();
    481         final ContentValues values = new ContentValues();
    482         values.put(Data.RAW_CONTACT_ID, rawContactId);
    483         values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
    484         values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
    485         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
    486                 rawContactId), RawContacts.Data.CONTENT_DIRECTORY);
    487         Uri dataUri = resolver.insert(insertUri, values);
    488         return ContentUris.parseId(dataUri);
    489     }
    490 
    491     protected void setAggregationException(int type, long rawContactId1, long rawContactId2) {
    492         ContentValues values = new ContentValues();
    493         values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1);
    494         values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2);
    495         values.put(AggregationExceptions.TYPE, type);
    496         resolver.update(AggregationExceptions.CONTENT_URI, values, null, null);
    497     }
    498 
    499     public void setAccounts(Account[] accounts) {
    500         mAccounts = accounts;
    501     }
    502 
    503     /**
    504      * Various internal database projections.
    505      */
    506     private interface Projections {
    507         static final String[] PROJ_ID = new String[] {
    508                 BaseColumns._ID,
    509         };
    510 
    511         static final int COL_ID = 0;
    512 
    513         static final String[] PROJ_RAW_CONTACTS = new String[] {
    514                 RawContacts.CONTACT_ID
    515         };
    516 
    517         static final int COL_CONTACTS_ID = 0;
    518     }
    519 }
    520