From 4aece9536ed64da965fd42da07a393f935abd6b1 Mon Sep 17 00:00:00 2001 From: Yorke Lee Date: Sat, 2 May 2015 22:22:54 -0700 Subject: Dynamically check for permission denials Don't crash when performing certain operations that the dialer might not have permissions to execute: * Access voicemail provider * Certain TelecomManager methods Add a class TelecomUtil that checks for permissions/default dialer status, and handles the privileged operations appropriately. Bug: 20266292 Change-Id: Ibe8a85440b9ca20169b5ce3be24d1a385caaebb6 --- src/com/android/dialer/CallDetailActivity.java | 7 +- src/com/android/dialer/SpecialCharSequenceMgr.java | 8 +- .../android/dialer/calllog/CallLogActivity.java | 2 +- .../android/dialer/calllog/CallLogFragment.java | 20 ++-- .../dialer/calllog/CallLogNotificationsHelper.java | 12 +-- .../calllog/CallLogNotificationsService.java | 23 ++++ .../dialer/calllog/CallLogQueryHandler.java | 23 ++-- .../android/dialer/calllog/CallLogReceiver.java | 10 +- .../android/dialer/calllog/ContactInfoHelper.java | 5 +- src/com/android/dialer/calllog/IntentProvider.java | 5 +- src/com/android/dialer/util/TelecomUtil.java | 120 +++++++++++++++++++++ 11 files changed, 192 insertions(+), 43 deletions(-) create mode 100644 src/com/android/dialer/util/TelecomUtil.java diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java index 7a23944a0..0a295c4fe 100644 --- a/src/com/android/dialer/CallDetailActivity.java +++ b/src/com/android/dialer/CallDetailActivity.java @@ -66,6 +66,7 @@ import com.android.dialer.util.AsyncTaskExecutor; import com.android.dialer.util.AsyncTaskExecutors; import com.android.dialer.util.CallIntentUtil; import com.android.dialer.util.DialerUtils; +import com.android.dialer.util.TelecomUtil; import com.android.dialer.voicemail.VoicemailPlaybackFragment; import com.android.dialer.voicemail.VoicemailStatusHelper; import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage; @@ -307,7 +308,8 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware final int numIds = ids == null ? 0 : ids.length; final Uri[] uris = new Uri[numIds]; for (int index = 0; index < numIds; ++index) { - uris[index] = ContentUris.withAppendedId(Calls.CONTENT_URI_WITH_VOICEMAIL, ids[index]); + uris[index] = ContentUris.withAppendedId( + TelecomUtil.getCallLogUri(CallDetailActivity.this), ids[index]); } return uris; } @@ -670,7 +672,8 @@ public class CallDetailActivity extends Activity implements ProximitySensorAware new AsyncTask() { @Override public Void doInBackground(Void... params) { - getContentResolver().delete(Calls.CONTENT_URI_WITH_VOICEMAIL, + getContentResolver().delete( + TelecomUtil.getCallLogUri(CallDetailActivity.this), Calls._ID + " IN (" + callIds + ")", null); return null; } diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java index 36145aaef..a57e2ee57 100644 --- a/src/com/android/dialer/SpecialCharSequenceMgr.java +++ b/src/com/android/dialer/SpecialCharSequenceMgr.java @@ -46,6 +46,7 @@ import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment; import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener; import com.android.dialer.calllog.PhoneAccountUtils; +import com.android.dialer.util.TelecomUtil; import java.util.Arrays; import java.util.ArrayList; @@ -272,7 +273,7 @@ public class SpecialCharSequenceMgr { sPreviousAdnQueryHandler = handler; } - static boolean handlePinEntry(Context context, final String input) { + static boolean handlePinEntry(final Context context, final String input) { if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) { final TelecomManager telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); @@ -284,13 +285,14 @@ public class SpecialCharSequenceMgr { if (subscriptionAccountHandles.size() == 1 || hasUserSelectedDefault) { // Don't bring up the dialog for single-SIM or if the default outgoing account is // a subscription account. - return telecomManager.handleMmi(input); + return TelecomUtil.handleMmi(context, input, null); } else if (subscriptionAccountHandles.size() > 1){ SelectPhoneAccountListener listener = new SelectPhoneAccountListener() { @Override public void onPhoneAccountSelected(PhoneAccountHandle selectedAccountHandle, boolean setDefault) { - telecomManager.handleMmi(input, selectedAccountHandle); + TelecomUtil.handleMmi(context.getApplicationContext(), + input, selectedAccountHandle); //TODO: show error dialog if result isn't valid } @Override diff --git a/src/com/android/dialer/calllog/CallLogActivity.java b/src/com/android/dialer/calllog/CallLogActivity.java index c4e16a1b7..8a0cc1364 100644 --- a/src/com/android/dialer/calllog/CallLogActivity.java +++ b/src/com/android/dialer/calllog/CallLogActivity.java @@ -193,7 +193,7 @@ public class CallLogActivity extends Activity implements CallLogQueryHandler.Lis mIsResumed = true; super.onResume(); CallLogQueryHandler callLogQueryHandler = - new CallLogQueryHandler(this.getContentResolver(), this); + new CallLogQueryHandler(this, this.getContentResolver(), this); callLogQueryHandler.fetchVoicemailStatus(); sendScreenViewForChildFragment(mViewPager.getCurrentItem()); } diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java index 85fca9139..02970f66c 100644 --- a/src/com/android/dialer/calllog/CallLogFragment.java +++ b/src/com/android/dialer/calllog/CallLogFragment.java @@ -23,6 +23,7 @@ import android.app.Activity; import android.app.DialogFragment; import android.app.Fragment; import android.app.KeyguardManager; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; @@ -179,17 +180,16 @@ public class CallLogFragment extends Fragment mDateLimit = state.getLong(KEY_DATE_LIMIT, mDateLimit); } - String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity()); - mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), - this, mLogLimit); + final Activity activity = getActivity(); + final ContentResolver resolver = activity.getContentResolver(); + String currentCountryIso = GeoUtil.getCurrentCountryIso(activity); + mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit); mKeyguardManager = - (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE); - getActivity().getContentResolver().registerContentObserver( - CallLog.CONTENT_URI, true, mCallLogObserver); - getActivity().getContentResolver().registerContentObserver( - ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver); - getActivity().getContentResolver().registerContentObserver( - Status.CONTENT_URI, true, mVoicemailStatusObserver); + (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE); + resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver); + resolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, + mContactsObserver); + resolver.registerContentObserver(Status.CONTENT_URI, true, mVoicemailStatusObserver); setHasOptionsMenu(true); fetchCalls(); } diff --git a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java index f6ee8966b..367cb78c3 100644 --- a/src/com/android/dialer/calllog/CallLogNotificationsHelper.java +++ b/src/com/android/dialer/calllog/CallLogNotificationsHelper.java @@ -17,8 +17,8 @@ package com.android.dialer.calllog; import android.content.Context; -import android.content.Intent; -import android.telecom.TelecomManager; + +import com.android.dialer.util.TelecomUtil; /** * Helper class operating on call log notifications. @@ -26,15 +26,11 @@ import android.telecom.TelecomManager; public class CallLogNotificationsHelper { /** Removes the missed call notifications. */ public static void removeMissedCallNotifications(Context context) { - TelecomManager telecomManager = (TelecomManager) - context.getSystemService(Context.TELECOM_SERVICE); - telecomManager.cancelMissedCallsNotification(); + TelecomUtil.cancelMissedCallsNotification(context); } /** Update the voice mail notifications. */ public static void updateVoicemailNotifications(Context context) { - Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); - serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS); - context.startService(serviceIntent); + CallLogNotificationsService.updateVoicemailNotifications(context, null); } } diff --git a/src/com/android/dialer/calllog/CallLogNotificationsService.java b/src/com/android/dialer/calllog/CallLogNotificationsService.java index 2e0e50258..22809db8c 100644 --- a/src/com/android/dialer/calllog/CallLogNotificationsService.java +++ b/src/com/android/dialer/calllog/CallLogNotificationsService.java @@ -17,10 +17,13 @@ package com.android.dialer.calllog; import android.app.IntentService; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.util.Log; +import com.android.dialer.util.TelecomUtil; + /** * Provides operations for managing notifications. *

@@ -84,4 +87,24 @@ public class CallLogNotificationsService extends IntentService { Log.d(TAG, "onHandleIntent: could not handle: " + intent); } } + + /** + * Updates notifications for any new voicemails. + * + * @param context a valid context. + * @param voicemailUri The uri pointing to the voicemail to update the notification for. If + * {@code null}, then notifications for all new voicemails will be updated. + */ + public static void updateVoicemailNotifications(Context context, Uri voicemailUri) { + if (TelecomUtil.hasReadWriteVoicemailPermissions(context)) { + Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); + serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS); + // If voicemailUri is null, then notifications for all voicemails will be updated. + if (voicemailUri != null) { + serviceIntent.putExtra( + CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, voicemailUri); + } + context.startService(serviceIntent); + } + } } diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java index 7eb5f8a0b..49d6a4121 100644 --- a/src/com/android/dialer/calllog/CallLogQueryHandler.java +++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java @@ -19,6 +19,7 @@ package com.android.dialer.calllog; import android.content.AsyncQueryHandler; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabaseCorruptException; import android.database.sqlite.SQLiteDiskIOException; @@ -34,6 +35,7 @@ import android.provider.VoicemailContract.Voicemails; import android.util.Log; import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler; +import com.android.dialer.util.TelecomUtil; import com.android.dialer.voicemail.VoicemailStatusHelperImpl; import com.google.common.collect.Lists; @@ -67,6 +69,8 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { private final WeakReference mListener; + private final Context mContext; + /** * Simple handler that wraps background calls to catch * {@link SQLiteException}, such as when the disk is full. @@ -99,12 +103,15 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { return new CatchingWorkerHandler(looper); } - public CallLogQueryHandler(ContentResolver contentResolver, Listener listener) { - this(contentResolver, listener, -1); + public CallLogQueryHandler(Context context, ContentResolver contentResolver, + Listener listener) { + this(context, contentResolver, listener, -1); } - public CallLogQueryHandler(ContentResolver contentResolver, Listener listener, int limit) { + public CallLogQueryHandler(Context context, ContentResolver contentResolver, Listener listener, + int limit) { super(contentResolver); + mContext = context.getApplicationContext(); mListener = new WeakReference(listener); mLogLimit = limit; } @@ -125,8 +132,10 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { } public void fetchVoicemailStatus() { - startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null, Status.CONTENT_URI, - VoicemailStatusHelperImpl.PROJECTION, null, null, null); + if (TelecomUtil.hasReadWriteVoicemailPermissions(mContext)) { + startQuery(QUERY_VOICEMAIL_STATUS_TOKEN, null, Status.CONTENT_URI, + VoicemailStatusHelperImpl.PROJECTION, null, null, null); + } } /** Fetches the list of calls in the call log. */ @@ -163,7 +172,7 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit; final String selection = where.length() > 0 ? where.toString() : null; - Uri uri = Calls.CONTENT_URI_WITH_VOICEMAIL.buildUpon() + Uri uri = TelecomUtil.getCallLogUri(mContext).buildUpon() .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit)) .build(); startQuery(token, null, uri, @@ -186,7 +195,7 @@ public class CallLogQueryHandler extends NoNullCursorAsyncQueryHandler { ContentValues values = new ContentValues(1); values.put(Calls.NEW, "0"); - startUpdate(UPDATE_MARK_AS_OLD_TOKEN, null, Calls.CONTENT_URI_WITH_VOICEMAIL, + startUpdate(UPDATE_MARK_AS_OLD_TOKEN, null, TelecomUtil.getCallLogUri(mContext), values, where.toString(), null); } diff --git a/src/com/android/dialer/calllog/CallLogReceiver.java b/src/com/android/dialer/calllog/CallLogReceiver.java index 97d2951c1..fef76086c 100644 --- a/src/com/android/dialer/calllog/CallLogReceiver.java +++ b/src/com/android/dialer/calllog/CallLogReceiver.java @@ -34,15 +34,9 @@ public class CallLogReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (VoicemailContract.ACTION_NEW_VOICEMAIL.equals(intent.getAction())) { - Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); - serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS); - serviceIntent.putExtra( - CallLogNotificationsService.EXTRA_NEW_VOICEMAIL_URI, intent.getData()); - context.startService(serviceIntent); + CallLogNotificationsService.updateVoicemailNotifications(context, intent.getData()); } else if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { - Intent serviceIntent = new Intent(context, CallLogNotificationsService.class); - serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS); - context.startService(serviceIntent); + CallLogNotificationsService.updateVoicemailNotifications(context, null); } else { Log.w(TAG, "onReceive: could not handle: " + intent); } diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java index 8e8aa3ce1..38c9bba87 100644 --- a/src/com/android/dialer/calllog/ContactInfoHelper.java +++ b/src/com/android/dialer/calllog/ContactInfoHelper.java @@ -34,6 +34,7 @@ import com.android.contacts.common.util.PhoneNumberHelper; import com.android.contacts.common.util.UriUtils; import com.android.dialer.service.CachedNumberLookupService; import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo; +import com.android.dialer.util.TelecomUtil; import com.android.dialerbind.ObjectFactory; import org.json.JSONException; @@ -368,13 +369,13 @@ public class ContactInfoHelper { try { if (countryIso == null) { mContext.getContentResolver().update( - Calls.CONTENT_URI_WITH_VOICEMAIL, + TelecomUtil.getCallLogUri(mContext), values, Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " IS NULL", new String[]{ number }); } else { mContext.getContentResolver().update( - Calls.CONTENT_URI_WITH_VOICEMAIL, + TelecomUtil.getCallLogUri(mContext), values, Calls.NUMBER + " = ? AND " + Calls.COUNTRY_ISO + " = ?", new String[]{ number, countryIso }); diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java index 4be2d1c9e..9f5150ac7 100644 --- a/src/com/android/dialer/calllog/IntentProvider.java +++ b/src/com/android/dialer/calllog/IntentProvider.java @@ -31,6 +31,7 @@ import com.android.dialer.CallDetailActivity; import com.android.dialer.DialtactsActivity; import com.android.dialer.PhoneCallDetails; import com.android.dialer.util.CallIntentUtil; +import com.android.dialer.util.TelecomUtil; import java.util.ArrayList; @@ -125,8 +126,8 @@ public abstract class IntentProvider { intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, extraIds); } else { // If there is a single item, use the direct URI for it. - intent.setData(ContentUris.withAppendedId( - Calls.CONTENT_URI_WITH_VOICEMAIL, id)); + intent.setData(ContentUris.withAppendedId(TelecomUtil.getCallLogUri(context), + id)); } return intent; } diff --git a/src/com/android/dialer/util/TelecomUtil.java b/src/com/android/dialer/util/TelecomUtil.java new file mode 100644 index 000000000..1cd270c9b --- /dev/null +++ b/src/com/android/dialer/util/TelecomUtil.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dialer.util; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.provider.CallLog.Calls; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.text.TextUtils; +import android.util.Log; + +public class TelecomUtil { + private static final String TAG = "TelecomUtil"; + private static boolean sWarningLogged = false; + + public static void silenceRinger(Context context) { + if (hasModifyPhoneStatePermission(context)) { + try { + getTelecomManager(context).silenceRinger(); + } catch (SecurityException e) { + // Just in case + Log.w(TAG, "TelecomManager.silenceRinger called without permission."); + } + } + } + + public static void cancelMissedCallsNotification(Context context) { + if (hasModifyPhoneStatePermission(context)) { + try { + getTelecomManager(context).cancelMissedCallsNotification(); + } catch (SecurityException e) { + Log.w(TAG, "TelecomManager.cancelMissedCalls called without permission."); + } + } + } + + public static Uri getAdnUriForPhoneAccount(Context context, PhoneAccountHandle handle) { + if (hasModifyPhoneStatePermission(context)) { + try { + return getTelecomManager(context).getAdnUriForPhoneAccount(handle); + } catch (SecurityException e) { + Log.w(TAG, "TelecomManager.getAdnUriForPhoneAccount called without permission."); + } + } + return null; + } + + public static boolean handleMmi(Context context, String dialString, + PhoneAccountHandle handle) { + if (hasModifyPhoneStatePermission(context)) { + try { + if (handle == null) { + return getTelecomManager(context).handleMmi(dialString); + } else { + return getTelecomManager(context).handleMmi(dialString, handle); + } + } catch (SecurityException e) { + Log.w(TAG, "TelecomManager.handleMmi called without permission."); + } + } + return false; + } + + public static Uri getCallLogUri(Context context) { + return hasReadWriteVoicemailPermissions(context) ? Calls.CONTENT_URI_WITH_VOICEMAIL + : Calls.CONTENT_URI; + } + + public static boolean hasReadWriteVoicemailPermissions(Context context) { + return isDefaultDialer(context) + || (hasPermission(context, Manifest.permission.READ_VOICEMAIL) + && hasPermission(context, Manifest.permission.WRITE_VOICEMAIL)); + } + + public static boolean hasModifyPhoneStatePermission(Context context) { + return isDefaultDialer(context) + || hasPermission(context, Manifest.permission.MODIFY_PHONE_STATE); + } + + private static boolean hasPermission(Context context, String permission) { + return context.checkSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + public static boolean isDefaultDialer(Context context) { + final boolean result = TextUtils.equals(context.getPackageName(), + getTelecomManager(context).getDefaultDialerPackage()); + if (result) { + sWarningLogged = false; + } else { + if (!sWarningLogged) { + // Log only once to prevent spam. + Log.w(TAG, "Dialer is not currently set to be default dialer"); + sWarningLogged = true; + } + } + return result; + } + + private static TelecomManager getTelecomManager(Context context) { + return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + } +} -- cgit v1.2.3