Camera: Propogate onCameraOpened flag at listener registration
- If camera is opened before the lister is registered, the corresponding
camera state needs to be propagated
- Handle the case of binder death
Test: testCameraManagerListenerCallbacks
Bug: 193475498
Change-Id: I4948c37750340e5e8edaf8b3fe290b1346f8c25c
diff --git a/core/java/android/hardware/CameraStatus.java b/core/java/android/hardware/CameraStatus.java
index 29802cb..874af29 100644
--- a/core/java/android/hardware/CameraStatus.java
+++ b/core/java/android/hardware/CameraStatus.java
@@ -31,6 +31,7 @@
public String cameraId;
public int status;
public String[] unavailablePhysicalCameras;
+ public String clientPackage;
@Override
public int describeContents() {
@@ -42,12 +43,14 @@
out.writeString(cameraId);
out.writeInt(status);
out.writeStringArray(unavailablePhysicalCameras);
+ out.writeString(clientPackage);
}
public void readFromParcel(Parcel in) {
cameraId = in.readString();
status = in.readInt();
unavailablePhysicalCameras = in.readStringArray();
+ clientPackage = in.readString();
}
public static final @android.annotation.NonNull Parcelable.Creator<CameraStatus> CREATOR =
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 5833b3d..f5306b8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -24,6 +24,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -90,12 +91,19 @@
private final Context mContext;
private final Object mLock = new Object();
+ private static final String CAMERA_OPEN_CLOSE_LISTENER_PERMISSION =
+ "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
+ private final boolean mHasOpenCloseListenerPermission;
+
/**
* @hide
*/
public CameraManager(Context context) {
synchronized(mLock) {
mContext = context;
+ mHasOpenCloseListenerPermission =
+ mContext.checkSelfPermission(CAMERA_OPEN_CLOSE_LISTENER_PERMISSION) ==
+ PackageManager.PERMISSION_GRANTED;
}
}
@@ -253,7 +261,7 @@
public void registerAvailabilityCallback(@NonNull AvailabilityCallback callback,
@Nullable Handler handler) {
CameraManagerGlobal.get().registerAvailabilityCallback(callback,
- CameraDeviceImpl.checkAndWrapHandler(handler));
+ CameraDeviceImpl.checkAndWrapHandler(handler), mHasOpenCloseListenerPermission);
}
/**
@@ -274,7 +282,8 @@
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
- CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor);
+ CameraManagerGlobal.get().registerAvailabilityCallback(callback, executor,
+ mHasOpenCloseListenerPermission);
}
/**
@@ -1303,6 +1312,8 @@
// Camera ID -> (physical camera ID -> Status map)
private final ArrayMap<String, ArrayList<String>> mUnavailablePhysicalDevices =
new ArrayMap<String, ArrayList<String>>();
+ // Opened Camera ID -> apk name map
+ private final ArrayMap<String, String> mOpenedDevices = new ArrayMap<String, String>();
private final Set<Set<String>> mConcurrentCameraIdCombinations =
new ArraySet<Set<String>>();
@@ -1325,6 +1336,7 @@
// Access only through getCameraService to deal with binder death
private ICameraService mCameraService;
+ private boolean mHasOpenCloseListenerPermission = false;
// Singleton, don't allow construction
private CameraManagerGlobal() {
@@ -1403,6 +1415,12 @@
c.cameraId, unavailPhysicalCamera);
}
}
+
+ if (mHasOpenCloseListenerPermission &&
+ c.status == ICameraServiceListener.STATUS_NOT_AVAILABLE &&
+ !c.clientPackage.isEmpty()) {
+ onCameraOpenedLocked(c.cameraId, c.clientPackage);
+ }
}
mCameraService = cameraService;
} catch(ServiceSpecificException e) {
@@ -1893,6 +1911,12 @@
ICameraServiceListener.STATUS_NOT_PRESENT);
}
}
+
+ }
+ for (int i = 0; i < mOpenedDevices.size(); i++) {
+ String id = mOpenedDevices.keyAt(i);
+ String clientPackageId = mOpenedDevices.valueAt(i);
+ postSingleCameraOpenedUpdate(callback, executor, id, clientPackageId);
}
}
@@ -2058,9 +2082,15 @@
*
* @param callback the new callback to send camera availability notices to
* @param executor The executor which should invoke the callback. May not be null.
+ * @param hasOpenCloseListenerPermission whether the client has permission for
+ * onCameraOpened/onCameraClosed callback
*/
- public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor) {
+ public void registerAvailabilityCallback(AvailabilityCallback callback, Executor executor,
+ boolean hasOpenCloseListenerPermission) {
synchronized (mLock) {
+ // In practice, this permission doesn't change. So we don't need one flag for each
+ // callback object.
+ mHasOpenCloseListenerPermission = hasOpenCloseListenerPermission;
connectCameraServiceLocked();
Executor oldExecutor = mCallbackMap.put(callback, executor);
@@ -2152,26 +2182,53 @@
@Override
public void onCameraOpened(String cameraId, String clientPackageId) {
synchronized (mLock) {
- final int callbackCount = mCallbackMap.size();
- for (int i = 0; i < callbackCount; i++) {
- Executor executor = mCallbackMap.valueAt(i);
- final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+ onCameraOpenedLocked(cameraId, clientPackageId);
+ }
+ }
- postSingleCameraOpenedUpdate(callback, executor, cameraId, clientPackageId);
+ private void onCameraOpenedLocked(String cameraId, String clientPackageId) {
+ String oldApk = mOpenedDevices.put(cameraId, clientPackageId);
+
+ if (oldApk != null) {
+ if (oldApk.equals(clientPackageId)) {
+ Log.w(TAG,
+ "onCameraOpened was previously called for " + oldApk
+ + " and is now again called for the same package name, "
+ + "so no new client visible update will be sent");
+ return;
+ } else {
+ Log.w(TAG,
+ "onCameraOpened was previously called for " + oldApk
+ + " and is now called for " + clientPackageId
+ + " without onCameraClosed being called first");
}
}
+
+ final int callbackCount = mCallbackMap.size();
+ for (int i = 0; i < callbackCount; i++) {
+ Executor executor = mCallbackMap.valueAt(i);
+ final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+
+ postSingleCameraOpenedUpdate(callback, executor, cameraId, clientPackageId);
+ }
}
@Override
public void onCameraClosed(String cameraId) {
synchronized (mLock) {
- final int callbackCount = mCallbackMap.size();
- for (int i = 0; i < callbackCount; i++) {
- Executor executor = mCallbackMap.valueAt(i);
- final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+ onCameraClosedLocked(cameraId);
+ }
+ }
- postSingleCameraClosedUpdate(callback, executor, cameraId);
- }
+ private void onCameraClosedLocked(String cameraId) {
+ mOpenedDevices.remove(cameraId);
+
+ final int callbackCount = mCallbackMap.size();
+ for (int i = 0; i < callbackCount; i++) {
+ Executor executor = mCallbackMap.valueAt(i);
+ final AvailabilityCallback callback = mCallbackMap.keyAt(i);
+
+ postSingleCameraClosedUpdate(callback, executor, cameraId);
}
}
@@ -2229,6 +2286,10 @@
for (int i = mDeviceStatus.size() - 1; i >= 0; i--) {
String cameraId = mDeviceStatus.keyAt(i);
onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, cameraId);
+
+ if (mHasOpenCloseListenerPermission) {
+ onCameraClosedLocked(cameraId);
+ }
}
for (int i = 0; i < mTorchStatus.size(); i++) {
String cameraId = mTorchStatus.keyAt(i);