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

Commit efe0ce2b authored by Samuel Huang's avatar Samuel Huang Committed by Android (Google) Code Review
Browse files

Merge "Disable SIM On/Off operation when device is in Satellite Enabled Mode" into 24D1-dev

parents 5278c0f8 316e7bf3
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -18,9 +18,8 @@
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="mobile_network_pref_screen">

    <com.android.settings.widget.SettingsMainSwitchPreference
    <com.android.settings.spa.preference.ComposePreference
        android:key="use_sim_switch"
        android:title="@string/mobile_network_use_sim_on"
        settings:controller="com.android.settings.network.telephony.MobileNetworkSwitchController"/>

    <PreferenceCategory
+138 −0
Original line number Diff line number Diff line
@@ -19,29 +19,36 @@ package com.android.settings.network
import android.content.Context
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteModemStateCallback
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.concurrent.futures.CallbackToFutureAdapter
import com.google.common.util.concurrent.Futures.immediateFuture
import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOf

/**
 * Utility class for interacting with the SatelliteManager API.
 * A repository class for interacting with the SatelliteManager API.
 */
object SatelliteManagerUtil {

    private const val TAG: String = "SatelliteManagerUtil"
class SatelliteRepository(
    private val context: Context,
) {

    /**
     * Checks if the satellite modem is enabled.
     *
     * @param context  The application context
     * @param executor The executor to run the asynchronous operation on
     * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled,
     *         `false` otherwise.
     */
    @JvmStatic
    fun requestIsEnabled(context: Context, executor: Executor): ListenableFuture<Boolean> {
    fun requestIsEnabled(executor: Executor): ListenableFuture<Boolean> {
        val satelliteManager: SatelliteManager? =
            context.getSystemService(SatelliteManager::class.java)
        if (satelliteManager == null) {
@@ -66,4 +73,66 @@ object SatelliteManagerUtil {
            "requestIsEnabled"
        }
    }

    /**
     * Provides a Flow that emits the enabled state of the satellite modem. Updates are triggered
     * when the modem state changes.
     *
     * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
     * @return A Flow emitting `true` when the modem is enabled and `false` otherwise.
     */
    fun getIsModemEnabledFlow(
        defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
    ): Flow<Boolean> {
        val satelliteManager: SatelliteManager? =
            context.getSystemService(SatelliteManager::class.java)
        if (satelliteManager == null) {
            Log.w(TAG, "SatelliteManager is null")
            return flowOf(false)
        }

        return callbackFlow {
            val callback = SatelliteModemStateCallback { state ->
                val isEnabled = convertSatelliteModemStateToEnabledState(state)
                Log.i(TAG, "Satellite modem state changed: state=$state, isEnabled=$isEnabled")
                trySend(isEnabled)
            }

            val result = satelliteManager.registerForModemStateChanged(
                defaultDispatcher.asExecutor(),
                callback
            )
            Log.i(TAG, "Call registerForModemStateChanged: result=$result")

            awaitClose { satelliteManager.unregisterForModemStateChanged(callback) }
        }
    }

    /**
     * Converts a [SatelliteManager.SatelliteModemState] to a boolean representing whether the modem
     * is enabled.
     *
     * @param state The SatelliteModemState provided by the SatelliteManager.
     * @return `true` if the modem is enabled, `false` otherwise.
     */
    @VisibleForTesting
    fun convertSatelliteModemStateToEnabledState(
        @SatelliteManager.SatelliteModemState state: Int,
    ): Boolean {
        // Mapping table based on logic from b/315928920#comment24
        return when (state) {
            SatelliteManager.SATELLITE_MODEM_STATE_IDLE,
            SatelliteManager.SATELLITE_MODEM_STATE_LISTENING,
            SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING,
            SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING,
            SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED,
            SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED -> true
            else -> false
        }
    }

    companion object {
        private const val TAG: String = "SatelliteRepository"
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.app.Application
import android.telephony.SubscriptionManager
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.android.settings.network.telephony.getSelectableSubscriptionInfoList
import com.android.settings.network.telephony.subscriptionsChangedFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
@@ -41,10 +42,10 @@ class SubscriptionInfoListViewModel(application: Application) : AndroidViewModel
    }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())

    /**
     * Getting the Selectable SubscriptionInfo List from the SubscriptionManager's
     * Getting the Selectable SubscriptionInfo List from the SubscriptionRepository's
     * getAvailableSubscriptionInfoList
     */
    val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
        SubscriptionUtil.getSelectableSubscriptionInfoList(application)
        application.getSelectableSubscriptionInfoList()
    }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
}
+2 −35
Original line number Diff line number Diff line
@@ -50,12 +50,12 @@ import com.android.settings.network.helper.SelectableSubscriptions;
import com.android.settings.network.helper.SubscriptionAnnotation;
import com.android.settings.network.telephony.DeleteEuiccSubscriptionDialogActivity;
import com.android.settings.network.telephony.EuiccRacConnectivityDialogActivity;
import com.android.settings.network.telephony.SubscriptionRepositoryKt;
import com.android.settings.network.telephony.ToggleSubscriptionDialogActivity;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -505,40 +505,7 @@ public class SubscriptionUtil {
     * @return list of user selectable subscriptions.
     */
    public static List<SubscriptionInfo> getSelectableSubscriptionInfoList(Context context) {
        SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
        List<SubscriptionInfo> availableList = subManager.getAvailableSubscriptionInfoList();
        if (availableList == null) {
            return null;
        } else {
            // Multiple subscriptions in a group should only have one representative.
            // It should be the current active primary subscription if any, or any
            // primary subscription.
            List<SubscriptionInfo> selectableList = new ArrayList<>();
            Map<ParcelUuid, SubscriptionInfo> groupMap = new HashMap<>();

            for (SubscriptionInfo info : availableList) {
                // Opportunistic subscriptions are considered invisible
                // to users so they should never be returned.
                if (!isSubscriptionVisible(subManager, context, info)) continue;

                ParcelUuid groupUuid = info.getGroupUuid();
                if (groupUuid == null) {
                    // Doesn't belong to any group. Add in the list.
                    selectableList.add(info);
                } else if (!groupMap.containsKey(groupUuid)
                        || (groupMap.get(groupUuid).getSimSlotIndex() == INVALID_SIM_SLOT_INDEX
                        && info.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX)) {
                    // If it belongs to a group that has never been recorded or it's the current
                    // active subscription, add it in the list.
                    selectableList.remove(groupMap.get(groupUuid));
                    selectableList.add(info);
                    groupMap.put(groupUuid, info);
                }

            }
            Log.d(TAG, "getSelectableSubscriptionInfoList: " + selectableList);
            return selectableList;
        }
        return SubscriptionRepositoryKt.getSelectableSubscriptionInfoList(context);
    }

    /**
+0 −147
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.settings.network.telephony;

import static android.telephony.TelephonyManager.CALL_STATE_IDLE;

import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE;
import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;

import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;

import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.PreferenceScreen;

import com.android.settings.core.BasePreferenceController;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.network.SubscriptionsChangeListener;
import com.android.settings.widget.SettingsMainSwitchPreference;

/** This controls a switch to allow enabling/disabling a mobile network */
public class MobileNetworkSwitchController extends BasePreferenceController implements
        SubscriptionsChangeListener.SubscriptionsChangeListenerClient, LifecycleObserver {
    private static final String TAG = "MobileNetworkSwitchCtrl";
    private SettingsMainSwitchPreference mSwitchBar;
    private int mSubId;
    private SubscriptionsChangeListener mChangeListener;
    private SubscriptionManager mSubscriptionManager;
    private TelephonyManager mTelephonyManager;
    private CallStateTelephonyCallback mCallStateCallback;

    public MobileNetworkSwitchController(Context context, String preferenceKey) {
        super(context, preferenceKey);
        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
        mChangeListener = new SubscriptionsChangeListener(context, this);
    }

    void init(int subId) {
        mSubId = subId;
        mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
    }

    @OnLifecycleEvent(ON_RESUME)
    public void onResume() {
        mChangeListener.start();

        if (mCallStateCallback == null) {
            mCallStateCallback = new CallStateTelephonyCallback();
            mTelephonyManager.registerTelephonyCallback(
                    mContext.getMainExecutor(), mCallStateCallback);
        }
        update();
    }

    @OnLifecycleEvent(ON_PAUSE)
    public void onPause() {
        if (mCallStateCallback != null) {
            mTelephonyManager.unregisterTelephonyCallback(mCallStateCallback);
            mCallStateCallback = null;
        }
        mChangeListener.stop();
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mSwitchBar = (SettingsMainSwitchPreference) screen.findPreference(mPreferenceKey);

        mSwitchBar.setOnBeforeCheckedChangeListener((isChecked) -> {
            // TODO b/135222940: re-evaluate whether to use
            // mSubscriptionManager#isSubscriptionEnabled
            if (mSubscriptionManager.isActiveSubscriptionId(mSubId) != isChecked) {
                SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, mSubId, isChecked);
                return true;
            }
            return false;
        });
        update();
    }

    private void update() {
        if (mSwitchBar == null) {
            return;
        }

        SubscriptionInfo subInfo = null;
        for (SubscriptionInfo info : SubscriptionUtil.getAvailableSubscriptions(mContext)) {
            if (info.getSubscriptionId() == mSubId) {
                subInfo = info;
                break;
            }
        }

        // For eSIM, we always want the toggle. If telephony stack support disabling a pSIM
        // directly, we show the toggle.
        if (subInfo == null || (!subInfo.isEmbedded() && !SubscriptionUtil.showToggleForPhysicalSim(
                mSubscriptionManager))) {
            mSwitchBar.hide();
        } else {
            mSwitchBar.show();
            mSwitchBar.setCheckedInternal(mSubscriptionManager.isActiveSubscriptionId(mSubId));
        }
    }

    @Override
    public int getAvailabilityStatus() {
        return AVAILABLE_UNSEARCHABLE;

    }

    @Override
    public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
    }

    @Override
    public void onSubscriptionsChanged() {
        update();
    }

    private class CallStateTelephonyCallback extends TelephonyCallback implements
            TelephonyCallback.CallStateListener {
        @Override
        public void onCallStateChanged(int state) {
            mSwitchBar.setSwitchBarEnabled(state == CALL_STATE_IDLE);
        }
    }
}
Loading