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