Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea97b13f authored by rambowang's avatar rambowang Committed by Rambo Wang
Browse files

Implements satellite state change listener APIs

This change implemements the new satellite state change listener APIs by
the help of TelephonyRegistryManager.

Bug: 357638490
Test: atest FrameworkTelephonyTests SatelliteManagerTest
Flag: com.android.internal.telephony.flags.satellite_state_change_listener
Change-Id: I5b76aa8756bc35ccd94b332de708601f11ea6b7f
parent 31f6988e
Loading
Loading
Loading
Loading
+109 −0
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ import android.telephony.emergency.EmergencyNumber;
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.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.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 @@ public class TelephonyRegistryManager {
                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;
+21 −0
Original line number Diff line number Diff line
/*
 * 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
+5 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.internal.telephony.ICarrierConfigChangeListener;
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 @@ interface ITelephonyRegistry {
    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);
}
+131 −0
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ import com.android.internal.telephony.ICarrierConfigChangeListener;
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.Map;
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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
        IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
        ICarrierPrivilegesCallback carrierPrivilegesCallback;
        ICarrierConfigChangeListener carrierConfigChangeListener;
        ISatelliteStateChangeListener satelliteStateChangeListener;

        int callerUid;
        int callerPid;
@@ -196,6 +199,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
            return carrierConfigChangeListener != null;
        }

        boolean matchSatelliteStateChangeListener() {
            return satelliteStateChangeListener != null;
        }

        boolean canReadCallLog() {
            try {
                return TelephonyPermissions.checkReadCallLog(
@@ -215,6 +222,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
                    + onOpportunisticSubscriptionsChangedListenerCallback
                    + " carrierPrivilegesCallback=" + carrierPrivilegesCallback
                    + " carrierConfigChangeListener=" + carrierConfigChangeListener
                    + " satelliteStateChangeListener=" + satelliteStateChangeListener
                    + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
        }
    }
@@ -433,6 +441,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {

    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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
        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;
@@ -3424,6 +3439,94 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
        }
    }

    @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()")) {
@@ -4622,4 +4725,32 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
        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);
        }
    }
}