Files
app_Settings/src/com/android/settings/datausage/DataSaverSummary.kt
Chaohui Wang 68d638707b Fix Data Saver page crashed when rotate
This is because before fix mLoadAppRunnable is async run. And the
getContext() within it will return null when the Fragment in some not
ready lifecycle.

Use viewLifecycleOwner.lifecycleScope.launch to ensure the async
function will only be run when the view is ready, and automatically
canceled when out of scope.

Since this requires Kotlin Coroutine so migrate DataSaverSummary to
Kotlin, other functionality are keep no change.

Fix: 279863347
Test: Manual
Change-Id: I2e97a071c103e63b3306b801fc38f4704e3be0d2
2023-04-27 20:08:19 +08:00

176 lines
6.5 KiB
Kotlin

/*
* 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)
}
}
}