• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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