From 866d3a4d038671e8f822d3dfe92cb31ed636798a Mon Sep 17 00:00:00 2001 From: Hunter Knepshield Date: Fri, 3 Dec 2021 17:54:05 -0800 Subject: [PATCH] Create CarrierPrivilegesListener callback. This callback will allow privileged components to listen to changes around carrier privileged apps in a way that is aware and accounts for SIM profile switches. This callback will be backed by CarrierPrivilegesTracker as a follow-up change. Bug: 205736323 Test: atest android.telephony.cts.TelephonyRegistryManagerTest Change-Id: I432ed00be16c68303bf5c8e074fb9d6cf83e82c9 --- core/api/system-current.txt | 6 + .../telephony/TelephonyRegistryManager.java | 119 ++++++++++++++++ .../telephony/ICarrierPrivilegesListener.aidl | 22 +++ .../telephony/ITelephonyRegistry.aidl | 7 + .../com/android/server/TelephonyRegistry.java | 129 +++++++++++++++++- .../android/telephony/TelephonyManager.java | 72 ++++++++++ 6 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cd1fef9d18ba7..bc31a7b5ca6bd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11992,6 +11992,7 @@ package android.telephony { } public class TelephonyManager { + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addCarrierPrivilegesListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) @WorkerThread public void bootstrapAuthenticationRequest(int, @NonNull android.net.Uri, @NonNull android.telephony.gba.UaSecurityProtocolIdentifier, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.BootstrapAuthenticationCallback); method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String); @@ -12084,6 +12085,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); + method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); @@ -12259,6 +12261,10 @@ package android.telephony { field public static final int RESULT_SUCCESS = 0; // 0x0 } + public static interface TelephonyManager.CarrierPrivilegesListener { + method public void onCarrierPrivilegesChanged(@NonNull java.util.List, @NonNull int[]); + } + public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception { method public int getErrorCode(); field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2 diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index cb1cff9cda22e..2d059823f8042 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -36,18 +36,24 @@ import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SimActivationState; import android.telephony.Annotation.SrvccState; +import android.telephony.TelephonyManager.CarrierPrivilegesListener; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.listeners.ListenerExecutor; +import com.android.internal.telephony.ICarrierPrivilegesListener; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.ITelephonyRegistry; +import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.Executor; /** @@ -1181,4 +1187,117 @@ public class TelephonyRegistryManager { @NonNull TelephonyCallback callback, boolean notifyNow) { listenFromCallback(subId, pkgName, attributionTag, callback, new int[0], notifyNow); } + + private static class CarrierPrivilegesListenerWrapper extends ICarrierPrivilegesListener.Stub + implements ListenerExecutor { + private final WeakReference mListener; + private final Executor mExecutor; + + CarrierPrivilegesListenerWrapper(CarrierPrivilegesListener listener, Executor executor) { + mListener = new WeakReference<>(listener); + mExecutor = executor; + } + + @Override + public void onCarrierPrivilegesChanged( + List privilegedPackageNames, int[] privilegedUids) { + Binder.withCleanCallingIdentity( + () -> + executeSafely( + mExecutor, + mListener::get, + cpl -> + cpl.onCarrierPrivilegesChanged( + privilegedPackageNames, privilegedUids))); + } + } + + @GuardedBy("sCarrierPrivilegeListeners") + private static final WeakHashMap< + CarrierPrivilegesListener, WeakReference> + sCarrierPrivilegeListeners = new WeakHashMap<>(); + + /** + * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to + * receive callbacks when the set of packages with carrier privileges changes. The callback will + * immediately be called with the latest state. + * + * @param logicalSlotIndex The SIM slot to listen on + * @param executor The executor where {@code listener} will be invoked + * @param listener The callback to register + */ + public void addCarrierPrivilegesListener( + int logicalSlotIndex, + @NonNull @CallbackExecutor Executor executor, + @NonNull CarrierPrivilegesListener listener) { + if (listener == null || executor == null) { + throw new IllegalArgumentException("listener and executor must be non-null"); + } + synchronized (sCarrierPrivilegeListeners) { + WeakReference existing = + sCarrierPrivilegeListeners.get(listener); + if (existing != null && existing.get() != null) { + Log.d(TAG, "addCarrierPrivilegesListener: listener already registered"); + return; + } + CarrierPrivilegesListenerWrapper wrapper = + new CarrierPrivilegesListenerWrapper(listener, executor); + sCarrierPrivilegeListeners.put(listener, new WeakReference<>(wrapper)); + try { + sRegistry.addCarrierPrivilegesListener( + logicalSlotIndex, + wrapper, + mContext.getOpPackageName(), + mContext.getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Unregisters a {@link CarrierPrivilegesListener}. + * + * @param listener The callback to unregister + */ + public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener must be non-null"); + } + synchronized (sCarrierPrivilegeListeners) { + WeakReference ref = + sCarrierPrivilegeListeners.remove(listener); + if (ref == null) return; + CarrierPrivilegesListenerWrapper wrapper = ref.get(); + if (wrapper == null) return; + try { + sRegistry.removeCarrierPrivilegesListener(wrapper, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Notify listeners that the set of packages with carrier privileges has changed. + * + * @param logicalSlotIndex The SIM slot the change occurred on + * @param privilegedPackageNames The updated set of packages names with carrier privileges + * @param privilegedUids The updated set of UIDs with carrier privileges + */ + public void notifyCarrierPrivilegesChanged( + int logicalSlotIndex, + @NonNull List privilegedPackageNames, + @NonNull int[] privilegedUids) { + if (privilegedPackageNames == null || privilegedUids == null) { + throw new IllegalArgumentException( + "privilegedPackageNames and privilegedUids must be non-null"); + } + try { + sRegistry.notifyCarrierPrivilegesChanged( + logicalSlotIndex, privilegedPackageNames, privilegedUids); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl new file mode 100644 index 0000000000000..6ca8cecba3c85 --- /dev/null +++ b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 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.internal.telephony; + +oneway interface ICarrierPrivilegesListener { + void onCarrierPrivilegesChanged( + in List privilegedPackageNames, in int[] privilegedUids); +} diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index a0a0f3276b99e..f24a5828a9baa 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -32,6 +32,7 @@ import android.telephony.PreciseDataConnectionState; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; +import com.android.internal.telephony.ICarrierPrivilegesListener; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.IOnSubscriptionsChangedListener; @@ -98,4 +99,10 @@ interface ITelephonyRegistry { void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType); void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId, in List linkCapacityEstimateList); + + void addCarrierPrivilegesListener( + int phoneId, ICarrierPrivilegesListener callback, String pkg, String featureId); + void removeCarrierPrivilegesListener(ICarrierPrivilegesListener callback, String pkg); + void notifyCarrierPrivilegesChanged( + int phoneId, in List privilegedPackageNames, in int[] privilegedUids); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 5ed6c86bdc189..ff0d2fae3abf3 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -91,6 +91,7 @@ import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.ICarrierPrivilegesListener; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.ITelephonyRegistry; @@ -106,6 +107,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -149,6 +151,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { IPhoneStateListener callback; IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback; + ICarrierPrivilegesListener carrierPrivilegesListener; int callerUid; int callerPid; @@ -171,6 +174,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return (onOpportunisticSubscriptionsChangedListenerCallback != null); } + boolean matchCarrierPrivilegesListener() { + return carrierPrivilegesListener != null; + } + boolean canReadCallLog() { try { return TelephonyPermissions.checkReadCallLog( @@ -187,8 +194,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + " onSubscriptionsChangedListenererCallback=" + onSubscriptionsChangedListenerCallback + " onOpportunisticSubscriptionsChangedListenererCallback=" - + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId - + " phoneId=" + phoneId + " events=" + eventList + "}"; + + onOpportunisticSubscriptionsChangedListenerCallback + + " carrierPrivilegesListener=" + carrierPrivilegesListener + + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}"; } } @@ -400,6 +408,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { */ private List, PreciseDataConnectionState>> mPreciseDataConnectionStates; + + /** Per-phoneId snapshot of privileged packages (names + UIDs). */ + private List, int[]>> mCarrierPrivilegeStates; + /** * Support backward compatibility for {@link android.telephony.TelephonyDisplayInfo}. */ @@ -686,6 +698,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { cutListToSize(mBarringInfo, mNumPhones); cutListToSize(mPhysicalChannelConfigs, mNumPhones); cutListToSize(mLinkCapacityEstimateLists, mNumPhones); + cutListToSize(mCarrierPrivilegeStates, mNumPhones); return; } @@ -725,6 +738,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mAllowedNetworkTypeReason[i] = -1; mAllowedNetworkTypeValue[i] = -1; mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST); + mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0])); } } @@ -789,6 +803,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mIsDataEnabled = new boolean[numPhones]; mDataEnabledReason = new int[numPhones]; mLinkCapacityEstimateLists = new ArrayList<>(); + mCarrierPrivilegeStates = new ArrayList<>(); for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; @@ -825,6 +840,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mAllowedNetworkTypeReason[i] = -1; mAllowedNetworkTypeValue[i] = -1; mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST); + mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0])); } mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -2731,6 +2747,104 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + @Override + public void addCarrierPrivilegesListener( + int phoneId, + ICarrierPrivilegesListener callback, + String callingPackage, + String callingFeatureId) { + int callerUserId = UserHandle.getCallingUserId(); + mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + "addCarrierPrivilegesListener"); + if (VDBG) { + log( + "listen carrier privs: E pkg=" + pii(callingPackage) + " phoneId=" + phoneId + + " uid=" + Binder.getCallingUid() + + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId + + " callback=" + callback + + " callback.asBinder=" + callback.asBinder()); + } + if (!validatePhoneId(phoneId)) { + throw new IllegalArgumentException("Invalid slot index: " + phoneId); + } + + synchronized (mRecords) { + Record r = add( + callback.asBinder(), Binder.getCallingUid(), Binder.getCallingPid(), false); + + if (r == null) return; + + r.context = mContext; + r.carrierPrivilegesListener = callback; + r.callingPackage = callingPackage; + r.callingFeatureId = callingFeatureId; + r.callerUid = Binder.getCallingUid(); + r.callerPid = Binder.getCallingPid(); + r.phoneId = phoneId; + r.eventList = new ArraySet<>(); + if (DBG) { + log("listen carrier privs: Register r=" + r); + } + + Pair, int[]> state = mCarrierPrivilegeStates.get(phoneId); + try { + r.carrierPrivilegesListener.onCarrierPrivilegesChanged( + Collections.unmodifiableList(state.first), + Arrays.copyOf(state.second, state.second.length)); + } catch (RemoteException ex) { + remove(r.binder); + } + } + } + + @Override + public void removeCarrierPrivilegesListener( + ICarrierPrivilegesListener callback, String callingPackage) { + mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + "removeCarrierPrivilegesListener"); + remove(callback.asBinder()); + } + + @Override + public void notifyCarrierPrivilegesChanged( + int phoneId, List privilegedPackageNames, int[] privilegedUids) { + if (!checkNotifyPermission("notifyCarrierPrivilegesChanged")) { + return; + } + if (!validatePhoneId(phoneId)) return; + if (VDBG) { + log( + "notifyCarrierPrivilegesChanged: phoneId=" + phoneId + + ", "); + } + synchronized (mRecords) { + mCarrierPrivilegeStates.set( + phoneId, new Pair<>(privilegedPackageNames, privilegedUids)); + for (Record r : mRecords) { + // Listeners are per-slot, not per-subscription. This is to provide a stable + // view across SIM profile switches. + if (!r.matchCarrierPrivilegesListener() + || !idMatch(r, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phoneId)) { + continue; + } + try { + // Make sure even in-process listeners can't modify the values. + r.carrierPrivilegesListener.onCarrierPrivilegesChanged( + Collections.unmodifiableList(privilegedPackageNames), + Arrays.copyOf(privilegedUids, privilegedUids.length)); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } + } + handleRemoveListLocked(); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -2779,6 +2893,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]); pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i)); pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i)); + // We need to obfuscate package names, and primitive arrays' native toString is ugly + Pair, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i); + pw.println( + "mCarrierPrivilegeState="); pw.decreaseIndent(); } pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); @@ -3499,4 +3618,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private static String pii(String packageName) { return Build.IS_DEBUGGABLE ? packageName : "***"; } + + /** Redacts an entire list of package names if necessary. */ + private static String pii(List packageNames) { + if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString(); + return "[***, size=" + packageNames.size() + "]"; + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index a2efc962ed9ca..33b7e3e3a5f09 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15832,4 +15832,76 @@ public class TelephonyManager { } return null; } + + /** + * Callback to listen for when the set of packages with carrier privileges for a SIM changes. + * + * @hide + */ + @SystemApi + public interface CarrierPrivilegesListener { + /** + * Called when the set of packages with carrier privileges has changed. + * + *

Of note, this callback will not be fired if a carrier triggers a SIM profile + * switch and the same set of packages remains privileged after the switch. + * + *

At registration, the callback will receive the current set of privileged packages. + * + * @param privilegedPackageNames The updated set of package names that have carrier + * privileges + * @param privilegedUids The updated set of UIDs that have carrier privileges + */ + void onCarrierPrivilegesChanged( + @NonNull List privilegedPackageNames, @NonNull int[] privilegedUids); + } + + /** + * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to + * receive callbacks when the set of packages with carrier privileges changes. The callback will + * immediately be called with the latest state. + * + * @param logicalSlotIndex The SIM slot to listen on + * @param executor The executor where {@code listener} will be invoked + * @param listener The callback to register + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void addCarrierPrivilegesListener( + int logicalSlotIndex, + @NonNull @CallbackExecutor Executor executor, + @NonNull CarrierPrivilegesListener listener) { + if (mContext == null) { + throw new IllegalStateException("Telephony service is null"); + } else if (executor == null || listener == null) { + throw new IllegalArgumentException( + "CarrierPrivilegesListener and executor must be non-null"); + } + mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class); + if (mTelephonyRegistryMgr == null) { + throw new IllegalStateException("Telephony registry service is null"); + } + mTelephonyRegistryMgr.addCarrierPrivilegesListener(logicalSlotIndex, executor, listener); + } + + /** + * Unregisters an existing {@link CarrierPrivilegesListener}. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) { + if (mContext == null) { + throw new IllegalStateException("Telephony service is null"); + } else if (listener == null) { + throw new IllegalArgumentException("CarrierPrivilegesListener must be non-null"); + } + mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class); + if (mTelephonyRegistryMgr == null) { + throw new IllegalStateException("Telephony registry service is null"); + } + mTelephonyRegistryMgr.removeCarrierPrivilegesListener(listener); + } }