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

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

Merge "Refresh the AppList for changes"

parents 0c1fb3ca 7c3abc7a
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.settingslib.spa.testutils

import java.util.concurrent.TimeoutException

/**
 * Blocks until the given condition is satisfied.
 */
fun waitUntil(timeoutMillis: Long = 1000, condition: () -> Boolean) {
    val startTime = System.currentTimeMillis()
    while (!condition()) {
        // Let Android run measure, draw and in general any other async operations.
        Thread.sleep(10)
        if (System.currentTimeMillis() - startTime > timeoutMillis) {
            throw TimeoutException("Condition still not satisfied after $timeoutMillis ms")
        }
    }
}
+9 −9
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.content.IntentFilter
import android.os.UserHandle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
@@ -34,24 +33,25 @@ import androidx.lifecycle.LifecycleEventObserver
 */
@Composable
fun DisposableBroadcastReceiverAsUser(
    userId: Int,
    intentFilter: IntentFilter,
    userHandle: UserHandle,
    onStart: () -> Unit = {},
    onReceive: (Intent) -> Unit,
) {
    val broadcastReceiver = remember {
        object : BroadcastReceiver() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val broadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                onReceive(intent)
            }
        }
    }
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                context.registerReceiverAsUser(
                    broadcastReceiver, UserHandle.of(userId), intentFilter, null, null)
                    broadcastReceiver, userHandle, intentFilter, null, null
                )
                onStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                context.unregisterReceiver(broadcastReceiver)
            }
+30 −27
Original line number Diff line number Diff line
@@ -21,13 +21,10 @@ import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map

/**
 * The config used to load the App List.
@@ -40,15 +37,22 @@ internal data class AppListConfig(
/**
 * The repository to load the App List data.
 */
internal class AppListRepository(context: Context) {
    private val packageManager = context.packageManager
internal interface AppListRepository {
    /** Loads the list of [ApplicationInfo]. */
    suspend fun loadApps(config: AppListConfig): List<ApplicationInfo>

    fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
        .map { loadApps(it) }
        .flowOn(Dispatchers.Default)
    /** Gets the flow of predicate that could used to filter system app. */
    fun showSystemPredicate(
        userIdFlow: Flow<Int>,
        showSystemFlow: Flow<Boolean>,
    ): Flow<(app: ApplicationInfo) -> Boolean>
}

    private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
        return coroutineScope {

internal class AppListRepositoryImpl(context: Context) : AppListRepository {
    private val packageManager = context.packageManager

    override suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> = coroutineScope {
        val hiddenSystemModulesDeferred = async {
            packageManager.getInstalledModules(0)
                .filter { it.isHidden }
@@ -67,9 +71,8 @@ internal class AppListRepository(context: Context) {
            app.isInAppList(config.showInstantApps, hiddenSystemModules)
        }
    }
    }

    fun showSystemPredicate(
    override fun showSystemPredicate(
        userIdFlow: Flow<Int>,
        showSystemFlow: Flow<Boolean>,
    ): Flow<(app: ApplicationInfo) -> Boolean> =
+24 −5
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settingslib.spaprivileged.model.app

import android.app.Application
import android.content.Context
import android.content.pm.ApplicationInfo
import android.icu.text.Collator
import androidx.lifecycle.AndroidViewModel
@@ -27,12 +28,16 @@ import com.android.settingslib.spa.framework.util.waitFirst
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus

internal data class AppListData<T : AppRecord>(
@@ -43,9 +48,15 @@ internal data class AppListData<T : AppRecord>(
        AppListData(appEntries.filter(predicate), option)
}

@OptIn(ExperimentalCoroutinesApi::class)
internal class AppListViewModel<T : AppRecord>(
    application: Application,
) : AppListViewModelImpl<T>(application)

@OptIn(ExperimentalCoroutinesApi::class)
internal open class AppListViewModelImpl<T : AppRecord>(
    application: Application,
    appListRepositoryFactory: (Context) -> AppListRepository = ::AppListRepositoryImpl,
    appRepositoryFactory: (Context) -> AppRepository = ::AppRepositoryImpl,
) : AndroidViewModel(application) {
    val appListConfig = StateFlowBridge<AppListConfig>()
    val listModel = StateFlowBridge<AppListModel<T>>()
@@ -53,16 +64,18 @@ internal class AppListViewModel<T : AppRecord>(
    val option = StateFlowBridge<Int>()
    val searchQuery = StateFlowBridge<String>()

    private val appListRepository = AppListRepository(application)
    private val appRepository = AppRepositoryImpl(application)
    private val appListRepository = appListRepositoryFactory(application)
    private val appRepository = appRepositoryFactory(application)
    private val collator = Collator.getInstance().freeze()
    private val labelMap = ConcurrentHashMap<String, String>()
    private val scope = viewModelScope + Dispatchers.Default
    private val scope = viewModelScope + Dispatchers.IO

    private val userIdFlow = appListConfig.flow.map { it.userId }

    private val appsStateFlow = MutableStateFlow<List<ApplicationInfo>?>(null)

    private val recordListFlow = listModel.flow
        .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
        .flatMapLatest { it.transform(userIdFlow, appsStateFlow.filterNotNull()) }
        .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)

    private val systemFilteredFlow =
@@ -83,6 +96,12 @@ internal class AppListViewModel<T : AppRecord>(
        scheduleOnFirstLoaded()
    }

    fun reloadApps() {
        viewModelScope.launch {
            appsStateFlow.value = appListRepository.loadApps(appListConfig.flow.first())
        }
    }

    private fun filterAndSort(option: Int) = listModel.flow.flatMapLatest { listModel ->
        listModel.filter(userIdFlow, option, systemFilteredFlow)
            .asyncMapItem { record ->
+8 −8
Original line number Diff line number Diff line
@@ -22,8 +22,10 @@ import android.graphics.drawable.Drawable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import androidx.compose.ui.res.stringResource
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.compose.rememberContext
import com.android.settingslib.spaprivileged.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

@@ -34,7 +36,12 @@ interface AppRepository {
    fun loadLabel(app: ApplicationInfo): String

    @Composable
    fun produceLabel(app: ApplicationInfo): State<String>
    fun produceLabel(app: ApplicationInfo) =
        produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
            withContext(Dispatchers.IO) {
                value = loadLabel(app)
            }
        }

    @Composable
    fun produceIcon(app: ApplicationInfo): State<Drawable?>
@@ -45,13 +52,6 @@ internal class AppRepositoryImpl(private val context: Context) : AppRepository {

    override fun loadLabel(app: ApplicationInfo): String = app.loadLabel(packageManager).toString()

    @Composable
    override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
        withContext(Dispatchers.Default) {
            value = app.loadLabel(packageManager).toString()
        }
    }

    @Composable
    override fun produceIcon(app: ApplicationInfo) =
        produceState<Drawable?>(initialValue = null, app) {
Loading