diff --git a/core/api/current.txt b/core/api/current.txt index c381b16dc414..f2a48c0fd2c2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -41276,6 +41276,8 @@ package android.telephony { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public android.os.PersistableBundle getConfigForSubId(int, @NonNull java.lang.String...); method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int); + method public void registerCarrierConfigChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener); + method public void unregisterCarrierConfigChangeListener(@NonNull android.telephony.CarrierConfigManager.CarrierConfigChangeListener); field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1 field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2 @@ -41590,6 +41592,10 @@ package android.telephony { field public static final String KEY_PREFIX = "bsf."; } + public static interface CarrierConfigManager.CarrierConfigChangeListener { + method public void onCarrierConfigChanged(int, int, int, int); + } + public static final class CarrierConfigManager.Gps { field public static final String KEY_PERSIST_LPP_MODE_BOOL = "gps.persist_lpp_mode_bool"; field public static final String KEY_PREFIX = "gps."; diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index a3696e398668..4bcdf0d0d50d 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -45,6 +45,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.listeners.ListenerExecutor; +import com.android.internal.telephony.ICarrierConfigChangeListener; import com.android.internal.telephony.ICarrierPrivilegesCallback; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.ITelephonyRegistry; @@ -54,8 +55,10 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -89,6 +92,14 @@ public class TelephonyRegistryManager { IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap = new HashMap<>(); + /** + * A mapping between {@link CarrierConfigManager.CarrierConfigChangeListener} and its callback + * ICarrierConfigChangeListener. + */ + private final ConcurrentHashMap + mCarrierConfigChangeListenerMap = new ConcurrentHashMap<>(); + /** @hide **/ public TelephonyRegistryManager(@NonNull Context context) { @@ -1409,4 +1420,94 @@ public class TelephonyRegistryManager { throw e.rethrowFromSystemServer(); } } + + /** + * Register a {@link android.telephony.CarrierConfigManager.CarrierConfigChangeListener} to get + * notification when carrier configurations have changed. + * + * @param executor The executor on which the callback will be executed. + * @param listener The CarrierConfigChangeListener to be registered with. + */ + public void addCarrierConfigChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) { + Objects.requireNonNull(executor, "Executor should be non-null."); + Objects.requireNonNull(listener, "Listener should be non-null."); + if (mCarrierConfigChangeListenerMap.get(listener) != null) { + Log.e(TAG, "registerCarrierConfigChangeListener: listener already present"); + return; + } + + ICarrierConfigChangeListener callback = new ICarrierConfigChangeListener.Stub() { + @Override + public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, + int specificCarrierId) { + Log.d(TAG, "onCarrierConfigChanged call in ICarrierConfigChangeListener callback"); + final long identify = Binder.clearCallingIdentity(); + try { + executor.execute(() -> listener.onCarrierConfigChanged(slotIndex, subId, + carrierId, specificCarrierId)); + } finally { + Binder.restoreCallingIdentity(identify); + } + } + }; + + try { + sRegistry.addCarrierConfigChangeListener(callback, + mContext.getOpPackageName(), mContext.getAttributionTag()); + mCarrierConfigChangeListenerMap.put(listener, callback); + } catch (RemoteException re) { + // system server crashes + throw re.rethrowFromSystemServer(); + } + } + + /** + * Unregister to stop the notification when carrier configurations changed. + * + * @param listener The CarrierConfigChangeListener to be unregistered with. + */ + public void removeCarrierConfigChangedListener( + @NonNull CarrierConfigManager.CarrierConfigChangeListener listener) { + Objects.requireNonNull(listener, "Listener should be non-null."); + if (mCarrierConfigChangeListenerMap.get(listener) == null) { + Log.e(TAG, "removeCarrierConfigChangedListener: listener was not present"); + return; + } + + try { + sRegistry.removeCarrierConfigChangeListener( + mCarrierConfigChangeListenerMap.get(listener), mContext.getOpPackageName()); + mCarrierConfigChangeListenerMap.remove(listener); + } catch (RemoteException re) { + // System sever crashes + throw re.rethrowFromSystemServer(); + } + } + + /** + * Notify the registrants the carrier configurations have changed. + * + * @param slotIndex The SIM slot index on which to monitor and get notification. + * @param subId The subscription on the SIM slot. May be + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * @param carrierId The optional carrier Id, may be + * {@link TelephonyManager#UNKNOWN_CARRIER_ID}. + * @param specificCarrierId The optional specific carrier Id, may be {@link + * TelephonyManager#UNKNOWN_CARRIER_ID}. + */ + public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId, + int specificCarrierId) { + // Only validate slotIndex, all others are optional and allowed to be invalid + if (!SubscriptionManager.isValidPhoneId(slotIndex)) { + Log.e(TAG, "notifyCarrierConfigChanged, ignored: invalid slotIndex " + slotIndex); + return; + } + try { + sRegistry.notifyCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl new file mode 100644 index 000000000000..0f7ab0a3d4fb --- /dev/null +++ b/core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 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 ICarrierConfigChangeListener { + void onCarrierConfigChanged(int slotIndex, int subId, int carrierId, int specificCarrierId); +} \ No newline at end of file diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index c7fa757ac0b7..747c40df9492 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.ICarrierConfigChangeListener; import com.android.internal.telephony.ICarrierPrivilegesCallback; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.IOnSubscriptionsChangedListener; @@ -109,4 +110,8 @@ interface ITelephonyRegistry { int phoneId, in List privilegedPackageNames, in int[] privilegedUids); void notifyCarrierServiceChanged(int phoneId, in String packageName, int uid); + void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener, + String pkg, String featureId); + void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener, String pkg); + void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId, int specificCarrierId); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index ca86021cd629..a69d3f0276f3 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.ICarrierConfigChangeListener; import com.android.internal.telephony.ICarrierPrivilegesCallback; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.IPhoneStateListener; @@ -154,6 +155,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback; ICarrierPrivilegesCallback carrierPrivilegesCallback; + ICarrierConfigChangeListener carrierConfigChangeListener; int callerUid; int callerPid; @@ -182,6 +184,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return carrierPrivilegesCallback != null; } + boolean matchCarrierConfigChangeListener() { + return carrierConfigChangeListener != null; + } + boolean canReadCallLog() { try { return TelephonyPermissions.checkReadCallLog( @@ -200,6 +206,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + " onOpportunisticSubscriptionsChangedListenererCallback=" + onOpportunisticSubscriptionsChangedListenerCallback + " carrierPrivilegesCallback=" + carrierPrivilegesCallback + + " carrierConfigChangeListener=" + carrierConfigChangeListener + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}"; } } @@ -2955,6 +2962,82 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } + @Override + public void addCarrierConfigChangeListener(ICarrierConfigChangeListener listener, + String pkg, String featureId) { + final int callerUserId = UserHandle.getCallingUserId(); + mAppOps.checkPackage(Binder.getCallingUid(), pkg); + if (VDBG) { + log("addCarrierConfigChangeListener pkg=" + pii(pkg) + " uid=" + Binder.getCallingUid() + + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId + + " listener=" + listener + " listener.asBinder=" + listener.asBinder()); + } + + synchronized (mRecords) { + IBinder b = listener.asBinder(); + boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(), + Process.myUid()); + Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply); + + if (r == null) { + loge("Can not create Record instance!"); + return; + } + + r.context = mContext; + r.carrierConfigChangeListener = listener; + r.callingPackage = pkg; + r.callingFeatureId = featureId; + r.callerUid = Binder.getCallingUid(); + r.callerPid = Binder.getCallingPid(); + r.eventList = new ArraySet<>(); + if (DBG) { + log("addCarrierConfigChangeListener: Register r=" + r); + } + } + } + + @Override + public void removeCarrierConfigChangeListener(ICarrierConfigChangeListener listener, + String pkg) { + if (DBG) log("removeCarrierConfigChangeListener listener=" + listener + ", pkg=" + pkg); + mAppOps.checkPackage(Binder.getCallingUid(), pkg); + remove(listener.asBinder()); + } + + @Override + public void notifyCarrierConfigChanged(int phoneId, int subId, int carrierId, + int specificCarrierId) { + if (!validatePhoneId(phoneId)) { + throw new IllegalArgumentException("Invalid phoneId: " + phoneId); + } + if (!checkNotifyPermission("notifyCarrierConfigChanged")) { + loge("Caller has no notify permission!"); + return; + } + if (VDBG) { + log("notifyCarrierConfigChanged: phoneId=" + phoneId + ", subId=" + subId + + ", carrierId=" + carrierId + ", specificCarrierId=" + specificCarrierId); + } + + synchronized (mRecords) { + mRemoveList.clear(); + for (Record r : mRecords) { + // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here + if (!r.matchCarrierConfigChangeListener()) { + continue; + } + try { + r.carrierConfigChangeListener.onCarrierConfigChanged(phoneId, subId, carrierId, + specificCarrierId); + } catch (RemoteException re) { + mRemoveList.add(r.binder); + } + } + handleRemoveListLocked(); + } + } + @NeverCompile // Avoid size overhead of debugging code. @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 7cccc5dd06c0..5ad5001cb121 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -17,6 +17,7 @@ package android.telephony; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -53,6 +54,7 @@ import com.android.telephony.Rlog; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; /** @@ -9726,4 +9728,85 @@ public class CarrierConfigManager { configs.putPersistableBundle(key, (PersistableBundle) value); } } + + /** + * Listener interface to get a notification when the carrier configurations have changed. + * + * Use this listener to receive timely updates when the carrier configuration changes. System + * components should prefer this listener over {@link #ACTION_CARRIER_CONFIG_CHANGED} + * whenever possible. + * + * To register the listener, call + * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}. + * To unregister, call + * {@link #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener)}. + * + * Note that on registration, registrants will NOT receive a notification on last carrier config + * change. Only carrier configs change AFTER the registration will be sent to registrants. And + * unlike {@link #ACTION_CARRIER_CONFIG_CHANGED}, notification wouldn't send when the device is + * unlocked. Registrants only receive the notification when there has been real carrier config + * changes. + * + * @see #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener) + * @see #unregisterCarrierConfigChangeListener(CarrierConfigChangeListener) + * @see #ACTION_CARRIER_CONFIG_CHANGED + * @see #getConfig(String...) + * @see #getConfigForSubId(int, String...) + */ + public interface CarrierConfigChangeListener { + /** + * Called when carrier configurations have changed. + * + * @param logicalSlotIndex The logical SIM slot index on which to monitor and get + * notification. It is guaranteed to be valid. + * @param subscriptionId The subscription on the SIM slot. May be + * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. + * @param carrierId The optional carrier Id, may be + * {@link TelephonyManager#UNKNOWN_CARRIER_ID}. + * See {@link TelephonyManager#getSimCarrierId()}. + * @param specificCarrierId The optional fine-grained carrierId, may be {@link + * TelephonyManager#UNKNOWN_CARRIER_ID}. A specific carrierId may + * be different from the carrierId above in a MVNO scenario. See + * detail in {@link TelephonyManager#getSimSpecificCarrierId()}. + */ + void onCarrierConfigChanged(int logicalSlotIndex, int subscriptionId, int carrierId, + int specificCarrierId); + } + + /** + * Register a {@link CarrierConfigChangeListener} to get a notification when carrier + * configurations have changed. + * + * @param executor The executor on which the listener will be called. + * @param listener The CarrierConfigChangeListener called when carrier configs has changed. + */ + public void registerCarrierConfigChangeListener(@NonNull @CallbackExecutor Executor executor, + @NonNull CarrierConfigChangeListener listener) { + Objects.requireNonNull(executor, "Executor should be non-null."); + Objects.requireNonNull(listener, "Listener should be non-null."); + + TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class); + if (trm == null) { + throw new IllegalStateException("Telephony registry service is null"); + } + trm.addCarrierConfigChangedListener(executor, listener); + } + + /** + * Unregister the {@link CarrierConfigChangeListener} to stop notification on carrier + * configurations change. + * + * @param listener The CarrierConfigChangeListener which was registered with method + * {@link #registerCarrierConfigChangeListener(Executor, CarrierConfigChangeListener)}. + */ + public void unregisterCarrierConfigChangeListener( + @NonNull CarrierConfigChangeListener listener) { + Objects.requireNonNull(listener, "Listener should be non-null."); + + TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class); + if (trm == null) { + throw new IllegalStateException("Telephony registry service is null"); + } + trm.removeCarrierConfigChangedListener(listener); + } }