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

Commit cfd401b0 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Fix crash in RoamingPreferenceController

With new MobileDataRepository.isDataRoamingEnabledFlow() to provide
the data instead of MobileNetworkRepository.

Fix: 347224962
Flag: EXEMPT bug fix
Test: manual on Mobile Settings
Test: unit test
Change-Id: I2a994cb11c93296fb46558f566d6d4467ba4c846
parent ef12f1dd
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -85,13 +85,9 @@
            android:summary="@string/auto_data_switch_summary"
            settings:controller="com.android.settings.network.telephony.AutoDataSwitchPreferenceController"/>

        <com.android.settingslib.RestrictedSwitchPreference
        <com.android.settings.spa.preference.ComposePreference
            android:key="button_roaming_key"
            android:title="@string/roaming"
            android:persistent="false"
            android:summaryOn="@string/roaming_enable"
            android:summaryOff="@string/roaming_disable"
            settings:userRestriction="no_data_roaming"
            settings:controller="com.android.settings.network.telephony.RoamingPreferenceController"/>

        <Preference
+12 −0
Original line number Diff line number Diff line
@@ -118,6 +118,18 @@ class MobileDataRepository(
        }
    }

    /** Creates an instance of a cold Flow for whether data roaming is enabled of given [subId]. */
    fun isDataRoamingEnabledFlow(subId: Int): Flow<Boolean> {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
        val telephonyManager = context.telephonyManager(subId)
        return mobileSettingsGlobalChangedFlow(Settings.Global.DATA_ROAMING, subId)
            .map { telephonyManager.isDataRoamingEnabled }
            .distinctUntilChanged()
            .conflate()
            .onEach { Log.d(TAG, "[$subId] isDataRoamingEnabledFlow: $it") }
            .flowOn(Dispatchers.Default)
    }

    private companion object {
        private const val TAG = "MobileDataRepository"
    }
+1 −5
Original line number Diff line number Diff line
@@ -79,7 +79,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
    @VisibleForTesting
    static final String KEY_CLICKED_PREF = "key_clicked_pref";

    private static final String KEY_ROAMING_PREF = "button_roaming_key";
    private static final String KEY_CALLS_PREF = "calls_preference";
    private static final String KEY_SMS_PREF = "sms_preference";
    private static final String KEY_MOBILE_DATA_PREF = "mobile_data_enable";
@@ -178,8 +177,6 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme

        return Arrays.asList(
                new DataUsageSummaryPreferenceController(context, mSubId),
                new RoamingPreferenceController(context, KEY_ROAMING_PREF, getSettingsLifecycle(),
                        this, mSubId),
                new CallsDefaultSubscriptionController(context, KEY_CALLS_PREF,
                        getSettingsLifecycle(), this),
                new SmsDefaultSubscriptionController(context, KEY_SMS_PREF, getSettingsLifecycle(),
@@ -263,8 +260,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
        final RoamingPreferenceController roamingPreferenceController =
                use(RoamingPreferenceController.class);
        if (roamingPreferenceController != null) {
            roamingPreferenceController.init(getFragmentManager(), mSubId,
                    mMobileNetworkInfoEntity);
            roamingPreferenceController.init(getParentFragmentManager(), mSubId);
        }
        final SatelliteSettingPreferenceController satelliteSettingPreferenceController = use(
                SatelliteSettingPreferenceController.class);
+0 −215
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;

import android.content.Context;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.network.MobileNetworkRepository;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;

import java.util.ArrayList;
import java.util.List;

/**
 * Preference controller for "Roaming"
 */
public class RoamingPreferenceController extends TelephonyTogglePreferenceController implements
        LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback {
    private static final String TAG = "RoamingController";
    private static final String DIALOG_TAG = "MobileDataDialog";

    private RestrictedSwitchPreference mSwitchPreference;
    private TelephonyManager mTelephonyManager;
    private CarrierConfigManager mCarrierConfigManager;
    protected MobileNetworkRepository mMobileNetworkRepository;
    protected LifecycleOwner mLifecycleOwner;
    private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList = new ArrayList<>();

    @VisibleForTesting
    FragmentManager mFragmentManager;
    MobileNetworkInfoEntity mMobileNetworkInfoEntity;

    public RoamingPreferenceController(Context context, String key, Lifecycle lifecycle,
            LifecycleOwner lifecycleOwner, int subId) {
        this(context, key);
        mSubId = subId;
        mLifecycleOwner = lifecycleOwner;
        if (lifecycle != null) {
            lifecycle.addObserver(this);
        }
    }

    public RoamingPreferenceController(Context context, String key) {
        super(context, key);
        mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
        mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
    }

    @Override
    public int getAvailabilityStatus() {
        final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
        if (carrierConfig != null && carrierConfig.getBoolean(
                CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL)) {
            return CONDITIONALLY_UNAVAILABLE;
        }
        return AVAILABLE;
    }

    @OnLifecycleEvent(ON_START)
    public void onStart() {
        mMobileNetworkRepository.addRegister(mLifecycleOwner, this, mSubId);
        mMobileNetworkRepository.updateEntity();
    }

    @OnLifecycleEvent(ON_STOP)
    public void onStop() {
        mMobileNetworkRepository.removeRegister(this);
    }

    @Override
    public void displayPreference(PreferenceScreen screen) {
        super.displayPreference(screen);
        mSwitchPreference = screen.findPreference(getPreferenceKey());
    }

    @Override
    public int getAvailabilityStatus(int subId) {
        return mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
                ? AVAILABLE
                : AVAILABLE_UNSEARCHABLE;
    }

    @Override
    public boolean setChecked(boolean isChecked) {
        if (isDialogNeeded()) {
            showDialog();
        } else {
            // Update data directly if we don't need dialog
            mTelephonyManager.setDataRoamingEnabled(isChecked);
            return true;
        }

        return false;
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);
        mSwitchPreference = (RestrictedSwitchPreference) preference;
        update();
    }

    private void update() {
        if (mSwitchPreference == null) {
            return;
        }
        if (!mSwitchPreference.isDisabledByAdmin()) {
            mSwitchPreference.setEnabled(mSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
            mSwitchPreference.setChecked(isChecked());
        }
    }

    @VisibleForTesting
    boolean isDialogNeeded() {
        final boolean isRoamingEnabled = mMobileNetworkInfoEntity == null ? false
                : mMobileNetworkInfoEntity.isDataRoamingEnabled;
        final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(
                mSubId);
        // Need dialog if we need to turn on roaming and the roaming charge indication is allowed
        if (!isRoamingEnabled && (carrierConfig == null || !carrierConfig.getBoolean(
                CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL))) {
            return true;
        }
        return false;
    }

    @Override
    public boolean isChecked() {
        return mMobileNetworkInfoEntity == null ? false
                : mMobileNetworkInfoEntity.isDataRoamingEnabled;
    }

    public void init(FragmentManager fragmentManager, int subId, MobileNetworkInfoEntity entity) {
        mFragmentManager = fragmentManager;
        mSubId = subId;
        mMobileNetworkInfoEntity = entity;
        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
        if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
            return;
        }
        final TelephonyManager telephonyManager = mTelephonyManager
                .createForSubscriptionId(mSubId);
        if (telephonyManager == null) {
            Log.w(TAG, "fail to init in sub" + mSubId);
            mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
            return;
        }
        mTelephonyManager = telephonyManager;
    }

    private void showDialog() {
        final RoamingDialogFragment dialogFragment = RoamingDialogFragment.newInstance(mSubId);

        dialogFragment.show(mFragmentManager, DIALOG_TAG);
    }

    @VisibleForTesting
    public void setMobileNetworkInfoEntity(MobileNetworkInfoEntity mobileNetworkInfoEntity) {
        mMobileNetworkInfoEntity = mobileNetworkInfoEntity;
    }

    @Override
    public void onAllMobileNetworkInfoChanged(
            List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
        mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
        mMobileNetworkInfoEntityList.forEach(entity -> {
            if (Integer.parseInt(entity.subId) == mSubId) {
                mMobileNetworkInfoEntity = entity;
                update();
                refreshSummary(mSwitchPreference);
                return;
            }
        });
    }

    @Override
    public void onDataRoamingChanged(int subId, boolean enabled) {
        if (subId != mSubId) {
            Log.d(TAG, "onDataRoamingChanged - wrong subId : " + subId + " / " + enabled);
            return;
        }
        update();
    }
}
+105 −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.network.telephony

import android.content.Context
import android.os.UserManager
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference

/** Preference controller for "Roaming" */
class RoamingPreferenceController
@JvmOverloads
constructor(
    context: Context,
    key: String,
    private val mobileDataRepository: MobileDataRepository = MobileDataRepository(context),
) : ComposePreferenceController(context, key) {
    @VisibleForTesting var fragmentManager: FragmentManager? = null
    private var subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID

    private var telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
    private val carrierConfigRepository = CarrierConfigRepository(context)

    fun init(fragmentManager: FragmentManager, subId: Int) {
        this.fragmentManager = fragmentManager
        this.subId = subId
        telephonyManager = telephonyManager.createForSubscriptionId(subId)
    }

    override fun getAvailabilityStatus(): Int {
        if (!SubscriptionManager.isValidSubscriptionId(subId)) return CONDITIONALLY_UNAVAILABLE
        val isForceHomeNetwork =
            carrierConfigRepository.getBoolean(
                subId, CarrierConfigManager.KEY_FORCE_HOME_NETWORK_BOOL)

        return if (isForceHomeNetwork) CONDITIONALLY_UNAVAILABLE else AVAILABLE
    }

    @Composable
    override fun Content() {
        val summary = stringResource(R.string.roaming_enable)
        val isDataRoamingEnabled by
            remember { mobileDataRepository.isDataRoamingEnabledFlow(subId) }
                .collectAsStateWithLifecycle(null)
        RestrictedSwitchPreference(
            model =
                object : SwitchPreferenceModel {
                    override val title = stringResource(R.string.roaming)
                    override val summary = { summary }
                    override val checked = { isDataRoamingEnabled }
                    override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
                        if (newChecked && isDialogNeeded()) {
                            showDialog()
                        } else {
                            // Update data directly if we don't need dialog
                            telephonyManager.isDataRoamingEnabled = newChecked
                        }
                    }
                },
            restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_DATA_ROAMING)),
        )
    }

    @VisibleForTesting
    fun isDialogNeeded(): Boolean {
        // Need dialog if we need to turn on roaming and the roaming charge indication is allowed
        return !carrierConfigRepository.getBoolean(
            subId, CarrierConfigManager.KEY_DISABLE_CHARGE_INDICATION_BOOL)
    }

    private fun showDialog() {
        fragmentManager?.let { RoamingDialogFragment.newInstance(subId).show(it, DIALOG_TAG) }
    }

    companion object {
        private const val DIALOG_TAG = "MobileDataDialog"
    }
}
Loading