1 /* <lambda>null2 * Copyright (C) 2024 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.car.carlauncher.repositories 18 19 import android.Manifest.permission.MANAGE_OWN_CALLS 20 import android.content.ComponentName 21 import android.content.Intent 22 import android.content.pm.PackageManager 23 import android.content.pm.PackageManager.NameNotFoundException 24 import android.content.pm.ResolveInfo 25 import android.graphics.drawable.Drawable 26 import android.os.UserManager 27 import android.util.Log 28 import com.android.car.carlauncher.AppItem 29 import com.android.car.carlauncher.AppMetaData 30 import com.android.car.carlauncher.datasources.AppOrderDataSource 31 import com.android.car.carlauncher.datasources.AppOrderDataSource.AppOrderInfo 32 import com.android.car.carlauncher.datasources.ControlCenterMirroringDataSource 33 import com.android.car.carlauncher.datasources.LauncherActivitiesDataSource 34 import com.android.car.carlauncher.datasources.MediaTemplateAppsDataSource 35 import com.android.car.carlauncher.datasources.UXRestrictionDataSource 36 import com.android.car.carlauncher.datasources.restricted.DisabledAppsDataSource 37 import com.android.car.carlauncher.datasources.restricted.TosDataSource 38 import com.android.car.carlauncher.datasources.restricted.TosState 39 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory 40 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory.AppLauncherProviderType 41 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory.AppLauncherProviderType.DISABLED 42 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory.AppLauncherProviderType.LAUNCHER 43 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory.AppLauncherProviderType.MEDIA 44 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory.AppLauncherProviderType.MIRRORING 45 import com.android.car.carlauncher.repositories.appactions.AppLaunchProviderFactory.AppLauncherProviderType.TOS_DISABLED 46 import com.android.car.carlauncher.repositories.appactions.AppShortcutsFactory 47 import kotlinx.coroutines.CoroutineDispatcher 48 import kotlinx.coroutines.flow.Flow 49 import kotlinx.coroutines.flow.combine 50 import kotlinx.coroutines.flow.distinctUntilChanged 51 import kotlinx.coroutines.flow.flowOn 52 import kotlinx.coroutines.flow.map 53 54 interface AppGridRepository { 55 56 /** 57 * Returns a flow of all applications available in the app grid, including 58 * system apps, media apps, and potentially restricted apps. 59 * 60 * @return A Flow emitting a list of AppItem objects. 61 */ 62 fun getAllAppsList(): Flow<List<AppItem>> 63 64 /** 65 * Provides a flow indicating whether distraction optimization is required for the device. 66 * Distraction optimization might limit the features or visibility of apps. 67 * 68 * @return A Flow emitting a Boolean value, where `true` indicates distraction optimization is 69 * needed. 70 */ 71 fun requiresDistractionOptimization(): Flow<Boolean> 72 73 /** 74 * Returns a continuous flow representing the Terms of Service (ToS) state for apps. 75 * This state may determine the availability or restrictions of certain apps. 76 * 77 * @return A Flow emitting the current TosState. 78 */ 79 fun getTosState(): Flow<TosState> 80 81 /** 82 * Suspends execution to save the provided app order to persistent storage. 83 * Used to maintain the arrangement of apps within the app grid. 84 * 85 * @param currentAppOrder A list of AppItem representing the desired app order. 86 */ 87 suspend fun saveAppOrder(currentAppOrder: List<AppItem>) 88 89 /** 90 * Returns a flow of media-related apps installed on the device. 91 * 92 * @return A Flow emitting a list of AppItem representing media applications. 93 */ 94 fun getMediaAppsList(): Flow<List<AppItem>> 95 } 96 97 /** 98 * The core implementation of the AppGridRepository interface. This class is responsible for: 99 * 100 * * Fetching and combining app information from various sources (launcher activities, 101 * media services, disabled apps, etc.) 102 * * Applying restrictions and filtering app lists based on distraction optimization, ToS status, 103 * and mirroring state. 104 * * Managing app order and saving it to persistent storage. 105 * * Providing real-time updates of the app grid as changes occur. 106 */ 107 class AppGridRepositoryImpl( 108 private val launcherActivities: LauncherActivitiesDataSource, 109 private val mediaTemplateApps: MediaTemplateAppsDataSource, 110 private val disabledApps: DisabledAppsDataSource, 111 private val tosApps: TosDataSource, 112 private val controlCenterMirroring: ControlCenterMirroringDataSource, 113 private val uxRestriction: UXRestrictionDataSource, 114 private val appOrder: AppOrderDataSource, 115 private val packageManager: PackageManager, 116 private val appLaunchFactory: AppLaunchProviderFactory, 117 private val appShortcutsFactory: AppShortcutsFactory, 118 userManager: UserManager, 119 private val bgDispatcher: CoroutineDispatcher 120 ) : AppGridRepository { 121 122 private val isVisibleBackgroundUser = !userManager.isUserForeground && 123 userManager.isUserVisible && !userManager.isProfile 124 125 /** 126 * Provides a flow of all apps in the app grid. 127 * It combines data from multiple sources, filters apps based on restrictions, handles dynamic 128 * updates and returns the list in the last known savedOrder. 129 * 130 * @return A Flow emitting lists of AppItem objects. 131 */ getAllAppsListnull132 override fun getAllAppsList(): Flow<List<AppItem>> { 133 return combine( 134 getAllLauncherAndMediaApps(), 135 getRestrictedApps(), 136 controlCenterMirroring.getAppMirroringSession(), 137 appOrder.getSavedAppOrderComparator(), 138 uxRestriction.isDistractionOptimized() 139 ) { apps, restrictedApps, mirroringSession, order, isDistractionOptimized -> 140 val alreadyAddedComponents = apps.map { it.componentName.packageName }.toSet() 141 return@combine (apps + restrictedApps.filterNot { 142 it.componentName.packageName in alreadyAddedComponents 143 }).sortedWith { a1, a2 -> 144 order.compare(a1.appOrderInfo, a2.appOrderInfo) 145 }.filter { 146 !shouldHideApp(it) 147 }.map { 148 if (mirroringSession.packageName == it.componentName.packageName) { 149 it.redirectIntent = mirroringSession.launchIntent 150 } else if (it.launchActionType == MIRRORING) { 151 it.redirectIntent = null 152 } 153 it.toAppItem(isDistractionOptimized(it.componentName, it.launchActionType == MEDIA)) 154 } 155 }.flowOn(bgDispatcher).distinctUntilChanged() 156 } 157 158 /** 159 * Emitting distraction optimization status changes. 160 * 161 * @return A Flow of Boolean values, where `true` indicates distraction optimization is 162 * required. 163 */ requiresDistractionOptimizationnull164 override fun requiresDistractionOptimization(): Flow<Boolean> { 165 return uxRestriction.requiresDistractionOptimization() 166 } 167 168 /** 169 * Provides the Terms of Service state for apps. 170 * 171 * @return A Flow emitting the current TosState. 172 */ getTosStatenull173 override fun getTosState(): Flow<TosState> { 174 return tosApps.getTosState() 175 } 176 177 /** 178 * Suspends saving the given app order to persistent storage. 179 * Updates to the app order are posted to the subscribers of 180 * [AppGridRepositoryImpl.getAllAppsList] 181 * 182 * @param currentAppOrder A list of AppItem representing the desired app order. 183 */ saveAppOrdernull184 override suspend fun saveAppOrder(currentAppOrder: List<AppItem>) { 185 appOrder.saveAppOrder(currentAppOrder.toAppOrderInfoList()) 186 } 187 188 /** 189 * Providing a flow of media-related apps. 190 * Handles dynamic updates to the list of media apps. 191 * 192 * @return A Flow emitting lists of AppItem objects representing media apps. 193 */ getMediaAppsListnull194 override fun getMediaAppsList(): Flow<List<AppItem>> { 195 return launcherActivities.getOnPackagesChanged().map { 196 mediaTemplateApps.getAllMediaServices(true).map { 197 it.toAppInfo(MEDIA).toAppItem(true) 198 } 199 }.flowOn(bgDispatcher).distinctUntilChanged() 200 } 201 getAllLauncherAndMediaAppsnull202 private fun getAllLauncherAndMediaApps(): Flow<List<AppInfo>> { 203 return launcherActivities.getOnPackagesChanged().map { 204 val launcherApps = launcherActivities.getAllLauncherActivities().map { 205 AppInfo(it.label, it.componentName, it.getBadgedIcon(0), LAUNCHER) 206 } 207 val mediaTemplateApps = mediaTemplateApps.getAllMediaServices(false).map { 208 it.toAppInfo(MEDIA) 209 } 210 launcherApps + mediaTemplateApps 211 }.flowOn(bgDispatcher).distinctUntilChanged() 212 } 213 getRestrictedAppsnull214 private fun getRestrictedApps(): Flow<List<AppInfo>> { 215 return disabledApps.getDisabledApps() 216 .combine(tosApps.getTosState()) { disabledApps, tosApps -> 217 return@combine disabledApps.map { 218 it.toAppInfo(DISABLED) 219 } + tosApps.restrictedApps.map { 220 it.toAppInfo(TOS_DISABLED) 221 } 222 }.flowOn(bgDispatcher).distinctUntilChanged() 223 } 224 225 private data class AppInfo( 226 val displayName: CharSequence, 227 val componentName: ComponentName, 228 val icon: Drawable, 229 private val _launchActionType: AppLauncherProviderType, 230 var redirectIntent: Intent? = null 231 ) { 232 val launchActionType get() = if (redirectIntent == null) { 233 _launchActionType 234 } else { 235 MIRRORING 236 } 237 238 val appOrderInfo = 239 AppOrderInfo(componentName.packageName, componentName.className, displayName.toString()) 240 } 241 toAppItemnull242 private fun AppInfo.toAppItem(isDistractionOptimized: Boolean): AppItem { 243 val metaData = AppMetaData( 244 displayName, 245 componentName, 246 icon, 247 isDistractionOptimized, 248 launchActionType == MIRRORING, 249 launchActionType == TOS_DISABLED, 250 { context -> 251 appLaunchFactory 252 .get(launchActionType) 253 ?.launch(context, componentName, redirectIntent) 254 }, 255 { contextViewPair -> 256 appShortcutsFactory.showShortcuts( 257 componentName, 258 displayName, 259 contextViewPair.first, 260 contextViewPair.second 261 ) 262 } 263 ) 264 return AppItem(metaData) 265 } 266 ResolveInfonull267 private fun ResolveInfo.toAppInfo(launchActionType: AppLauncherProviderType): AppInfo { 268 val componentName: ComponentName 269 val icon: Drawable 270 if (launchActionType == MEDIA) { 271 componentName = ComponentName(serviceInfo.packageName, serviceInfo.name) 272 icon = serviceInfo.loadIcon(packageManager) 273 } else { 274 componentName = ComponentName(activityInfo.packageName, activityInfo.name) 275 icon = activityInfo.loadIcon(packageManager) 276 } 277 return AppInfo( 278 loadLabel(packageManager), 279 componentName, 280 icon, 281 launchActionType 282 ) 283 } 284 toAppOrderInfoListnull285 private fun List<AppItem>.toAppOrderInfoList(): List<AppOrderInfo> { 286 return map { AppOrderInfo(it.packageName, it.className, it.displayName.toString()) } 287 } 288 shouldHideAppnull289 private fun shouldHideApp(appInfo: AppInfo): Boolean { 290 // Disable telephony apps for MUMD passenger since accepting a call will 291 // drop the driver's call. 292 if (isVisibleBackgroundUser) { 293 return try { 294 packageManager.getPackageInfo( 295 appInfo.componentName.packageName, PackageManager.GET_PERMISSIONS) 296 .requestedPermissions?.any {it == MANAGE_OWN_CALLS} ?: false 297 } catch (e: NameNotFoundException) { 298 Log.e(TAG, "Unable to query app permissions for $appInfo $e") 299 false 300 } 301 } 302 303 return false 304 } 305 306 companion object { 307 const val TAG = "AppGridRepository" 308 } 309 } 310