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

Commit 275fbccd authored by Chaohui Wang's avatar Chaohui Wang Committed by Android (Google) Code Review
Browse files

Merge "Create MobileNetworkSummaryRepository" into main

parents 38623b00 777a179b
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -52,9 +52,8 @@
        android:order="-15"
        settings:keywords="@string/keywords_more_mobile_networks"
        settings:userRestriction="no_config_mobile_networks"
        settings:isPreferenceVisible="@bool/config_show_sim_info"
        settings:useAdminDisabledSummary="true"
        settings:searchable="@bool/config_show_sim_info"/>
        settings:controller="com.android.settings.network.MobileNetworkSummaryController" />

    <com.android.settingslib.RestrictedSwitchPreference
        android:key="airplane_mode"
+0 −219
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;

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

import android.content.Context;
import android.content.Intent;
import android.telephony.SubscriptionManager;
import android.telephony.euicc.EuiccManager;

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.network.telephony.SimRepository;
import com.android.settings.network.telephony.euicc.EuiccRepository;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import com.android.settingslib.mobile.dataservice.UiccInfoEntity;

import java.util.List;
import java.util.stream.Collectors;

public class MobileNetworkSummaryController extends AbstractPreferenceController implements
        LifecycleObserver, PreferenceControllerMixin,
        MobileNetworkRepository.MobileNetworkCallback {
    private static final String TAG = "MobileNetSummaryCtlr";

    private static final String KEY = "mobile_network_list";

    private final MetricsFeatureProvider mMetricsFeatureProvider;
    private RestrictedPreference mPreference;

    private MobileNetworkRepository mMobileNetworkRepository;
    private List<SubscriptionInfoEntity> mSubInfoEntityList;
    private List<UiccInfoEntity> mUiccInfoEntityList;
    private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList;
    private boolean mIsAirplaneModeOn;
    private LifecycleOwner mLifecycleOwner;

    /**
     * This controls the summary text and click behavior of the "Mobile network" item on the
     * Network & internet page. There are 3 separate cases depending on the number of mobile network
     * subscriptions:
     * <ul>
     * <li>No subscription: click action begins a UI flow to add a network subscription, and
     * the summary text indicates this</li>
     *
     * <li>One subscription: click action takes you to details for that one network, and
     * the summary text is the network name</li>
     *
     * <li>More than one subscription: click action takes you to a page listing the subscriptions,
     * and the summary text gives the count of SIMs</li>
     * </ul>
     */
    public MobileNetworkSummaryController(Context context, Lifecycle lifecycle,
            LifecycleOwner lifecycleOwner) {
        super(context);
        mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
        mLifecycleOwner = lifecycleOwner;
        mMobileNetworkRepository = MobileNetworkRepository.getInstance(context);
        mIsAirplaneModeOn = mMobileNetworkRepository.isAirplaneModeOn();
        if (lifecycle != null) {
            lifecycle.addObserver(this);
        }
    }

    @OnLifecycleEvent(ON_RESUME)
    public void onResume() {
        mMobileNetworkRepository.addRegister(mLifecycleOwner, this,
                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
        mMobileNetworkRepository.updateEntity();
    }

    @OnLifecycleEvent(ON_PAUSE)
    public void onPause() {
        mMobileNetworkRepository.removeRegister(this);
    }

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

    @Override
    public CharSequence getSummary() {

        if ((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) || (
                mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty()) || (
                mMobileNetworkInfoEntityList == null || mMobileNetworkInfoEntityList.isEmpty())) {
            if (new EuiccRepository(mContext).showEuiccSettings()) {
                return mContext.getResources().getString(
                        R.string.mobile_network_summary_add_a_network);
            }
            // set empty string to override previous text for carrier when SIM available
            return "";
        } else if (mSubInfoEntityList.size() == 1) {
            SubscriptionInfoEntity info = mSubInfoEntityList.get(0);
            CharSequence displayName = info.uniqueName;
            if (info.isEmbedded || mUiccInfoEntityList.get(0).isActive
                    || mMobileNetworkInfoEntityList.get(0).showToggleForPhysicalSim) {
                return displayName;
            }
            return mContext.getString(R.string.mobile_network_tap_to_activate, displayName);
        } else {
            return mSubInfoEntityList.stream()
                    .map(SubscriptionInfoEntity::getUniqueDisplayName)
                    .collect(Collectors.joining(", "));
        }
    }

    private void logPreferenceClick(Preference preference) {
        mMetricsFeatureProvider.logClickedPreference(preference,
                preference.getExtras().getInt(DashboardFragment.CATEGORY));
    }

    private void startAddSimFlow() {
        final Intent intent = new Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION);
        intent.setPackage(com.android.settings.Utils.PHONE_PACKAGE_NAME);
        intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true);
        mContext.startActivity(intent);
    }

    private void initPreference() {
        refreshSummary(mPreference);
        mPreference.setOnPreferenceClickListener(null);
        mPreference.setFragment(null);
        mPreference.setEnabled(!mIsAirplaneModeOn);
    }

    private void update() {
        if (mPreference == null || mPreference.isDisabledByAdmin()) {
            return;
        }

        initPreference();
        if (((mSubInfoEntityList == null || mSubInfoEntityList.isEmpty())
                || (mUiccInfoEntityList == null || mUiccInfoEntityList.isEmpty())
                || (mMobileNetworkInfoEntityList == null
                || mMobileNetworkInfoEntityList.isEmpty()))) {
            if (new EuiccRepository(mContext).showEuiccSettings()) {
                mPreference.setOnPreferenceClickListener((Preference pref) -> {
                    logPreferenceClick(pref);
                    startAddSimFlow();
                    return true;
                });
            } else {
                mPreference.setEnabled(false);
            }
            return;
        }

        mPreference.setFragment(MobileNetworkListFragment.class.getCanonicalName());
    }

    @Override
    public boolean isAvailable() {
        return new SimRepository(mContext).showMobileNetworkPage();
    }

    @Override
    public String getPreferenceKey() {
        return KEY;
    }

    @Override
    public void onAirplaneModeChanged(boolean airplaneModeEnabled) {
        if (mIsAirplaneModeOn != airplaneModeEnabled) {
            mIsAirplaneModeOn = airplaneModeEnabled;
            update();
        }
    }

    @Override
    public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
        mSubInfoEntityList = subInfoEntityList;
        update();
    }

    @Override
    public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
        mUiccInfoEntityList = uiccInfoEntityList;
        update();
    }

    @Override
    public void onAllMobileNetworkInfoChanged(
            List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
        mMobileNetworkInfoEntityList = mobileNetworkInfoEntityList;
        update();
    }
}
+121 −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

import android.content.Context
import android.provider.Settings
import androidx.lifecycle.LifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.network.telephony.SimRepository
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settings.spa.network.startAddSimFlow
import com.android.settingslib.RestrictedPreference
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBooleanFlow
import kotlinx.coroutines.flow.Flow

/**
 * This controls the summary text and click behavior of the "Mobile network" item on the Network &
 * internet page. There are 2 separate cases depending on the number of mobile network
 * subscriptions:
 * - No subscription: click action begins a UI flow to add a network subscription, and the summary
 *   text indicates this
 * - Has subscriptions: click action takes you to a page listing the subscriptions, and the summary
 *   text gives the count of SIMs
 */
class MobileNetworkSummaryController
@JvmOverloads
constructor(
    private val context: Context,
    preferenceKey: String,
    private val repository: MobileNetworkSummaryRepository =
        MobileNetworkSummaryRepository(context),
    private val airplaneModeOnFlow: Flow<Boolean> =
        context.settingsGlobalBooleanFlow(Settings.Global.AIRPLANE_MODE_ON),
) : BasePreferenceController(context, preferenceKey) {
    private val metricsFeatureProvider = featureFactory.metricsFeatureProvider
    private var preference: RestrictedPreference? = null

    private var isAirplaneModeOn = false

    override fun getAvailabilityStatus() =
        if (SimRepository(mContext).showMobileNetworkPage()) AVAILABLE
        else CONDITIONALLY_UNAVAILABLE

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

    override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) {
        repository
            .subscriptionsStateFlow()
            .collectLatestWithLifecycle(viewLifecycleOwner, action = ::update)
        airplaneModeOnFlow.collectLatestWithLifecycle(viewLifecycleOwner) {
            isAirplaneModeOn = it
            updateEnabled()
        }
    }

    private fun update(state: MobileNetworkSummaryRepository.SubscriptionsState) {
        val preference = preference ?: return
        preference.onPreferenceClickListener = null
        preference.fragment = null
        when (state) {
            MobileNetworkSummaryRepository.AddNetwork -> {
                preference.summary =
                    context.getString(R.string.mobile_network_summary_add_a_network)
                preference.onPreferenceClickListener =
                    Preference.OnPreferenceClickListener {
                        logPreferenceClick()
                        startAddSimFlow(context)
                        true
                    }
            }

            MobileNetworkSummaryRepository.NoSubscriptions -> {
                preference.summary = null
            }

            is MobileNetworkSummaryRepository.HasSubscriptions -> {
                preference.summary = state.displayNames.joinToString(", ")
                preference.fragment = MobileNetworkListFragment::class.java.canonicalName
            }
        }
        updateEnabled()
    }

    private fun updateEnabled() {
        val preference = preference ?: return
        if (preference.isDisabledByAdmin) return
        preference.isEnabled =
            (preference.onPreferenceClickListener != null || preference.fragment != null) &&
                !isAirplaneModeOn
    }

    private fun logPreferenceClick() {
        val preference = preference ?: return
        metricsFeatureProvider.logClickedPreference(
            preference,
            preference.extras.getInt(DashboardFragment.CATEGORY),
        )
    }
}
+66 −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

import android.content.Context
import android.telephony.SubscriptionInfo
import com.android.settings.network.telephony.SubscriptionRepository
import com.android.settings.network.telephony.euicc.EuiccRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map

class MobileNetworkSummaryRepository(
    private val context: Context,
    private val subscriptionRepository: SubscriptionRepository = SubscriptionRepository(context),
    private val euiccRepository: EuiccRepository = EuiccRepository(context),
    private val getDisplayName: (SubscriptionInfo) -> String = { subInfo ->
        SubscriptionUtil.getUniqueSubscriptionDisplayName(subInfo, context).toString()
    },
) {
    sealed interface SubscriptionsState

    data object AddNetwork : SubscriptionsState

    data object NoSubscriptions : SubscriptionsState

    data class HasSubscriptions(val displayNames: List<String>) : SubscriptionsState

    fun subscriptionsStateFlow(): Flow<SubscriptionsState> =
        subDisplayNamesFlow()
            .map { displayNames ->
                if (displayNames.isEmpty()) {
                    if (euiccRepository.showEuiccSettings()) AddNetwork else NoSubscriptions
                } else {
                    HasSubscriptions(displayNames)
                }
            }
            .distinctUntilChanged()
            .conflate()
            .flowOn(Dispatchers.Default)

    private fun subDisplayNamesFlow(): Flow<List<String>> =
        subscriptionRepository
            .selectableSubscriptionInfoListFlow()
            .map { subInfos -> subInfos.map(getDisplayName) }
            .distinctUntilChanged()
            .conflate()
            .flowOn(Dispatchers.Default)
}
+4 −7
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;

import androidx.lifecycle.LifecycleOwner;
import androidx.annotation.Nullable;

import com.android.settings.R;
import com.android.settings.SettingsDumpService;
@@ -69,12 +69,11 @@ public class NetworkDashboardFragment extends DashboardFragment implements

    @Override
    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
        return buildPreferenceControllers(context, getSettingsLifecycle(),
                this /* LifecycleOwner */);
        return buildPreferenceControllers(context, getSettingsLifecycle());
    }

    private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
            Lifecycle lifecycle, LifecycleOwner lifecycleOwner) {
            @Nullable Lifecycle lifecycle) {
        final VpnPreferenceController vpnPreferenceController =
                new VpnPreferenceController(context);
        final PrivateDnsPreferenceController privateDnsPreferenceController =
@@ -87,7 +86,6 @@ public class NetworkDashboardFragment extends DashboardFragment implements

        final List<AbstractPreferenceController> controllers = new ArrayList<>();

        controllers.add(new MobileNetworkSummaryController(context, lifecycle, lifecycleOwner));
        controllers.add(vpnPreferenceController);
        controllers.add(privateDnsPreferenceController);

@@ -114,8 +112,7 @@ public class NetworkDashboardFragment extends DashboardFragment implements
                @Override
                public List<AbstractPreferenceController> createPreferenceControllers(Context
                        context) {
                    return buildPreferenceControllers(context, null /* lifecycle */,
                            null /* LifecycleOwner */);
                    return buildPreferenceControllers(context, null /* lifecycle */);
                }
            };
}
Loading