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