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