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.datasources
18 
19 import android.car.content.pm.CarPackageManager
20 import android.car.drivingstate.CarUxRestrictionsManager
21 import android.content.ComponentName
22 import android.content.res.Resources
23 import android.media.session.MediaController
24 import android.media.session.MediaSessionManager
25 import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener
26 import android.util.Log
27 import com.android.car.carlauncher.Flags
28 import com.android.car.carlauncher.R
29 import kotlinx.coroutines.CoroutineDispatcher
30 import kotlinx.coroutines.Dispatchers
31 import kotlinx.coroutines.channels.awaitClose
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.callbackFlow
34 import kotlinx.coroutines.flow.conflate
35 import kotlinx.coroutines.flow.distinctUntilChanged
36 import kotlinx.coroutines.flow.flowOf
37 import kotlinx.coroutines.flow.flowOn
38 import kotlinx.coroutines.flow.map
39 
40 /**
41  * DataSource interface for providing ux restriction state
42  */
43 interface UXRestrictionDataSource {
44 
45     /**
46      * Flow notifying if distraction optimization is required
47      */
48     fun requiresDistractionOptimization(): Flow<Boolean>
49 
50     fun isDistractionOptimized(): Flow<(componentName: ComponentName, isMedia: Boolean) -> Boolean>
51 }
52 
53 /**
54  * Impl of [UXRestrictionDataSource]
55  *
56  * @property [uxRestrictionsManager] Used to listen for distraction optimization changes.
57  * @property [carPackageManager]
58  * @property [mediaSessionManager]
59  * @property [resources] Application resources, not bound to activity's configuration changes.
60  * @property [bgDispatcher] Executes all the operations on this background coroutine dispatcher.
61  */
62 class UXRestrictionDataSourceImpl(
63     private val uxRestrictionsManager: CarUxRestrictionsManager,
64     private val carPackageManager: CarPackageManager,
65     private val mediaSessionManager: MediaSessionManager,
66     private val resources: Resources,
67     private val bgDispatcher: CoroutineDispatcher = Dispatchers.Default
68 ) : UXRestrictionDataSource {
69 
70     /**
71      * Gets a flow producer which provides updates if distraction optimization is currently required
72      * This conveys if the foreground activity needs to be distraction optimized.
73      *
74      * When the scope in which this flow is collected is closed/canceled
75      * [CarUxRestrictionsManager.unregisterListener] is triggered.
76      */
requiresDistractionOptimizationnull77     override fun requiresDistractionOptimization(): Flow<Boolean> {
78         return callbackFlow {
79             val currentRestrictions = uxRestrictionsManager.currentCarUxRestrictions
80             if (currentRestrictions == null) {
81                 Log.e(TAG, "CurrentCarUXRestrictions is not initialized")
82                 trySend(false)
83             } else {
84                 trySend(currentRestrictions.isRequiresDistractionOptimization)
85             }
86             uxRestrictionsManager.registerListener {
87                 trySend(it.isRequiresDistractionOptimization)
88             }
89             awaitClose {
90                 uxRestrictionsManager.unregisterListener()
91             }
92         }.flowOn(bgDispatcher).conflate()
93     }
94 
isDistractionOptimizednull95     override fun isDistractionOptimized():
96             Flow<(componentName: ComponentName, isMedia: Boolean) -> Boolean> {
97         if (!(Flags.mediaSessionCard() &&
98                     resources.getBoolean(R.bool.config_enableMediaSessionAppsWhileDriving))
99         ) {
100             return flowOf(fun(componentName: ComponentName, isMedia: Boolean): Boolean {
101                 return isMedia || (carPackageManager.isActivityDistractionOptimized(
102                     componentName.packageName,
103                     componentName.className
104                 ))
105             })
106         }
107         return getActiveMediaPlaybackSessions().map {
108             fun(componentName: ComponentName, isMedia: Boolean): Boolean {
109                 if (it.contains(componentName.packageName)) {
110                     return true
111                 }
112                 return isMedia || (carPackageManager.isActivityDistractionOptimized(
113                     componentName.packageName,
114                     componentName.className
115                 ))
116             }
117         }.distinctUntilChanged()
118     }
119 
getActiveMediaPlaybackSessionsnull120     private fun getActiveMediaPlaybackSessions(): Flow<List<String>> {
121         return callbackFlow {
122             val filterActiveMediaPackages: (List<MediaController>) -> List<String> =
123                 { mediaControllers ->
124                     mediaControllers.filter {
125                         it.playbackState?.isActive ?: false
126                     }.map { it.packageName }
127                 }
128             // Emits the initial list of filtered packages upon subscription
129             trySend(
130                 filterActiveMediaPackages(mediaSessionManager.getActiveSessions(null))
131             )
132             val sessionsChangedListener =
133                 OnActiveSessionsChangedListener {
134                     if (it != null) {
135                         trySend(filterActiveMediaPackages(it))
136                     }
137                 }
138             mediaSessionManager.addOnActiveSessionsChangedListener(sessionsChangedListener, null)
139             awaitClose {
140                 mediaSessionManager.removeOnActiveSessionsChangedListener(sessionsChangedListener)
141             }
142             // Note this flow runs on the Main dispatcher, as the MediaSessionsChangedListener
143             // expects to dispatch updates on the Main looper.
144         }.flowOn(Dispatchers.Main).conflate()
145     }
146 
147     companion object {
148         val TAG: String = UXRestrictionDataSourceImpl::class.java.simpleName
149     }
150 }
151