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.server.companion.virtual; 18 19 import static android.hardware.camera2.CameraInjectionSession.InjectionStatusCallback.ERROR_INJECTION_UNSUPPORTED; 20 21 import android.annotation.NonNull; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.UserInfo; 26 import android.hardware.camera2.CameraAccessException; 27 import android.hardware.camera2.CameraInjectionSession; 28 import android.hardware.camera2.CameraManager; 29 import android.os.Process; 30 import android.os.UserManager; 31 import android.util.ArrayMap; 32 import android.util.ArraySet; 33 import android.util.Slog; 34 35 import com.android.internal.annotations.GuardedBy; 36 37 import java.util.List; 38 import java.util.Set; 39 40 /** 41 * Handles blocking access to the camera for apps running on virtual devices. 42 */ 43 class CameraAccessController extends CameraManager.AvailabilityCallback implements AutoCloseable { 44 private static final String TAG = "CameraAccessController"; 45 46 private final Object mLock = new Object(); 47 48 private final Context mContext; 49 private final VirtualDeviceManagerInternal mVirtualDeviceManagerInternal; 50 private final CameraAccessBlockedCallback mBlockedCallback; 51 private final CameraManager mCameraManager; 52 private final PackageManager mPackageManager; 53 private final UserManager mUserManager; 54 55 @GuardedBy("mLock") 56 private int mObserverCount = 0; 57 58 @GuardedBy("mLock") 59 private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>(); 60 61 /** 62 * Mapping from camera ID to open camera app associations. Key is the camera id, value is the 63 * information of the app's uid and package name. 64 */ 65 @GuardedBy("mLock") 66 private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>(); 67 68 static class InjectionSessionData { 69 public int appUid; 70 public ArrayMap<String, CameraInjectionSession> cameraIdToSession = new ArrayMap<>(); 71 } 72 73 static class OpenCameraInfo { 74 public String packageName; 75 public Set<Integer> packageUids; 76 } 77 78 interface CameraAccessBlockedCallback { 79 /** 80 * Called whenever an app was blocked from accessing a camera. 81 * @param appUid uid for the app which was blocked 82 */ onCameraAccessBlocked(int appUid)83 void onCameraAccessBlocked(int appUid); 84 } 85 CameraAccessController(Context context, VirtualDeviceManagerInternal virtualDeviceManagerInternal, CameraAccessBlockedCallback blockedCallback)86 CameraAccessController(Context context, 87 VirtualDeviceManagerInternal virtualDeviceManagerInternal, 88 CameraAccessBlockedCallback blockedCallback) { 89 mContext = context; 90 mVirtualDeviceManagerInternal = virtualDeviceManagerInternal; 91 mBlockedCallback = blockedCallback; 92 mCameraManager = mContext.getSystemService(CameraManager.class); 93 mPackageManager = mContext.getPackageManager(); 94 mUserManager = mContext.getSystemService(UserManager.class); 95 } 96 97 /** 98 * Starts watching for camera access by uids running on a virtual device, if we were not 99 * already doing so. 100 */ startObservingIfNeeded()101 public void startObservingIfNeeded() { 102 synchronized (mLock) { 103 if (mObserverCount == 0) { 104 mCameraManager.registerAvailabilityCallback(mContext.getMainExecutor(), this); 105 } 106 mObserverCount++; 107 } 108 } 109 110 /** 111 * Stop watching for camera access. 112 */ stopObservingIfNeeded()113 public void stopObservingIfNeeded() { 114 synchronized (mLock) { 115 mObserverCount--; 116 if (mObserverCount <= 0) { 117 close(); 118 } 119 } 120 } 121 122 /** 123 * Need to block camera access for applications running on virtual displays. 124 * <p> 125 * Apps that open the camera on the main display will need to block camera access if moved to a 126 * virtual display. 127 * 128 * @param runningUids uids of the application running on the virtual display 129 */ blockCameraAccessIfNeeded(Set<Integer> runningUids)130 public void blockCameraAccessIfNeeded(Set<Integer> runningUids) { 131 synchronized (mLock) { 132 for (int i = 0; i < mAppsToBlockOnVirtualDevice.size(); i++) { 133 final String cameraId = mAppsToBlockOnVirtualDevice.keyAt(i); 134 final OpenCameraInfo openCameraInfo = mAppsToBlockOnVirtualDevice.get(cameraId); 135 final String packageName = openCameraInfo.packageName; 136 for (int packageUid : openCameraInfo.packageUids) { 137 if (runningUids.contains(packageUid)) { 138 InjectionSessionData data = mPackageToSessionData.get(packageName); 139 if (data == null) { 140 data = new InjectionSessionData(); 141 data.appUid = packageUid; 142 mPackageToSessionData.put(packageName, data); 143 } 144 startBlocking(packageName, cameraId); 145 break; 146 } 147 } 148 } 149 } 150 } 151 152 @Override close()153 public void close() { 154 synchronized (mLock) { 155 if (mObserverCount < 0) { 156 Slog.wtf(TAG, "Unexpected negative mObserverCount: " + mObserverCount); 157 } else if (mObserverCount > 0) { 158 Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount); 159 } 160 } 161 mCameraManager.unregisterAvailabilityCallback(this); 162 } 163 164 @Override onCameraOpened(@onNull String cameraId, @NonNull String packageName)165 public void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) { 166 synchronized (mLock) { 167 InjectionSessionData data = mPackageToSessionData.get(packageName); 168 List<UserInfo> aliveUsers = mUserManager.getAliveUsers(); 169 ArraySet<Integer> packageUids = new ArraySet<>(); 170 for (UserInfo user : aliveUsers) { 171 int userId = user.getUserHandle().getIdentifier(); 172 int appUid = queryUidFromPackageName(userId, packageName); 173 if (mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(appUid)) { 174 if (data == null) { 175 data = new InjectionSessionData(); 176 data.appUid = appUid; 177 mPackageToSessionData.put(packageName, data); 178 } 179 if (data.cameraIdToSession.containsKey(cameraId)) { 180 return; 181 } 182 startBlocking(packageName, cameraId); 183 return; 184 } else { 185 if (appUid != Process.INVALID_UID) { 186 packageUids.add(appUid); 187 } 188 } 189 } 190 OpenCameraInfo openCameraInfo = new OpenCameraInfo(); 191 openCameraInfo.packageName = packageName; 192 openCameraInfo.packageUids = packageUids; 193 mAppsToBlockOnVirtualDevice.put(cameraId, openCameraInfo); 194 CameraInjectionSession existingSession = 195 (data != null) ? data.cameraIdToSession.get(cameraId) : null; 196 if (existingSession != null) { 197 existingSession.close(); 198 data.cameraIdToSession.remove(cameraId); 199 if (data.cameraIdToSession.isEmpty()) { 200 mPackageToSessionData.remove(packageName); 201 } 202 } 203 } 204 } 205 206 @Override onCameraClosed(@onNull String cameraId)207 public void onCameraClosed(@NonNull String cameraId) { 208 synchronized (mLock) { 209 mAppsToBlockOnVirtualDevice.remove(cameraId); 210 for (int i = mPackageToSessionData.size() - 1; i >= 0; i--) { 211 InjectionSessionData data = mPackageToSessionData.valueAt(i); 212 CameraInjectionSession session = data.cameraIdToSession.get(cameraId); 213 if (session != null) { 214 session.close(); 215 data.cameraIdToSession.remove(cameraId); 216 if (data.cameraIdToSession.isEmpty()) { 217 mPackageToSessionData.removeAt(i); 218 } 219 } 220 } 221 } 222 } 223 224 /** 225 * Turns on blocking for a particular camera and package. 226 */ startBlocking(String packageName, String cameraId)227 private void startBlocking(String packageName, String cameraId) { 228 try { 229 Slog.d( 230 TAG, 231 "startBlocking() cameraId: " + cameraId + " packageName: " + packageName); 232 mCameraManager.injectCamera(packageName, cameraId, /* externalCamId */ "", 233 mContext.getMainExecutor(), 234 new CameraInjectionSession.InjectionStatusCallback() { 235 @Override 236 public void onInjectionSucceeded( 237 @NonNull CameraInjectionSession session) { 238 CameraAccessController.this.onInjectionSucceeded(cameraId, packageName, 239 session); 240 } 241 242 @Override 243 public void onInjectionError(@NonNull int errorCode) { 244 CameraAccessController.this.onInjectionError(cameraId, packageName, 245 errorCode); 246 } 247 }); 248 } catch (CameraAccessException e) { 249 Slog.e(TAG, 250 "Failed to injectCamera for cameraId:" + cameraId + " package:" + packageName, 251 e); 252 } 253 } 254 onInjectionSucceeded(String cameraId, String packageName, @NonNull CameraInjectionSession session)255 private void onInjectionSucceeded(String cameraId, String packageName, 256 @NonNull CameraInjectionSession session) { 257 synchronized (mLock) { 258 InjectionSessionData data = mPackageToSessionData.get(packageName); 259 if (data == null) { 260 Slog.e(TAG, "onInjectionSucceeded didn't find expected entry for package " 261 + packageName); 262 session.close(); 263 return; 264 } 265 CameraInjectionSession existingSession = data.cameraIdToSession.put(cameraId, session); 266 if (existingSession != null) { 267 Slog.e(TAG, "onInjectionSucceeded found unexpected existing session for camera " 268 + cameraId); 269 existingSession.close(); 270 } 271 } 272 } 273 onInjectionError(String cameraId, String packageName, @NonNull int errorCode)274 private void onInjectionError(String cameraId, String packageName, @NonNull int errorCode) { 275 if (errorCode != ERROR_INJECTION_UNSUPPORTED) { 276 // ERROR_INJECTION_UNSUPPORTED means that there wasn't an external camera to map to the 277 // internal camera, which is expected when using the injection interface as we are in 278 // this class to simply block camera access. Any other error is unexpected. 279 Slog.e(TAG, "Unexpected injection error code:" + errorCode + " for camera:" + cameraId 280 + " and package:" + packageName); 281 return; 282 } 283 synchronized (mLock) { 284 InjectionSessionData data = mPackageToSessionData.get(packageName); 285 if (data != null) { 286 mBlockedCallback.onCameraAccessBlocked(data.appUid); 287 } 288 } 289 } 290 queryUidFromPackageName(int userId, String packageName)291 private int queryUidFromPackageName(int userId, String packageName) { 292 try { 293 final ApplicationInfo ainfo = 294 mPackageManager.getApplicationInfoAsUser(packageName, 295 PackageManager.GET_ACTIVITIES, userId); 296 return ainfo.uid; 297 } catch (PackageManager.NameNotFoundException e) { 298 Slog.w(TAG, "queryUidFromPackageName - unknown package " + packageName, e); 299 return Process.INVALID_UID; 300 } 301 } 302 } 303