• 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.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