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.BroadcastReceiver
22 import android.content.ComponentName
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.content.pm.PackageManager
27 import android.content.pm.PackageManager.ResolveInfoFlags
28 import android.os.UserHandle
29 import android.service.quicksettings.TileService
30 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
31 import com.android.systemui.dagger.SysUISingleton
32 import com.android.systemui.dagger.qualifiers.Application
33 import com.android.systemui.dagger.qualifiers.Background
34 import com.android.systemui.util.kotlin.isComponentActuallyEnabled
35 import javax.inject.Inject
36 import kotlinx.coroutines.CoroutineDispatcher
37 import kotlinx.coroutines.channels.awaitClose
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.distinctUntilChanged
40 import kotlinx.coroutines.flow.flowOn
41 import kotlinx.coroutines.flow.map
42 import kotlinx.coroutines.flow.onStart
43 
44 interface InstalledTilesComponentRepository {
45 
46     fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>>
47 }
48 
49 @SysUISingleton
50 class InstalledTilesComponentRepositoryImpl
51 @Inject
52 constructor(
53     @Application private val applicationContext: Context,
54     @Background private val backgroundDispatcher: CoroutineDispatcher,
55 ) : InstalledTilesComponentRepository {
56 
57     override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
58         /*
59          * In order to query [PackageManager] for different users, this implementation will call
60          * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
61          */
62         val packageManager =
63             if (applicationContext.userId == userId) {
64                 applicationContext.packageManager
65             } else {
66                 applicationContext
67                     .createContextAsUser(
68                         UserHandle.of(userId),
69                         /* flags */ 0,
70                     )
71                     .packageManager
72             }
73         return conflatedCallbackFlow {
74                 val receiver =
75                     object : BroadcastReceiver() {
76                         override fun onReceive(context: Context?, intent: Intent?) {
77                             trySend(Unit)
78                         }
79                     }
80                 applicationContext.registerReceiverAsUser(
81                     receiver,
82                     UserHandle.of(userId),
83                     INTENT_FILTER,
84                     /* broadcastPermission = */ null,
85                     /* scheduler = */ null
86                 )
87 
88                 awaitClose { applicationContext.unregisterReceiver(receiver) }
89             }
90             .onStart { emit(Unit) }
91             .map { reloadComponents(userId, packageManager) }
92             .distinctUntilChanged()
93             .flowOn(backgroundDispatcher)
94     }
95 
96     @WorkerThread
97     private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
98         return packageManager
99             .queryIntentServicesAsUser(INTENT, FLAGS, userId)
100             .mapNotNull { it.serviceInfo }
101             .filter { it.permission == BIND_QUICK_SETTINGS_TILE }
102             .filter { packageManager.isComponentActuallyEnabled(it) }
103             .mapTo(mutableSetOf()) { it.componentName }
104     }
105 
106     companion object {
107         private val INTENT_FILTER =
108             IntentFilter().apply {
109                 addAction(Intent.ACTION_PACKAGE_ADDED)
110                 addAction(Intent.ACTION_PACKAGE_CHANGED)
111                 addAction(Intent.ACTION_PACKAGE_REMOVED)
112                 addAction(Intent.ACTION_PACKAGE_REPLACED)
113                 addDataScheme("package")
114             }
115         private val INTENT = Intent(TileService.ACTION_QS_TILE)
116         private val FLAGS =
117             ResolveInfoFlags.of(
118                 (PackageManager.GET_SERVICES or
119                         PackageManager.MATCH_DIRECT_BOOT_AWARE or
120                         PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
121                     .toLong()
122             )
123     }
124 }
125