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

Commit c30f98ad authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Automerger Merge Worker
Browse files

Merge "Support restoring TileService before app install" into udc-dev am: 3937472d

parents 6d81f30b 3937472d
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -65,14 +65,12 @@ import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.DisplayTracker;

import dagger.Lazy;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.inject.Inject;


import dagger.Lazy;

public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
    public static final String PREFIX = "custom(";
+7 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -44,6 +46,11 @@ abstract class QSPipelineModule {
        impl: CurrentTilesInteractorImpl
    ): CurrentTilesInteractor

    @Binds
    abstract fun provideInstalledTilesPackageRepository(
        impl: InstalledTilesComponentRepositoryImpl
    ): InstalledTilesComponentRepository

    @Binds
    @IntoMap
    @ClassKey(PrototypeCoreStartable::class)
+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.systemui.qs.pipeline.data.repository

import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
import android.annotation.WorkerThread
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.os.UserHandle
import android.service.quicksettings.TileService
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart

interface InstalledTilesComponentRepository {

    fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
}

@SysUISingleton
class InstalledTilesComponentRepositoryImpl
@Inject
constructor(
    @Application private val applicationContext: Context,
    private val packageManager: PackageManager,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : InstalledTilesComponentRepository {

    override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
        conflatedCallbackFlow {
                val receiver =
                    object : BroadcastReceiver() {
                        override fun onReceive(context: Context?, intent: Intent?) {
                            trySend(Unit)
                        }
                    }
                applicationContext.registerReceiverAsUser(
                    receiver,
                    UserHandle.of(userId),
                    INTENT_FILTER,
                    /* broadcastPermission = */ null,
                    /* scheduler = */ null
                )

                awaitClose { applicationContext.unregisterReceiver(receiver) }
            }
            .onStart { emit(Unit) }
            .map { reloadComponents(userId) }
            .distinctUntilChanged()
            .flowOn(backgroundDispatcher)

    @WorkerThread
    private fun reloadComponents(userId: Int): Set<ComponentName> {
        return packageManager
            .queryIntentServicesAsUser(INTENT, FLAGS, userId)
            .mapNotNull { it.serviceInfo }
            .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
            .filter { packageManager.isComponentActuallyEnabled(it) }
            .mapTo(mutableSetOf()) { it.componentName }
    }

    companion object {
        private val INTENT_FILTER =
            IntentFilter().apply {
                addAction(Intent.ACTION_PACKAGE_ADDED)
                addAction(Intent.ACTION_PACKAGE_CHANGED)
                addAction(Intent.ACTION_PACKAGE_REMOVED)
                addAction(Intent.ACTION_PACKAGE_REPLACED)
                addDataScheme("package")
            }
        private val INTENT = Intent(TileService.ACTION_QS_TILE)
        private val FLAGS =
            ResolveInfoFlags.of(
                (PackageManager.GET_SERVICES or
                        PackageManager.MATCH_DIRECT_BOOT_AWARE or
                        PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
                    .toLong()
            )
    }
}
+32 −25
Original line number Diff line number Diff line
@@ -42,6 +42,8 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

/** Repository that tracks the current tiles. */
@@ -104,6 +106,8 @@ constructor(
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : TileSpecRepository {

    private val mutex = Mutex()

    private val retailModeTiles by lazy {
        resources
            .getString(R.string.quick_settings_tiles_retail_mode)
@@ -145,7 +149,8 @@ constructor(
            .flowOn(backgroundDispatcher)
    }

    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
    override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) =
        mutex.withLock {
            if (tile == TileSpec.Invalid) {
                return
            }
@@ -160,7 +165,8 @@ constructor(
            }
        }

    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
    override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) =
        mutex.withLock {
            if (tiles.all { it == TileSpec.Invalid }) {
                return
            }
@@ -170,7 +176,8 @@ constructor(
            }
        }

    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
    override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) =
        mutex.withLock {
            val filtered = tiles.filter { it != TileSpec.Invalid }
            if (filtered.isNotEmpty()) {
                storeTiles(userId, filtered)
+158 −85
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.systemui.qs.external.CustomTileStatePersister
import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -52,6 +53,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
@@ -117,11 +120,13 @@ interface CurrentTilesInteractor : ProtoDumpable {
 * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
 * * [CustomTile]s will only be destroyed if the user changes.
 */
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CurrentTilesInteractorImpl
@Inject
constructor(
    private val tileSpecRepository: TileSpecRepository,
    private val installedTilesComponentRepository: InstalledTilesComponentRepository,
    private val userRepository: UserRepository,
    private val customTileStatePersister: CustomTileStatePersister,
    private val tileFactory: QSFactory,
@@ -141,7 +146,7 @@ constructor(
    override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()

    // This variable should only be accessed inside the collect of `startTileCollection`.
    private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
    private val specsToTiles = mutableMapOf<TileSpec, TileOrNotInstalled>()

    private val currentUser = MutableStateFlow(userTracker.userId)
    override val userId = currentUser.asStateFlow()
@@ -149,6 +154,20 @@ constructor(
    private val _userContext = MutableStateFlow(userTracker.userContext)
    override val userContext = _userContext.asStateFlow()

    private val userAndTiles =
        currentUser
            .flatMapLatest { userId ->
                tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) }
            }
            .distinctUntilChanged()
            .pairwise(UserAndTiles(-1, emptyList()))
            .flowOn(backgroundDispatcher)

    private val installedPackagesWithTiles =
        currentUser.flatMapLatest {
            installedTilesComponentRepository.getInstalledTilesComponents(it)
        }

    init {
        if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
            startTileCollection()
@@ -158,23 +177,33 @@ constructor(
    @OptIn(ExperimentalCoroutinesApi::class)
    private fun startTileCollection() {
        scope.launch {
            userRepository.selectedUserInfo
                .flatMapLatest { user ->
            launch {
                userRepository.selectedUserInfo.collect { user ->
                    currentUser.value = user.id
                    _userContext.value = userTracker.userContext
                    tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
                }
                .distinctUntilChanged()
                .pairwise(-1 to emptyList())
                .flowOn(backgroundDispatcher)
                .collect { (old, new) ->
                    val newTileList = new.second
                    val userChanged = old.first != new.first
                    val newUser = new.first
            }

            launch(backgroundDispatcher) {
                userAndTiles
                    .combine(installedPackagesWithTiles) { usersAndTiles, packages ->
                        Data(
                            usersAndTiles.previousValue,
                            usersAndTiles.newValue,
                            packages,
                        )
                    }
                    .collectLatest {
                        val newTileList = it.newData.tiles
                        val userChanged = it.oldData.userId != it.newData.userId
                        val newUser = it.newData.userId
                        val components = it.installedComponents

                        // Destroy all tiles that are not in the new set
                        specsToTiles
                        .filter { it.key !in newTileList }
                            .filter {
                                it.key !in newTileList && it.value is TileOrNotInstalled.Tile
                            }
                            .forEach { entry ->
                                logger.logTileDestroyed(
                                    entry.key,
@@ -185,13 +214,21 @@ constructor(
                                        QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
                                    }
                                )
                            entry.value.destroy()
                                (entry.value as TileOrNotInstalled.Tile).tile.destroy()
                            }
                        // MutableMap will keep the insertion order
                    val newTileMap = mutableMapOf<TileSpec, QSTile>()
                        val newTileMap = mutableMapOf<TileSpec, TileOrNotInstalled>()

                        newTileList.forEach { tileSpec ->
                            if (tileSpec !in newTileMap) {
                                if (
                                    tileSpec is TileSpec.CustomTileSpec &&
                                        tileSpec.componentName !in components
                                ) {
                                    newTileMap[tileSpec] = TileOrNotInstalled.NotInstalled
                                } else {
                                    // Create tile here will never try to create a CustomTile that
                                    // is not installed
                                    val newTile =
                                        if (tileSpec in specsToTiles) {
                                            processExistingTile(
@@ -205,7 +242,8 @@ constructor(
                                            createTile(tileSpec)
                                        }
                                    if (newTile != null) {
                                newTileMap[tileSpec] = newTile
                                        newTileMap[tileSpec] = TileOrNotInstalled.Tile(newTile)
                                    }
                                }
                            }
                        }
@@ -213,15 +251,26 @@ constructor(
                        val resolvedSpecs = newTileMap.keys.toList()
                        specsToTiles.clear()
                        specsToTiles.putAll(newTileMap)
                    _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
                        _currentSpecsAndTiles.value =
                            newTileMap
                                .filter { it.value is TileOrNotInstalled.Tile }
                                .map {
                                    TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
                                }
                        logger.logTilesNotInstalled(
                            newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
                            newUser
                        )
                        if (resolvedSpecs != newTileList) {
                        // There were some tiles that couldn't be created. Change the value in the
                            // There were some tiles that couldn't be created. Change the value in
                            // the
                            // repository
                            launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
                        }
                    }
            }
        }
    }

    override fun addTile(spec: TileSpec, position: Int) {
        scope.launch {
@@ -301,11 +350,15 @@ constructor(

    private fun processExistingTile(
        tileSpec: TileSpec,
        qsTile: QSTile,
        tileOrNotInstalled: TileOrNotInstalled,
        userChanged: Boolean,
        user: Int,
    ): QSTile? {
        return when {
        return when (tileOrNotInstalled) {
            is TileOrNotInstalled.NotInstalled -> null
            is TileOrNotInstalled.Tile -> {
                val qsTile = tileOrNotInstalled.tile
                when {
                    !qsTile.isAvailable -> {
                        logger.logTileDestroyed(
                            tileSpec,
@@ -317,7 +370,8 @@ constructor(
                    // Tile is in the current list of tiles and available.
                    // We have a handful of different cases
                    qsTile !is CustomTile -> {
                // The tile is not a custom tile. Make sure they are reset to the correct user
                        // The tile is not a custom tile. Make sure they are reset to the correct
                        // user
                        if (userChanged) {
                            qsTile.userSwitch(user)
                            logger.logTileUserChanged(tileSpec, user)
@@ -340,3 +394,22 @@ constructor(
                }
            }
        }
    }

    private sealed interface TileOrNotInstalled {
        object NotInstalled : TileOrNotInstalled

        @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled
    }

    private data class UserAndTiles(
        val userId: Int,
        val tiles: List<TileSpec>,
    )

    private data class Data(
        val oldData: UserAndTiles,
        val newData: UserAndTiles,
        val installedComponents: Set<ComponentName>,
    )
}
Loading