Project: platform_packages_providers_contactsprovider
/*
 * Copyright (C) 2009 The Android Open Source Project 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
 
package com.android.providers.contacts; 
 
import com.android.providers.contacts.util.MockSharedPreferences; 
import com.google.android.collect.Sets; 
 
import android.accounts.Account; 
import android.accounts.AccountManager; 
import android.accounts.AccountManagerCallback; 
import android.accounts.AccountManagerFuture; 
import android.accounts.AuthenticatorException; 
import android.accounts.OnAccountsUpdateListener; 
import android.accounts.OperationCanceledException; 
import android.content.ContentProvider; 
import android.content.ContentResolver; 
import android.content.ContentUris; 
import android.content.ContentValues; 
import android.content.Context; 
import android.content.SharedPreferences; 
import android.content.pm.ApplicationInfo; 
import android.content.pm.PackageManager; 
import android.content.pm.ProviderInfo; 
import android.content.res.Configuration; 
import android.content.res.Resources; 
import android.database.Cursor; 
import android.location.Country; 
import android.location.CountryDetector; 
import android.location.CountryListener; 
import android.net.Uri; 
import android.os.Handler; 
import android.os.Looper; 
import android.provider.BaseColumns; 
import android.provider.ContactsContract; 
import android.provider.ContactsContract.AggregationExceptions; 
import android.provider.ContactsContract.CommonDataKinds; 
import android.provider.ContactsContract.CommonDataKinds.Email; 
import android.provider.ContactsContract.CommonDataKinds.Phone; 
import android.provider.ContactsContract.Contacts; 
import android.provider.ContactsContract.Data; 
import android.provider.ContactsContract.RawContacts; 
import android.provider.ContactsContract.StatusUpdates; 
import android.test.IsolatedContext; 
import android.test.RenamingDelegatingContext; 
import android.test.mock.MockContentResolver; 
import android.test.mock.MockContext; 
 
import java.io.File; 
import java.io.IOException; 
import java.util.Arrays; 
import java.util.Locale; 
import java.util.Set; 
 
/**
 * Helper class that encapsulates an "actor" which is owned by a specific 
 * package name. It correctly maintains a wrapped {@link Context} and an 
 * attached {@link MockContentResolver}. Multiple actors can be used to test 
 * security scenarios between multiple packages. 
 */
 
public class ContactsActor { 
    private static final String FILENAME_PREFIX = "test."
 
    public static final String PACKAGE_GREY = "edu.example.grey"
    public static final String PACKAGE_RED = "net.example.red"
    public static final String PACKAGE_GREEN = "com.example.green"
    public static final String PACKAGE_BLUE = "org.example.blue"
 
    public Context context; 
    public String packageName; 
    public MockContentResolver resolver; 
    public ContentProvider provider; 
    private Country mMockCountry = new Country("us"0); 
 
    private Account[] mAccounts = new Account[0]; 
 
    private Set<String> mGrantedPermissions = Sets.newHashSet(); 
    private final Set<Uri> mGrantedUriPermissions = Sets.newHashSet(); 
 
    private CountryDetector mMockCountryDetector = new CountryDetector(null){ 
        @Override 
        public Country detectCountry() { 
            return mMockCountry; 
        } 
 
        @Override 
        public void addCountryListener(CountryListener listener, Looper looper) { 
        } 
    }; 
 
    private AccountManager mMockAccountManager; 
 
    private class MockAccountManager extends AccountManager { 
        public MockAccountManager(Context conteact) { 
            super(context, nullnull); 
        } 
 
        @Override 
        public void addOnAccountsUpdatedListener(OnAccountsUpdateListener listener, 
                Handler handler, boolean updateImmediately) { 
            // do nothing 
        } 
 
        @Override 
        public Account[] getAccounts() { 
            return mAccounts; 
        } 
 
        @Override 
        public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures
                final String type, final String[] features, 
                AccountManagerCallback<Account[]> callback, Handler handler) { 
            return null
        } 
 
        @Override 
        public String blockingGetAuthToken(Account account, String authTokenType, 
                boolean notifyAuthFailure) 
                throws OperationCanceledException, IOException, AuthenticatorException { 
            return null
        } 
    } 
 
    private IsolatedContext mProviderContext; 
 
    /**
     * Create an "actor" using the given parent {@link Context} and the specific 
     * package name. Internally, all {@link Context} method calls are passed to 
     * a new instance of {@link RestrictionMockContext}, which stubs out the 
     * security infrastructure. 
     */
 
    public ContactsActor(Context overallContext, String packageName, 
            Class<? extends ContentProvider> providerClass, String authority) throws Exception { 
        resolver = new MockContentResolver(); 
        context = new RestrictionMockContext(overallContext, packageName, resolver, 
                mGrantedPermissions, mGrantedUriPermissions); 
        this.packageName = packageName; 
 
        // Let the Secure class initialize the settings provider, which is done when we first 
        // tries to get any setting.  Because our mock context/content resolver doesn't have the 
        // settings provider, we need to do this with an actual context, before other classes 
        // try to do this with a mock context. 
        // (Otherwise ContactsProvider2.initialzie() will crash trying to get a setting with 
        // a mock context.) 
        android.provider.Settings.Secure.getString(overallContext.getContentResolver(), "dummy"); 
 
        RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext(context, 
                overallContext, FILENAME_PREFIX); 
        mProviderContext = new IsolatedContext(resolver, targetContextWrapper) { 
            private final MockSharedPreferences mPrefs = new MockSharedPreferences(); 
 
            @Override 
            public File getFilesDir() { 
                // TODO: Need to figure out something more graceful than this. 
                return new File("/data/data/com.android.providers.contacts.tests/files"); 
            } 
 
            @Override 
            public Object getSystemService(String name) { 
                if (Context.COUNTRY_DETECTOR.equals(name)) { 
                    return mMockCountryDetector; 
                } 
                if (Context.ACCOUNT_SERVICE.equals(name)) { 
                    return mMockAccountManager; 
                } 
                return super.getSystemService(name); 
            } 
 
            @Override 
            public SharedPreferences getSharedPreferences(String name, int mode) { 
                return mPrefs; 
            } 
        }; 
 
        mMockAccountManager = new MockAccountManager(mProviderContext); 
        provider = addProvider(providerClass, authority); 
    } 
 
    public void addAuthority(String authority) { 
        resolver.addProvider(authority, provider); 
    } 
 
    public ContentProvider addProvider(Class<? extends ContentProvider> providerClass, 
            String authority) throws Exception { 
        ContentProvider provider = providerClass.newInstance(); 
        ProviderInfo info = new ProviderInfo(); 
        info.authority = authority; 
        provider.attachInfo(mProviderContext, info); 
        resolver.addProvider(authority, provider); 
        return provider; 
    } 
 
    public void addPermissions(String... permissions) { 
        mGrantedPermissions.addAll(Arrays.asList(permissions)); 
    } 
 
    public void removePermissions(String... permissions) { 
        mGrantedPermissions.removeAll(Arrays.asList(permissions)); 
    } 
 
    public void addUriPermissions(Uri... uris) { 
        mGrantedUriPermissions.addAll(Arrays.asList(uris)); 
    } 
 
    public void removeUriPermissions(Uri... uris) { 
        mGrantedUriPermissions.removeAll(Arrays.asList(uris)); 
    } 
 
    /**
     * Mock {@link Context} that reports specific well-known values for testing 
     * data protection. The creator can override the owner package name, and 
     * force the {@link PackageManager} to always return a well-known package 
     * list for any call to {@link PackageManager#getPackagesForUid(int)}. 
     * <p> 
     * For example, the creator could request that the {@link Context} lives in 
     * package name "com.example.red", and also cause the {@link PackageManager} 
     * to report that no UID contains that package name. 
     */
 
    private static class RestrictionMockContext extends MockContext { 
        private final Context mOverallContext; 
        private final String mReportedPackageName; 
        private final ContactsMockPackageManager mPackageManager; 
        private final ContentResolver mResolver; 
        private final Resources mRes; 
        private final Set<String> mGrantedPermissions; 
        private final Set<Uri> mGrantedUriPermissions; 
 
        /**
         * Create a {@link Context} under the given package name. 
         */
 
        public RestrictionMockContext(Context overallContext, String reportedPackageName, 
                ContentResolver resolver, Set<String> grantedPermissions, 
                Set<Uri> grantedUriPermissions) { 
            mOverallContext = overallContext; 
            mReportedPackageName = reportedPackageName; 
            mResolver = resolver; 
            mGrantedPermissions = grantedPermissions; 
            mGrantedUriPermissions = grantedUriPermissions; 
 
            mPackageManager = new ContactsMockPackageManager(); 
            mPackageManager.addPackage(1000, PACKAGE_GREY); 
            mPackageManager.addPackage(2000, PACKAGE_RED); 
            mPackageManager.addPackage(3000, PACKAGE_GREEN); 
            mPackageManager.addPackage(4000, PACKAGE_BLUE); 
 
            Resources resources = overallContext.getResources(); 
            Configuration configuration = new Configuration(resources.getConfiguration()); 
            configuration.locale = Locale.US; 
            resources.updateConfiguration(configuration, resources.getDisplayMetrics()); 
            mRes = resources; 
        } 
 
        @Override 
        public String getPackageName() { 
            return mReportedPackageName; 
        } 
 
        @Override 
        public PackageManager getPackageManager() { 
            return mPackageManager; 
        } 
 
        @Override 
        public Resources getResources() { 
            return mRes; 
        } 
 
        @Override 
        public ContentResolver getContentResolver() { 
            return mResolver; 
        } 
 
        @Override 
        public ApplicationInfo getApplicationInfo() { 
            ApplicationInfo ai = new ApplicationInfo(); 
            ai.packageName = "contactsTestPackage"
            return ai; 
        } 
 
        // All permission checks are implemented to simply check against the granted permission set. 
 
        @Override 
        public int checkPermission(String permission, int pid, int uid) { 
            return checkCallingPermission(permission); 
        } 
 
        @Override 
        public int checkCallingPermission(String permission) { 
            if (mGrantedPermissions.contains(permission)) { 
                return PackageManager.PERMISSION_GRANTED; 
            } else { 
                return PackageManager.PERMISSION_DENIED; 
            } 
        } 
 
        @Override 
        public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { 
            return checkCallingUriPermission(uri, modeFlags); 
        } 
 
        @Override 
        public int checkCallingUriPermission(Uri uri, int modeFlags) { 
            if (mGrantedUriPermissions.contains(uri)) { 
                return PackageManager.PERMISSION_GRANTED; 
            } else { 
                return PackageManager.PERMISSION_DENIED; 
            } 
        } 
 
        @Override 
        public int checkCallingOrSelfPermission(String permission) { 
            return checkCallingPermission(permission); 
        } 
 
        @Override 
        public void enforcePermission(String permission, int pid, int uid, String message) { 
            enforceCallingPermission(permission, message); 
        } 
 
        @Override 
        public void enforceCallingPermission(String permission, String message) { 
            if (!mGrantedPermissions.contains(permission)) { 
                throw new SecurityException(message); 
            } 
        } 
 
        @Override 
        public void enforceCallingOrSelfPermission(String permission, String message) { 
            enforceCallingPermission(permission, message); 
        } 
    } 
 
    static String sCallingPackage = null
 
    void ensureCallingPackage() { 
        sCallingPackage = this.packageName; 
    } 
 
    public long createRawContact(String name) { 
        ensureCallingPackage(); 
        long rawContactId = createRawContact(); 
        createName(rawContactId, name); 
        return rawContactId; 
    } 
 
    public long createRawContact() { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
 
        Uri rawContactUri = resolver.insert(RawContacts.CONTENT_URI, values); 
        return ContentUris.parseId(rawContactUri); 
    } 
 
    public long createRawContactWithStatus(String name, String address, 
            String status) { 
        final long rawContactId = createRawContact(name); 
        final long dataId = createEmail(rawContactId, address); 
        createStatus(dataId, status); 
        return rawContactId; 
    } 
 
    public long createName(long contactId, String name) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(Data.RAW_CONTACT_ID, contactId); 
        values.put(Data.IS_PRIMARY, 1); 
        values.put(Data.IS_SUPER_PRIMARY, 1); 
        values.put(Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); 
        values.put(CommonDataKinds.StructuredName.FAMILY_NAME, name); 
        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 
                contactId), RawContacts.Data.CONTENT_DIRECTORY); 
        Uri dataUri = resolver.insert(insertUri, values); 
        return ContentUris.parseId(dataUri); 
    } 
 
    public long createPhone(long contactId, String phoneNumber) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(Data.RAW_CONTACT_ID, contactId); 
        values.put(Data.IS_PRIMARY, 1); 
        values.put(Data.IS_SUPER_PRIMARY, 1); 
        values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 
        values.put(ContactsContract.CommonDataKinds.Phone.TYPE, 
                ContactsContract.CommonDataKinds.Phone.TYPE_HOME); 
        values.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber); 
        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 
                contactId), RawContacts.Data.CONTENT_DIRECTORY); 
        Uri dataUri = resolver.insert(insertUri, values); 
        return ContentUris.parseId(dataUri); 
    } 
 
    public long createEmail(long contactId, String address) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(Data.RAW_CONTACT_ID, contactId); 
        values.put(Data.IS_PRIMARY, 1); 
        values.put(Data.IS_SUPER_PRIMARY, 1); 
        values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); 
        values.put(Email.TYPE, Email.TYPE_HOME); 
        values.put(Email.DATA, address); 
        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 
                contactId), RawContacts.Data.CONTENT_DIRECTORY); 
        Uri dataUri = resolver.insert(insertUri, values); 
        return ContentUris.parseId(dataUri); 
    } 
 
    public long createStatus(long dataId, String status) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(StatusUpdates.DATA_ID, dataId); 
        values.put(StatusUpdates.STATUS, status); 
        Uri dataUri = resolver.insert(StatusUpdates.CONTENT_URI, values); 
        return ContentUris.parseId(dataUri); 
    } 
 
    public void updateException(String packageProvider, String packageClient, boolean allowAccess) { 
        throw new UnsupportedOperationException("RestrictionExceptions are hard-coded"); 
    } 
 
    public long getContactForRawContact(long rawContactId) { 
        ensureCallingPackage(); 
        Uri contactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 
        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_RAW_CONTACTS, null
                nullnull); 
        if (!cursor.moveToFirst()) { 
            cursor.close(); 
            throw new RuntimeException("Contact didn't have an aggregate"); 
        } 
        final long aggId = cursor.getLong(Projections.COL_CONTACTS_ID); 
        cursor.close(); 
        return aggId; 
    } 
 
    public int getDataCountForContact(long contactId) { 
        ensureCallingPackage(); 
        Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(Contacts.CONTENT_URI, 
                contactId), Contacts.Data.CONTENT_DIRECTORY); 
        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, nullnull
                null); 
        final int count = cursor.getCount(); 
        cursor.close(); 
        return count; 
    } 
 
    public int getDataCountForRawContact(long rawContactId) { 
        ensureCallingPackage(); 
        Uri contactUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 
                rawContactId), Contacts.Data.CONTENT_DIRECTORY); 
        final Cursor cursor = resolver.query(contactUri, Projections.PROJ_ID, nullnull
                null); 
        final int count = cursor.getCount(); 
        cursor.close(); 
        return count; 
    } 
 
    public void setSuperPrimaryPhone(long dataId) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(Data.IS_PRIMARY, 1); 
        values.put(Data.IS_SUPER_PRIMARY, 1); 
        Uri updateUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId); 
        resolver.update(updateUri, values, nullnull); 
    } 
 
    public long createGroup(String groupName) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(ContactsContract.Groups.RES_PACKAGE, packageName); 
        values.put(ContactsContract.Groups.TITLE, groupName); 
        Uri groupUri = resolver.insert(ContactsContract.Groups.CONTENT_URI, values); 
        return ContentUris.parseId(groupUri); 
    } 
 
    public long createGroupMembership(long rawContactId, long groupId) { 
        ensureCallingPackage(); 
        final ContentValues values = new ContentValues(); 
        values.put(Data.RAW_CONTACT_ID, rawContactId); 
        values.put(Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE); 
        values.put(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId); 
        Uri insertUri = Uri.withAppendedPath(ContentUris.withAppendedId(RawContacts.CONTENT_URI, 
                rawContactId), RawContacts.Data.CONTENT_DIRECTORY); 
        Uri dataUri = resolver.insert(insertUri, values); 
        return ContentUris.parseId(dataUri); 
    } 
 
    protected void setAggregationException(int type, long rawContactId1, long rawContactId2) { 
        ContentValues values = new ContentValues(); 
        values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); 
        values.put(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); 
        values.put(AggregationExceptions.TYPE, type); 
        resolver.update(AggregationExceptions.CONTENT_URI, values, nullnull); 
    } 
 
    public void setAccounts(Account[] accounts) { 
        mAccounts = accounts; 
    } 
 
    /**
     * Various internal database projections. 
     */
 
    private interface Projections { 
        static final String[] PROJ_ID = new String[] { 
                BaseColumns._ID, 
        }; 
 
        static final int COL_ID = 0
 
        static final String[] PROJ_RAW_CONTACTS = new String[] { 
                RawContacts.CONTACT_ID 
        }; 
 
        static final int COL_CONTACTS_ID = 0
    } 
}