1 /* 2 * 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 package com.android.systemui.statusbar.policy 17 18 import android.content.Context 19 import android.content.pm.PackageManager 20 import android.media.MediaRouter 21 import android.media.projection.MediaProjectionInfo 22 import android.text.TextUtils 23 import com.android.systemui.log.core.LogLevel 24 import com.android.systemui.res.R 25 import com.android.systemui.util.Utils 26 27 /** Represents a specific cast session. */ 28 data class CastDevice( 29 val id: String, 30 /** A human-readable name of what is receiving the cast (e.g. "Home Speaker", "Abc App"). */ 31 val name: String?, 32 /** An optional description with more information about the cast. */ 33 val description: String? = null, 34 val state: CastState, 35 val origin: CastOrigin, 36 /** Optional tag to use as a comparison value between cast sessions. */ 37 val tag: Any? = null, 38 ) { 39 val isCasting = state == CastState.Connecting || state == CastState.Connected 40 41 val shortLogString: String = 42 "CastDevice(id=$id name=$name description=$description state=$state origin=$origin)" 43 44 companion object { 45 /** Creates a [CastDevice] based on the provided information from MediaRouter. */ toCastDevicenull46 fun MediaRouter.RouteInfo.toCastDevice(context: Context): CastDevice { 47 val state = 48 when { 49 statusCode == MediaRouter.RouteInfo.STATUS_CONNECTING -> CastState.Connecting 50 this.isSelected || statusCode == MediaRouter.RouteInfo.STATUS_CONNECTED -> 51 CastState.Connected 52 else -> CastState.Disconnected 53 } 54 return CastDevice( 55 id = this.tag.toString(), 56 name = this.getName(context)?.toString(), 57 description = this.description?.toString(), 58 state = state, 59 tag = this, 60 origin = CastOrigin.MediaRouter, 61 ) 62 } 63 64 /** Creates a [CastDevice] based on the provided information from MediaProjection. */ MediaProjectionInfonull65 fun MediaProjectionInfo.toCastDevice( 66 context: Context, 67 packageManager: PackageManager, 68 logger: CastControllerLogger, 69 ): CastDevice { 70 return CastDevice( 71 id = this.packageName, 72 name = getAppName(this.packageName, packageManager, logger), 73 description = context.getString(R.string.quick_settings_casting), 74 state = CastState.Connected, 75 tag = this, 76 origin = CastOrigin.MediaProjection, 77 ) 78 } 79 getAppNamenull80 private fun getAppName( 81 packageName: String, 82 packageManager: PackageManager, 83 logger: CastControllerLogger, 84 ): String { 85 if (Utils.isHeadlessRemoteDisplayProvider(packageManager, packageName)) { 86 return "" 87 } 88 try { 89 val appInfo = packageManager.getApplicationInfo(packageName, 0) 90 val label = appInfo.loadLabel(packageManager) 91 if (!TextUtils.isEmpty(label)) { 92 return label.toString() 93 } 94 logger.log( 95 "#getAppName", 96 LogLevel.WARNING, 97 { str1 = packageName }, 98 { "No label found for package: $str1" }, 99 ) 100 } catch (e: PackageManager.NameNotFoundException) { 101 logger.log( 102 "#getAppName", 103 LogLevel.WARNING, 104 { str1 = packageName }, 105 { "Error getting appName for package=$str1" }, 106 e, 107 ) 108 } 109 return packageName 110 } 111 } 112 113 enum class CastState { 114 Disconnected, 115 Connecting, 116 Connected, 117 } 118 119 enum class CastOrigin { 120 /** SysUI found out about this cast device from MediaRouter APIs. */ 121 MediaRouter, 122 /** SysUI found out about this cast device from MediaProjection APIs. */ 123 MediaProjection, 124 } 125 } 126