1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.qs.pipeline.data.repository
18 
19 import android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
20 import android.annotation.WorkerThread
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.Intent
24 import android.content.pm.PackageManager
25 import android.content.pm.PackageManager.ResolveInfoFlags
26 import android.content.pm.ServiceInfo
27 import android.os.UserHandle
28 import android.service.quicksettings.TileService
29 import androidx.annotation.GuardedBy
30 import com.android.systemui.common.data.repository.PackageChangeRepository
31 import com.android.systemui.common.shared.model.PackageChangeModel
32 import com.android.systemui.dagger.SysUISingleton
33 import com.android.systemui.dagger.qualifiers.Application
34 import com.android.systemui.dagger.qualifiers.Background
35 import com.android.systemui.util.kotlin.isComponentActuallyEnabled
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.SharingStarted
40 import kotlinx.coroutines.flow.StateFlow
41 import kotlinx.coroutines.flow.distinctUntilChanged
42 import kotlinx.coroutines.flow.map
43 import kotlinx.coroutines.flow.onStart
44 import kotlinx.coroutines.flow.stateIn
45 
46 interface InstalledTilesComponentRepository {
47 
getInstalledTilesComponentsnull48     fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
49 
50     fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo>
51 }
52 
53 @SysUISingleton
54 class InstalledTilesComponentRepositoryImpl
55 @Inject
56 constructor(
57     @Application private val applicationContext: Context,
58     @Background private val backgroundScope: CoroutineScope,
59     private val packageChangeRepository: PackageChangeRepository
60 ) : InstalledTilesComponentRepository {
61 
62     @GuardedBy("userMap") private val userMap = mutableMapOf<Int, StateFlow<List<ServiceInfo>>>()
63 
64     override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
65         synchronized(userMap) { getForUserLocked(userId) }
66             .map { it.mapTo(mutableSetOf()) { it.componentName } }
67 
68     override fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo> {
69         return synchronized(userMap) { getForUserLocked(userId).value }
70     }
71 
72     private fun getForUserLocked(userId: Int): StateFlow<List<ServiceInfo>> {
73         return userMap.getOrPut(userId) {
74             /*
75              * In order to query [PackageManager] for different users, this implementation will
76              * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
77              * context.
78              */
79             val packageManager =
80                 if (applicationContext.userId == userId) {
81                     applicationContext.packageManager
82                 } else {
83                     applicationContext
84                         .createContextAsUser(
85                             UserHandle.of(userId),
86                             /* flags */ 0,
87                         )
88                         .packageManager
89                 }
90             packageChangeRepository
91                 .packageChanged(UserHandle.of(userId))
92                 .onStart { emit(PackageChangeModel.Empty) }
93                 .map { reloadComponents(userId, packageManager) }
94                 .distinctUntilChanged()
95                 .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), emptyList())
96         }
97     }
98 
99     @WorkerThread
100     private fun reloadComponents(userId: Int, packageManager: PackageManager): List<ServiceInfo> {
101         return packageManager
102             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
103             .mapNotNull { it.serviceInfo }
104             .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
105             .filter {
106                 try {
107                     packageManager.isComponentActuallyEnabled(it)
108                 } catch (e: IllegalArgumentException) {
109                     // If the package is not found, it means it was uninstalled between query
110                     // and now. So it's clearly not enabled.
111                     false
112                 }
113             }
114     }
115 
116     companion object {
117         private val INTENT = Intent(TileService.ACTION_QS_TILE)
118         private val FLAGS =
119             ResolveInfoFlags.of(
120                 (PackageManager.GET_SERVICES or
121                         PackageManager.MATCH_DIRECT_BOOT_AWARE or
122                         PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
123                     .toLong()
124             )
125     }
126 }
127