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