1 /* <lambda>null2 * 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.car.docklib 18 19 import android.annotation.CallSuper 20 import android.app.ActivityOptions 21 import android.app.NotificationManager 22 import android.car.Car 23 import android.car.content.pm.CarPackageManager 24 import android.car.drivingstate.CarUxRestrictionsManager 25 import android.car.media.CarMediaManager 26 import android.content.ComponentName 27 import android.content.Context 28 import android.content.Intent 29 import android.content.pm.LauncherApps 30 import android.media.session.MediaController 31 import android.media.session.MediaSessionManager 32 import android.media.session.PlaybackState 33 import android.os.Build 34 import android.os.RemoteException 35 import android.os.UserHandle 36 import android.util.Log 37 import androidx.core.content.getSystemService 38 import com.android.car.carlauncher.Flags 39 import com.android.car.docklib.data.DockProtoDataController 40 import com.android.car.docklib.events.DockEventsReceiver 41 import com.android.car.docklib.events.DockPackageChangeReceiver 42 import com.android.car.docklib.media.MediaUtils 43 import com.android.car.docklib.task.DockTaskStackChangeListener 44 import com.android.car.docklib.view.DockAdapter 45 import com.android.car.docklib.view.DockView 46 import com.android.car.dockutil.events.DockCompatUtils.isDockSupportedOnDisplay 47 import com.android.launcher3.icons.IconFactory 48 import com.android.systemui.shared.system.TaskStackChangeListeners 49 import java.io.File 50 import java.lang.ref.WeakReference 51 import java.util.UUID 52 53 /** 54 * Create a controller for DockView. It initializes the view with default and persisted icons. Upon 55 * initializing, it will listen to broadcast events, and update the view. 56 * 57 * @param dockView the inflated dock view 58 * @param userContext the foreground user context, since the view may be hosted on system context 59 * @param dataFile a file to store user's pinned apps with read and write permission 60 */ 61 open class DockViewController( 62 dockView: DockView, 63 val userContext: Context = dockView.context, 64 dataFile: File, 65 ) : DockInterface { 66 companion object { 67 private const val TAG = "DockViewController" 68 private val DEBUG = Build.isDebuggable() 69 } 70 71 private val numItems = dockView.context.resources.getInteger(R.integer.config_numDockApps) 72 private val car: Car 73 private val dockViewWeakReference: WeakReference<DockView> 74 private val dockViewModel: DockViewModel 75 private val adapter: DockAdapter 76 private val dockEventsReceiver: DockEventsReceiver 77 private val dockPackageChangeReceiver: DockPackageChangeReceiver 78 private val taskStackChangeListeners: TaskStackChangeListeners 79 private val dockTaskStackChangeListener: DockTaskStackChangeListener 80 private val launcherApps = userContext.getSystemService<LauncherApps>() 81 private val excludedItemsProviders: Set<ExcludedItemsProvider> = 82 hashSetOf(ResourceExcludedItemsProvider(userContext)) 83 private val mediaSessionManager: MediaSessionManager 84 private val sessionChangedListener: MediaSessionManager.OnActiveSessionsChangedListener = 85 MediaSessionManager.OnActiveSessionsChangedListener { mediaControllers -> 86 handleMediaSessionChange(mediaControllers) 87 } 88 89 init { 90 if (DEBUG) Log.d(TAG, "Init DockViewController for user ${userContext.userId}") 91 val displayId = dockView.context.displayId 92 if (!isDockSupportedOnDisplay(dockView.context, displayId)) { 93 throw IllegalStateException("Dock tried to init on unsupported display: $displayId") 94 } 95 adapter = DockAdapter(this, userContext) 96 dockView.setAdapter(adapter) 97 dockViewWeakReference = WeakReference(dockView) 98 99 val launcherActivities = launcherApps 100 ?.getActivityList(null, userContext.user) 101 ?.map { it.componentName } 102 ?.toMutableSet() ?: mutableSetOf() 103 104 dockViewModel = DockViewModel( 105 maxItemsInDock = numItems, 106 context = userContext, 107 packageManager = userContext.packageManager, 108 launcherActivities = launcherActivities, 109 defaultPinnedItems = dockView.resources 110 .getStringArray(R.array.config_defaultDockApps) 111 .mapNotNull(ComponentName::unflattenFromString), 112 isPackageExcluded = { pkg -> 113 getExcludedItemsProviders() 114 .map { it.isPackageExcluded(pkg) } 115 .reduce { res1, res2 -> res1 or res2 } 116 }, 117 isComponentExcluded = { cmp -> 118 getExcludedItemsProviders() 119 .map { it.isComponentExcluded(cmp) } 120 .reduce { res1, res2 -> res1 or res2 } 121 }, 122 iconFactory = IconFactory.obtain(dockView.context), 123 dockProtoDataController = DockProtoDataController(dataFile), 124 ) { updatedApps -> 125 dockViewWeakReference.get()?.getAdapter()?.submitList(updatedApps) 126 ?: throw NullPointerException("the View referenced does not exist") 127 } 128 car = Car.createCar( 129 userContext, 130 null, // handler 131 Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT 132 ) { car, ready -> 133 run { 134 if (ready) { 135 car.getCarManager(CarPackageManager::class.java)?.let { carPM -> 136 dockViewModel.setCarPackageManager(carPM) 137 } 138 car.getCarManager(CarMediaManager::class.java)?.let { carMM -> 139 adapter.setCarMediaManager(carMM) 140 } 141 car.getCarManager(CarUxRestrictionsManager::class.java)?.let { 142 adapter.setUxRestrictions( 143 isUxRestrictionEnabled = 144 it.currentCarUxRestrictions?.isRequiresDistractionOptimization ?: false 145 ) 146 it.registerListener { carUxRestrictions -> 147 adapter.setUxRestrictions( 148 isUxRestrictionEnabled = 149 carUxRestrictions.isRequiresDistractionOptimization 150 ) 151 } 152 } 153 } 154 } 155 } 156 157 mediaSessionManager = 158 userContext.getSystemService(MediaSessionManager::class.java) as MediaSessionManager 159 if (Flags.mediaSessionCard() && userContext.resources.getBoolean( 160 com.android.car.carlaunchercommon.R.bool 161 .config_enableMediaSessionAppsWhileDriving)) { 162 handleMediaSessionChange(mediaSessionManager.getActiveSessionsForUser( 163 /* notificationListener= */ 164 null, 165 UserHandle.of(userContext.userId) 166 )) 167 mediaSessionManager.addOnActiveSessionsChangedListener( 168 /* notificationListener= */ 169 null, 170 UserHandle.of(userContext.userId), 171 userContext.getMainExecutor(), 172 sessionChangedListener 173 ) 174 } 175 176 dockEventsReceiver = DockEventsReceiver.registerDockReceiver(userContext, this) 177 dockPackageChangeReceiver = DockPackageChangeReceiver.registerReceiver(userContext, this) 178 dockTaskStackChangeListener = 179 DockTaskStackChangeListener(userContext.userId, this) 180 taskStackChangeListeners = TaskStackChangeListeners.getInstance() 181 taskStackChangeListeners.registerTaskStackListener(dockTaskStackChangeListener) 182 } 183 184 /** Method to stop the dock. Call this upon View being destroyed. */ 185 @CallSuper 186 open fun destroy() { 187 if (DEBUG) Log.d(TAG, "Destroy called") 188 car.getCarManager(CarUxRestrictionsManager::class.java)?.unregisterListener() 189 car.disconnect() 190 userContext.unregisterReceiver(dockEventsReceiver) 191 userContext.unregisterReceiver(dockPackageChangeReceiver) 192 taskStackChangeListeners.unregisterTaskStackListener(dockTaskStackChangeListener) 193 mediaSessionManager.removeOnActiveSessionsChangedListener(sessionChangedListener) 194 dockViewModel.destroy() 195 } 196 197 open fun getExcludedItemsProviders(): Set<ExcludedItemsProvider> = excludedItemsProviders 198 199 override fun appPinned(componentName: ComponentName) = dockViewModel.pinItem(componentName) 200 201 override fun appPinned(componentName: ComponentName, index: Int) = 202 dockViewModel.pinItem(componentName, index) 203 204 override fun appPinned(id: UUID) = dockViewModel.pinItem(id) 205 206 override fun appUnpinned(componentName: ComponentName) { 207 // TODO: Not yet implemented 208 } 209 210 override fun appUnpinned(id: UUID) = dockViewModel.removeItem(id) 211 212 override fun appLaunched(componentName: ComponentName) = 213 dockViewModel.addDynamicItem(componentName) 214 215 override fun launchApp(componentName: ComponentName, isMediaApp: Boolean) { 216 val intent = if (isMediaApp) { 217 MediaUtils.createLaunchIntent(componentName) 218 } else { 219 Intent(Intent.ACTION_MAIN) 220 .setComponent(componentName) 221 .addCategory(Intent.CATEGORY_LAUNCHER) 222 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 223 } 224 val options = ActivityOptions.makeBasic() 225 options.setLaunchDisplayId(userContext.display.displayId) 226 // todo(b/312718542): hidden api(context.startActivityAsUser) usage 227 userContext.startActivityAsUser(intent, options.toBundle(), userContext.user) 228 } 229 230 override fun getIconColorWithScrim(componentName: ComponentName) = 231 dockViewModel.getIconColorWithScrim(componentName) 232 233 override fun packageRemoved(packageName: String) = dockViewModel.removeItems(packageName) 234 235 override fun packageAdded(packageName: String) { 236 dockViewModel.addMediaComponents(packageName) 237 dockViewModel.addLauncherComponents( 238 launcherApps?.getActivityList(packageName, userContext.user) 239 ?.map { it.componentName } ?: listOf() 240 ) 241 } 242 243 override fun getMediaServiceComponents(): Set<ComponentName> = 244 dockViewModel.getMediaServiceComponents() 245 246 private fun handleMediaSessionChange(mediaControllers: List<MediaController>?) { 247 val mediaNotificationPackages = getActiveMediaNotificationPackages() 248 val activeMediaSessions = mediaControllers?.filter { 249 it.playbackState?.let { playbackState -> 250 (playbackState.isActive || playbackState.state == PlaybackState.STATE_PAUSED) 251 } ?: false 252 }?.map { it.packageName }?.filter { mediaNotificationPackages.contains(it) } ?: emptyList() 253 254 adapter.onMediaSessionChange(activeMediaSessions) 255 } 256 257 private fun getActiveMediaNotificationPackages(): List<String> { 258 try { 259 // todo(b/312718542): hidden api(NotificationManager.getService()) usage 260 return NotificationManager.getService() 261 .getActiveNotificationsWithAttribution( 262 userContext.packageName, 263 null 264 ).toList().filter { 265 it.notification.extras != null && it.notification.isMediaNotification 266 }.map { it.packageName } 267 } catch (e: RemoteException) { 268 Log.e( 269 TAG, 270 "Exception trying to get active notifications $e" 271 ) 272 return listOf() 273 } 274 } 275 } 276