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