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