Merge changes from topic "satellite-state-change-listener" into main
* changes:
Add READ_BASIC_PHONE_STATE permission to Shell for CTS
Implements satellite state change listener APIs
Introduce satellite state change listener APIs
Expose SatelliteManager as public manager class
diff --git a/core/api/current.txt b/core/api/current.txt
index 4b90f72..39d086a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10949,6 +10949,7 @@
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
field public static final String RESTRICTIONS_SERVICE = "restrictions";
field public static final String ROLE_SERVICE = "role";
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public static final String SATELLITE_SERVICE = "satellite";
field public static final String SEARCH_SERVICE = "search";
field @FlaggedApi("android.os.security_state_service") public static final String SECURITY_STATE_SERVICE = "security_state";
field public static final String SENSOR_SERVICE = "sensor";
@@ -47866,6 +47867,19 @@
}
+package android.telephony.satellite {
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void registerStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateChangeListener);
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void unregisterStateChangeListener(@NonNull android.telephony.satellite.SatelliteStateChangeListener);
+ }
+
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public interface SatelliteStateChangeListener {
+ method public void onEnabledStateChanged(boolean);
+ }
+
+}
+
package android.text {
@Deprecated public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e202bfb..e4d5589 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -18253,7 +18253,7 @@
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
}
- public final class SatelliteManager {
+ @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1d26b77..186f7b3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6677,8 +6677,8 @@
*
* @see #getSystemService(String)
* @see android.telephony.satellite.SatelliteManager
- * @hide
*/
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
public static final String SATELLITE_SERVICE = "satellite";
/**
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4d50a45..1dab2cf 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -47,6 +47,8 @@
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.SatelliteStateChangeListener;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -55,12 +57,14 @@
import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.server.telecom.flags.Flags;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.WeakHashMap;
@@ -1482,6 +1486,111 @@
pkgName, attributionTag, callback, new int[0], notifyNow);
}
+ @NonNull
+ @GuardedBy("sSatelliteStateChangeListeners")
+ private static final Map<SatelliteStateChangeListener,
+ WeakReference<SatelliteStateChangeListenerWrapper>>
+ sSatelliteStateChangeListeners = new ArrayMap<>();
+
+ /**
+ * Register a {@link SatelliteStateChangeListener} to receive notification when Satellite state
+ * has changed.
+ *
+ * @param executor The {@link Executor} where the {@code listener} will be invoked
+ * @param listener The listener to monitor the satellite state change
+ * @hide
+ */
+ public void addSatelliteStateChangeListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteStateChangeListener listener) {
+ if (listener == null || executor == null) {
+ throw new IllegalArgumentException("Listener and executor must be non-null");
+ }
+
+ synchronized (sSatelliteStateChangeListeners) {
+ WeakReference<SatelliteStateChangeListenerWrapper> existing =
+ sSatelliteStateChangeListeners.get(listener);
+ if (existing != null && existing.get() != null) {
+ Log.d(TAG, "addSatelliteStateChangeListener: listener already registered");
+ return;
+ }
+ SatelliteStateChangeListenerWrapper wrapper =
+ new SatelliteStateChangeListenerWrapper(executor, listener);
+ try {
+ sRegistry.addSatelliteStateChangeListener(
+ wrapper,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ sSatelliteStateChangeListeners.put(listener, new WeakReference<>(wrapper));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Unregister a {@link SatelliteStateChangeListener} to stop receiving notification when
+ * satellite state has changed.
+ *
+ * @param listener The listener previously registered with addSatelliteStateChangeListener.
+ * @hide
+ */
+ public void removeSatelliteStateChangeListener(@NonNull SatelliteStateChangeListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must be non-null");
+ }
+
+ synchronized (sSatelliteStateChangeListeners) {
+ WeakReference<SatelliteStateChangeListenerWrapper> ref =
+ sSatelliteStateChangeListeners.get(listener);
+ if (ref == null) return;
+ SatelliteStateChangeListenerWrapper wrapper = ref.get();
+ if (wrapper == null) return;
+ try {
+ sRegistry.removeSatelliteStateChangeListener(wrapper, mContext.getOpPackageName());
+ sSatelliteStateChangeListeners.remove(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Notify the registrants that the satellite state has changed.
+ *
+ * @param isEnabled True if the satellite modem is enabled, false otherwise
+ * @hide
+ */
+ public void notifySatelliteStateChanged(boolean isEnabled) {
+ try {
+ sRegistry.notifySatelliteStateChanged(isEnabled);
+ } catch (RemoteException ex) {
+ // system process is dead
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ private static class SatelliteStateChangeListenerWrapper extends
+ ISatelliteStateChangeListener.Stub implements ListenerExecutor {
+ @NonNull private final WeakReference<SatelliteStateChangeListener> mListener;
+ @NonNull private final Executor mExecutor;
+
+ SatelliteStateChangeListenerWrapper(@NonNull Executor executor,
+ @NonNull SatelliteStateChangeListener listener) {
+ mExecutor = executor;
+ mListener = new WeakReference<>(listener);
+ }
+
+ @Override
+ public void onSatelliteEnabledStateChanged(boolean isEnabled) {
+ Binder.withCleanCallingIdentity(
+ () ->
+ executeSafely(
+ mExecutor,
+ mListener::get,
+ sscl -> sscl.onEnabledStateChanged(isEnabled)));
+ }
+ }
+
private static class CarrierPrivilegesCallbackWrapper extends ICarrierPrivilegesCallback.Stub
implements ListenerExecutor {
@NonNull private final WeakReference<CarrierPrivilegesCallback> mCallback;
diff --git a/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl b/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl
new file mode 100644
index 0000000..4d195c2
--- /dev/null
+++ b/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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 ISatelliteStateChangeListener {
+ void onSatelliteEnabledStateChanged(boolean isEnabled);
+}
\ 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 ca75abd..1c76a6c 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -38,6 +38,7 @@
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
interface ITelephonyRegistry {
void addOnSubscriptionsChangedListener(String pkg, String featureId,
@@ -124,4 +125,8 @@
void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
+
+ void addSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg, String featureId);
+ void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg);
+ void notifySatelliteStateChanged(boolean isEnabled);
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 05c5e5d..1919572 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -951,6 +951,9 @@
<!-- Permission required for CTS test - CtsAppTestCases -->
<uses-permission android:name="android.permission.KILL_UID" />
+ <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+ <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 363807d..72a9a2d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -102,6 +102,7 @@
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.telephony.flags.Flags;
@@ -126,6 +127,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
/**
@@ -164,6 +166,7 @@
IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
ICarrierPrivilegesCallback carrierPrivilegesCallback;
ICarrierConfigChangeListener carrierConfigChangeListener;
+ ISatelliteStateChangeListener satelliteStateChangeListener;
int callerUid;
int callerPid;
@@ -196,6 +199,10 @@
return carrierConfigChangeListener != null;
}
+ boolean matchSatelliteStateChangeListener() {
+ return satelliteStateChangeListener != null;
+ }
+
boolean canReadCallLog() {
try {
return TelephonyPermissions.checkReadCallLog(
@@ -215,6 +222,7 @@
+ onOpportunisticSubscriptionsChangedListenerCallback
+ " carrierPrivilegesCallback=" + carrierPrivilegesCallback
+ " carrierConfigChangeListener=" + carrierConfigChangeListener
+ + " satelliteStateChangeListener=" + satelliteStateChangeListener
+ " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
}
}
@@ -433,6 +441,10 @@
private List<IntArray> mCarrierRoamingNtnAvailableServices;
+ // Local cache to check if Satellite Modem is enabled
+ private AtomicBoolean mIsSatelliteEnabled;
+ private AtomicBoolean mWasSatelliteEnabledNotified;
+
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
* type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -871,6 +883,9 @@
mCarrierRoamingNtnMode = new boolean[numPhones];
mCarrierRoamingNtnEligible = new boolean[numPhones];
mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+ mIsSatelliteEnabled = new AtomicBoolean();
+ mWasSatelliteEnabledNotified = new AtomicBoolean();
+
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -3425,6 +3440,94 @@
}
@Override
+ public void addSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+ @NonNull String pkg, @Nullable String featureId) {
+ final int callerUserId = UserHandle.getCallingUserId();
+ mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, featureId,
+ "addSatelliteStateChangeListener");
+ if (VDBG) {
+ log("addSatelliteStateChangeListener pkg=" + pii(pkg)
+ + " uid=" + Binder.getCallingUid()
+ + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+ + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+ }
+
+ synchronized (mRecords) {
+ final IBinder b = listener.asBinder();
+ boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+ Process.myUid());
+ Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+ if (r == null) {
+ loge("addSatelliteStateChangeListener: can not create Record instance!");
+ return;
+ }
+
+ r.context = mContext;
+ r.satelliteStateChangeListener = listener;
+ r.callingPackage = pkg;
+ r.callingFeatureId = featureId;
+ r.callerUid = Binder.getCallingUid();
+ r.callerPid = Binder.getCallingPid();
+ r.eventList = new ArraySet<>();
+ if (DBG) {
+ log("addSatelliteStateChangeListener: Register r=" + r);
+ }
+
+ // Always notify registrants on registration if it has been notified before
+ if (mWasSatelliteEnabledNotified.get() && r.matchSatelliteStateChangeListener()) {
+ try {
+ r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(
+ mIsSatelliteEnabled.get());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+ @NonNull String pkg) {
+ if (DBG) log("removeSatelliteStateChangeListener listener=" + listener + ", pkg=" + pkg);
+ mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+ enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, null,
+ "removeSatelliteStateChangeListener");
+ remove(listener.asBinder());
+ }
+
+ @Override
+ public void notifySatelliteStateChanged(boolean isEnabled) {
+ if (!checkNotifyPermission("notifySatelliteStateChanged")) {
+ loge("notifySatelliteStateChanged: Caller has no notify permission!");
+ return;
+ }
+ if (VDBG) {
+ log("notifySatelliteStateChanged: isEnabled=" + isEnabled);
+ }
+
+ mWasSatelliteEnabledNotified.set(true);
+ mIsSatelliteEnabled.set(isEnabled);
+
+ synchronized (mRecords) {
+ mRemoveList.clear();
+ for (Record r : mRecords) {
+ // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+ if (!r.matchSatelliteStateChangeListener()) {
+ continue;
+ }
+ try {
+ r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(isEnabled);
+ } catch (RemoteException re) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ @Override
public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) {
if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) {
return;
@@ -4622,4 +4725,32 @@
if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
return "[***, size=" + packageNames.size() + "]";
}
+
+ /**
+ * The method enforces the calling package at least has READ_BASIC_PHONE_STATE permission.
+ * That is, calling package either has READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE or Carrier
+ * Privileges on ANY active subscription, or has READ_BASIC_PHONE_STATE permission.
+ */
+ private void enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(String pkgName,
+ String featureId, String message) {
+ // Check if calling app has READ_PHONE_STATE on ANY active subscription
+ boolean hasReadPhoneState = false;
+ SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+ if (sm != null) {
+ for (int subId : sm.getActiveSubscriptionIdList()) {
+ if (TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(mContext, subId,
+ pkgName, featureId, message)) {
+ hasReadPhoneState = true;
+ break;
+ }
+ }
+ }
+
+ // If yes, pass. If not, then enforce READ_BASIC_PHONE_STATE permission
+ if (!hasReadPhoneState) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.READ_BASIC_PHONE_STATE,
+ message);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e332d0f..be02232 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -39,6 +40,7 @@
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.ITelephony;
@@ -61,13 +63,19 @@
import java.util.stream.Collectors;
/**
- * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
- * To get the object, call {@link Context#getSystemService(String)}.
+ * Manages satellite states such as monitoring enabled state and operations such as provisioning,
+ * pointing, messaging, location sharing, etc.
*
- * @hide
+ * <p>To get the object, call {@link Context#getSystemService(String)} with
+ * {@link Context#SATELLITE_SERVICE}.
+ *
+ * <p>SatelliteManager is intended for use on devices with feature
+ * {@link PackageManager#FEATURE_TELEPHONY_SATELLITE}. On devices without the feature, the behavior
+ * is not reliable.
*/
+@SystemService(Context.SATELLITE_SERVICE)
+@FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
-@SystemApi
public final class SatelliteManager {
private static final String TAG = "SatelliteManager";
@@ -103,6 +111,8 @@
*/
@Nullable private final Context mContext;
+ private TelephonyRegistryManager mTelephonyRegistryMgr;
+
/**
* Create an instance of the SatelliteManager.
*
@@ -736,6 +746,65 @@
"android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
/**
+ * Registers a {@link SatelliteStateChangeListener} to receive callbacks when the satellite
+ * state may have changed.
+ *
+ * <p>The callback method is immediately triggered with latest state on invoking this method if
+ * the state change has been notified before.
+ *
+ * @param executor The {@link Executor} where the {@code listener} will be invoked
+ * @param listener The listener to monitor the satellite state change
+ *
+ * @see SatelliteStateChangeListener
+ * @see TelephonyManager#hasCarrierPrivileges()
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+ @RequiresPermission(anyOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.READ_PHONE_STATE,
+ "carrier privileges"})
+ public void registerStateChangeListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteStateChangeListener listener) {
+ if (mContext == null) {
+ throw new IllegalStateException("Telephony service is null");
+ }
+
+ mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (mTelephonyRegistryMgr == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ mTelephonyRegistryMgr.addSatelliteStateChangeListener(executor, listener);
+ }
+
+ /**
+ * Unregisters the {@link SatelliteStateChangeListener} previously registered with
+ * {@link #registerStateChangeListener(Executor, SatelliteStateChangeListener)}.
+ *
+ * <p>It will be a no-op if the {@code listener} is not currently registered.
+ *
+ * @param listener The listener to unregister
+ *
+ * @see SatelliteStateChangeListener
+ * @see TelephonyManager#hasCarrierPrivileges()
+ */
+ @FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+ @RequiresPermission(anyOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.READ_PHONE_STATE,
+ "carrier privileges"})
+ public void unregisterStateChangeListener(@NonNull SatelliteStateChangeListener listener) {
+ if (mContext == null) {
+ throw new IllegalStateException("Telephony service is null");
+ }
+
+ mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+ if (mTelephonyRegistryMgr == null) {
+ throw new IllegalStateException("Telephony registry service is null");
+ }
+ mTelephonyRegistryMgr.removeSatelliteStateChangeListener(listener);
+ }
+
+ /**
* Request to enable or disable the satellite modem and demo mode.
* If satellite modem and cellular modem cannot work concurrently,
* then this will disable the cellular modem if satellite modem is enabled,
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java b/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java
new file mode 100644
index 0000000..3aa910d
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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 android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A listener interface to monitor satellite state change events.
+ *
+ * <p>Call
+ * {@link SatelliteManager#registerStateChangeListener(Executor, SatelliteStateChangeListener)}
+ * to monitor. Call
+ * {@link SatelliteManager#unregisterStateChangeListener(SatelliteStateChangeListener)} to cancel.
+ *
+ * @see SatelliteManager#registerStateChangeListener(Executor, SatelliteStateChangeListener)
+ * @see SatelliteManager#unregisterStateChangeListener(SatelliteStateChangeListener)
+ */
+@FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+public interface SatelliteStateChangeListener {
+ /**
+ * Called when satellite modem enabled state may have changed.
+ *
+ * <p>Note:there is no guarantee that this callback will only be invoked upon a change of state.
+ * In other word, in some cases, the callback may report with the same enabled states. It is the
+ * caller's responsibility to filter uninterested states.
+ *
+ * <p>Note:satellite enabled state is a device state that is NOT associated with subscription or
+ * SIM slot.
+ *
+ * @param isEnabled {@code true} means satellite modem is enabled.
+ */
+ void onEnabledStateChanged(boolean isEnabled);
+}