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