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

Commit 2ec38409 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Improve AutoSelectPreferenceController

Deprecate ServiceStateStatus (which could produce wrong value),
and replaced with serviceStateFlow.

Fix: 299068234
Test: manual - turn auto select off and on
Test: unit test
Change-Id: I42fe160500c68cc9ee0fe383121f64146ddbb7f2
parent 87f76d1c
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -210,7 +210,7 @@
            android:title="@string/network_operator_category"
            settings:controller="com.android.settings.network.telephony.NetworkPreferenceCategoryController">

            <SwitchPreferenceCompat
            <com.android.settings.spa.preference.ComposePreference
                android:key="auto_select_key"
                android:title="@string/select_automatically"
                settings:controller="com.android.settings.network.telephony.gsm.AutoSelectPreferenceController"/>
+1 −1
Original line number Diff line number Diff line
@@ -276,7 +276,7 @@ public class MobileNetworkSettings extends AbstractMobileNetworkSettings impleme
                use(OpenNetworkSelectPagePreferenceController.class).init(mSubId);
        final AutoSelectPreferenceController autoSelectPreferenceController =
                use(AutoSelectPreferenceController.class)
                        .init(getLifecycle(), mSubId)
                        .init(mSubId)
                        .addListener(openNetworkSelectPagePreferenceController);
        use(NetworkPreferenceCategoryController.class).init(mSubId)
                .setChildren(Arrays.asList(autoSelectPreferenceController));
+0 −339
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.gsm;

import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;

import com.android.settings.R;
import com.android.settings.network.AllowedNetworkTypesListener;
import com.android.settings.network.CarrierConfigCache;
import com.android.settings.network.helper.ServiceStateStatus;
import com.android.settings.network.telephony.MobileNetworkUtils;
import com.android.settings.network.telephony.TelephonyTogglePreferenceController;
import com.android.settingslib.utils.ThreadUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Preference controller for "Auto Select Network"
 */
public class AutoSelectPreferenceController extends TelephonyTogglePreferenceController
        implements LifecycleEventObserver{
    private static final long MINIMUM_DIALOG_TIME_MILLIS = TimeUnit.SECONDS.toMillis(1);
    private static final String LOG_TAG = "AutoSelectPreferenceController";
    private static final String INTERNAL_LOG_TAG_ONRESUME = "OnResume";
    private static final String INTERNAL_LOG_TAG_AFTERSET = "AfterSet";

    private final Handler mUiHandler;
    private PreferenceScreen mPreferenceScreen;
    private AllowedNetworkTypesListener mAllowedNetworkTypesListener;
    private TelephonyManager mTelephonyManager;
    private boolean mOnlyAutoSelectInHome;
    private List<OnNetworkSelectModeListener> mListeners;
    @VisibleForTesting
    ProgressDialog mProgressDialog;
    @VisibleForTesting
    TwoStatePreference mSwitchPreference;
    private AtomicBoolean mUpdatingConfig;
    private int mCacheOfModeStatus;
    private AtomicLong mRecursiveUpdate;
    ServiceStateStatus mServiceStateStatus;

    public AutoSelectPreferenceController(Context context, String key) {
        super(context, key);
        mTelephonyManager = context.getSystemService(TelephonyManager.class);
        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
        mRecursiveUpdate = new AtomicLong();
        mUpdatingConfig = new AtomicBoolean();
        mCacheOfModeStatus = TelephonyManager.NETWORK_SELECTION_MODE_UNKNOWN;
        mListeners = new ArrayList<>();
        mUiHandler = new Handler(Looper.getMainLooper());
        mAllowedNetworkTypesListener = new AllowedNetworkTypesListener(
                new HandlerExecutor(mUiHandler));
        mAllowedNetworkTypesListener.setAllowedNetworkTypesListener(
                () -> updatePreference());
    }

    private void updatePreference() {
        if (mPreferenceScreen != null) {
            displayPreference(mPreferenceScreen);
        }
        if (mSwitchPreference != null) {
            mRecursiveUpdate.getAndIncrement();
            updateState(mSwitchPreference);
            mRecursiveUpdate.decrementAndGet();
        }
    }

    /**
     * Implementation of LifecycleEventObserver.
     */
    @SuppressWarnings("FutureReturnValueIgnored")
    public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
            @NonNull Lifecycle.Event event) {
        switch (event) {
            case ON_START:
                mAllowedNetworkTypesListener.register(mContext, mSubId);
                break;
            case ON_RESUME:
                ThreadUtils.postOnBackgroundThread(() -> {
                    queryNetworkSelectionMode(INTERNAL_LOG_TAG_ONRESUME);
                    //Update UI in UI thread
                    mUiHandler.post(() -> {
                        if (mSwitchPreference != null) {
                            mRecursiveUpdate.getAndIncrement();
                            mSwitchPreference.setChecked(isChecked());
                            mRecursiveUpdate.decrementAndGet();
                            updateListenerValue();
                        }
                    });
                });
                break;
            case ON_STOP:
                mAllowedNetworkTypesListener.unregister(mContext, mSubId);
                break;
            default:
                // Do nothing
                break;
        }
    }

    @Override
    public int getAvailabilityStatus(int subId) {
        return MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId)
                ? AVAILABLE
                : CONDITIONALLY_UNAVAILABLE;
    }

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

    @Override
    public boolean isChecked() {
        return mCacheOfModeStatus == TelephonyManager.NETWORK_SELECTION_MODE_AUTO;
    }

    @Override
    public void updateState(Preference preference) {
        super.updateState(preference);

        preference.setSummary(null);
        final ServiceState serviceState = mTelephonyManager.getServiceState();
        if (serviceState == null) {
            preference.setEnabled(false);
            return;
        }

        if (serviceState.getRoaming()) {
            preference.setEnabled(true);
        } else {
            preference.setEnabled(!mOnlyAutoSelectInHome);
            if (mOnlyAutoSelectInHome) {
                preference.setSummary(mContext.getString(
                        R.string.manual_mode_disallowed_summary,
                        mTelephonyManager.getSimOperatorName()));
            }
        }
    }

    @Override
    public boolean setChecked(boolean isChecked) {
        if (mRecursiveUpdate.get() != 0) {
            // Changing from software are allowed and changing presentation only.
            return true;
        }
        if (isChecked) {
            setAutomaticSelectionMode();
        } else {
            if (mSwitchPreference != null) {
                Intent intent = new Intent();
                intent.setClassName(SETTINGS_PACKAGE_NAME,
                        SETTINGS_PACKAGE_NAME + ".Settings$NetworkSelectActivity");
                intent.putExtra(Settings.EXTRA_SUB_ID, mSubId);
                mSwitchPreference.setIntent(intent);
            }
        }
        return false;
    }

    @VisibleForTesting
    Future setAutomaticSelectionMode() {
        final long startMillis = SystemClock.elapsedRealtime();
        showAutoSelectProgressBar();
        if (mSwitchPreference != null) {
            mSwitchPreference.setIntent(null);
            mSwitchPreference.setEnabled(false);
        }
        return ThreadUtils.postOnBackgroundThread(() -> {
            // set network selection mode in background
            mUpdatingConfig.set(true);
            mTelephonyManager.setNetworkSelectionModeAutomatic();
            mUpdatingConfig.set(false);

            //Update UI in UI thread
            final long durationMillis = SystemClock.elapsedRealtime() - startMillis;

            mUiHandler.postDelayed(() -> {
                ThreadUtils.postOnBackgroundThread(() -> {
                    queryNetworkSelectionMode(INTERNAL_LOG_TAG_AFTERSET);

                    //Update UI in UI thread
                    mUiHandler.post(() -> {
                        mRecursiveUpdate.getAndIncrement();
                        if (mSwitchPreference != null) {
                            mSwitchPreference.setEnabled(true);
                            mSwitchPreference.setChecked(isChecked());
                        }
                        mRecursiveUpdate.decrementAndGet();
                        updateListenerValue();
                        dismissProgressBar();
                    });
                });
            }, Math.max(MINIMUM_DIALOG_TIME_MILLIS - durationMillis, 0));
        });
    }

    /**
     * Initialization based on given subscription id.
     **/
    public AutoSelectPreferenceController init(Lifecycle lifecycle, int subId) {
        mSubId = subId;
        mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
                .createForSubscriptionId(mSubId);
        final PersistableBundle carrierConfig =
                CarrierConfigCache.getInstance(mContext).getConfigForSubId(mSubId);
        mOnlyAutoSelectInHome = carrierConfig != null
                ? carrierConfig.getBoolean(
                CarrierConfigManager.KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL)
                : false;

        mServiceStateStatus = new ServiceStateStatus(lifecycle, mTelephonyManager,
                new HandlerExecutor(mUiHandler)) {
            @Override
            protected void setValue(ServiceState status) {
                if (status == null) {
                    return;
                }
                updateUiAutoSelectValue(status);
            }
        };
        return this;
    }

    public AutoSelectPreferenceController addListener(OnNetworkSelectModeListener lsn) {
        mListeners.add(lsn);

        return this;
    }

    private void queryNetworkSelectionMode(String tag) {
        mCacheOfModeStatus = mTelephonyManager.getNetworkSelectionMode();
        Log.d(LOG_TAG, tag + ": query command done. mCacheOfModeStatus: " + mCacheOfModeStatus);
    }

    @VisibleForTesting
    void updateUiAutoSelectValue(ServiceState status) {
        if (status == null) {
            return;
        }
        if (!mUpdatingConfig.get()) {
            int networkSelectionMode = status.getIsManualSelection()
                    ? TelephonyManager.NETWORK_SELECTION_MODE_MANUAL
                    : TelephonyManager.NETWORK_SELECTION_MODE_AUTO;
            if (mCacheOfModeStatus == networkSelectionMode) {
                return;
            }
            mCacheOfModeStatus = networkSelectionMode;
            Log.d(LOG_TAG, "updateUiAutoSelectValue: mCacheOfModeStatus: " + mCacheOfModeStatus);

            mRecursiveUpdate.getAndIncrement();
            updateState(mSwitchPreference);
            mRecursiveUpdate.decrementAndGet();
            updateListenerValue();
        }
    }

    private void updateListenerValue() {
        for (OnNetworkSelectModeListener lsn : mListeners) {
            lsn.onNetworkSelectModeUpdated(mCacheOfModeStatus);
        }
    }

    private void showAutoSelectProgressBar() {
        if (mProgressDialog == null) {
            mProgressDialog = new ProgressDialog(mContext);
            mProgressDialog.setMessage(
                    mContext.getResources().getString(R.string.register_automatically));
            mProgressDialog.setCanceledOnTouchOutside(false);
            mProgressDialog.setCancelable(false);
            mProgressDialog.setIndeterminate(true);
        }
        mProgressDialog.show();
    }

    private void dismissProgressBar() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            try {
                mProgressDialog.dismiss();
            } catch (IllegalArgumentException e) {
                // Ignore exception since the dialog will be gone anyway.
            }
        }
    }

    /**
     * Callback when network select mode might get updated
     *
     * @see TelephonyManager#getNetworkSelectionMode()
     */
    public interface OnNetworkSelectModeListener {
        void onNetworkSelectModeUpdated(int mode);
    }
}
+224 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.gsm

import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
import android.os.PersistableBundle
import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.ServiceState
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.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.Settings.NetworkSelectActivity
import com.android.settings.network.CarrierConfigCache
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.network.telephony.allowedNetworkTypesFlow
import com.android.settings.network.telephony.serviceStateFlow
import com.android.settings.spa.preference.ComposePreferenceController
import com.android.settingslib.spa.framework.compose.OverridableFlow
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import kotlin.properties.Delegates.notNull
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
 * Preference controller for "Auto Select Network"
 */
class AutoSelectPreferenceController @JvmOverloads constructor(
    context: Context,
    key: String,
    private val allowedNetworkTypesFlowFactory: (subId: Int) -> Flow<Long> =
        context::allowedNetworkTypesFlow,
    private val serviceStateFlowFactory: (subId: Int) -> Flow<ServiceState> =
        context::serviceStateFlow,
    private val getConfigForSubId: (subId: Int) -> PersistableBundle = { subId ->
        CarrierConfigCache.getInstance(context).getConfigForSubId(subId)
    },
) : ComposePreferenceController(context, key) {

    private lateinit var telephonyManager: TelephonyManager
    private val listeners = mutableListOf<OnNetworkSelectModeListener>()

    @VisibleForTesting
    var progressDialog: ProgressDialog? = null

    private lateinit var preference: Preference

    private var subId by notNull<Int>()

    /**
     * Initialization based on given subscription id.
     */
    fun init(subId: Int): AutoSelectPreferenceController {
        this.subId = subId
        telephonyManager = mContext.getSystemService(TelephonyManager::class.java)!!
            .createForSubscriptionId(subId)

        return this
    }

    override fun getAvailabilityStatus() =
        if (MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId)) AVAILABLE
        else CONDITIONALLY_UNAVAILABLE

    override fun displayPreference(screen: PreferenceScreen) {
        super.displayPreference(screen)
        preference = screen.findPreference(preferenceKey)!!
    }

    @Composable
    override fun Content() {
        val coroutineScope = rememberCoroutineScope()
        val serviceStateFlow = remember {
            serviceStateFlowFactory(subId)
                .stateIn(coroutineScope, SharingStarted.Lazily, null)
                .filterNotNull()
        }
        val isAutoOverridableFlow = remember {
            OverridableFlow(serviceStateFlow.map { !it.isManualSelection })
        }
        val isAuto by isAutoOverridableFlow.flow
            .onEach(::updateListenerValue)
            .collectAsStateWithLifecycle(initialValue = null)
        val disallowedSummary by serviceStateFlow.map(::getDisallowedSummary)
            .collectAsStateWithLifecycle(initialValue = "")
        SwitchPreference(object : SwitchPreferenceModel {
            override val title = stringResource(R.string.select_automatically)
            override val summary = { disallowedSummary }
            override val changeable = { disallowedSummary.isEmpty() }
            override val checked = { isAuto }
            override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
                if (newChecked) {
                    coroutineScope.launch { setAutomaticSelectionMode(isAutoOverridableFlow) }
                } else {
                    mContext.startActivity(Intent().apply {
                        setClass(mContext, NetworkSelectActivity::class.java)
                        putExtra(Settings.EXTRA_SUB_ID, subId)
                    })
                }
            }
        })
    }

    private suspend fun getDisallowedSummary(serviceState: ServiceState): String =
        withContext(Dispatchers.Default) {
            if (!serviceState.roaming && onlyAutoSelectInHome()) {
                mContext.getString(
                    R.string.manual_mode_disallowed_summary,
                    telephonyManager.simOperatorName
                )
            } else ""
        }

    private fun onlyAutoSelectInHome(): Boolean =
        getConfigForSubId(subId)
            .getBoolean(CarrierConfigManager.KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL)

    private suspend fun setAutomaticSelectionMode(overrideChannel: OverridableFlow<Boolean>) {
        showAutoSelectProgressBar()

        withContext(Dispatchers.Default) {
            val minimumDialogTimeDeferred = async { delay(MINIMUM_DIALOG_TIME) }
            telephonyManager.setNetworkSelectionModeAutomatic()
            minimumDialogTimeDeferred.await()
        }
        overrideChannel.override(true)

        dismissProgressBar()
    }

    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
        allowedNetworkTypesFlowFactory(subId).collectLatestWithLifecycle(viewLifecycleOwner) {
            preference.isVisible = withContext(Dispatchers.Default) {
                MobileNetworkUtils.shouldDisplayNetworkSelectOptions(mContext, subId)
            }
        }
    }

    fun addListener(listener: OnNetworkSelectModeListener): AutoSelectPreferenceController {
        listeners.add(listener)
        return this
    }

    private fun updateListenerValue(isAuto: Boolean) {
        for (listener in listeners) {
            listener.onNetworkSelectModeUpdated(
                if (isAuto) TelephonyManager.NETWORK_SELECTION_MODE_AUTO
                else TelephonyManager.NETWORK_SELECTION_MODE_MANUAL
            )
        }
    }

    private fun showAutoSelectProgressBar() {
        if (progressDialog == null) {
            progressDialog = ProgressDialog(mContext).apply {
                setMessage(mContext.resources.getString(R.string.register_automatically))
                setCanceledOnTouchOutside(false)
                setCancelable(false)
                isIndeterminate = true
            }
        }
        progressDialog?.show()
    }

    private fun dismissProgressBar() {
        if (progressDialog?.isShowing == true) {
            try {
                progressDialog?.dismiss()
            } catch (e: IllegalArgumentException) {
                // Ignore exception since the dialog will be gone anyway.
            }
        }
    }

    /**
     * Callback when network select mode might get updated
     *
     * @see TelephonyManager.getNetworkSelectionMode
     */
    interface OnNetworkSelectModeListener {
        fun onNetworkSelectModeUpdated(mode: Int)
    }

    companion object {
        private val MINIMUM_DIALOG_TIME = 1.seconds
    }
}
+197 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading