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