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.app.admin.DevicePolicyManager; 27 import android.content.ContentProvider; 28 import android.content.ContentResolver; 29 import android.content.ContentUris; 30 import android.content.ContentValues; 31 import android.content.Context; 32 import android.content.ContextWrapper; 33 import android.content.Intent; 34 import android.content.SharedPreferences; 35 import android.content.pm.ApplicationInfo; 36 import android.content.pm.PackageManager; 37 import android.content.pm.ProviderInfo; 38 import android.content.pm.UserInfo; 39 import android.content.res.Configuration; 40 import android.content.res.Resources; 41 import android.database.Cursor; 42 import android.location.Country; 43 import android.location.CountryDetector; 44 import android.location.CountryListener; 45 import android.net.Uri; 46 import android.os.Bundle; 47 import android.os.Handler; 48 import android.os.IUserManager; 49 import android.os.Looper; 50 import android.os.UserHandle; 51 import android.os.UserManager; 52 import android.provider.BaseColumns; 53 import android.provider.ContactsContract; 54 import android.provider.ContactsContract.AggregationExceptions; 55 import android.provider.ContactsContract.CommonDataKinds; 56 import android.provider.ContactsContract.CommonDataKinds.Email; 57 import android.provider.ContactsContract.CommonDataKinds.Phone; 58 import android.provider.ContactsContract.Contacts; 59 import android.provider.ContactsContract.Data; 60 import android.provider.ContactsContract.RawContacts; 61 import android.provider.ContactsContract.StatusUpdates; 62 import android.test.IsolatedContext; 63 import android.test.RenamingDelegatingContext; 64 import android.test.mock.MockContentResolver; 65 import android.test.mock.MockContext; 66 import android.util.Log; 67 68 import com.android.providers.contacts.util.MockSharedPreferences; 69 import com.google.android.collect.Sets; 70 71 import java.io.File; 72 import java.io.IOException; 73 import java.util.ArrayList; 74 import java.util.Arrays; 75 import java.util.List; 76 import java.util.Locale; 77 import java.util.Set; 78 79 /** 80 * Helper class that encapsulates an "actor" which is owned by a specific 81 * package name. It correctly maintains a wrapped {@link Context} and an 82 * attached {@link MockContentResolver}. Multiple actors can be used to test 83 * security scenarios between multiple packages. 84 */ 85 public class ContactsActor { 86 private static final String FILENAME_PREFIX = "test."; 87 88 public static final String PACKAGE_GREY = "edu.example.grey"; 89 public static final String PACKAGE_RED = "net.example.red"; 90 public static final String PACKAGE_GREEN = "com.example.green"; 91 public static final String PACKAGE_BLUE = "org.example.blue"; 92 93 public Context context; 94 public String packageName; 95 public MockContentResolver resolver; 96 public ContentProvider provider; 97 private Country mMockCountry = new Country("us", 0); 98 99 private Account[] mAccounts = new Account[0]; 100 101 private Set<String> mGrantedPermissions = Sets.newHashSet(); 102 private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet(); 103 104 private CountryDetector mMockCountryDetector = new CountryDetector(null){ 105 @Override 106 public Country detectCountry() { 107 return mMockCountry; 108 } 109 110 @Override 111 public void addCountryListener(CountryListener listener, Looper looper) { 112 } 113 }; 114 115 private AccountManager mMockAccountManager; 116 117 private class MockAccountManager extends AccountManager { 118 public MockAccountManager(Context conteact) { 119 super(context, null, null); 120 } 121 122 @Override 123 public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener, 124 Handler handler, boolean updateImmediately) { 125 // do nothing 126 } 127 128 @Override 129 public Account[] getAccounts() { 130 return mAccounts; 131 } 132 133 @Override 134 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 135 final String type, final String[] features, 136 AccountManagerCallback<Account[]> callback, Handler handler) { 137 return null; 138 } 139 140 @Override 141 public String blockingGetAuthToken(Account account, String authTokenType, 142 boolean notifyAuthFailure) 143 throws OperationCanceledException, IOException, AuthenticatorException { 144 return null; 145 } 146 } 147 148 public MockUserManager mockUserManager; 149 150 public static class MockUserManager extends UserManager { 151 public static UserInfo createUserInfo(String name, int id, int groupId, int flags) { 152 final UserInfo ui = new UserInfo(); 153 ui.name = name; 154 ui.id = id; 155 ui.profileGroupId = groupId; 156 ui.flags = flags | UserInfo.FLAG_INITIALIZED; 157 return ui; 158 } 159 160 public static final UserInfo PRIMARY_USER = createUserInfo("primary", 0, 0, 161 UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN); 162 public static final UserInfo CORP_USER = createUserInfo("corp", 10, 0, 163 UserInfo.FLAG_MANAGED_PROFILE); 164 public static final UserInfo SECONDARY_USER = createUserInfo("2nd", 11, 11, 0); 165 166 /** "My" user. Set it to change the current user. */ 167 public int myUser = 0; 168 169 private ArrayList<UserInfo> mUsers = new ArrayList<>(); 170 171 public MockUserManager(Context context) { 172 super(context, /* IUserManager */ null); 173 174 mUsers.add(PRIMARY_USER); // Add the primary user. 175 } 176 177 /** Replaces users. */ 178 public void setUsers(UserInfo... users) { 179 mUsers.clear(); 180 for (UserInfo ui : users) { 181 mUsers.add(ui); 182 } 183 } 184 185 @Override 186 public int getUserHandle() { 187 return myUser; 188 } 189 190 @Override 191 public UserInfo getUserInfo(int userHandle) { 192 for (UserInfo ui : mUsers) { 193 if (ui.id == userHandle) { 194 return ui; 195 } 196 } 197 return null; 198 } 199 200 @Override 201 public UserInfo getProfileParent(int userHandle) { 202 final UserInfo child = getUserInfo(userHandle); 203 if (child == null) { 204 return null; 205 } 206 for (UserInfo ui : mUsers) { 207 if (ui.id != userHandle && ui.id == child.profileGroupId) { 208 return ui; 209 } 210 } 211 return null; 212 } 213 214 @Override 215 public List<UserInfo> getUsers() { 216 return mUsers; 217 } 218 219 @Override 220 public Bundle getUserRestrictions(UserHandle userHandle) { 221 return new Bundle(); 222 } 223 } 224 225 /** 226 * A context wrapper that reports a different user id. 227 * 228 * TODO This should override getSystemService() and returns a UserManager that returns the 229 * same, altered user ID too. 230 */ 231 public static class AlteringUserContext extends ContextWrapper { 232 private final int mUserId; 233 234 public AlteringUserContext(Context base, int userId) { 235 super(base); 236 mUserId = userId; 237 } 238 239 @Override 240 public int getUserId() { 241 return mUserId; 242 } 243 } 244 245 private IsolatedContext mProviderContext; 246 247 /** 248 * Create an "actor" using the given parent {@link Context} and the specific 249 * package name. Internally, all {@link Context} method calls are passed to 250 * a new instance of {@link RestrictionMockContext}, which stubs out the 251 * security infrastructure. 252 */ 253 public ContactsActor(final Context overallContext, String packageName, 254 Class<? extends ContentProvider> providerClass, String authority) throws Exception { 255 resolver = new MockContentResolver(); 256 context = new RestrictionMockContext(overallContext, packageName, resolver, 257 mGrantedPermissions, mGrantedUriPermissions); 258 this.packageName = packageName; 259 260 // Let the Secure class initialize the settings provider, which is done when we first 261 // tries to get any setting. Because our mock context/content resolver doesn't have the 262 // settings provider, we need to do this with an actual context, before other classes 263 // try to do this with a mock context. 264 // (Otherwise ContactsProvider2.initialzie() will crash trying to get a setting with 265 // a mock context.) 266 android.provider.Settings.Secure.getString(overallContext.getContentResolver(), "dummy"); 267 268 RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context, 269 overallContext, FILENAME_PREFIX); 270 mProviderContext = new IsolatedContext(resolver, targetContextWrapper) { 271 private final MockSharedPreferences mPrefs = new MockSharedPreferences(); 272 273 @Override 274 public File getFilesDir() { 275 // TODO: Need to figure out something more graceful than this. 276 return new File("/data/data/com.android.providers.contacts.tests/files"); 277 } 278 279 @Override 280 public Object getSystemService(String name) { 281 if (Context.COUNTRY_DETECTOR.equals(name)) { 282 return mMockCountryDetector; 283 } 284 if (Context.ACCOUNT_SERVICE.equals(name)) { 285 return mMockAccountManager; 286 } 287 if (Context.USER_SERVICE.equals(name)) { 288 return mockUserManager; 289 } 290 // Use overallContext here; super.getSystemService() somehow won't return 291 // DevicePolicyManager. 292 return overallContext.getSystemService(name); 293 } 294 295 @Override 296 public SharedPreferences getSharedPreferences(String name, int mode) { 297 return mPrefs; 298 } 299 300 @Override 301 public int getUserId() { 302 return mockUserManager.getUserHandle(); 303 } 304 }; 305 306 mMockAccountManager = new MockAccountManager(mProviderContext); 307 mockUserManager = new MockUserManager(mProviderContext); 308 provider = addProvider(providerClass, authority); 309 } 310 311 public Context getProviderContext() { 312 return mProviderContext; 313 } 314 315 public void addAuthority(String authority) { 316 resolver.addProvider(authority, provider); 317 } 318 319 public <T extends ContentProvider> T addProvider(Class<T> providerClass, 320 String authority) throws Exception { 321 return addProvider(providerClass, authority, mProviderContext); 322 } 323 324 public <T extends ContentProvider> T addProvider(Class<T> providerClass, 325 String authority, Context providerContext) throws Exception { 326 T provider = providerClass.newInstance(); 327 ProviderInfo info = new ProviderInfo(); 328 329 // Here, authority can have "user-id@". We want to use it for addProvider, but provider 330 // info shouldn't have it. 331 info.authority = stripOutUserIdFromAuthority(authority); 332 provider.attachInfoForTesting(providerContext, info); 333 resolver.addProvider(authority, provider); 334 return provider; 335 } 336 337 /** 338 * Takes an provider authority. If it has "userid@", then remove it. 339 */ 340 private String stripOutUserIdFromAuthority(String authority) { 341 final int pos = authority.indexOf('@'); 342 return pos < 0 ? authority : authority.substring(pos + 1); 343 } 344 345 public void addPermissions(String... permissions) { 346 mGrantedPermissions.addAll(Arrays.asList(permissions)); 347 } 348 349 public void removePermissions(String... permissions) { 350 mGrantedPermissions.removeAll(Arrays.asList(permissions)); 351 } 352 353 public void addUriPermissions(Uri... uris) { 354 mGrantedUriPermissions.addAll(Arrays.asList(uris)); 355 } 356 357 public void removeUriPermissions(Uri... uris) { 358 mGrantedUriPermissions.removeAll(Arrays.asList(uris)); 359 } 360 361 /** 362 * Mock {@link Context} that reports specific well-known values for testing 363 * data protection. The creator can override the owner package name, and 364 * force the {@link PackageManager} to always return a well-known package 365 * list for any call to {@link PackageManager#getPackagesForUid(int)}. 366 * <p> 367 * For example, the creator could request that the {@link Context} lives in 368 * package name "com.example.red", and also cause the {@link PackageManager} 369 * to report that no UID contains that package name. 370 */ 371 private static class RestrictionMockContext extends MockContext { 372 private final Context mOverallContext; 373 private final String mReportedPackageName; 374 private final ContactsMockPackageManager mPackageManager; 375 private final ContentResolver mResolver; 376 private final Resources mRes; 377 private final Set<String> mGrantedPermissions; 378 private final Set<Uri> mGrantedUriPermissions; 379 380 /** 381 * Create a {@link Context} under the given package name. 382 */ 383 public RestrictionMockContext(Context overallContext, String reportedPackageName, 384 ContentResolver resolver, Set<String> grantedPermissions, 385 Set<Uri> grantedUriPermissions) { 386 mOverallContext = overallContext; 387 mReportedPackageName = reportedPackageName; 388 mResolver = resolver; 389 mGrantedPermissions = grantedPermissions; 390 mGrantedUriPermissions = grantedUriPermissions; 391 392 mPackageManager = new ContactsMockPackageManager(); 393 mPackageManager.addPackage(1000, PACKAGE_GREY); 394 mPackageManager.addPackage(2000, PACKAGE_RED); 395 mPackageManager.addPackage(3000, PACKAGE_GREEN); 396 mPackageManager.addPackage(4000, PACKAGE_BLUE); 397 398 Resources resources = overallContext.getResources(); 399 Configuration configuration = new Configuration(resources.getConfiguration()); 400 configuration.locale = Locale.US; 401 resources.updateConfiguration(configuration, resources.getDisplayMetrics()); 402 mRes = resources; 403 } 404 405 @Override 406 public String getPackageName() { 407 return mReportedPackageName; 408 } 409 410 @Override 411 public PackageManager getPackageManager() { 412 return mPackageManager; 413 } 414 415 @Override 416 public Resources getResources() { 417 return mRes; 418 } 419 420 @Override 421 public ContentResolver getContentResolver() { 422 return mResolver; 423 } 424 425 @Override 426 public ApplicationInfo getApplicationInfo() { 427 ApplicationInfo ai = new ApplicationInfo(); 428 ai.packageName = "contactsTestPackage"; 429 return ai; 430 } 431 432 // All permission checks are implemented to simply check against the granted permission set. 433 434 @Override 435 public int checkPermission(String permission, int pid, int uid) { 436 return checkCallingPermission(permission); 437 } 438 439 @Override 440 public int checkCallingPermission(String permission) { 441 if (mGrantedPermissions.contains(permission)) { 442 return PackageManager.PERMISSION_GRANTED; 443 } else { 444 return PackageManager.PERMISSION_DENIED; 445 } 446 } 447 448 @Override 449 public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { 450 return checkCallingUriPermission(uri, modeFlags); 451 } 452 453 @Override 454 public int checkCallingUriPermission(Uri uri, int modeFlags) { 455 if (mGrantedUriPermissions.contains(uri)) { 456 return PackageManager.PERMISSION_GRANTED; 457 } else { 458 return PackageManager.PERMISSION_DENIED; 459 } 460 } 461 462 @Override 463 public int checkCallingOrSelfPermission(String permission) { 464 return checkCallingPermission(permission); 465 } 466 467 @Override 468 public void enforcePermission(String permission, int pid, int uid, String message) { 469 enforceCallingPermission(permission, message); 470 } 471 472 @Override 473 public void enforceCallingPermission(String permission, String message) { 474 if (!mGrantedPermissions.contains(permission)) { 475 throw new SecurityException(message); 476 } 477 } 478 479 @Override 480 public void enforceCallingOrSelfPermission(String permission, String message) { 481 enforceCallingPermission(permission, message); 482 } 483 484 @Override 485 public void sendBroadcast(Intent intent) { 486 mOverallContext.sendBroadcast(intent); 487 } 488 489 @Override 490 public void sendBroadcast(Intent intent, String receiverPermission) { 491 mOverallContext.sendBroadcast(intent, receiverPermission); 492 } 493 } 494 495 static String sCallingPackage = null; 496 497 void ensureCallingPackage() { 498 sCallingPackage = this.packageName; 499 } 500 501 public long createRawContact(String name) { 502 ensureCallingPackage(); 503 long rawContactId = createRawContact(); 504 createName(rawContactId, name); 505 return rawContactId; 506 } 507 508 public long createRawContact() { 509 ensureCallingPackage(); 510 final ContentValues values = new ContentValues(); 511 512 Uri rawContactUri = resolver.insert(RawContacts.CONTENT_URI, values); 513 return ContentUris.parseId(rawContactUri); 514 } 515 516 public long createRawContactWithStatus(String name, String address, 517 String status) { 518 final long rawContactId = createRawContact(name); 519 final long dataId = createEmail(rawContactId, address); 520 createStatus(dataId, status); 521 return rawContactId; 522 } 523 524 public long createName(long contactId, String name) { 525 ensureCallingPackage(); 526 final ContentValues values = new ContentValues(); 527 values.put(Data.RAW_CONTACT_ID, contactId); 528 values.put(Data.IS_PRIMARY, 1); 529 values.put(Data.IS_SUPER_PRIMARY, 1); 530 values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); 531 values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name); 532 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 533 contactId), RawContacts.Data.CONTENT_DIRECTORY); 534 Uri dataUri = resolver.insert(insertUri, values); 535 return ContentUris.parseId(dataUri); 536 } 537 538 public long createPhone(long contactId, String phoneNumber) { 539 ensureCallingPackage(); 540 final ContentValues values = new ContentValues(); 541 values.put(Data.RAW_CONTACT_ID, contactId); 542 values.put(Data.IS_PRIMARY, 1); 543 values.put(Data.IS_SUPER_PRIMARY, 1); 544 values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 545 values.put(ContactsContract.CommonDataKinds.Phone.TYPE, 546 ContactsContract.CommonDataKinds.Phone.TYPE_HOME); 547 values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber); 548 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 549 contactId), RawContacts.Data.CONTENT_DIRECTORY); 550 Uri dataUri = resolver.insert(insertUri, values); 551 return ContentUris.parseId(dataUri); 552 } 553 554 public long createEmail(long contactId, String address) { 555 ensureCallingPackage(); 556 final ContentValues values = new ContentValues(); 557 values.put(Data.RAW_CONTACT_ID, contactId); 558 values.put(Data.IS_PRIMARY, 1); 559 values.put(Data.IS_SUPER_PRIMARY, 1); 560 values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 561 values.put(Email.TYPE, Email.TYPE_HOME); 562 values.put(Email.DATA, address); 563 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 564 contactId), RawContacts.Data.CONTENT_DIRECTORY); 565 Uri dataUri = resolver.insert(insertUri, values); 566 return ContentUris.parseId(dataUri); 567 } 568 569 public long createStatus(long dataId, String status) { 570 ensureCallingPackage(); 571 final ContentValues values = new ContentValues(); 572 values.put(StatusUpdates.DATA_ID, dataId); 573 values.put(StatusUpdates.STATUS, status); 574 Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values); 575 return ContentUris.parseId(dataUri); 576 } 577 578 public void updateException(String packageProvider, String packageClient, boolean allowAccess) { 579 throw new UnsupportedOperationException("RestrictionExceptions are hard-coded"); 580 } 581 582 public long getContactForRawContact(long rawContactId) { 583 ensureCallingPackage(); 584 Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 585 final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null, 586 null, null); 587 if (!cursor.moveToFirst()) { 588 cursor.close(); 589 throw new RuntimeException("Contact didn't have an aggregate"); 590 } 591 final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID); 592 cursor.close(); 593 return aggId; 594 } 595 596 public int getDataCountForContact(long contactId) { 597 ensureCallingPackage(); 598 Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, 599 contactId), Contacts.Data.CONTENT_DIRECTORY); 600 final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null, 601 null); 602 final int count = cursor.getCount(); 603 cursor.close(); 604 return count; 605 } 606 607 public int getDataCountForRawContact(long rawContactId) { 608 ensureCallingPackage(); 609 Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 610 rawContactId), Contacts.Data.CONTENT_DIRECTORY); 611 final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null, 612 null); 613 final int count = cursor.getCount(); 614 cursor.close(); 615 return count; 616 } 617 618 public void setSuperPrimaryPhone(long dataId) { 619 ensureCallingPackage(); 620 final ContentValues values = new ContentValues(); 621 values.put(Data.IS_PRIMARY, 1); 622 values.put(Data.IS_SUPER_PRIMARY, 1); 623 Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId); 624 resolver.update(updateUri, values, null, null); 625 } 626 627 public long createGroup(String groupName) { 628 ensureCallingPackage(); 629 final ContentValues values = new ContentValues(); 630 values.put(ContactsContract.Groups.RES_PACKAGE, packageName); 631 values.put(ContactsContract.Groups.TITLE, groupName); 632 Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values); 633 return ContentUris.parseId(groupUri); 634 } 635 636 public long createGroupMembership(long rawContactId, long groupId) { 637 ensureCallingPackage(); 638 final ContentValues values = new ContentValues(); 639 values.put(Data.RAW_CONTACT_ID, rawContactId); 640 values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE); 641 values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId); 642 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 643 rawContactId), RawContacts.Data.CONTENT_DIRECTORY); 644 Uri dataUri = resolver.insert(insertUri, values); 645 return ContentUris.parseId(dataUri); 646 } 647 648 protected void setAggregationException(int type, long rawContactId1, long rawContactId2) { 649 ContentValues values = new ContentValues(); 650 values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 651 values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 652 values.put(AggregationExceptions.TYPE, type); 653 resolver.update(AggregationExceptions.CONTENT_URI, values, null, null); 654 } 655 656 public void setAccounts(Account[] accounts) { 657 mAccounts = accounts; 658 } 659 660 /** 661 * Various internal database projections. 662 */ 663 private interface Projections { 664 static final String[] PROJ_ID = new String[] { 665 BaseColumns._ID, 666 }; 667 668 static final int COL_ID = 0; 669 670 static final String[] PROJ_RAW_CONTACTS = new String[] { 671 RawContacts.CONTACT_ID 672 }; 673 674 static final int COL_CONTACTS_ID = 0; 675 } 676 } 677