• 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.content.ContentProvider;
20 import android.content.ContentResolver;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.content.res.Resources;
27 import android.content.res.Resources.NotFoundException;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.Binder;
31 import android.provider.BaseColumns;
32 import android.provider.ContactsContract;
33 import android.provider.ContactsContract.CommonDataKinds;
34 import android.provider.ContactsContract.Contacts;
35 import android.provider.ContactsContract.Data;
36 import android.provider.ContactsContract.RawContacts;
37 import android.provider.ContactsContract.StatusUpdates;
38 import android.provider.ContactsContract.CommonDataKinds.Email;
39 import android.provider.ContactsContract.CommonDataKinds.Phone;
40 import android.test.IsolatedContext;
41 import android.test.RenamingDelegatingContext;
42 import android.test.mock.MockContentResolver;
43 import android.test.mock.MockContext;
44 import android.test.mock.MockPackageManager;
45 import android.test.mock.MockResources;
46 import android.util.Log;
47 import android.util.TypedValue;
48 
49 import java.util.HashMap;
50 
51 /**
52  * Helper class that encapsulates an "actor" which is owned by a specific
53  * package name. It correctly maintains a wrapped {@link Context} and an
54  * attached {@link MockContentResolver}. Multiple actors can be used to test
55  * security scenarios between multiple packages.
56  */
57 public class ContactsActor {
58     private static final String FILENAME_PREFIX = "test.";
59 
60     public static final String PACKAGE_GREY = "edu.example.grey";
61     public static final String PACKAGE_RED = "net.example.red";
62     public static final String PACKAGE_GREEN = "com.example.green";
63     public static final String PACKAGE_BLUE = "org.example.blue";
64 
65     public Context context;
66     public String packageName;
67     public MockContentResolver resolver;
68     public ContentProvider provider;
69 
70     private IsolatedContext mProviderContext;
71 
72     /**
73      * Create an "actor" using the given parent {@link Context} and the specific
74      * package name. Internally, all {@link Context} method calls are passed to
75      * a new instance of {@link RestrictionMockContext}, which stubs out the
76      * security infrastructure.
77      */
ContactsActor(Context overallContext, String packageName, Class<? extends ContentProvider> providerClass, String authority)78     public ContactsActor(Context overallContext, String packageName,
79             Class<? extends ContentProvider> providerClass, String authority) throws Exception {
80         resolver = new MockContentResolver();
81         context = new RestrictionMockContext(overallContext, packageName, resolver);
82         this.packageName = packageName;
83 
84         RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context,
85                 overallContext, FILENAME_PREFIX);
86         mProviderContext = new IsolatedContext(resolver, targetContextWrapper);
87         provider = addProvider(providerClass, authority);
88     }
89 
addAuthority(String authority)90     public void addAuthority(String authority) {
91         resolver.addProvider(authority, provider);
92     }
93 
addProvider(Class<? extends ContentProvider> providerClass, String authority)94     public ContentProvider addProvider(Class<? extends ContentProvider> providerClass,
95             String authority) throws Exception {
96         ContentProvider provider = providerClass.newInstance();
97         provider.attachInfo(mProviderContext, null);
98         resolver.addProvider(authority, provider);
99         return provider;
100     }
101 
102     /**
103      * Mock {@link Context} that reports specific well-known values for testing
104      * data protection. The creator can override the owner package name, and
105      * force the {@link PackageManager} to always return a well-known package
106      * list for any call to {@link PackageManager#getPackagesForUid(int)}.
107      * <p>
108      * For example, the creator could request that the {@link Context} lives in
109      * package name "com.example.red", and also cause the {@link PackageManager}
110      * to report that no UID contains that package name.
111      */
112     private static class RestrictionMockContext extends MockContext {
113         private final Context mOverallContext;
114         private final String mReportedPackageName;
115         private final RestrictionMockPackageManager mPackageManager;
116         private final ContentResolver mResolver;
117         private final Resources mRes;
118 
119         /**
120          * Create a {@link Context} under the given package name.
121          */
RestrictionMockContext(Context overallContext, String reportedPackageName, ContentResolver resolver)122         public RestrictionMockContext(Context overallContext, String reportedPackageName,
123                 ContentResolver resolver) {
124             mOverallContext = overallContext;
125             mReportedPackageName = reportedPackageName;
126             mResolver = resolver;
127 
128             mPackageManager = new RestrictionMockPackageManager();
129             mPackageManager.addPackage(1000, PACKAGE_GREY);
130             mPackageManager.addPackage(2000, PACKAGE_RED);
131             mPackageManager.addPackage(3000, PACKAGE_GREEN);
132             mPackageManager.addPackage(4000, PACKAGE_BLUE);
133 
134             mRes = new RestrictionMockResources(overallContext.getResources());
135         }
136 
137         @Override
getPackageName()138         public String getPackageName() {
139             return mReportedPackageName;
140         }
141 
142         @Override
getPackageManager()143         public PackageManager getPackageManager() {
144             return mPackageManager;
145         }
146 
147         @Override
getResources()148         public Resources getResources() {
149             return mRes;
150         }
151 
152         @Override
getContentResolver()153         public ContentResolver getContentResolver() {
154             return mResolver;
155         }
156     }
157 
158     private static class RestrictionMockResources extends MockResources {
159         private static final String UNRESTRICTED = "unrestricted_packages";
160         private static final int UNRESTRICTED_ID = 1024;
161 
162         private static final String[] UNRESTRICTED_LIST = new String[] {
163             PACKAGE_GREY
164         };
165 
166         private final Resources mRes;
167 
RestrictionMockResources(Resources res)168         public RestrictionMockResources(Resources res) {
169             mRes = res;
170         }
171 
172         @Override
getIdentifier(String name, String defType, String defPackage)173         public int getIdentifier(String name, String defType, String defPackage) {
174             if (UNRESTRICTED.equals(name)) {
175                 return UNRESTRICTED_ID;
176             } else {
177                 return mRes.getIdentifier(name, defType, defPackage);
178             }
179         }
180 
181         @Override
getStringArray(int id)182         public String[] getStringArray(int id) throws NotFoundException {
183             if (id == UNRESTRICTED_ID) {
184                 return UNRESTRICTED_LIST;
185             } else {
186                 return mRes.getStringArray(id);
187             }
188         }
189 
190         @Override
getValue(int id, TypedValue outValue, boolean resolveRefs)191         public void getValue(int id, TypedValue outValue, boolean resolveRefs)
192                 throws NotFoundException {
193             mRes.getValue(id, outValue, resolveRefs);
194         }
195 
196         @Override
getString(int id)197         public String getString(int id) throws NotFoundException {
198             return mRes.getString(id);
199         }
200 
201         @Override
getString(int id, Object... formatArgs)202         public String getString(int id, Object... formatArgs) throws NotFoundException {
203             return mRes.getString(id, formatArgs);
204         }
205 
206         @Override
getText(int id)207         public CharSequence getText(int id) throws NotFoundException {
208             return mRes.getText(id);
209         }
210     }
211 
212     private static String sCallingPackage = null;
213 
ensureCallingPackage()214     void ensureCallingPackage() {
215         sCallingPackage = this.packageName;
216     }
217 
218     /**
219      * Mock {@link PackageManager} that knows about a specific set of packages
220      * to help test security models. Because {@link Binder#getCallingUid()}
221      * can't be mocked, you'll have to find your mock-UID manually using your
222      * {@link Context#getPackageName()}.
223      */
224     private static class RestrictionMockPackageManager extends MockPackageManager {
225         private final HashMap<Integer, String> mForward = new HashMap<Integer, String>();
226         private final HashMap<String, Integer> mReverse = new HashMap<String, Integer>();
227 
RestrictionMockPackageManager()228         public RestrictionMockPackageManager() {
229         }
230 
231         /**
232          * Add a UID-to-package mapping, which is then stored internally.
233          */
addPackage(int packageUid, String packageName)234         public void addPackage(int packageUid, String packageName) {
235             mForward.put(packageUid, packageName);
236             mReverse.put(packageName, packageUid);
237         }
238 
239         @Override
getPackagesForUid(int uid)240         public String[] getPackagesForUid(int uid) {
241             return new String[] { sCallingPackage };
242         }
243 
244         @Override
getApplicationInfo(String packageName, int flags)245         public ApplicationInfo getApplicationInfo(String packageName, int flags) {
246             ApplicationInfo info = new ApplicationInfo();
247             Integer uid = mReverse.get(packageName);
248             info.uid = (uid != null) ? uid : -1;
249             return info;
250         }
251     }
252 
createContact(boolean isRestricted, String name)253     public long createContact(boolean isRestricted, String name) {
254         ensureCallingPackage();
255         long contactId = createContact(isRestricted);
256         createName(contactId, name);
257         return contactId;
258     }
259 
createContact(boolean isRestricted)260     public long createContact(boolean isRestricted) {
261         ensureCallingPackage();
262         final ContentValues values = new ContentValues();
263         if (isRestricted) {
264             values.put(RawContacts.IS_RESTRICTED, 1);
265         }
266 
267         Uri contactUri = resolver.insert(RawContacts.CONTENT_URI, values);
268         return ContentUris.parseId(contactUri);
269     }
270 
createContactWithStatus(boolean isRestricted, String name, String address, String status)271     public long createContactWithStatus(boolean isRestricted, String name, String address,
272             String status) {
273         final long rawContactId = createContact(isRestricted, name);
274         final long dataId = createEmail(rawContactId, address);
275         createStatus(dataId, status);
276         return rawContactId;
277     }
278 
createName(long contactId, String name)279     public long createName(long contactId, String name) {
280         ensureCallingPackage();
281         final ContentValues values = new ContentValues();
282         values.put(Data.RAW_CONTACT_ID, contactId);
283         values.put(Data.IS_PRIMARY, 1);
284         values.put(Data.IS_SUPER_PRIMARY, 1);
285         values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
286         values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name);
287         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
288                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
289         Uri dataUri = resolver.insert(insertUri, values);
290         return ContentUris.parseId(dataUri);
291     }
292 
createPhone(long contactId, String phoneNumber)293     public long createPhone(long contactId, String phoneNumber) {
294         ensureCallingPackage();
295         final ContentValues values = new ContentValues();
296         values.put(Data.RAW_CONTACT_ID, contactId);
297         values.put(Data.IS_PRIMARY, 1);
298         values.put(Data.IS_SUPER_PRIMARY, 1);
299         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
300         values.put(ContactsContract.CommonDataKinds.Phone.TYPE,
301                 ContactsContract.CommonDataKinds.Phone.TYPE_HOME);
302         values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
303         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
304                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
305         Uri dataUri = resolver.insert(insertUri, values);
306         return ContentUris.parseId(dataUri);
307     }
308 
createEmail(long contactId, String address)309     public long createEmail(long contactId, String address) {
310         ensureCallingPackage();
311         final ContentValues values = new ContentValues();
312         values.put(Data.RAW_CONTACT_ID, contactId);
313         values.put(Data.IS_PRIMARY, 1);
314         values.put(Data.IS_SUPER_PRIMARY, 1);
315         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
316         values.put(Email.TYPE, Email.TYPE_HOME);
317         values.put(Email.DATA, address);
318         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
319                 contactId), RawContacts.Data.CONTENT_DIRECTORY);
320         Uri dataUri = resolver.insert(insertUri, values);
321         return ContentUris.parseId(dataUri);
322     }
323 
createStatus(long dataId, String status)324     public long createStatus(long dataId, String status) {
325         ensureCallingPackage();
326         final ContentValues values = new ContentValues();
327         values.put(StatusUpdates.DATA_ID, dataId);
328         values.put(StatusUpdates.STATUS, status);
329         Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values);
330         return ContentUris.parseId(dataUri);
331     }
332 
updateException(String packageProvider, String packageClient, boolean allowAccess)333     public void updateException(String packageProvider, String packageClient, boolean allowAccess) {
334         throw new UnsupportedOperationException("RestrictionExceptions are hard-coded");
335     }
336 
getContactForRawContact(long rawContactId)337     public long getContactForRawContact(long rawContactId) {
338         ensureCallingPackage();
339         Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
340         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null,
341                 null, null);
342         if (!cursor.moveToFirst()) {
343             cursor.close();
344             throw new RuntimeException("Contact didn't have an aggregate");
345         }
346         final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID);
347         cursor.close();
348         return aggId;
349     }
350 
getDataCountForContact(long contactId)351     public int getDataCountForContact(long contactId) {
352         ensureCallingPackage();
353         Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI,
354                 contactId), Contacts.Data.CONTENT_DIRECTORY);
355         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
356                 null);
357         final int count = cursor.getCount();
358         cursor.close();
359         return count;
360     }
361 
getDataCountForRawContact(long rawContactId)362     public int getDataCountForRawContact(long rawContactId) {
363         ensureCallingPackage();
364         Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
365                 rawContactId), Contacts.Data.CONTENT_DIRECTORY);
366         final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null,
367                 null);
368         final int count = cursor.getCount();
369         cursor.close();
370         return count;
371     }
372 
setSuperPrimaryPhone(long dataId)373     public void setSuperPrimaryPhone(long dataId) {
374         ensureCallingPackage();
375         final ContentValues values = new ContentValues();
376         values.put(Data.IS_PRIMARY, 1);
377         values.put(Data.IS_SUPER_PRIMARY, 1);
378         Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
379         resolver.update(updateUri, values, null, null);
380     }
381 
createGroup(String groupName)382     public long createGroup(String groupName) {
383         ensureCallingPackage();
384         final ContentValues values = new ContentValues();
385         values.put(ContactsContract.Groups.RES_PACKAGE, packageName);
386         values.put(ContactsContract.Groups.TITLE, groupName);
387         Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values);
388         return ContentUris.parseId(groupUri);
389     }
390 
createGroupMembership(long rawContactId, long groupId)391     public long createGroupMembership(long rawContactId, long groupId) {
392         ensureCallingPackage();
393         final ContentValues values = new ContentValues();
394         values.put(Data.RAW_CONTACT_ID, rawContactId);
395         values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE);
396         values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
397         Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI,
398                 rawContactId), RawContacts.Data.CONTENT_DIRECTORY);
399         Uri dataUri = resolver.insert(insertUri, values);
400         return ContentUris.parseId(dataUri);
401     }
402 
403     /**
404      * Various internal database projections.
405      */
406     private interface Projections {
407         static final String[] PROJ_ID = new String[] {
408                 BaseColumns._ID,
409         };
410 
411         static final int COL_ID = 0;
412 
413         static final String[] PROJ_RAW_CONTACTS = new String[] {
414                 RawContacts.CONTACT_ID
415         };
416 
417         static final int COL_CONTACTS_ID = 0;
418     }
419 }
420