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.Context
23 import android.content.res.Resources
24 import android.media.session.MediaSessionManager
25 import android.util.Log
26 import androidx.lifecycle.asFlow
27 import com.android.car.carlauncher.Flags
28 import com.android.car.carlauncher.MediaSessionUtils
29 import com.android.car.carlaunchercommon.R
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.Dispatchers
32 import kotlinx.coroutines.channels.awaitClose
33 import kotlinx.coroutines.flow.Flow
34 import kotlinx.coroutines.flow.callbackFlow
35 import kotlinx.coroutines.flow.conflate
36 import kotlinx.coroutines.flow.distinctUntilChanged
37 import kotlinx.coroutines.flow.flowOf
38 import kotlinx.coroutines.flow.flowOn
39 import kotlinx.coroutines.flow.map
40 
41 /**
42  * DataSource interface for providing ux restriction state
43  */
44 interface UXRestrictionDataSource {
45 
46     /**
47      * Flow notifying if distraction optimization is required
48      */
49     fun requiresDistractionOptimization(): Flow<Boolean>
50 
51     fun isDistractionOptimized(): Flow<(componentName: ComponentName, isMedia: Boolean) -> Boolean>
52 }
53 
54 /**
55  * Impl of [UXRestrictionDataSource]
56  *
57  * @property [uxRestrictionsManager] Used to listen for distraction optimization changes.
58  * @property [carPackageManager]
59  * @property [mediaSessionManager]
60  * @property [resources] Application resources, not bound to activity's configuration changes.
61  * @property [bgDispatcher] Executes all the operations on this background coroutine dispatcher.
62  */
63 class UXRestrictionDataSourceImpl(
64     private val context: Context,
65     private val uxRestrictionsManager: CarUxRestrictionsManager,
66     private val carPackageManager: CarPackageManager,
67     private val mediaSessionManager: MediaSessionManager,
68     private val resources: Resources,
69     private val bgDispatcher: CoroutineDispatcher = Dispatchers.Default,
70 ) : UXRestrictionDataSource {
71 
72     /**
73      * Gets a flow producer which provides updates if distraction optimization is currently required
74      * This conveys if the foreground activity needs to be distraction optimized.
75      *
76      * When the scope in which this flow is collected is closed/canceled
77      * [CarUxRestrictionsManager.unregisterListener] is triggered.
78      */
requiresDistractionOptimizationnull79     override fun requiresDistractionOptimization(): Flow<Boolean> {
80         return callbackFlow {
81             val currentRestrictions = uxRestrictionsManager.currentCarUxRestrictions
82             if (currentRestrictions == null) {
83                 Log.e(TAG, "CurrentCarUXRestrictions is not initialized")
84                 trySend(false)
85             } else {
86                 trySend(currentRestrictions.isRequiresDistractionOptimization)
87             }
88             uxRestrictionsManager.registerListener {
89                 trySend(it.isRequiresDistractionOptimization)
90             }
91             awaitClose {
92                 uxRestrictionsManager.unregisterListener()
93             }
94         }.flowOn(bgDispatcher).conflate()
95     }
96 
isDistractionOptimizednull97     override fun isDistractionOptimized():
98             Flow<(componentName: ComponentName, isMedia: Boolean) -> Boolean> {
99         if (!(Flags.mediaSessionCard() &&
100                     resources.getBoolean(R.bool.config_enableMediaSessionAppsWhileDriving))
101         ) {
102             return flowOf(fun(componentName: ComponentName, isMedia: Boolean): Boolean {
103                 return isMedia || (carPackageManager.isActivityDistractionOptimized(
104                     componentName.packageName,
105                     componentName.className
106                 ))
107             })
108         }
109         return getActiveMediaPlaybackSessions().map {
110             fun(componentName: ComponentName, isMedia: Boolean): Boolean {
111                 if (it.contains(componentName.packageName)) {
112                     return true
113                 }
114                 return isMedia || (carPackageManager.isActivityDistractionOptimized(
115                     componentName.packageName,
116                     componentName.className
117                 ))
118             }
119         }.distinctUntilChanged()
120     }
121 
getActiveMediaPlaybackSessionsnull122     private fun getActiveMediaPlaybackSessions(): Flow<List<String>> {
123         return MediaSessionUtils.getMediaSessionHelper(context).activeOrPausedMediaSources.asFlow()
124             .map { mediaSources ->
125                 mediaSources.mapNotNull {
126                     it.packageName
127                 }
128             }
129     }
130 
131     companion object {
132         val TAG: String = UXRestrictionDataSourceImpl::class.java.simpleName
133     }
134 }
135