blob: 1d9cbb73a6683a018a034807a1223e3cd08cfeb8 [file] [log] [blame]
/*
* 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<ApplicationsState.AppEntry>?) {}
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)
}
}
}