VoicemailContentProvider.java revision aafbe295d67686870c64c74a59e589d1dfb506fa
152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee/*
252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * Copyright (C) 2011 The Android Open Source Project
352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee *
452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * Licensed under the Apache License, Version 2.0 (the "License");
552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * you may not use this file except in compliance with the License.
652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * You may obtain a copy of the License at
752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee *
852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee *      http://www.apache.org/licenses/LICENSE-2.0
952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee *
1052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * Unless required by applicable law or agreed to in writing, software
1152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * distributed under the License is distributed on an "AS IS" BASIS,
1252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * See the License for the specific language governing permissions and
1452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * limitations under the License
1552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee */
1652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeepackage com.android.providers.contacts;
1752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
1800e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjeeimport static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
1952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport static com.android.providers.contacts.util.DbQueryUtils.concatenateClauses;
2052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
2152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
2252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.ContentProvider;
2352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.ContentResolver;
2452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.ContentUris;
2552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.ContentValues;
2652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.Context;
2752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.Intent;
2852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.content.pm.PackageManager;
2952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.database.Cursor;
3052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.database.sqlite.SQLiteDatabase;
3152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.database.sqlite.SQLiteQueryBuilder;
3252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.net.Uri;
3352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.os.Binder;
3452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.os.ParcelFileDescriptor;
3552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.provider.CallLog.Calls;
3652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.provider.VoicemailContract;
3752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.provider.VoicemailContract.Voicemails;
3852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport android.util.Log;
3952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
4052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport com.android.providers.contacts.ContactsDatabaseHelper.Tables;
4152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport com.android.providers.contacts.ContactsDatabaseHelper.Views;
4252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport com.android.providers.contacts.util.CloseUtils;
4300e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjeeimport com.android.providers.contacts.util.DbQueryUtils;
4452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport com.android.providers.contacts.util.TypedUriMatcherImpl;
4552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
4652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport java.io.File;
4752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport java.io.FileNotFoundException;
4852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport java.io.IOException;
4952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport java.util.HashMap;
5052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport java.util.List;
5152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeeimport java.util.Map;
5252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
5352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee// TODO: Restrict access to only voicemail columns (i.e no access to call_log
5452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee// specific fields)
5552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee// TODO: Port unit tests from perforce.
5652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee/**
5752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee * An implementation of the Voicemail content provider.
5852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee */
5952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjeepublic class VoicemailContentProvider extends ContentProvider {
6052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static final String TAG = "VoicemailContentProvider";
6152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
6252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /** The private directory in which to store the data associated with the voicemail. */
6352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static final String DATA_DIRECTORY = "voicemail-data";
6452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
6552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static final String[] MIME_TYPE_ONLY_PROJECTION = new String[] { Voicemails.MIME_TYPE };
6652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static final String[] FILENAME_ONLY_PROJECTION = new String[] { Voicemails._DATA };
6752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static final String VOICEMAILS_TABLE_NAME = Tables.CALLS;
6852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
6952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    // Voicemail projection map
7052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static final ProjectionMap sVoicemailProjectionMap = new ProjectionMap.Builder()
7152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails._ID)
7252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.NUMBER)
7352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.DATE)
7452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.DURATION)
7552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.NEW)
7652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.STATE)
7752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.SOURCE_DATA)
7852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.SOURCE_PACKAGE)
7952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.HAS_CONTENT)
8052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails.MIME_TYPE)
8152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .add(Voicemails._DATA)
8252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            .build();
8352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private ContentResolver mContentResolver;
8452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private ContactsDatabaseHelper mDbHelper;
85aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee    private VoicemailPermissions mVoicemailPermissions;
8652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
8752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
8852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public boolean onCreate() {
8952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        Context context = context();
9052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        mContentResolver = context.getContentResolver();
911975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        mDbHelper = getDatabaseHelper(context);
92aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions = new VoicemailPermissions(context);
9352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return true;
9452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
9552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
961975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee    /*package for testing*/ ContactsDatabaseHelper getDatabaseHelper(Context context) {
971975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        return ContactsDatabaseHelper.getInstance(context);
981975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee    }
991975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee
10052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /*package for testing*/ Context context() {
10152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return getContext();
10252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
10352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
10452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
10552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public String getType(Uri uri) {
10652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        UriData uriData = null;
10752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        try {
10852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            uriData = createUriData(uri);
10952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        } catch (IllegalArgumentException ignored) {
11052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // Special case: for illegal URIs, we return null rather than thrown an exception.
11152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return null;
11252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
11352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // TODO: DB lookup for the mime type may cause strict mode exception for the callers of
11452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // getType(). See if this could be avoided.
11552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (uriData.hasId()) {
11652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // An individual voicemail - so lookup the MIME type in the db.
11752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return lookupMimeType(uriData);
11852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
11952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // Not an individual voicemail - must be a directory listing type.
12052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return VoicemailContract.DIR_TYPE;
12152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
12252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
12352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /** Query the db for the MIME type of the given URI, called only from {@link #getType(Uri)}. */
12452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private String lookupMimeType(UriData uriData) {
12552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        Cursor cursor = null;
12652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        try {
12752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // Use queryInternal, bypassing provider permission check. This is needed because
12852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // getType() can be called from any application context (even without voicemail
12952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // permissions) to know the MIME type of the URI. There is no security issue here as we
13052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // do not expose any sensitive data through this interface.
13152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            cursor = queryInternal(uriData, MIME_TYPE_ONLY_PROJECTION, null, null, null);
13252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            if (cursor.moveToFirst()) {
13352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                return cursor.getString(cursor.getColumnIndex(Voicemails.MIME_TYPE));
13452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
13552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        } finally {
13652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            CloseUtils.closeQuietly(cursor);
13752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
13852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return null;
13952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
14052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
14152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
14252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
14352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            String sortOrder) {
144aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
14552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        UriData uriData = createUriData(uri);
14652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        checkPackagePermission(uriData);
14752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return queryInternal(uriData, projection,
14852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                concatenateClauses(selection, getPackageRestrictionClause()), selectionArgs,
14952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                sortOrder);
15052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
15152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
15252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
15352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Internal version of query(), that does not apply any provider restriction and lets the query
15452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * flow through without such checks.
15552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * <p>
15652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * This is useful for internal queries when we do not worry about access permissions.
15752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
15852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private Cursor queryInternal(UriData uriData, String[] projection, String selection,
15952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            String[] selectionArgs, String sortOrder) {
16052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
16152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        qb.setTables(Tables.CALLS);
16252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        qb.setProjectionMap(sVoicemailProjectionMap);
16352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        qb.setStrict(true);
16452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
16552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        String combinedClause = concatenateClauses(selection, getWhereClause(uriData),
16652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                getCallTypeClause());
16752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        SQLiteDatabase db = mDbHelper.getReadableDatabase();
16852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        Cursor c = qb.query(db, projection, combinedClause, selectionArgs, null, null, sortOrder);
16952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (c != null) {
17052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            c.setNotificationUri(mContentResolver, VoicemailContract.CONTENT_URI);
17152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
17252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return c;
17352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
17452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
17552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private String getWhereClause(UriData uriData) {
17652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return concatenateClauses(
17752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                (uriData.hasId() ?
17852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        getEqualityClause(Voicemails._ID, uriData.getId())
17952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        : null),
18052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                (uriData.hasSourcePackage() ?
18152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        getEqualityClause(Voicemails.SOURCE_PACKAGE, uriData.getSourcePackage())
18252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        : null));
18352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
18452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
18552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
18652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public int bulkInsert(Uri uri, ContentValues[] valuesArray) {
187aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
18852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // TODO: There is scope to optimize this method further. At the least we can avoid doing the
18952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // extra work related to the calling provider and checking permissions.
19052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        UriData uriData = createUriData(uri);
19152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        int numInserted = 0;
19252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        for (ContentValues values : valuesArray) {
19352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            if (insertInternal(uriData, values, false) != null) {
19452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                numInserted++;
19552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
19652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
19752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (numInserted > 0) {
19852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            notifyChange(uri, Intent.ACTION_PROVIDER_CHANGED);
19952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
20052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return numInserted;
20152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
20252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
20352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
20452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public Uri insert(Uri uri, ContentValues values) {
205aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
20652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return insertInternal(createUriData(uri), values, true);
20752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
20852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
20952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private Uri insertInternal(UriData uriData, ContentValues values,
21052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            boolean sendProviderChangedNotification) {
21100e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        checkForSupportedColumns(sVoicemailProjectionMap, values);
2121975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        ContentValues copiedValues = new ContentValues(values);
21352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        checkInsertSupported(uriData);
2141975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        checkAndAddSourcePackageIntoValues(uriData, copiedValues);
21552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
21652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // "_data" column is used by base ContentProvider's openFileHelper() to determine filename
21752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // when Input/Output stream is requested to be opened.
2181975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        copiedValues.put(Voicemails._DATA, generateDataFile());
21952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
22052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // call type is always voicemail.
2211975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        copiedValues.put(Calls.TYPE, Calls.VOICEMAIL_TYPE);
22252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
22352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        SQLiteDatabase db = mDbHelper.getWritableDatabase();
2241975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee        long rowId = db.insert(VOICEMAILS_TABLE_NAME, null, copiedValues);
22552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (rowId > 0) {
22652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            Uri newUri = ContentUris.withAppendedId(
22752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    Uri.withAppendedPath(VoicemailContract.CONTENT_URI_SOURCE,
2281975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee                            copiedValues.getAsString(Voicemails.SOURCE_PACKAGE)), rowId);
22952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
23052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            if (sendProviderChangedNotification) {
23152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                notifyChange(newUri, VoicemailContract.ACTION_NEW_VOICEMAIL,
23252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        Intent.ACTION_PROVIDER_CHANGED);
23352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            } else {
23452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                notifyChange(newUri, VoicemailContract.ACTION_NEW_VOICEMAIL);
23552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
23652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // Populate the 'voicemail_uri' field to be used by the call_log provider.
23700e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee            updateVoicemailUri(db, newUri);
23852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return newUri;
23952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
24052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return null;
24152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
24252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
24300e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee    private void updateVoicemailUri(SQLiteDatabase db, Uri newUri) {
24452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        ContentValues values = new ContentValues();
24552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        values.put(Calls.VOICEMAIL_URI, newUri.toString());
24600e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        // Directly update the db because we cannot update voicemail_uri through external
24700e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        // update() due to projectionMap check. This also avoids unnecessary permission
24800e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        // checks that are already done as part of insert request.
24900e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        db.update(VOICEMAILS_TABLE_NAME, values, getWhereClause(createUriData(newUri)), null);
25052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
25152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
25252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private void checkAndAddSourcePackageIntoValues(UriData uriData, ContentValues values) {
25352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // If content values don't contain the provider, calculate the right provider to use.
25452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (!values.containsKey(Voicemails.SOURCE_PACKAGE)) {
25552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            String provider = uriData.hasSourcePackage() ?
25652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    uriData.getSourcePackage() : getCallingPackage();
25752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            values.put(Voicemails.SOURCE_PACKAGE, provider);
25852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
25952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // If you put a provider in the URI and in the values, they must match.
26052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (uriData.hasSourcePackage() && values.containsKey(Voicemails.SOURCE_PACKAGE)) {
26152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            if (!uriData.getSourcePackage().equals(values.get(Voicemails.SOURCE_PACKAGE))) {
2621975b56a3368b4b7684429ffa79e7b9dbc35b475Debashish Chatterjee                throw new SecurityException(
26352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        "Provider in URI was " + uriData.getSourcePackage() +
26452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        " but doesn't match provider in ContentValues which was "
26552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        + values.get(Voicemails.SOURCE_PACKAGE));
26652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
26752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
26852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // You must have access to the provider given in values.
269aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        if (!mVoicemailPermissions.callerHasFullAccess()) {
27052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            checkPackagesMatch(getCallingPackage(), values.getAsString(Voicemails.SOURCE_PACKAGE),
27152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    uriData.getUri());
27252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
27352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
27452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
27552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
27652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Checks that the callingProvider is same as voicemailProvider. Throws {@link
27752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * SecurityException} if they don't match.
27852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
27952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private final void checkPackagesMatch(String callingProvider, String voicemailProvider,
28052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            Uri uri) {
28152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (!voicemailProvider.equals(callingProvider)) {
28252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            String errorMsg = String.format("Permission denied for URI: %s\n. " +
28352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    "Provider %s cannot perform this operation for %s. Requires %s permission.",
28452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    uri, callingProvider, voicemailProvider,
28552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    Manifest.permission.READ_WRITE_ALL_VOICEMAIL);
28652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            throw new SecurityException(errorMsg);
28752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
28852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
28952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
29052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private void checkInsertSupported(UriData uriData) {
29152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (uriData.hasId()) {
29252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            throw new UnsupportedOperationException(String.format(
29352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    "Cannot insert URI: %s. Inserted URIs should not contain an id.",
29452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    uriData.getUri()));
29552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
29652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
29752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
29852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
29952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
300aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
30152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        UriData uriData = createUriData(uri);
30252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        checkPackagePermission(uriData);
30300e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        checkForSupportedColumns(sVoicemailProjectionMap, values);
30400e7c94b70f4b477653534dbe559d1759d796157Debashish Chatterjee        checkUpdateSupported(uriData);
30552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
30652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // TODO: This implementation does not allow bulk update because it only accepts
30752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // URI that include message Id. I think we do want to support bulk update.
30852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        String combinedClause = concatenateClauses(selection, getPackageRestrictionClause(),
30952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                getWhereClause(uriData), getCallTypeClause());
31052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        int count = db.update(VOICEMAILS_TABLE_NAME, values, combinedClause, selectionArgs);
31152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (count > 0) {
31252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            notifyChange(uri, Intent.ACTION_PROVIDER_CHANGED);
31352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
31452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return count;
31552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
31652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
31752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private void checkUpdateSupported(UriData uriData) {
31852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (!uriData.hasId()) {
31952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            throw new UnsupportedOperationException(String.format(
32052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    "Cannot update URI: %s.  Bulk update not supported", uriData.getUri()));
32152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
32252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
32352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
32452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
32552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public int delete(Uri uri, String selection, String[] selectionArgs) {
326aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
32752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        UriData uriData = createUriData(uri);
32852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        checkPackagePermission(uriData);
32952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
33052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        String combinedClause = concatenateClauses(selection, getPackageRestrictionClause(),
33152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                getWhereClause(uriData), getCallTypeClause());
33252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
33352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // Delete all the files associated with this query.  Once we've deleted the rows, there will
33452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // be no way left to get hold of the files.
33552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        Cursor cursor = null;
33652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        try {
33752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            cursor = queryInternal(uriData, FILENAME_ONLY_PROJECTION, selection, selectionArgs,
33852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    null);
33952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            while (cursor.moveToNext()) {
34052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                File file = new File(cursor.getString(0));
34152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                if (file.exists()) {
34252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    boolean success = file.delete();
34352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    if (!success) {
34452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        Log.e(TAG, "Failed to delete file: " + file.getAbsolutePath());
34552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                    }
34652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                }
34752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
34852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        } finally {
34952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            CloseUtils.closeQuietly(cursor);
35052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
35152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
35252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // Now delete the rows themselves.
35352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        int count = db.delete(VOICEMAILS_TABLE_NAME, combinedClause, selectionArgs);
35452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (count > 0) {
35552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            notifyChange(uri, Intent.ACTION_PROVIDER_CHANGED);
35652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
35752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return count;
35852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
35952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
36052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    @Override
36152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
362aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        mVoicemailPermissions.checkCallerHasOwnVoicemailAccess();
36352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        UriData uriData = createUriData(uri);
36452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        checkPackagePermission(uriData);
36552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
36652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // This relies on "_data" column to be populated with the file path.
36752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        ParcelFileDescriptor openFileHelper = openFileHelper(uri, mode);
36852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
36952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // If the open succeeded, then update the file exists bit in the table.
37052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (mode.contains("w")) {
37152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            ContentValues contentValues = new ContentValues();
37252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            contentValues.put(Voicemails.HAS_CONTENT, 1);
37352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            update(uri, contentValues, null, null);
37452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
37552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
37652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return openFileHelper;
37752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
37852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
37952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
38052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Notifies the content resolver and fires required broadcast intent(s) to notify about the
38152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * change.
38252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     *
38352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * @param notificationUri The URI that got impacted due to the change. This is the URI that is
38452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     *            included in content resolver and broadcast intent notification.
38552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * @param intentActions List of intent actions that needs to be fired. A separate intent is
38652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     *            fired for each intent action.
38752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
38852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private void notifyChange(Uri notificationUri, String... intentActions) {
38952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // Notify the observers.
39052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        mContentResolver.notifyChange(notificationUri, null, true);
39152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // Fire notification intents.
39252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        for (String intentAction : intentActions) {
39352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // TODO: We can possibly be more intelligent here and send targeted intents based on
39452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // what voicemail permission the package has. If possible, here is what we would like to
39552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // do for a given broadcast intent -
39652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // 1) Send it to all packages that have READ_WRITE_ALL_VOICEMAIL permission.
39752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // 2) Send it to only the owner package that has just READ_WRITE_OWN_VOICEMAIL, if not
39852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // already sent in (1).
39952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            Intent intent = new Intent(intentAction, notificationUri);
40052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            intent.putExtra(VoicemailContract.EXTRA_CHANGED_BY, getCallingPackage());
40152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            context().sendOrderedBroadcast(intent, Manifest.permission.READ_WRITE_OWN_VOICEMAIL);
40252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
40352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
40452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
40552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /** Generates a random file for storing audio data. */
40652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private String generateDataFile() {
40752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        try {
40852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            File dataDirectory = context().getDir(DATA_DIRECTORY, Context.MODE_PRIVATE);
40952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            File voicemailFile = File.createTempFile("voicemail", "", dataDirectory);
41052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return voicemailFile.getAbsolutePath();
41152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        } catch (IOException e) {
41252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            // If we are unable to create a temporary file, something went horribly wrong.
41352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            throw new RuntimeException("unable to create temp file", e);
41452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
41552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
41652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
41752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
41852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Decorates a URI by providing methods to get various properties from the URI.
41952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
42052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static class UriData {
42152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        private final Uri mUri;
42252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        private final String mId;
42352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        private final String mSourcePackage;
42452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
42552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        public UriData(Uri uri, String id, String sourcePackage) {
42652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            mUri = uri;
42752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            mId = id;
42852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            mSourcePackage = sourcePackage;
42952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
43052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
43152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        /** Gets the original URI to which this {@link UriData} corresponds. */
43252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        public final Uri getUri() {
43352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return mUri;
43452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
43552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
43652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        /** Tells us if our URI has an individual voicemail id. */
43752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        public final boolean hasId() {
43852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return mId != null;
43952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
44052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
44152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        /** Gets the ID for the voicemail. */
44252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        public final String getId() {
44352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return mId;
44452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
44552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
44652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        /** Tells us if our URI has a source package string. */
44752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        public final boolean hasSourcePackage() {
44852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return mSourcePackage != null;
44952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
45052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
45152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        /** Gets the source package. */
45252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        public final String getSourcePackage() {
45352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return mSourcePackage;
45452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
45552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
45652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
45752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
45852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Checks that either the caller has READ_WRITE_ALL_VOICEMAIL permission, or has the
45952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * READ_WRITE_OWN_VOICEMAIL permission and is using a URI that matches
46052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * /voicemail/source/[source-package] where [source-package] is the same as the calling
46152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * package.
46252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     *
46352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * @throws SecurityException if the check fails.
46452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
46552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private void checkPackagePermission(UriData uriData) {
466aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        if (!mVoicemailPermissions.callerHasFullAccess()) {
46752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            if (!uriData.hasSourcePackage()) {
46852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                // You cannot have a match if this is not a provider uri.
46952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                throw new SecurityException(String.format(
47052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        "Provider %s does not have %s permission." +
47152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                                "\nPlease use /voicemail/provider/ query path instead.\nURI: %s",
47252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        getCallingPackage(), Manifest.permission.READ_WRITE_ALL_VOICEMAIL,
47352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                        uriData.getUri()));
47452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
47552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            checkPackagesMatch(getCallingPackage(), uriData.getSourcePackage(), uriData.getUri());
47652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
47752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
47852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
47952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private static TypedUriMatcherImpl<VoicemailUriType> createUriMatcher() {
48052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return new TypedUriMatcherImpl<VoicemailUriType>(
48152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                VoicemailContract.AUTHORITY, VoicemailUriType.values());
48252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
48352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
48452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /** Get a {@link UriData} corresponding to a given uri. */
48552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private UriData createUriData(Uri uri) {
48652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        List<String> segments = uri.getPathSegments();
48752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        switch (createUriMatcher().match(uri)) {
48852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            case VOICEMAILS:
48952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                return new UriData(uri, null, null);
49052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            case VOICEMAILS_ID:
49152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                return new UriData(uri, segments.get(1), null);
49252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            case VOICEMAILS_SOURCE:
49352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                return new UriData(uri, null, segments.get(2));
49452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            case VOICEMAILS_SOURCE_ID:
49552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                return new UriData(uri, segments.get(3), segments.get(2));
49652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            case NO_MATCH:
49752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                throw new IllegalArgumentException("Invalid URI: " + uri);
49852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            default:
49952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                throw new IllegalStateException("Impossible, all cases are covered");
50052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
50152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
50252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
50352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
50452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Gets the name of the calling package.
50552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * <p>
50652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * It's possible (though unlikely) for there to be more than one calling package (requires that
50752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * your manifest say you want to share process ids) in which case we will return an arbitrary
50852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * package name. It's also possible (though very unlikely) for us to be unable to work out what
50952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * your calling package is, in which case we will return null.
51052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
51152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /* package for test */String getCallingPackage() {
51252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        int caller = Binder.getCallingUid();
51352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (caller == 0) {
51452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return null;
51552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
51652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        String[] callerPackages = context().getPackageManager().getPackagesForUid(caller);
51752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (callerPackages == null || callerPackages.length == 0) {
51852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return null;
51952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
52052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        if (callerPackages.length == 1) {
52152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return callerPackages[0];
52252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
52352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // If we have more than one caller package, which is very unlikely, let's return the one
52452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // with the highest permissions. If more than one has the same permission, we don't care
52552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        // which one we return.
52652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        String bestSoFar = callerPackages[0];
52752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        for (String callerPackage : callerPackages) {
528aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee            if (mVoicemailPermissions.packageHasFullAccess(callerPackage)) {
52952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                // Full always wins, we can return early.
53052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                return callerPackage;
53152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
532aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee            if (mVoicemailPermissions.packageHasOwnVoicemailAccess(callerPackage)) {
53352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee                bestSoFar = callerPackage;
53452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            }
53552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
53652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return bestSoFar;
53752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
53852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
53952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /**
54052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * Creates a clause to restrict the selection to the calling provider or null if the caller has
54152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     * access to all data.
54252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee     */
54352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private String getPackageRestrictionClause() {
544aafbe295d67686870c64c74a59e589d1dfb506faDebashish Chatterjee        if (mVoicemailPermissions.callerHasFullAccess()) {
54552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee            return null;
54652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        }
54752e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return getEqualityClause(Voicemails.SOURCE_PACKAGE, getCallingPackage());
54852e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
54952e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
55052e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
55152e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    /** Creates a clause to restrict the selection to only voicemail call type.*/
55252e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    private String getCallTypeClause() {
55352e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee        return getEqualityClause(Calls.TYPE, String.valueOf(Calls.VOICEMAIL_TYPE));
55452e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee    }
55552e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee
55652e8d24f8492116f0b49b147576ce13a5f913aa2Debashish Chatterjee}
557