/* * 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.datausage import android.app.Application import android.app.settings.SettingsEnums import android.content.Context import android.os.Bundle import android.telephony.SubscriptionManager import android.widget.Switch import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.android.settings.R import com.android.settings.SettingsActivity import com.android.settings.SettingsPreferenceFragment import com.android.settings.applications.AppStateBaseBridge import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState import com.android.settings.search.BaseSearchIndexProvider import com.android.settings.widget.SettingsMainSwitchBar import com.android.settingslib.applications.ApplicationsState import com.android.settingslib.search.SearchIndexable import com.android.settingslib.spa.framework.util.formatString import kotlinx.coroutines.launch @SearchIndexable class DataSaverSummary : SettingsPreferenceFragment() { private lateinit var switchBar: SettingsMainSwitchBar private lateinit var dataSaverBackend: DataSaverBackend private lateinit var unrestrictedAccess: Preference private var dataUsageBridge: AppStateDataUsageBridge? = null private var session: ApplicationsState.Session? = null // Flag used to avoid infinite loop due if user switch it on/off too quick. private var switching = false override fun onCreate(bundle: Bundle?) { super.onCreate(bundle) if (!requireContext().isDataSaverVisible()) { finishFragment() return } addPreferencesFromResource(R.xml.data_saver) unrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS)!! dataSaverBackend = DataSaverBackend(requireContext()) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) switchBar = (activity as SettingsActivity).switchBar.apply { setTitle(getString(R.string.data_saver_switch_title)) show() addOnSwitchChangeListener { _: Switch, isChecked: Boolean -> onSwitchChanged(isChecked) } } } override fun onResume() { super.onResume() dataSaverBackend.refreshAllowlist() dataSaverBackend.refreshDenylist() dataSaverBackend.addListener(dataSaverBackendListener) dataUsageBridge?.resume(/* forceLoadAllApps= */ true) ?: viewLifecycleOwner.lifecycleScope.launch { val applicationsState = ApplicationsState.getInstance( requireContext().applicationContext as Application ) dataUsageBridge = AppStateDataUsageBridge( applicationsState, dataUsageBridgeCallbacks, dataSaverBackend ) session = applicationsState.newSession(applicationsStateCallbacks, settingsLifecycle) dataUsageBridge?.resume(/* forceLoadAllApps= */ true) } } override fun onPause() { super.onPause() dataSaverBackend.remListener(dataSaverBackendListener) dataUsageBridge?.pause() } private fun onSwitchChanged(isChecked: Boolean) { synchronized(this) { if (!switching) { switching = true dataSaverBackend.isDataSaverEnabled = isChecked } } } override fun getMetricsCategory() = SettingsEnums.DATA_SAVER_SUMMARY override fun getHelpResource() = R.string.help_url_data_saver private val dataSaverBackendListener = object : DataSaverBackend.Listener { override fun onDataSaverChanged(isDataSaving: Boolean) { synchronized(this) { switchBar.isChecked = isDataSaving switching = false } } override fun onAllowlistStatusChanged(uid: Int, isAllowlisted: Boolean) {} override fun onDenylistStatusChanged(uid: Int, isDenylisted: Boolean) {} } private val dataUsageBridgeCallbacks = AppStateBaseBridge.Callback { updateUnrestrictedAccessSummary() } private val applicationsStateCallbacks = object : ApplicationsState.Callbacks { override fun onRunningStateChanged(running: Boolean) {} override fun onPackageListChanged() {} override fun onRebuildComplete(apps: ArrayList?) {} override fun onPackageIconChanged() {} override fun onPackageSizeChanged(packageName: String?) {} override fun onAllSizesComputed() { updateUnrestrictedAccessSummary() } override fun onLauncherInfoChanged() { updateUnrestrictedAccessSummary() } override fun onLoadEntriesCompleted() {} } private fun updateUnrestrictedAccessSummary() { if (!isAdded || isFinishingOrDestroyed) return val allApps = session?.allApps ?: return val count = allApps.count { ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(it) && (it.extraInfo as? DataUsageState)?.isDataSaverAllowlisted == true } unrestrictedAccess.summary = resources.formatString(R.string.data_saver_unrestricted_summary, "count" to count) } companion object { private const val KEY_UNRESTRICTED_ACCESS = "unrestricted_access" private fun Context.isDataSaverVisible(): Boolean = resources.getBoolean(R.bool.config_show_data_saver) @JvmField val SEARCH_INDEX_DATA_PROVIDER = object : BaseSearchIndexProvider(R.xml.data_saver) { override fun isPageSearchEnabled(context: Context): Boolean = context.isDataSaverVisible() && DataUsageUtils.hasMobileData(context) && (DataUsageUtils.getDefaultSubscriptionId(context) != SubscriptionManager.INVALID_SUBSCRIPTION_ID) } } }