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