diff options
10 files changed, 135 insertions, 21 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 9a491bc38280..95117862a8da 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1048,6 +1048,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(); String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 64998a38ef55..133f339499fb 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1977,6 +1977,33 @@ public abstract class Context { /** * 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 * call is asynchronous; it returns immediately, and you will continue * executing while the receivers are run. No results are propagated from diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 1867a6d879c7..bae99b85d6b8 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -456,6 +456,13 @@ public class ContextWrapper extends Context { } /** @hide */ + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions); + } + + /** @hide */ @SystemApi @Override public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) { diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 41f413d5f07d..607db4efb63e 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 e2ba4d5f4aa8..213961c1e1d9 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -254,6 +254,12 @@ public class DpmMockContext extends MockContext { } @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 88bed4ed41e7..842016511d39 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -453,7 +453,7 @@ public class PhoneStateListener { * * @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 43ec716677e3..512ffb112884 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -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. diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java index a182f2be421f..bbe38b7f709a 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. * - * <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. @@ -132,6 +135,40 @@ public final class TelephonyPermissions { } /** + * 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. * * <p>Besides apps with the ability to read phone state per {@link #checkReadPhoneState}, the @@ -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; } diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index 4dfd0507f351..9d260ebf7231 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -364,6 +364,13 @@ public class MockContext extends Context { } /** @hide */ + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + throw new UnsupportedOperationException(); + } + + /** @hide */ @SystemApi @Override public void sendBroadcast(Intent intent, String receiverPermission, Bundle options) { diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index 21662407db42..25bd7c06be49 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -175,6 +175,12 @@ public class BroadcastInterceptingContext extends ContextWrapper { } @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + sendBroadcast(intent); + } + + @Override public void sendBroadcastAsUser(Intent intent, UserHandle user) { sendBroadcast(intent); } |