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

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

Merge "Improve the loading time of DataSaverSummary" into udc-qpr-dev

parents 506b8af1 cb9374e2
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -196,8 +196,10 @@ public class DataSaverBackend {
    public interface Listener {
        void onDataSaverChanged(boolean isDataSaving);

        void onAllowlistStatusChanged(int uid, boolean isAllowlisted);
        /** This is called when allow list status is changed. */
        default void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {}

        void onDenylistStatusChanged(int uid, boolean isDenylisted);
        /** This is called when deny list status is changed. */
        default void onDenylistStatusChanged(int uid, boolean isDenylisted) {}
    }
}
+37 −62
Original line number Diff line number Diff line
@@ -15,33 +15,36 @@
 */
package com.android.settings.datausage

import android.app.Application
import android.app.settings.SettingsEnums
import android.content.Context
import android.net.NetworkPolicyManager
import android.os.Bundle
import android.os.UserHandle
import android.telephony.SubscriptionManager
import android.widget.Switch
import androidx.annotation.VisibleForTesting
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 com.android.settingslib.spaprivileged.model.app.AppListRepository
import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@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
@@ -72,27 +75,15 @@ class DataSaverSummary : SettingsPreferenceFragment() {

    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)
        viewLifecycleOwner.lifecycleScope.launch {
            unrestrictedAccess.summary = getUnrestrictedSummary(requireContext())
        }
    }

    override fun onPause() {
        super.onPause()
        dataSaverBackend.remListener(dataSaverBackendListener)
        dataUsageBridge?.pause()
    }

    private fun onSwitchChanged(isChecked: Boolean) {
@@ -115,52 +106,36 @@ class DataSaverSummary : SettingsPreferenceFragment() {
                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()
        }
    companion object {
        private const val KEY_UNRESTRICTED_ACCESS = "unrestricted_access"

        override fun onLauncherInfoChanged() {
            updateUnrestrictedAccessSummary()
        }
        @VisibleForTesting
        suspend fun getUnrestrictedSummary(
            context: Context,
            appListRepository: AppListRepository =
                AppListRepositoryImpl(context.applicationContext),
        ) = context.formatString(
            R.string.data_saver_unrestricted_summary,
            "count" to getAllowCount(context.applicationContext, appListRepository),
        )

        override fun onLoadEntriesCompleted() {}
        private suspend fun getAllowCount(context: Context, appListRepository: AppListRepository) =
            withContext(Dispatchers.IO) {
                coroutineScope {
                    val appsDeferred = async {
                        appListRepository.loadAndFilterApps(
                            userId = UserHandle.myUserId(),
                            isSystemApp = false,
                        )
                    }

    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
                    val uidsAllowed = NetworkPolicyManager.from(context)
                        .getUidsWithPolicy(NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND)
                    appsDeferred.await().count { app -> app.uid in uidsAllowed }
                }
        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)

+109 −0
Original line number Diff line number Diff line
/*
 * 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.content.Context
import android.content.pm.ApplicationInfo
import android.net.NetworkPolicyManager
import android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.datausage.DataSaverSummary.Companion.getUnrestrictedSummary
import com.android.settingslib.spaprivileged.model.app.AppListRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class DataSaverSummaryTest {
    @get:Rule
    val mockito: MockitoRule = MockitoJUnit.rule()

    @Spy
    private val context: Context = ApplicationProvider.getApplicationContext()

    @Mock
    private lateinit var networkPolicyManager: NetworkPolicyManager

    @Before
    fun setUp() {
        whenever(context.applicationContext).thenReturn(context)
        whenever(NetworkPolicyManager.from(context)).thenReturn(networkPolicyManager)
    }

    @Test
    fun getUnrestrictedSummary_whenTwoAppsAllowed() = runTest {
        whenever(
            networkPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)
        ).thenReturn(intArrayOf(APP1.uid, APP2.uid))

        val summary =
            getUnrestrictedSummary(context = context, appListRepository = FakeAppListRepository)

        assertThat(summary)
            .isEqualTo("2 apps allowed to use unrestricted data when Data Saver is on")
    }

    @Test
    fun getUnrestrictedSummary_whenNoAppsAllowed() = runTest {
        whenever(
            networkPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND)
        ).thenReturn(intArrayOf())

        val summary =
            getUnrestrictedSummary(context = context, appListRepository = FakeAppListRepository)

        assertThat(summary)
            .isEqualTo("0 apps allowed to use unrestricted data when Data Saver is on")
    }

    private companion object {
        val APP1 = ApplicationInfo().apply { uid = 10001 }
        val APP2 = ApplicationInfo().apply { uid = 10002 }
        val APP3 = ApplicationInfo().apply { uid = 10003 }

        object FakeAppListRepository : AppListRepository {
            override suspend fun loadApps(
                userId: Int,
                loadInstantApps: Boolean,
                matchAnyUserForAdmin: Boolean,
            ) = emptyList<ApplicationInfo>()

            override fun showSystemPredicate(
                userIdFlow: Flow<Int>,
                showSystemFlow: Flow<Boolean>,
            ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { false }

            override fun getSystemPackageNamesBlocking(userId: Int): Set<String> = emptySet()

            override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) =
                listOf(APP1, APP2, APP3)
        }
    }
}
 No newline at end of file