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

Commit 33d73862 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Refactor signal strength in SIM status

- Move data logic into repository for better testing
- Check carrier config first, if not shows some items, we don't need to
  load data
- Tests in SimStatusDialogControllerTest will be fixed in later cls

Bug: 337417520
Test: manual - on SIM status
Test: unit test
Change-Id: Iccd209fd455d66d4f6438652ee7481d2a0e72a99
parent 3b925a0c
Loading
Loading
Loading
Loading
+78 −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.settings.deviceinfo.simstatus

import android.content.Context
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
import android.util.Log
import com.android.settings.R
import com.android.settings.network.telephony.serviceStateFlow
import com.android.settings.network.telephony.telephonyCallbackFlow
import com.android.settingslib.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map

@OptIn(ExperimentalCoroutinesApi::class)
class SignalStrengthRepository(
    private val context: Context,
    private val serviceStateFlowFactory: (subId: Int) -> Flow<ServiceState> = {
        context.serviceStateFlow(it)
    },
) {
    fun signalStrengthDisplayFlow(subId: Int): Flow<String> =
        serviceStateFlowFactory(subId).flatMapLatest { serviceState ->
            if (Utils.isInService(serviceState)) {
                signalStrengthFlow(subId).map { it.displayString() }
            } else {
                flowOf("0")
            }
        }.conflate().flowOn(Dispatchers.Default)

    /** Creates an instance of a cold Flow for [SignalStrength] of given [subId]. */
    private fun signalStrengthFlow(subId: Int): Flow<SignalStrength> =
        context.telephonyCallbackFlow(subId) {
            object : TelephonyCallback(), TelephonyCallback.SignalStrengthsListener {
                override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
                    trySend(signalStrength)
                    val cellSignalStrengths = signalStrength.cellSignalStrengths
                    Log.d(TAG, "[$subId] onSignalStrengthsChanged: $cellSignalStrengths")
                }
            }
        }

    private fun SignalStrength.displayString() =
        context.getString(R.string.sim_signal_strength, signalDbm(), signalAsu())

    private companion object {
        private const val TAG = "SignalStrengthRepo"


        private fun SignalStrength.signalDbm(): Int =
            cellSignalStrengths.firstOrNull { it.dbm != -1 }?.dbm ?: 0

        private fun SignalStrength.signalAsu(): Int =
            cellSignalStrengths.firstOrNull { it.asuLevel != -1 }?.asuLevel ?: 0
    }
}
+6 −95
Original line number Diff line number Diff line
@@ -32,10 +32,8 @@ import android.telephony.Annotation;
import android.telephony.CarrierConfigManager;
import android.telephony.CellBroadcastIntents;
import android.telephony.CellBroadcastService;
import android.telephony.CellSignalStrength;
import android.telephony.ICellBroadcastService;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
@@ -113,7 +111,6 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {

    private SubscriptionInfo mSubscriptionInfo;
    private TelephonyDisplayInfo mTelephonyDisplayInfo;
    private ServiceState mPreviousServiceState;

    private final int mSlotIndex;
    private TelephonyManager mTelephonyManager;
@@ -219,11 +216,9 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {
        // getServiceState() may return null when the subscription is inactive
        // or when there was an error communicating with the phone process.
        final ServiceState serviceState = getTelephonyManager().getServiceState();
        final SignalStrength signalStrength = getTelephonyManager().getSignalStrength();

        updatePhoneNumber();
        updateServiceState(serviceState);
        updateSignalStrength(signalStrength);
        updateNetworkType();
        updateRoamingStatus(serviceState);
        updateIccidNumber();
@@ -419,12 +414,6 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {

    private void updateServiceState(ServiceState serviceState) {
        final int state = Utils.getCombinedServiceState(serviceState);
        if (!Utils.isInService(serviceState)) {
            resetSignalStrength();
        } else if (!Utils.isInService(mPreviousServiceState)) {
            // If ServiceState changed from out of service -> in service, update signal strength.
            updateSignalStrength(getTelephonyManager().getSignalStrength());
        }

        String serviceStateValue;

@@ -449,49 +438,11 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {
        mDialog.setText(SERVICE_STATE_VALUE_ID, serviceStateValue);
    }

    private void updateSignalStrength(SignalStrength signalStrength) {
        if (signalStrength == null) {
            return;
        }
        // by default we show the signal strength
        boolean showSignalStrength = true;
        if (mSubscriptionInfo != null) {
            final int subscriptionId = mSubscriptionInfo.getSubscriptionId();
            final PersistableBundle carrierConfig =
                    mCarrierConfigManager.getConfigForSubId(subscriptionId);
            if (carrierConfig != null) {
                showSignalStrength = carrierConfig.getBoolean(
                        CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL);
            }
        }
        if (!showSignalStrength) {
            mDialog.removeSettingFromScreen(SIGNAL_STRENGTH_LABEL_ID);
            mDialog.removeSettingFromScreen(SIGNAL_STRENGTH_VALUE_ID);
            return;
        }

        ServiceState serviceState = getTelephonyManager().getServiceState();
        if (!Utils.isInService(serviceState)) {
            return;
        }

        int signalDbm = getDbm(signalStrength);
        int signalAsu = getAsuLevel(signalStrength);

        if (signalDbm == -1) {
            signalDbm = 0;
        }

        if (signalAsu == -1) {
            signalAsu = 0;
        }

        mDialog.setText(SIGNAL_STRENGTH_VALUE_ID, mRes.getString(R.string.sim_signal_strength,
                signalDbm, signalAsu));
    }

    private void resetSignalStrength() {
        mDialog.setText(SIGNAL_STRENGTH_VALUE_ID, "0");
    private void updateSignalStrength(@Nullable String signalStrength) {
        boolean isVisible = signalStrength != null;
        mDialog.setSettingVisibility(SIGNAL_STRENGTH_LABEL_ID, isVisible);
        mDialog.setSettingVisibility(SIGNAL_STRENGTH_VALUE_ID, isVisible);
        mDialog.setText(SIGNAL_STRENGTH_VALUE_ID, signalStrength);
    }

    private void updateNetworkType() {
@@ -593,6 +544,7 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {
    private void collectSimStatusDialogInfo(@NonNull LifecycleOwner owner) {
        new SimStatusDialogRepository(mContext).collectSimStatusDialogInfo(
                owner, mSlotIndex, (simStatusDialogInfo) -> {
                    updateSignalStrength(simStatusDialogInfo.getSignalStrength());
                    updateImsRegistrationState(simStatusDialogInfo.getImsRegistered());
                    return Unit.INSTANCE;
                }
@@ -603,44 +555,9 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {
        return SubscriptionManager.from(mContext).getActiveSubscriptionInfoForSimSlotIndex(slotId);
    }

    private int getDbm(SignalStrength signalStrength) {
        List<CellSignalStrength> cellSignalStrengthList = signalStrength.getCellSignalStrengths();
        int dbm = -1;
        if (cellSignalStrengthList == null) {
            return dbm;
        }

        for (CellSignalStrength cell : cellSignalStrengthList) {
            if (cell.getDbm() != -1) {
                dbm = cell.getDbm();
                break;
            }
        }

        return dbm;
    }

    private int getAsuLevel(SignalStrength signalStrength) {
        List<CellSignalStrength> cellSignalStrengthList = signalStrength.getCellSignalStrengths();
        int asu = -1;
        if (cellSignalStrengthList == null) {
            return asu;
        }

        for (CellSignalStrength cell : cellSignalStrengthList) {
            if (cell.getAsuLevel() != -1) {
                asu = cell.getAsuLevel();
                break;
            }
        }

        return asu;
    }

    @VisibleForTesting
    class SimStatusDialogTelephonyCallback extends TelephonyCallback implements
            TelephonyCallback.DataConnectionStateListener,
            TelephonyCallback.SignalStrengthsListener,
            TelephonyCallback.ServiceStateListener,
            TelephonyCallback.DisplayInfoListener {
        @Override
@@ -649,17 +566,11 @@ public class SimStatusDialogController implements DefaultLifecycleObserver {
            updateNetworkType();
        }

        @Override
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
            updateSignalStrength(signalStrength);
        }

        @Override
        public void onServiceStateChanged(ServiceState serviceState) {
            updateNetworkProvider();
            updateServiceState(serviceState);
            updateRoamingStatus(serviceState);
            mPreviousServiceState = serviceState;
        }

        @Override
+2 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -113,7 +114,7 @@ public class SimStatusDialogFragment extends InstrumentedDialogFragment {
                    SimStatusDialogController.PHONE_NUMBER_VALUE_ID)
            .sorted().toArray();

    public void setText(int viewId, CharSequence text) {
    public void setText(int viewId, @Nullable CharSequence text) {
        if (!isAdded()) {
            Log.d(TAG, "Fragment not attached yet.");
            return;
+22 −9
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ import kotlinx.coroutines.launch
class SimStatusDialogRepository @JvmOverloads constructor(
    private val context: Context,
    private val simSlotRepository: SimSlotRepository = SimSlotRepository(context),
    private val signalStrengthRepository: SignalStrengthRepository =
        SignalStrengthRepository(context),
    private val imsMmTelRepositoryFactory: (subId: Int) -> ImsMmTelRepository = { subId ->
        ImsMmTelRepositoryImpl(context, subId)
    },
@@ -49,10 +51,12 @@ class SimStatusDialogRepository @JvmOverloads constructor(
    private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!

    data class SimStatusDialogInfo(
        val signalStrength: String? = null,
        val imsRegistered: Boolean? = null,
    )

    private data class SimStatusDialogVisibility(
        val signalStrengthShowUp: Boolean,
        val imsRegisteredShowUp: Boolean,
    )

@@ -83,25 +87,34 @@ class SimStatusDialogRepository @JvmOverloads constructor(
    private fun simStatusDialogInfoFlow(subId: Int): Flow<SimStatusDialogInfo> =
        showUpFlow(subId).flatMapLatest { visibility ->
            combine(
                if (visibility.signalStrengthShowUp) {
                    signalStrengthRepository.signalStrengthDisplayFlow(subId)
                } else flowOf(null),
                if (visibility.imsRegisteredShowUp) {
                    imsMmTelRepositoryFactory(subId).imsRegisteredFlow()
                } else flowOf(null),
            ) { (imsRegistered) ->
                SimStatusDialogInfo(imsRegistered = imsRegistered)
            ) { signalStrength, imsRegistered ->
                SimStatusDialogInfo(signalStrength = signalStrength, imsRegistered = imsRegistered)
            }
        }

    private fun showUpFlow(subId: Int) = flow {
        val config = carrierConfigManager.safeGetConfig(
            keys = listOf(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL),
            keys = listOf(
                CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
                CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL,
            ),
            subId = subId,
        )
        emit(
            SimStatusDialogVisibility(
        val visibility = SimStatusDialogVisibility(
            signalStrengthShowUp = config.getBoolean(
                CarrierConfigManager.KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL,
                true,  // by default we show the signal strength in sim status
            ),
            imsRegisteredShowUp = config.getBoolean(
                CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL
            ),
        )
        )
        emit(visibility)
    }
}
+148 −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.settings.deviceinfo.simstatus

import android.content.Context
import android.telephony.CellSignalStrengthCdma
import android.telephony.CellSignalStrengthGsm
import android.telephony.CellSignalStrengthLte
import android.telephony.CellSignalStrengthNr
import android.telephony.CellSignalStrengthTdscdma
import android.telephony.CellSignalStrengthWcdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy

@RunWith(AndroidJUnit4::class)
class SignalStrengthRepositoryTest {

    private var signalStrength = SignalStrength()

    private val mockTelephonyManager = mock<TelephonyManager> {
        on { createForSubscriptionId(SUB_ID) } doReturn mock
        on { registerTelephonyCallback(any(), any()) } doAnswer {
            val listener = it.getArgument<TelephonyCallback.SignalStrengthsListener>(1)
            listener.onSignalStrengthsChanged(signalStrength)
        }
    }

    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
        on { getSystemService(TelephonyManager::class.java) } doAnswer { mockTelephonyManager }
    }

    private val serviceState = ServiceState()

    private val repository = SignalStrengthRepository(context) { flowOf(serviceState) }

    @Test
    fun signalStrengthDisplayFlow_serviceStatePowerOff() = runBlocking {
        serviceState.state = ServiceState.STATE_POWER_OFF

        val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()

        assertThat(signalStrength).isEqualTo("0")
    }

    @Test
    fun signalStrengthDisplayFlow_lteWcdma() = runBlocking {
        serviceState.state = ServiceState.STATE_IN_SERVICE
        signalStrength = SignalStrength(
            CellSignalStrengthCdma(),
            CellSignalStrengthGsm(),
            mock<CellSignalStrengthWcdma> {
                on { isValid } doReturn true
                on { dbm } doReturn 40
                on { asuLevel } doReturn 41
            },
            CellSignalStrengthTdscdma(),
            mock<CellSignalStrengthLte> {
                on { isValid } doReturn true
                on { dbm } doReturn 50
                on { asuLevel } doReturn 51
            },
            CellSignalStrengthNr(),
        )

        val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()

        assertThat(signalStrength).isEqualTo("50 dBm 51 asu")
    }

    @Test
    fun signalStrengthDisplayFlow_lteCdma() = runBlocking {
        serviceState.state = ServiceState.STATE_IN_SERVICE
        signalStrength = SignalStrength(
            mock<CellSignalStrengthCdma> {
                on { isValid } doReturn true
                on { dbm } doReturn 30
                on { asuLevel } doReturn 31
            },
            CellSignalStrengthGsm(),
            CellSignalStrengthWcdma(),
            CellSignalStrengthTdscdma(),
            mock<CellSignalStrengthLte> {
                on { isValid } doReturn true
                on { dbm } doReturn 50
                on { asuLevel } doReturn 51
            },
            CellSignalStrengthNr(),
        )

        val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()

        assertThat(signalStrength).isEqualTo("50 dBm 51 asu")
    }

    @Test
    fun signalStrengthDisplayFlow_lteOnly() = runBlocking {
        serviceState.state = ServiceState.STATE_IN_SERVICE
        signalStrength = SignalStrength(
            CellSignalStrengthCdma(),
            CellSignalStrengthGsm(),
            CellSignalStrengthWcdma(),
            CellSignalStrengthTdscdma(),
            mock<CellSignalStrengthLte> {
                on { isValid } doReturn true
                on { dbm } doReturn 50
                on { asuLevel } doReturn 51
            },
            CellSignalStrengthNr(),
        )

        val signalStrength = repository.signalStrengthDisplayFlow(SUB_ID).firstWithTimeoutOrNull()

        assertThat(signalStrength).isEqualTo("50 dBm 51 asu")
    }

    private companion object {
        const val SUB_ID = 1
    }
}
Loading