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