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
This commit is contained in:
Tyler Gunn
2018-04-26 14:43:31 -07:00
parent fd935d3a12
commit f955e56d51
10 changed files with 135 additions and 21 deletions

View File

@@ -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();

View File

@@ -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)}.
*
* <p>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

View File

@@ -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

View File

@@ -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,

View File

@@ -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);

View File

@@ -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.
*

View File

@@ -334,10 +334,12 @@ public class TelephonyManager {
*
* <p>
* 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.
* <p class="note">
* This was a {@link android.content.Context#sendStickyBroadcast sticky}
* broadcast in version 1.0, but it is no longer sticky.

View File

@@ -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.
*
* <p>This method behaves in one of the following ways:
* <p>This method behaves in one of the following ways:
* <ul>
* <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the
* READ_PHONE_STATE runtime permission, or carrier privileges on the given subId.
@@ -131,6 +134,40 @@ public final class TelephonyPermissions {
AppOpsManager.MODE_ALLOWED;
}
/**
* Check whether the app with the given pid/uid can read the call log.
* @return {@code true} if the specified app has the read call log permission and AppOpp granted
* to it, {@code false} otherwise.
*/
public static boolean checkReadCallLog(
Context context, int subId, int pid, int uid, String callingPackage) {
return checkReadCallLog(
context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage);
}
@VisibleForTesting
public static boolean checkReadCallLog(
Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
String callingPackage) {
if (context.checkPermission(Manifest.permission.READ_CALL_LOG, pid, uid)
!= PERMISSION_GRANTED) {
// If we don't have the runtime permission, but do have carrier privileges, that
// suffices for being able to see the call phone numbers.
if (SubscriptionManager.isValidSubscriptionId(subId)) {
enforceCarrierPrivilege(telephonySupplier, subId, uid, "readCallLog");
return true;
}
return false;
}
// We have READ_CALL_LOG permission, so return true as long as the AppOps bit hasn't been
// revoked.
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
return appOps.noteOp(AppOpsManager.OP_READ_CALL_LOG, uid, callingPackage) ==
AppOpsManager.MODE_ALLOWED;
}
/**
* Returns whether the caller can read phone numbers.
*
@@ -204,7 +241,7 @@ public final class TelephonyPermissions {
public static void enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
Context context, int subId, String message) {
if (context.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) ==
PackageManager.PERMISSION_GRANTED) {
PERMISSION_GRANTED) {
return;
}

View File

@@ -363,6 +363,13 @@ public class MockContext extends Context {
throw new UnsupportedOperationException();
}
/** @hide */
@Override
public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
String[] receiverPermissions) {
throw new UnsupportedOperationException();
}
/** @hide */
@SystemApi
@Override

View File

@@ -174,6 +174,12 @@ public class BroadcastInterceptingContext extends ContextWrapper {
sendBroadcast(intent);
}
@Override
public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
String[] receiverPermissions) {
sendBroadcast(intent);
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user) {
sendBroadcast(intent);