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