ContactsActor.java revision e99988b266dd1263162583e81e2b408e7329b1c8
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.res.Resources; 27import android.database.Cursor; 28import android.net.Uri; 29import android.os.Binder; 30import android.provider.BaseColumns; 31import android.provider.ContactsContract; 32import android.provider.Contacts.Phones; 33import android.provider.ContactsContract.CommonDataKinds; 34import android.provider.ContactsContract.Contacts; 35import android.provider.ContactsContract.Data; 36import android.provider.ContactsContract.RawContacts; 37import android.test.IsolatedContext; 38import android.test.RenamingDelegatingContext; 39import android.test.mock.MockContentResolver; 40import android.test.mock.MockContext; 41import android.test.mock.MockPackageManager; 42 43import java.util.HashMap; 44 45/** 46 * Helper class that encapsulates an "actor" which is owned by a specific 47 * package name. It correctly maintains a wrapped {@link Context} and an 48 * attached {@link MockContentResolver}. Multiple actors can be used to test 49 * security scenarios between multiple packages. 50 */ 51public class ContactsActor { 52 private static final String FILENAME_PREFIX = "test."; 53 54 public static final String PACKAGE_GREY = "edu.example.grey"; 55 public static final String PACKAGE_RED = "net.example.red"; 56 public static final String PACKAGE_GREEN = "com.example.green"; 57 public static final String PACKAGE_BLUE = "org.example.blue"; 58 59 public Context context; 60 public String packageName; 61 public MockContentResolver resolver; 62 public ContentProvider provider; 63 64 private IsolatedContext mProviderContext; 65 66 /** 67 * Create an "actor" using the given parent {@link Context} and the specific 68 * package name. Internally, all {@link Context} method calls are passed to 69 * a new instance of {@link RestrictionMockContext}, which stubs out the 70 * security infrastructure. 71 */ 72 public ContactsActor(Context overallContext, String packageName, 73 Class<? extends ContentProvider> providerClass, String authority) throws Exception { 74 resolver = new MockContentResolver(); 75 context = new RestrictionMockContext(overallContext, packageName, resolver); 76 this.packageName = packageName; 77 78 RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context, 79 overallContext, FILENAME_PREFIX); 80 mProviderContext = new IsolatedContext(resolver, targetContextWrapper); 81 provider = addProvider(providerClass, authority); 82 } 83 84 public void addAuthority(String authority) { 85 resolver.addProvider(authority, provider); 86 } 87 88 public ContentProvider addProvider(Class<? extends ContentProvider> providerClass, 89 String authority) throws Exception { 90 ContentProvider provider = providerClass.newInstance(); 91 provider.attachInfo(mProviderContext, null); 92 resolver.addProvider(authority, provider); 93 return provider; 94 } 95 96 /** 97 * Mock {@link Context} that reports specific well-known values for testing 98 * data protection. The creator can override the owner package name, and 99 * force the {@link PackageManager} to always return a well-known package 100 * list for any call to {@link PackageManager#getPackagesForUid(int)}. 101 * <p> 102 * For example, the creator could request that the {@link Context} lives in 103 * package name "com.example.red", and also cause the {@link PackageManager} 104 * to report that no UID contains that package name. 105 */ 106 private static class RestrictionMockContext extends MockContext { 107 private final Context mOverallContext; 108 private final String mReportedPackageName; 109 private final RestrictionMockPackageManager mPackageManager; 110 private final ContentResolver mResolver; 111 112 /** 113 * Create a {@link Context} under the given package name. 114 */ 115 public RestrictionMockContext(Context overallContext, String reportedPackageName, 116 ContentResolver resolver) { 117 mOverallContext = overallContext; 118 mReportedPackageName = reportedPackageName; 119 mResolver = resolver; 120 mPackageManager = new RestrictionMockPackageManager(); 121 mPackageManager.addPackage(1000, PACKAGE_GREY); 122 mPackageManager.addPackage(2000, PACKAGE_RED); 123 mPackageManager.addPackage(3000, PACKAGE_GREEN); 124 mPackageManager.addPackage(4000, PACKAGE_BLUE); 125 } 126 127 @Override 128 public String getPackageName() { 129 return mReportedPackageName; 130 } 131 132 @Override 133 public PackageManager getPackageManager() { 134 return mPackageManager; 135 } 136 137 @Override 138 public Resources getResources() { 139 return mOverallContext.getResources(); 140 } 141 142 @Override 143 public ContentResolver getContentResolver() { 144 return mResolver; 145 } 146 } 147 148 /** 149 * Mock {@link PackageManager} that knows about a specific set of packages 150 * to help test security models. Because {@link Binder#getCallingUid()} 151 * can't be mocked, you'll have to find your mock-UID manually using your 152 * {@link Context#getPackageName()}. 153 */ 154 private static class RestrictionMockPackageManager extends MockPackageManager { 155 private final HashMap<Integer, String> mForward = new HashMap<Integer, String>(); 156 private final HashMap<String, Integer> mReverse = new HashMap<String, Integer>(); 157 158 /** 159 * Add a UID-to-package mapping, which is then stored internally. 160 */ 161 public void addPackage(int packageUid, String packageName) { 162 mForward.put(packageUid, packageName); 163 mReverse.put(packageName, packageUid); 164 } 165 166 @Override 167 public String[] getPackagesForUid(int uid) { 168 return new String[] { mForward.get(uid) }; 169 } 170 171 @Override 172 public ApplicationInfo getApplicationInfo(String packageName, int flags) { 173 ApplicationInfo info = new ApplicationInfo(); 174 Integer uid = mReverse.get(packageName); 175 info.uid = (uid != null) ? uid : -1; 176 return info; 177 } 178 } 179 180 public long createContact(boolean isRestricted, String name) { 181 long contactId = createContact(isRestricted); 182 createName(contactId, name); 183 return contactId; 184 } 185 186 public long createContact(boolean isRestricted) { 187 final ContentValues values = new ContentValues(); 188 if (isRestricted) { 189 values.put(RawContacts.IS_RESTRICTED, 1); 190 } 191 192 Uri contactUri = resolver.insert(RawContacts.CONTENT_URI, values); 193 return ContentUris.parseId(contactUri); 194 } 195 196 public long createName(long contactId, String name) { 197 final ContentValues values = new ContentValues(); 198 values.put(Data.RAW_CONTACT_ID, contactId); 199 values.put(Data.IS_PRIMARY, 1); 200 values.put(Data.IS_SUPER_PRIMARY, 1); 201 values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); 202 values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name); 203 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 204 contactId), RawContacts.Data.CONTENT_DIRECTORY); 205 Uri dataUri = resolver.insert(insertUri, values); 206 return ContentUris.parseId(dataUri); 207 } 208 209 public long createPhone(long contactId, String phoneNumber) { 210 final ContentValues values = new ContentValues(); 211 values.put(Data.RAW_CONTACT_ID, contactId); 212 values.put(Data.IS_PRIMARY, 1); 213 values.put(Data.IS_SUPER_PRIMARY, 1); 214 values.put(Data.MIMETYPE, Phones.CONTENT_ITEM_TYPE); 215 values.put(ContactsContract.CommonDataKinds.Phone.TYPE, 216 ContactsContract.CommonDataKinds.Phone.TYPE_HOME); 217 values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber); 218 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 219 contactId), RawContacts.Data.CONTENT_DIRECTORY); 220 Uri dataUri = resolver.insert(insertUri, values); 221 return ContentUris.parseId(dataUri); 222 } 223 224 public void updateException(String packageProvider, String packageClient, boolean allowAccess) { 225 throw new UnsupportedOperationException("RestrictionExceptions are hard-coded"); 226 } 227 228 public long getContactForRawContact(long rawContactId) { 229 Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 230 final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null, 231 null, null); 232 if (!cursor.moveToFirst()) { 233 cursor.close(); 234 throw new RuntimeException("Contact didn't have an aggregate"); 235 } 236 final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID); 237 cursor.close(); 238 return aggId; 239 } 240 241 public int getDataCountForContact(long contactId) { 242 Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, 243 contactId), Contacts.Data.CONTENT_DIRECTORY); 244 final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, null, null, 245 null); 246 final int count = cursor.getCount(); 247 cursor.close(); 248 return count; 249 } 250 251 public void setSuperPrimaryPhone(long dataId) { 252 final ContentValues values = new ContentValues(); 253 values.put(Data.IS_PRIMARY, 1); 254 values.put(Data.IS_SUPER_PRIMARY, 1); 255 Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId); 256 resolver.update(updateUri, values, null, null); 257 } 258 259 public long getPrimaryPhoneId(long contactId) { 260 Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); 261 final Cursor cursor = resolver.query(contactUri, Projections.PROJ_CONTACTS, null, 262 null, null); 263 long primaryPhoneId = -1; 264 if (cursor.moveToFirst()) { 265 primaryPhoneId = cursor.getLong(Projections.COL_CONTACTS_PRIMARY_PHONE_ID); 266 } 267 cursor.close(); 268 return primaryPhoneId; 269 } 270 271 public long createGroup(String groupName) { 272 final ContentValues values = new ContentValues(); 273 values.put(ContactsContract.Groups.RES_PACKAGE, packageName); 274 values.put(ContactsContract.Groups.TITLE, groupName); 275 Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values); 276 return ContentUris.parseId(groupUri); 277 } 278 279 public long createGroupMembership(long rawContactId, long groupId) { 280 final ContentValues values = new ContentValues(); 281 values.put(Data.RAW_CONTACT_ID, rawContactId); 282 values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE); 283 values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId); 284 Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 285 rawContactId), RawContacts.Data.CONTENT_DIRECTORY); 286 Uri dataUri = resolver.insert(insertUri, values); 287 return ContentUris.parseId(dataUri); 288 } 289 290 /** 291 * Various internal database projections. 292 */ 293 private interface Projections { 294 static final String[] PROJ_ID = new String[] { 295 BaseColumns._ID, 296 }; 297 298 static final int COL_ID = 0; 299 300 static final String[] PROJ_RAW_CONTACTS = new String[] { 301 RawContacts.CONTACT_ID 302 }; 303 304 static final int COL_CONTACTS_ID = 0; 305 306 static final String[] PROJ_CONTACTS = new String[] { 307 Contacts.PRIMARY_PHONE_ID 308 }; 309 310 static final int COL_CONTACTS_PRIMARY_PHONE_ID = 0; 311 312 } 313} 314