• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
MockAccountManager(Context conteact)108         public MockAccountManager(Context conteact) {
109             super(context, null, null);
110         }
111 
112         @Override
addOnAccountsUpdatedListener(OnAccountsUpdateListener listener, Handler handler, boolean updateImmediately)113         public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener,
114                 Handler handler, boolean updateImmediately) {
115             // do nothing
116         }
117 
118         @Override
getAccounts()119         public Account[] getAccounts() {
120             return mAccounts;
121         }
122 
123         @Override
getAccountsByTypeAndFeatures( final String type, final String[] features, AccountManagerCallback<Account[]> callback, Handler handler)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
blockingGetAuthToken(Account account, String authTokenType, boolean notifyAuthFailure)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      */
ContactsActor(Context overallContext, String packageName, Class<? extends ContentProvider> providerClass, String authority)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 
addAuthority(String authority)193     public void addAuthority(String authority) {
194         resolver.addProvider(authority, provider);
195     }
196 
addProvider(Class<? extends ContentProvider> providerClass, String authority)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 
addPermissions(String... permissions)207     public void addPermissions(String... permissions) {
208         mGrantedPermissions.addAll(Arrays.asList(permissions));
209     }
210 
removePermissions(String... permissions)211     public void removePermissions(String... permissions) {
212         mGrantedPermissions.removeAll(Arrays.asList(permissions));
213     }
214 
addUriPermissions(Uri... uris)215     public void addUriPermissions(Uri... uris) {
216         mGrantedUriPermissions.addAll(Arrays.asList(uris));
217     }
218 
removeUriPermissions(Uri... uris)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          */
RestrictionMockContext(Context overallContext, String reportedPackageName, ContentResolver resolver, Set<String> grantedPermissions, Set<Uri> grantedUriPermissions)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
getPackageName()268         public String getPackageName() {
269             return mReportedPackageName;
270         }
271 
272         @Override
getPackageManager()273         public PackageManager getPackageManager() {
274             return mPackageManager;
275         }
276 
277         @Override
getResources()278         public Resources getResources() {
279             return mRes;
280         }
281 
282         @Override
getContentResolver()283         public ContentResolver getContentResolver() {
284             return mResolver;
285         }
286 
287         @Override
getApplicationInfo()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
checkPermission(String permission, int pid, int uid)297         public int checkPermission(String permission, int pid, int uid) {
298             return checkCallingPermission(permission);
299         }
300 
301         @Override
checkCallingPermission(String permission)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
checkUriPermission(Uri uri, int pid, int uid, int modeFlags)311         public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
312             return checkCallingUriPermission(uri, modeFlags);
313         }
314 
315         @Override
checkCallingUriPermission(Uri uri, int modeFlags)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
checkCallingOrSelfPermission(String permission)325         public int checkCallingOrSelfPermission(String permission) {
326             return checkCallingPermission(permission);
327         }
328 
329         @Override
enforcePermission(String permission, int pid, int uid, String message)330         public void enforcePermission(String permission, int pid, int uid, String message) {
331             enforceCallingPermission(permission, message);
332         }
333 
334         @Override
enforceCallingPermission(String permission, String message)335         public void enforceCallingPermission(String permission, String message) {
336             if (!mGrantedPermissions.contains(permission)) {
337                 throw new SecurityException(message);
338             }
339         }
340 
341         @Override
enforceCallingOrSelfPermission(String permission, String message)342         public void enforceCallingOrSelfPermission(String permission, String message) {
343             enforceCallingPermission(permission, message);
344         }
345 
346         @Override
sendBroadcast(Intent intent)347         public void sendBroadcast(Intent intent) {
348             mOverallContext.sendBroadcast(intent);
349         }
350 
351         @Override
sendBroadcast(Intent intent, String receiverPermission)352         public void sendBroadcast(Intent intent, String receiverPermission) {
353             mOverallContext.sendBroadcast(intent, receiverPermission);
354         }
355     }
356 
357     static String sCallingPackage = null;
358 
ensureCallingPackage()359     void ensureCallingPackage() {
360         sCallingPackage = this.packageName;
361     }
362 
createRawContact(String name)363     public long createRawContact(String name) {
364         ensureCallingPackage();
365         long rawContactId = createRawContact();
366         createName(rawContactId, name);
367         return rawContactId;
368     }
369 
createRawContact()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 
createRawContactWithStatus(String name, String address, String status)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 
createName(long contactId, String name)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 
createPhone(long contactId, String phoneNumber)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 
createEmail(long contactId, String address)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 
createStatus(long dataId, String status)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 
updateException(String packageProvider, String packageClient, boolean allowAccess)440     public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
441         throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
442     }
443 
getContactForRawContact(long rawContactId)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 
getDataCountForContact(long contactId)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 
getDataCountForRawContact(long rawContactId)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 
setSuperPrimaryPhone(long dataId)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 
createGroup(String groupName)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 
createGroupMembership(long rawContactId, long groupId)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 
setAggregationException(int type, long rawContactId1, long rawContactId2)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 
setAccounts(Account[] accounts)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