/* * 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.datausage import android.content.Context import android.net.NetworkPolicy import android.net.NetworkTemplate import android.text.TextUtils import android.util.Log import androidx.lifecycle.LifecycleOwner import androidx.preference.PreferenceScreen import com.android.settings.R import com.android.settings.datausage.lib.DataUsageLib.getMobileTemplate import com.android.settings.datausage.lib.INetworkCycleDataRepository import com.android.settings.datausage.lib.NetworkCycleDataRepository import com.android.settings.network.ProxySubscriptionManager import com.android.settings.network.policy.NetworkPolicyRepository import com.android.settings.network.telephony.TelephonyBasePreferenceController import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle import kotlin.math.max import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext /** * This is the controller for a data usage header that retrieves carrier data from the new * subscriptions framework API if available. The controller reads subscription information from the * framework and falls back to legacy usage data if none are available. */ open class DataUsageSummaryPreferenceController @JvmOverloads constructor( context: Context, subId: Int, private val proxySubscriptionManager: ProxySubscriptionManager = ProxySubscriptionManager.getInstance(context), private val networkPolicyRepository: NetworkPolicyRepository = NetworkPolicyRepository(context), private val networkCycleDataRepositoryFactory: ( template: NetworkTemplate, ) -> INetworkCycleDataRepository = { NetworkCycleDataRepository(context, it) }, private val dataPlanRepositoryFactory: ( networkCycleDataRepository: INetworkCycleDataRepository, ) -> DataPlanRepository = { DataPlanRepositoryImpl(it) } ) : TelephonyBasePreferenceController(context, KEY) { init { mSubId = subId } private val subInfo by lazy { if (DataUsageUtils.hasMobileData(mContext)) { proxySubscriptionManager.getAccessibleSubscriptionInfo(mSubId) } else null } private val networkTemplate by lazy { getMobileTemplate(mContext, mSubId) } private val networkCycleDataRepository by lazy { networkCycleDataRepositoryFactory(networkTemplate) } private lateinit var preference: DataUsageSummaryPreference override fun getAvailabilityStatus(subId: Int) = if (subInfo != null) AVAILABLE else CONDITIONALLY_UNAVAILABLE override fun displayPreference(screen: PreferenceScreen) { super.displayPreference(screen) preference = screen.findPreference(preferenceKey)!! } override fun onViewCreated(viewLifecycleOwner: LifecycleOwner) { networkPolicyRepository.networkPolicyFlow(networkTemplate) .collectLatestWithLifecycle(viewLifecycleOwner) { policy -> preference.isVisible = subInfo != null && policy != null if (policy != null) update(policy) } } private suspend fun update(policy: NetworkPolicy) { preference.setLimitInfo(policy.getLimitInfo()) val dataBarSize = max(policy.limitBytes, policy.warningBytes) if (dataBarSize > NetworkPolicy.WARNING_DISABLED) { setDataBarSize(dataBarSize) } val dataPlanInfo = withContext(Dispatchers.Default) { dataPlanRepositoryFactory(networkCycleDataRepository).getDataPlanInfo( policy = policy, plans = proxySubscriptionManager.get().getSubscriptionPlans(mSubId), ) } Log.d(TAG, "dataPlanInfo: $dataPlanInfo") preference.setUsageNumbers(dataPlanInfo.dataPlanUse, dataPlanInfo.dataPlanSize) if (dataPlanInfo.dataBarSize > 0) { preference.setChartEnabled(true) setDataBarSize(dataPlanInfo.dataBarSize) preference.setProgress(dataPlanInfo.dataPlanUse / dataPlanInfo.dataBarSize.toFloat()) } else { preference.setChartEnabled(false) } preference.setUsageInfo( dataPlanInfo.cycleEnd, dataPlanInfo.snapshotTime, subInfo?.carrierName, dataPlanInfo.dataPlanCount, ) } private fun setDataBarSize(dataBarSize: Long) { preference.setLabels( DataUsageUtils.formatDataUsage(mContext, /* byteValue = */ 0), DataUsageUtils.formatDataUsage(mContext, dataBarSize) ) } private fun NetworkPolicy.getLimitInfo(): CharSequence? = when { warningBytes > 0 && limitBytes > 0 -> { TextUtils.expandTemplate( mContext.getText(R.string.cell_data_warning_and_limit), DataUsageUtils.formatDataUsage(mContext, warningBytes), DataUsageUtils.formatDataUsage(mContext, limitBytes), ) } warningBytes > 0 -> { TextUtils.expandTemplate( mContext.getText(R.string.cell_data_warning), DataUsageUtils.formatDataUsage(mContext, warningBytes), ) } limitBytes > 0 -> { TextUtils.expandTemplate( mContext.getText(R.string.cell_data_limit), DataUsageUtils.formatDataUsage(mContext, limitBytes), ) } else -> null } companion object { private const val TAG = "DataUsageSummaryPC" private const val KEY = "status_header" } }