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