diff options
6 files changed, 353 insertions, 2 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5261019a9b62..bb454a642ff1 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -12001,6 +12001,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); @@ -12095,6 +12096,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<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>); @@ -12270,6 +12272,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<java.lang.String>, @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 e7f89204c1ec..9eaaa91532d0 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; /** @@ -1214,4 +1220,117 @@ public class TelephonyRegistryManager { listenFromCallback(false, false, subId, pkgName, attributionTag, callback, new int[0], notifyNow); } + + private static class CarrierPrivilegesListenerWrapper extends ICarrierPrivilegesListener.Stub + implements ListenerExecutor { + private final WeakReference<CarrierPrivilegesListener> mListener; + private final Executor mExecutor; + + CarrierPrivilegesListenerWrapper(CarrierPrivilegesListener listener, Executor executor) { + mListener = new WeakReference<>(listener); + mExecutor = executor; + } + + @Override + public void onCarrierPrivilegesChanged( + List<String> privilegedPackageNames, int[] privilegedUids) { + Binder.withCleanCallingIdentity( + () -> + executeSafely( + mExecutor, + mListener::get, + cpl -> + cpl.onCarrierPrivilegesChanged( + privilegedPackageNames, privilegedUids))); + } + } + + @GuardedBy("sCarrierPrivilegeListeners") + private static final WeakHashMap< + CarrierPrivilegesListener, WeakReference<CarrierPrivilegesListenerWrapper>> + 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<CarrierPrivilegesListenerWrapper> 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<CarrierPrivilegesListenerWrapper> 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<String> 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 000000000000..6ca8cecba3c8 --- /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<String> 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 15d4246e302a..9712d7e38a4b 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; @@ -100,4 +101,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<LinkCapacityEstimate> linkCapacityEstimateList); + + void addCarrierPrivilegesListener( + int phoneId, ICarrierPrivilegesListener callback, String pkg, String featureId); + void removeCarrierPrivilegesListener(ICarrierPrivilegesListener callback, String pkg); + void notifyCarrierPrivilegesChanged( + int phoneId, in List<String> 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 d7c1cfb7d1ed..811f2f5e5283 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; @@ -173,6 +176,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return (onOpportunisticSubscriptionsChangedListenerCallback != null); } + boolean matchCarrierPrivilegesListener() { + return carrierPrivilegesListener != null; + } + boolean canReadCallLog() { try { return TelephonyPermissions.checkReadCallLog( @@ -189,8 +196,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 + "}"; } } @@ -402,6 +410,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { */ private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>> mPreciseDataConnectionStates; + + /** Per-phoneId snapshot of privileged packages (names + UIDs). */ + private List<Pair<List<String>, int[]>> mCarrierPrivilegeStates; + /** * Support backward compatibility for {@link android.telephony.TelephonyDisplayInfo}. */ @@ -689,6 +701,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { cutListToSize(mBarringInfo, mNumPhones); cutListToSize(mPhysicalChannelConfigs, mNumPhones); cutListToSize(mLinkCapacityEstimateLists, mNumPhones); + cutListToSize(mCarrierPrivilegeStates, mNumPhones); return; } @@ -729,6 +742,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])); } } @@ -794,6 +808,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; @@ -831,6 +846,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); @@ -2766,6 +2782,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<List<String>, 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<String> privilegedPackageNames, int[] privilegedUids) { + if (!checkNotifyPermission("notifyCarrierPrivilegesChanged")) { + return; + } + if (!validatePhoneId(phoneId)) return; + if (VDBG) { + log( + "notifyCarrierPrivilegesChanged: phoneId=" + phoneId + + ", <packages=" + pii(privilegedPackageNames) + + ", uids=" + Arrays.toString(privilegedUids) + ">"); + } + 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, " "); @@ -2814,6 +2928,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<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i); + pw.println( + "mCarrierPrivilegeState=<packages=" + pii(carrierPrivilegeState.first) + + ", uids=" + Arrays.toString(carrierPrivilegeState.second) + ">"); pw.decreaseIndent(); } @@ -3540,4 +3659,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<String> 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 29ffcd12a181..30cb8ca64ad3 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -16028,4 +16028,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. + * + * <p>Of note, this callback will <b>not</b> be fired if a carrier triggers a SIM profile + * switch and the same set of packages remains privileged after the switch. + * + * <p>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<String> 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); + } } |