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