From c5e36e1c1f83cc130a96498f3117090065fa0531 Mon Sep 17 00:00:00 2001 From: rambowang Date: Sun, 2 Oct 2022 11:19:21 -0500 Subject: [PATCH] Introduce CarrierConfigChangeListener API to monitor CC change The new interface is used for system components and 3rd party apps to monitor carrier config changes, replacing the current carrier config change broadcast receivers which has known performance issue. Bug: 244087782 Test: atest CarrierConfigManagerTest Change-Id: I782e3aa2312886279cc7f449fa3a8238b4115407 --- core/api/current.txt | 6 ++ .../telephony/TelephonyRegistryManager.java | 101 ++++++++++++++++++ .../ICarrierConfigChangeListener.aidl | 21 ++++ .../telephony/ITelephonyRegistry.aidl | 5 + .../com/android/server/TelephonyRegistry.java | 83 ++++++++++++++ .../telephony/CarrierConfigManager.java | 82 ++++++++++++++ 6 files changed, 298 insertions(+) create mode 100644 core/java/com/android/internal/telephony/ICarrierConfigChangeListener.aidl diff --git a/core/api/current.txt b/core/api/current.txt index 3fc4d96286fde..e1e49bd455f97 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -42009,6 +42009,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 @@ -42338,6 +42340,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 0a1538de9f5d9..519647d805fda 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) { @@ -1412,4 +1423,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 0000000000000..0f7ab0a3d4fbb --- /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 7ba26866ee031..54936c67e4482 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 32afccadf455a..ee922f9b98dfb 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -92,6 +92,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; @@ -155,6 +156,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback; IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback; ICarrierPrivilegesCallback carrierPrivilegesCallback; + ICarrierConfigChangeListener carrierConfigChangeListener; int callerUid; int callerPid; @@ -183,6 +185,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return carrierPrivilegesCallback != null; } + boolean matchCarrierConfigChangeListener() { + return carrierConfigChangeListener != null; + } + boolean canReadCallLog() { try { return TelephonyPermissions.checkReadCallLog( @@ -201,6 +207,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { + " onOpportunisticSubscriptionsChangedListenererCallback=" + onOpportunisticSubscriptionsChangedListenerCallback + " carrierPrivilegesCallback=" + carrierPrivilegesCallback + + " carrierConfigChangeListener=" + carrierConfigChangeListener + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}"; } } @@ -3044,6 +3051,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 91036587320b1..8cd5a85011b9c 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; @@ -10337,4 +10338,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); + } }