From f955e56d51dac0fade192b544af5679d3d8193ac Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Thu, 26 Apr 2018 14:43:31 -0700 Subject: [PATCH] Require READ_CALL_LOG permission to see phone numbers in phone state. Incoming and outgoing call phone numbers are visible in the phone state broadcast and via the PhoneStateListener. To enhance user privacy, change to require the READ_CALL_LOG permission in order to receive the call phone numbers. This means to see phone numbers: 1. android.intent.action.PHONE_STATE - requires READ_PHONE_STATE and READ_CALL_LOG permission. 2. PhoneStateListener#onCallStateChanged - now required READ_CALL_LOG permission. To support this new behavior, added sendBroadcastAsUserMultiplePermissions method to context to allow sending the broadcast to all users while requiring the two permissions. Bug: 78650469 Test: Created PHONE_STATE broadcast receiver in test app and verified that when no permissions are granted, the phone number is empty for incoming and outgoing calls. Test: Granted Phone state permission to test app and verified that phone number is not populated. Test: Granted test app read call log permission and verified that phone number is populated. Test: Created PhoneStateListener in test app and verified that when no permissions are granted, phone number is empty for incoming and outgoing. calls. Test: Granted read call log permission to test app and verified that both the incoming and outgoing numbers are populated. Change-Id: I857ea00cc58a0abbb77960643f361dd6dd9c8b56 --- core/java/android/app/ContextImpl.java | 16 ++++++++ core/java/android/content/Context.java | 27 ++++++++++++ core/java/android/content/ContextWrapper.java | 7 ++++ .../com/android/server/TelephonyRegistry.java | 34 ++++++++------- .../server/devicepolicy/DpmMockContext.java | 6 +++ .../android/telephony/PhoneStateListener.java | 2 +- .../android/telephony/TelephonyManager.java | 10 +++-- .../telephony/TelephonyPermissions.java | 41 ++++++++++++++++++- .../src/android/test/mock/MockContext.java | 7 ++++ .../test/BroadcastInterceptingContext.java | 6 +++ 10 files changed, 135 insertions(+), 21 deletions(-) diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 9a491bc382809..95117862a8da8 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1047,6 +1047,22 @@ class ContextImpl extends Context { } } + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + warnIfCallingFromSystemProcess(); + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.prepareToLeaveProcess(this); + ActivityManager.getService().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE, + null, false, false, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + @Override public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) { warnIfCallingFromSystemProcess(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index ede7ee4b9a6b7..90a94ee76085b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1974,6 +1974,33 @@ public abstract class Context { public abstract void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions); + /** + * Broadcast the given intent to all interested BroadcastReceivers, allowing + * an array of required permissions to be enforced. This call is asynchronous; it returns + * immediately, and you will continue executing while the receivers are run. No results are + * propagated from receivers and receivers can not abort the broadcast. If you want to allow + * receivers to propagate results or abort the broadcast, you must send an ordered broadcast + * using {@link #sendOrderedBroadcast(Intent, String)}. + * + *

See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param user The user to send the broadcast to. + * @param receiverPermissions Array of names of permissions that a receiver must hold + * in order to receive your broadcast. + * If null or empty, no permissions are required. + * + * @see android.content.BroadcastReceiver + * @see #registerReceiver + * @see #sendBroadcast(Intent) + * @see #sendOrderedBroadcast(Intent, String) + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + * @hide + */ + public abstract void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions); + /** * Broadcast the given intent to all interested BroadcastReceivers, allowing * an optional required permission to be enforced. This diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 1867a6d879c72..bae99b85d6b83 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -455,6 +455,13 @@ public class ContextWrapper extends Context { mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions); } + /** @hide */ + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions); + } + /** @hide */ @SystemApi @Override diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 60d11d7e6f4b9..02222cc6f963d 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -117,10 +117,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { return (onSubscriptionsChangedListenerCallback != null); } - boolean canReadPhoneState() { + boolean canReadCallLog() { try { - return TelephonyPermissions.checkReadPhoneState( - context, subId, callerPid, callerUid, callingPackage, "listen"); + return TelephonyPermissions.checkReadCallLog( + context, subId, callerPid, callerUid, callingPackage); } catch (SecurityException e) { return false; } @@ -667,8 +667,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } private String getCallIncomingNumber(Record record, int phoneId) { - // Hide the number if record's process can't currently read phone state. - return record.canReadPhoneState() ? mCallIncomingNumber[phoneId] : ""; + // Only reveal the incoming number if the record has read call log permission. + return record.canReadCallLog() ? mCallIncomingNumber[phoneId] : ""; } private Record add(IBinder binder) { @@ -729,13 +729,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - public void notifyCallState(int state, String incomingNumber) { + public void notifyCallState(int state, String phoneNumber) { if (!checkNotifyPermission("notifyCallState()")) { return; } if (VDBG) { - log("notifyCallState: state=" + state + " incomingNumber=" + incomingNumber); + log("notifyCallState: state=" + state + " phoneNumber=" + phoneNumber); } synchronized (mRecords) { @@ -743,8 +743,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_CALL_STATE) && (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)) { try { - String incomingNumberOrEmpty = r.canReadPhoneState() ? incomingNumber : ""; - r.callback.onCallStateChanged(state, incomingNumberOrEmpty); + // Ensure the listener has read call log permission; if they do not return + // an empty phone number. + String phoneNumberOrEmpty = r.canReadCallLog() ? phoneNumber : ""; + r.callback.onCallStateChanged(state, phoneNumberOrEmpty); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -755,7 +757,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { // Called only by Telecomm to communicate call state across different phone accounts. So // there is no need to add a valid subId or slotId. - broadcastCallStateChanged(state, incomingNumber, + broadcastCallStateChanged(state, phoneNumber, SubscriptionManager.INVALID_PHONE_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); } @@ -1571,9 +1573,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); intent.putExtra(PhoneConstants.STATE_KEY, PhoneConstantConversions.convertCallState(state).toString()); - if (!TextUtils.isEmpty(incomingNumber)) { - intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber); - } // If a valid subId was specified, we should fire off a subId-specific state // change intent and include the subId. @@ -1589,13 +1588,20 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { // Wakeup apps for the (SUBSCRIPTION_)PHONE_STATE broadcast. intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + Intent intentWithPhoneNumber = new Intent(intent); + if (!TextUtils.isEmpty(incomingNumber)) { + intentWithPhoneNumber.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber); + } // Send broadcast twice, once for apps that have PRIVILEGED permission and once for those // that have the runtime one - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + mContext.sendBroadcastAsUser(intentWithPhoneNumber, UserHandle.ALL, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, android.Manifest.permission.READ_PHONE_STATE, AppOpsManager.OP_READ_PHONE_STATE); + mContext.sendBroadcastAsUserMultiplePermissions(intentWithPhoneNumber, UserHandle.ALL, + new String[] { android.Manifest.permission.READ_PHONE_STATE, + android.Manifest.permission.READ_CALL_LOG}); } private void broadcastDataConnectionStateChanged(int state, diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index e2ba4d5f4aa8d..213961c1e1d93 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -253,6 +253,12 @@ public class DpmMockContext extends MockContext { spiedContext.sendBroadcastMultiplePermissions(intent, receiverPermissions); } + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + spiedContext.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions); + } + @Override public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) { spiedContext.sendBroadcast(intent, receiverPermission, options); diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index c16701ba0f351..da75e04b77245 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -443,7 +443,7 @@ public class PhoneStateListener { * Callback invoked when device call state changes. * @param state call state * @param phoneNumber call phone number. If application does not have - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} permission or carrier + * {@link android.Manifest.permission#READ_CALL_LOG READ_CALL_LOG} permission or carrier * privileges (see {@link TelephonyManager#hasCarrierPrivileges}), an empty string will be * passed as an argument. * diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c850004a54d8c..ff135d59fc538 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -334,10 +334,12 @@ public class TelephonyManager { * *

* The {@link #EXTRA_STATE} extra indicates the new call state. - * If the new state is RINGING, a second extra - * {@link #EXTRA_INCOMING_NUMBER} provides the incoming phone number as - * a String. - * + * If a receiving app has {@link android.Manifest.permission#READ_CALL_LOG} permission, a second + * extra {@link #EXTRA_INCOMING_NUMBER} provides the phone number for incoming and outoing calls + * as a String. Note: If the receiving app has + * {@link android.Manifest.permission#READ_CALL_LOG} and + * {@link android.Manifest.permission#READ_PHONE_STATE} permission, it will receive the + * broadcast twice; one with the phone number and another without it. *

* This was a {@link android.content.Context#sendStickyBroadcast sticky} * broadcast in version 1.0, but it is no longer sticky. diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index a182f2be421fc..bbe38b7f709a9 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java @@ -15,6 +15,9 @@ */ package com.android.internal.telephony; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.Manifest; import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; @@ -75,7 +78,7 @@ public final class TelephonyPermissions { /** * Check whether the app with the given pid/uid can read phone state. * - *

This method behaves in one of the following ways: + *

This method behaves in one of the following ways: *