am 9b35b801: Merge "CameraManager: Separate service listener into a singleton" into lmp-mr1-dev

* commit '9b35b801fb90e31c1dff5535715e505f3a1e26fb':
  CameraManager: Separate service listener into a singleton
This commit is contained in:
Eino-Ville Talvala
2014-11-27 00:27:36 +00:00
committed by Android Git Automerger

View File

@@ -54,29 +54,17 @@ public final class CameraManager {
private static final String TAG = "CameraManager";
private final boolean DEBUG;
/**
* This should match the ICameraService definition
*/
private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
private static final int USE_CALLING_UID = -1;
@SuppressWarnings("unused")
private static final int API_VERSION_1 = 1;
private static final int API_VERSION_2 = 2;
// Access only through getCameraServiceLocked to deal with binder death
private ICameraService mCameraService;
private ArrayList<String> mDeviceIdList;
private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
new ArrayMap<AvailabilityCallback, Handler>();
private final Context mContext;
private final Object mLock = new Object();
private final CameraServiceListener mServiceListener = new CameraServiceListener();
/**
* @hide
*/
@@ -84,8 +72,6 @@ public final class CameraManager {
DEBUG = Log.isLoggable(TAG, Log.DEBUG);
synchronized(mLock) {
mContext = context;
connectCameraServiceLocked();
}
}
@@ -116,6 +102,12 @@ public final class CameraManager {
* <p>The first time a callback is registered, it is immediately called
* with the availability status of all currently known camera devices.</p>
*
* <p>Since this callback will be registered with the camera service, remember to unregister it
* once it is no longer needed; otherwise the callback will continue to receive events
* indefinitely and it may prevent other resources from being released. Specifically, the
* callbacks will be invoked independently of the general activity lifecycle and independently
* of the state of individual CameraManager instances.</p>
*
* @param callback the new callback to send camera availability notices to
* @param handler The handler on which the callback should be invoked, or
* {@code null} to use the current thread's {@link android.os.Looper looper}.
@@ -130,13 +122,7 @@ public final class CameraManager {
handler = new Handler(looper);
}
synchronized (mLock) {
Handler oldHandler = mCallbackMap.put(callback, handler);
// For new callbacks, provide initial availability information
if (oldHandler == null) {
mServiceListener.updateCallbackLocked(callback, handler);
}
}
CameraManagerGlobal.get().registerAvailabilityCallback(callback, handler);
}
/**
@@ -148,9 +134,7 @@ public final class CameraManager {
* @param callback The callback to remove from the notification list
*/
public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
synchronized (mLock) {
mCallbackMap.remove(callback);
}
CameraManagerGlobal.get().unregisterAvailabilityCallback(callback);
}
/**
@@ -187,7 +171,7 @@ public final class CameraManager {
* otherwise get them from the legacy shim instead.
*/
ICameraService cameraService = getCameraServiceLocked();
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
"Camera service is currently unavailable");
@@ -268,7 +252,7 @@ public final class CameraManager {
try {
if (supportsCamera2ApiLocked(cameraId)) {
// Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
ICameraService cameraService = getCameraServiceLocked();
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
if (cameraService == null) {
throw new CameraRuntimeException(
CameraAccessException.CAMERA_DISCONNECTED,
@@ -443,13 +427,6 @@ public final class CameraManager {
}
}
/**
* Temporary for migrating to Callback naming
* @hide
*/
public static abstract class AvailabilityListener extends AvailabilityCallback {
}
/**
* Return or create the list of currently connected camera devices.
*
@@ -458,7 +435,7 @@ public final class CameraManager {
private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
if (mDeviceIdList == null) {
int numCameras = 0;
ICameraService cameraService = getCameraServiceLocked();
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
ArrayList<String> deviceIdList = new ArrayList<>();
// If no camera service, then no devices
@@ -515,18 +492,6 @@ public final class CameraManager {
return mDeviceIdList;
}
private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
int problem = e.getReason();
switch (problem) {
case CameraAccessException.CAMERA_DISCONNECTED:
String errorMsg = CameraAccessException.getDefaultMessage(problem);
Log.w(TAG, msg + ": " + errorMsg);
break;
default:
throw new IllegalStateException(msg, e.asChecked());
}
}
/**
* Queries the camera service if it supports the camera2 api directly, or needs a shim.
*
@@ -556,7 +521,7 @@ public final class CameraManager {
* Anything else is an unexpected error we don't want to recover from.
*/
try {
ICameraService cameraService = getCameraServiceLocked();
ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
// If no camera service, no support
if (cameraService == null) return false;
@@ -578,97 +543,23 @@ public final class CameraManager {
}
/**
* Connect to the camera service if it's available, and set up listeners.
*
* <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
* A per-process global camera manager instance, to retain a connection to the camera service,
* and to distribute camera availability notices to API-registered callbacks
*/
private void connectCameraServiceLocked() {
mCameraService = null;
IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
if (cameraServiceBinder == null) {
// Camera service is now down, leave mCameraService as null
return;
}
try {
cameraServiceBinder.linkToDeath(new CameraServiceDeathListener(), /*flags*/ 0);
} catch (RemoteException e) {
// Camera service is now down, leave mCameraService as null
return;
}
private static final class CameraManagerGlobal extends ICameraServiceListener.Stub
implements IBinder.DeathRecipient {
ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
private static final String TAG = "CameraManagerGlobal";
private final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Singleton instance
private static final CameraManagerGlobal gCameraManager =
new CameraManagerGlobal();
/**
* Wrap the camera service in a decorator which automatically translates return codes
* into exceptions.
* This must match the ICameraService definition
*/
ICameraService cameraService = CameraServiceBinderDecorator.newInstance(cameraServiceRaw);
try {
CameraServiceBinderDecorator.throwOnError(
CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
} catch (CameraRuntimeException e) {
handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
}
try {
cameraService.addListener(mServiceListener);
mCameraService = cameraService;
} catch(CameraRuntimeException e) {
// Unexpected failure
throw new IllegalStateException("Failed to register a camera service listener",
e.asChecked());
} catch (RemoteException e) {
// Camera service is now down, leave mCameraService as null
}
}
/**
* Return a best-effort ICameraService.
*
* <p>This will be null if the camera service
* is not currently available. If the camera service has died since the last
* use of the camera service, will try to reconnect to the service.</p>
*/
private ICameraService getCameraServiceLocked() {
if (mCameraService == null) {
Log.i(TAG, "getCameraServiceLocked: Reconnecting to camera service");
connectCameraServiceLocked();
if (mCameraService == null) {
Log.e(TAG, "Camera service is unavailable");
}
}
return mCameraService;
}
/**
* Listener for camera service death.
*
* <p>The camera service isn't supposed to die under any normal circumstances, but can be turned
* off during debug, or crash due to bugs. So detect that and null out the interface object, so
* that the next calls to the manager can try to reconnect.</p>
*/
private class CameraServiceDeathListener implements IBinder.DeathRecipient {
public void binderDied() {
synchronized(mLock) {
mCameraService = null;
// Tell listeners that the cameras are _available_, because any existing clients
// will have gotten disconnected. This is optimistic under the assumption that the
// service will be back shortly.
//
// Without this, a camera service crash while a camera is open will never signal to
// listeners that previously in-use cameras are now available.
for (String cameraId : mDeviceIdList) {
mServiceListener.onStatusChangedLocked(CameraServiceListener.STATUS_PRESENT,
cameraId);
}
}
}
}
// TODO: this class needs unit tests
// TODO: extract class into top level
private class CameraServiceListener extends ICameraServiceListener.Stub {
private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
// Keep up-to-date with ICameraServiceListener.h
@@ -683,16 +574,112 @@ public final class CameraManager {
// Camera is in use by another app and cannot be used exclusively
public static final int STATUS_NOT_AVAILABLE = 0x80000000;
// End enums shared with ICameraServiceListener.h
// Camera ID -> Status map
private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
private static final String TAG = "CameraServiceListener";
// Registered availablility callbacks and their handlers
private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
new ArrayMap<AvailabilityCallback, Handler>();
private final Object mLock = new Object();
// Access only through getCameraService to deal with binder death
private ICameraService mCameraService;
// Singleton, don't allow construction
private CameraManagerGlobal() {
}
public static CameraManagerGlobal get() {
return gCameraManager;
}
@Override
public IBinder asBinder() {
return this;
}
/**
* Return a best-effort ICameraService.
*
* <p>This will be null if the camera service is not currently available. If the camera
* service has died since the last use of the camera service, will try to reconnect to the
* service.</p>
*/
public ICameraService getCameraService() {
synchronized(mLock) {
if (mCameraService == null) {
Log.i(TAG, "getCameraService: Reconnecting to camera service");
connectCameraServiceLocked();
if (mCameraService == null) {
Log.e(TAG, "Camera service is unavailable");
}
}
return mCameraService;
}
}
/**
* Connect to the camera service if it's available, and set up listeners.
*
* <p>Sets mCameraService to a valid pointer or null if the connection does not succeed.</p>
*/
private void connectCameraServiceLocked() {
mCameraService = null;
IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME);
if (cameraServiceBinder == null) {
// Camera service is now down, leave mCameraService as null
return;
}
try {
cameraServiceBinder.linkToDeath(this, /*flags*/ 0);
} catch (RemoteException e) {
// Camera service is now down, leave mCameraService as null
return;
}
ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
/**
* Wrap the camera service in a decorator which automatically translates return codes
* into exceptions.
*/
ICameraService cameraService =
CameraServiceBinderDecorator.newInstance(cameraServiceRaw);
try {
CameraServiceBinderDecorator.throwOnError(
CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
} catch (CameraRuntimeException e) {
handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
}
try {
cameraService.addListener(this);
mCameraService = cameraService;
} catch(CameraRuntimeException e) {
// Unexpected failure
throw new IllegalStateException("Failed to register a camera service listener",
e.asChecked());
} catch (RemoteException e) {
// Camera service is now down, leave mCameraService as null
}
}
private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
int problem = e.getReason();
switch (problem) {
case CameraAccessException.CAMERA_DISCONNECTED:
String errorMsg = CameraAccessException.getDefaultMessage(problem);
Log.w(TAG, msg + ": " + errorMsg);
break;
default:
throw new IllegalStateException(msg, e.asChecked());
}
}
private boolean isAvailable(int status) {
switch (status) {
case STATUS_PRESENT:
@@ -739,7 +726,7 @@ public final class CameraManager {
* Send the state of all known cameras to the provided listener, to initialize
* the listener's knowledge of camera state.
*/
public void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
private void updateCallbackLocked(AvailabilityCallback callback, Handler handler) {
for (int i = 0; i < mDeviceStatus.size(); i++) {
String id = mDeviceStatus.keyAt(i);
Integer status = mDeviceStatus.valueAt(i);
@@ -747,14 +734,7 @@ public final class CameraManager {
}
}
@Override
public void onStatusChanged(int status, int cameraId) throws RemoteException {
synchronized(CameraManager.this.mLock) {
onStatusChangedLocked(status, String.valueOf(cameraId));
}
}
public void onStatusChangedLocked(int status, String id) {
private void onStatusChangedLocked(int status, String id) {
if (DEBUG) {
Log.v(TAG,
String.format("Camera id %s has status changed to 0x%x", id, status));
@@ -811,5 +791,72 @@ public final class CameraManager {
}
} // onStatusChangedLocked
} // CameraServiceListener
/**
* Register a callback to be notified about camera device availability with the
* global listener singleton.
*
* @param callback the new callback to send camera availability notices to
* @param handler The handler on which the callback should be invoked. May not be null.
*/
public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
synchronized (mLock) {
Handler oldHandler = mCallbackMap.put(callback, handler);
// For new callbacks, provide initial availability information
if (oldHandler == null) {
updateCallbackLocked(callback, handler);
}
}
}
/**
* Remove a previously-added callback; the callback will no longer receive connection and
* disconnection callbacks, and is no longer referenced by the global listener singleton.
*
* @param callback The callback to remove from the notification list
*/
public void unregisterAvailabilityCallback(AvailabilityCallback callback) {
synchronized (mLock) {
mCallbackMap.remove(callback);
}
}
/**
* Callback from camera service notifying the process about camera availability changes
*/
@Override
public void onStatusChanged(int status, int cameraId) throws RemoteException {
synchronized(mLock) {
onStatusChangedLocked(status, String.valueOf(cameraId));
}
}
/**
* Listener for camera service death.
*
* <p>The camera service isn't supposed to die under any normal circumstances, but can be
* turned off during debug, or crash due to bugs. So detect that and null out the interface
* object, so that the next calls to the manager can try to reconnect.</p>
*/
public void binderDied() {
synchronized(mLock) {
// Only do this once per service death
if (mCameraService == null) return;
mCameraService = null;
// Tell listeners that the cameras are _available_, because any existing clients
// will have gotten disconnected. This is optimistic under the assumption that
// the service will be back shortly.
//
// Without this, a camera service crash while a camera is open will never signal
// to listeners that previously in-use cameras are now available.
for (int i = 0; i < mDeviceStatus.size(); i++) {
String cameraId = mDeviceStatus.keyAt(i);
onStatusChangedLocked(STATUS_PRESENT, cameraId);
}
}
}
} // CameraManagerGlobal
} // CameraManager