1 /* 2 * Copyright (C) 2022 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.systemui.privacy 18 19 import android.content.pm.PackageManager 20 import android.media.projection.MediaProjectionInfo 21 import android.media.projection.MediaProjectionManager 22 import android.os.Handler 23 import android.util.Log 24 import androidx.annotation.WorkerThread 25 import com.android.internal.annotations.GuardedBy 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Background 28 import com.android.systemui.privacy.logging.PrivacyLogger 29 import com.android.systemui.util.asIndenting 30 import com.android.systemui.util.time.SystemClock 31 import com.android.systemui.util.withIncreasedIndent 32 import java.io.PrintWriter 33 import javax.inject.Inject 34 35 /** 36 * Monitors the active media projection to update privacy items. 37 */ 38 @SysUISingleton 39 class MediaProjectionPrivacyItemMonitor @Inject constructor( 40 private val mediaProjectionManager: MediaProjectionManager, 41 private val packageManager: PackageManager, 42 private val privacyConfig: PrivacyConfig, 43 @Background private val bgHandler: Handler, 44 private val systemClock: SystemClock, 45 private val logger: PrivacyLogger 46 ) : PrivacyItemMonitor { 47 48 companion object { 49 const val TAG = "MediaProjectionPrivacyItemMonitor" 50 const val DEBUG = false 51 } 52 53 private val lock = Any() 54 55 @GuardedBy("lock") 56 private var callback: PrivacyItemMonitor.Callback? = null 57 58 @GuardedBy("lock") 59 private var mediaProjectionAvailable = privacyConfig.mediaProjectionAvailable 60 @GuardedBy("lock") 61 private var listening = false 62 63 @GuardedBy("lock") 64 private val privacyItems = mutableListOf<PrivacyItem>() 65 66 private val optionsCallback = object : PrivacyConfig.Callback { 67 override fun onFlagMediaProjectionChanged(flag: Boolean) { 68 synchronized(lock) { 69 mediaProjectionAvailable = privacyConfig.mediaProjectionAvailable 70 setListeningStateLocked() 71 } 72 dispatchOnPrivacyItemsChanged() 73 } 74 } 75 76 private val mediaProjectionCallback = object : MediaProjectionManager.Callback() { 77 @WorkerThread 78 override fun onStart(info: MediaProjectionInfo) { 79 synchronized(lock) { onMediaProjectionStartedLocked(info) } 80 dispatchOnPrivacyItemsChanged() 81 } 82 83 @WorkerThread 84 override fun onStop(info: MediaProjectionInfo) { 85 synchronized(lock) { onMediaProjectionStoppedLocked(info) } 86 dispatchOnPrivacyItemsChanged() 87 } 88 } 89 90 init { 91 privacyConfig.addCallback(optionsCallback) 92 setListeningStateLocked() 93 } 94 95 override fun startListening(callback: PrivacyItemMonitor.Callback) { 96 synchronized(lock) { 97 this.callback = callback 98 } 99 } 100 101 override fun stopListening() { 102 synchronized(lock) { 103 this.callback = null 104 } 105 } 106 107 @GuardedBy("lock") 108 @WorkerThread 109 private fun onMediaProjectionStartedLocked(info: MediaProjectionInfo) { 110 if (DEBUG) Log.d(TAG, "onMediaProjectionStartedLocked: info=$info") 111 val item = makePrivacyItem(info) 112 privacyItems.add(item) 113 logItemActive(item, true) 114 } 115 116 @GuardedBy("lock") 117 @WorkerThread 118 private fun onMediaProjectionStoppedLocked(info: MediaProjectionInfo) { 119 if (DEBUG) Log.d(TAG, "onMediaProjectionStoppedLocked: info=$info") 120 val item = makePrivacyItem(info) 121 privacyItems.removeAt(privacyItems.indexOfFirst { it.application == item.application }) 122 logItemActive(item, false) 123 } 124 125 @WorkerThread 126 private fun makePrivacyItem(info: MediaProjectionInfo): PrivacyItem { 127 val userId = info.userHandle.identifier 128 val uid = packageManager.getPackageUidAsUser(info.packageName, userId) 129 val app = PrivacyApplication(info.packageName, uid) 130 val now = systemClock.elapsedRealtime() 131 return PrivacyItem(PrivacyType.TYPE_MEDIA_PROJECTION, app, now) 132 } 133 134 private fun logItemActive(item: PrivacyItem, active: Boolean) { 135 logger.logUpdatedItemFromMediaProjection( 136 item.application.uid, item.application.packageName, active) 137 } 138 139 /** 140 * Updates listening status based on whether there are callbacks and the indicator is enabled. 141 */ 142 @GuardedBy("lock") 143 private fun setListeningStateLocked() { 144 val shouldListen = mediaProjectionAvailable 145 if (DEBUG) { 146 Log.d(TAG, "shouldListen=$shouldListen, " + 147 "mediaProjectionAvailable=$mediaProjectionAvailable") 148 } 149 if (listening == shouldListen) { 150 return 151 } 152 153 listening = shouldListen 154 if (shouldListen) { 155 if (DEBUG) Log.d(TAG, "Registering MediaProjectionManager callback") 156 mediaProjectionManager.addCallback(mediaProjectionCallback, bgHandler) 157 158 val activeProjection = mediaProjectionManager.activeProjectionInfo 159 if (activeProjection != null) { 160 onMediaProjectionStartedLocked(activeProjection) 161 dispatchOnPrivacyItemsChanged() 162 } 163 } else { 164 if (DEBUG) Log.d(TAG, "Unregistering MediaProjectionManager callback") 165 mediaProjectionManager.removeCallback(mediaProjectionCallback) 166 privacyItems.forEach { logItemActive(it, false) } 167 privacyItems.clear() 168 dispatchOnPrivacyItemsChanged() 169 } 170 } 171 172 override fun getActivePrivacyItems(): List<PrivacyItem> { 173 synchronized(lock) { 174 if (DEBUG) Log.d(TAG, "getActivePrivacyItems: privacyItems=$privacyItems") 175 return privacyItems.toList() 176 } 177 } 178 179 private fun dispatchOnPrivacyItemsChanged() { 180 if (DEBUG) Log.d(TAG, "dispatchOnPrivacyItemsChanged") 181 val cb = synchronized(lock) { callback } 182 if (cb != null) { 183 bgHandler.post { 184 cb.onPrivacyItemsChanged() 185 } 186 } 187 } 188 189 override fun dump(pw: PrintWriter, args: Array<out String>) { 190 val ipw = pw.asIndenting() 191 ipw.println("MediaProjectionPrivacyItemMonitor:") 192 ipw.withIncreasedIndent { 193 synchronized(lock) { 194 ipw.println("Listening: $listening") 195 ipw.println("mediaProjectionAvailable: $mediaProjectionAvailable") 196 ipw.println("Callback: $callback") 197 ipw.println("Privacy Items: $privacyItems") 198 } 199 } 200 ipw.flush() 201 } 202 } 203