diff options
4 files changed, 375 insertions, 177 deletions
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 9a3d806..a4a1559 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -23,7 +23,7 @@ import android.hardware.CameraInfo; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.legacy.CameraDeviceUserShim; import android.hardware.camera2.legacy.LegacyMetadataMapper; -import android.hardware.camera2.utils.CameraBinderDecorator; +import android.hardware.camera2.utils.CameraServiceBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; import android.hardware.camera2.utils.BinderHolder; import android.os.IBinder; @@ -52,6 +52,7 @@ import java.util.ArrayList; public final class CameraManager { private static final String TAG = "CameraManager"; + private final boolean DEBUG; /** * This should match the ICameraService definition @@ -63,7 +64,9 @@ public final class CameraManager { private static final int API_VERSION_1 = 1; private static final int API_VERSION_2 = 2; - private final ICameraService mCameraService; + // Access only through getCameraServiceLocked to deal with binder death + private ICameraService mCameraService; + private ArrayList<String> mDeviceIdList; private final ArrayMap<AvailabilityListener, Handler> mListenerMap = @@ -72,35 +75,17 @@ public final class CameraManager { private final Context mContext; private final Object mLock = new Object(); + private final CameraServiceListener mServiceListener = new CameraServiceListener(); + /** * @hide */ public CameraManager(Context context) { - mContext = context; - - IBinder cameraServiceBinder = ServiceManager.getService(CAMERA_SERVICE_BINDER_NAME); - ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder); - - /** - * Wrap the camera service in a decorator which automatically translates return codes - * into exceptions, and RemoteExceptions into other exceptions. - */ - mCameraService = CameraBinderDecorator.newInstance(cameraServiceRaw); - - try { - CameraBinderDecorator.throwOnError( - CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor()); - } catch (CameraRuntimeException e) { - handleRecoverableSetupErrors(e, "Failed to set up vendor tags"); - } + DEBUG = Log.isLoggable(TAG, Log.DEBUG); + synchronized(mLock) { + mContext = context; - try { - mCameraService.addListener(new CameraServiceListener()); - } catch(CameraRuntimeException e) { - throw new IllegalStateException("Failed to register a camera service listener", - e.asChecked()); - } catch (RemoteException e) { - // impossible + connectCameraServiceLocked(); } } @@ -116,13 +101,9 @@ public final class CameraManager { */ public String[] getCameraIdList() throws CameraAccessException { synchronized (mLock) { - try { - return getOrCreateDeviceIdListLocked().toArray(new String[0]); - } catch(CameraAccessException e) { - // this should almost never happen, except if mediaserver crashes - throw new IllegalStateException( - "Failed to query camera service for device ID list", e); - } + // ID list creation handles various known failures in device enumeration, so only + // exceptions it'll throw are unexpected, and should be propagated upward. + return getOrCreateDeviceIdListLocked().toArray(new String[0]); } } @@ -132,6 +113,9 @@ public final class CameraManager { * <p>Registering the same listener again will replace the handler with the * new one provided.</p> * + * <p>The first time a listener is registered, it is immediately called + * with the availability status of all currently known camera devices.</p> + * * @param listener The new listener to send camera availability notices to * @param handler The handler on which the listener should be invoked, or * {@code null} to use the current thread's {@link android.os.Looper looper}. @@ -147,10 +131,11 @@ public final class CameraManager { } synchronized (mLock) { - mListenerMap.put(listener, handler); - - // TODO: fire the current oldest known state when adding a new listener - // (must be done while holding lock) + Handler oldHandler = mListenerMap.put(listener, handler); + // For new listeners, provide initial availability information + if (oldHandler == null) { + mServiceListener.updateListenerLocked(listener, handler); + } } } @@ -176,64 +161,67 @@ public final class CameraManager { * @return The properties of the given camera * * @throws IllegalArgumentException if the cameraId does not match any - * currently connected camera device. - * @throws CameraAccessException if the camera is disabled by device policy. + * known camera device. + * @throws CameraAccessException if the camera is disabled by device policy, or + * the camera device has been disconnected. * @throws SecurityException if the application does not have permission to - * access the camera + * access the camera * * @see #getCameraIdList * @see android.app.admin.DevicePolicyManager#setCameraDisabled */ public CameraCharacteristics getCameraCharacteristics(String cameraId) throws CameraAccessException { + CameraCharacteristics characteristics = null; synchronized (mLock) { if (!getOrCreateDeviceIdListLocked().contains(cameraId)) { throw new IllegalArgumentException(String.format("Camera id %s does not match any" + " currently connected camera device", cameraId)); } - } - int id = Integer.valueOf(cameraId); + int id = Integer.valueOf(cameraId); - /* - * Get the camera characteristics from the camera service directly if it supports it, - * otherwise get them from the legacy shim instead. - */ + /* + * Get the camera characteristics from the camera service directly if it supports it, + * otherwise get them from the legacy shim instead. + */ - if (!supportsCamera2Api(cameraId)) { - // Legacy backwards compatibility path; build static info from the camera parameters - String[] outParameters = new String[1]; + ICameraService cameraService = getCameraServiceLocked(); + if (cameraService == null) { + throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable"); + } try { - mCameraService.getLegacyParameters(id, /*out*/outParameters); - String parameters = outParameters[0]; + if (!supportsCamera2ApiLocked(cameraId)) { + // Legacy backwards compatibility path; build static info from the camera + // parameters + String[] outParameters = new String[1]; - CameraInfo info = new CameraInfo(); - mCameraService.getCameraInfo(id, /*out*/info); + cameraService.getLegacyParameters(id, /*out*/outParameters); + String parameters = outParameters[0]; - return LegacyMetadataMapper.createCharacteristics(parameters, info); - } catch (RemoteException e) { - // Impossible - return null; - } catch (CameraRuntimeException e) { - throw e.asChecked(); - } + CameraInfo info = new CameraInfo(); + cameraService.getCameraInfo(id, /*out*/info); - } else { - // Normal path: Get the camera characteristics directly from the camera service - CameraMetadataNative info = new CameraMetadataNative(); + characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info); + } else { + // Normal path: Get the camera characteristics directly from the camera service + CameraMetadataNative info = new CameraMetadataNative(); - try { - mCameraService.getCameraCharacteristics(id, info); - } catch(CameraRuntimeException e) { + cameraService.getCameraCharacteristics(id, info); + + characteristics = new CameraCharacteristics(info); + } + } catch (CameraRuntimeException e) { throw e.asChecked(); - } catch(RemoteException e) { - // impossible - return null; + } catch (RemoteException e) { + // Camera service died - act as if the camera was disconnected + throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable", e); } - - return new CameraCharacteristics(info); } + return characteristics; } /** @@ -278,10 +266,16 @@ public final class CameraManager { ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); int id = Integer.parseInt(cameraId); try { - if (supportsCamera2Api(cameraId)) { + if (supportsCamera2ApiLocked(cameraId)) { // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices - mCameraService.connectDevice(callbacks, id, mContext.getPackageName(), - USE_CALLING_UID, holder); + ICameraService cameraService = getCameraServiceLocked(); + if (cameraService == null) { + throw new CameraRuntimeException( + CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable"); + } + cameraService.connectDevice(callbacks, id, + mContext.getPackageName(), USE_CALLING_UID, holder); cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); } else { // Use legacy camera implementation for HAL1 devices @@ -304,12 +298,19 @@ public final class CameraManager { if (e.getReason() == CameraAccessException.CAMERA_DISABLED || e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { // Per API docs, these failures call onError and throw - throw e; + throw e.asChecked(); } } else { // Unexpected failure - rethrow throw e; } + } catch (RemoteException e) { + // Camera service died - act as if it's a CAMERA_DISCONNECTED case + CameraRuntimeException ce = new CameraRuntimeException( + CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable", e); + deviceImpl.setRemoteFailure(ce); + throw ce.asChecked(); } // TODO: factor out listener to be non-nested, then move setter to constructor @@ -324,8 +325,6 @@ public final class CameraManager { + cameraId); } catch (CameraRuntimeException e) { throw e.asChecked(); - } catch (RemoteException e) { - // impossible } return device; } @@ -444,27 +443,38 @@ public final class CameraManager { } } + /** + * Return or create the list of currently connected camera devices. + * + * <p>In case of errors connecting to the camera service, will return an empty list.</p> + */ private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException { if (mDeviceIdList == null) { int numCameras = 0; + ICameraService cameraService = getCameraServiceLocked(); + ArrayList<String> deviceIdList = new ArrayList<>(); + + // If no camera service, then no devices + if (cameraService == null) { + return deviceIdList; + } try { - numCameras = mCameraService.getNumberOfCameras(); + numCameras = cameraService.getNumberOfCameras(); } catch(CameraRuntimeException e) { throw e.asChecked(); } catch (RemoteException e) { - // impossible - return null; + // camera service just died - if no camera service, then no devices + return deviceIdList; } - mDeviceIdList = new ArrayList<String>(); CameraMetadataNative info = new CameraMetadataNative(); for (int i = 0; i < numCameras; ++i) { // Non-removable cameras use integers starting at 0 for their // identifiers boolean isDeviceSupported = false; try { - mCameraService.getCameraCharacteristics(i, info); + cameraService.getCameraCharacteristics(i, info); if (!info.isEmpty()) { isDeviceSupported = true; } else { @@ -474,16 +484,26 @@ public final class CameraManager { // Got a BAD_VALUE from service, meaning that this // device is not supported. } catch(CameraRuntimeException e) { - throw e.asChecked(); + // DISCONNECTED means that the HAL reported an low-level error getting the + // device info; skip listing the device. Other errors, + // propagate exception onward + if (e.getReason() != CameraAccessException.CAMERA_DISCONNECTED) { + throw e.asChecked(); + } } catch(RemoteException e) { - // impossible + // Camera service died - no devices to list + deviceIdList.clear(); + return deviceIdList; } if (isDeviceSupported) { - mDeviceIdList.add(String.valueOf(i)); + deviceIdList.add(String.valueOf(i)); + } else { + Log.w(TAG, "Error querying camera device " + i + " for listing."); } - } + } + mDeviceIdList = deviceIdList; } return mDeviceIdList; } @@ -506,8 +526,8 @@ public final class CameraManager { * @param cameraId a non-{@code null} camera identifier * @return {@code false} if the legacy shim needs to be used, {@code true} otherwise. */ - private boolean supportsCamera2Api(String cameraId) { - return supportsCameraApi(cameraId, API_VERSION_2); + private boolean supportsCamera2ApiLocked(String cameraId) { + return supportsCameraApiLocked(cameraId, API_VERSION_2); } /** @@ -517,33 +537,125 @@ public final class CameraManager { * @param apiVersion the version, i.e. {@code API_VERSION_1} or {@code API_VERSION_2} * @return {@code true} if connecting will work for that device version. */ - private boolean supportsCameraApi(String cameraId, int apiVersion) { + private boolean supportsCameraApiLocked(String cameraId, int apiVersion) { int id = Integer.parseInt(cameraId); /* * Possible return values: - * - NO_ERROR => Camera2 API is supported - * - CAMERA_DEPRECATED_HAL => Camera2 API is *not* supported (thrown as an exception) + * - NO_ERROR => CameraX API is supported + * - CAMERA_DEPRECATED_HAL => CameraX API is *not* supported (thrown as an exception) + * - Remote exception => If the camera service died * * Anything else is an unexpected error we don't want to recover from. */ - try { - int res = mCameraService.supportsCameraApi(id, apiVersion); + ICameraService cameraService = getCameraServiceLocked(); + // If no camera service, no support + if (cameraService == null) return false; + + int res = cameraService.supportsCameraApi(id, apiVersion); - if (res != CameraBinderDecorator.NO_ERROR) { + if (res != CameraServiceBinderDecorator.NO_ERROR) { throw new AssertionError("Unexpected value " + res); } - return true; } catch (CameraRuntimeException e) { - if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) { - return false; - } else { + if (e.getReason() != CameraAccessException.CAMERA_DEPRECATED_HAL) { throw e; } + // API level is not supported } catch (RemoteException e) { - throw new AssertionError("Camera service unreachable", e); + // Camera service is now down, no support for any API level + } + return false; + } + + /** + * 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(new CameraServiceDeathListener(), /*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(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); + } + } } } @@ -595,77 +707,102 @@ public final class CameraManager { } } + private void postSingleUpdate(final AvailabilityListener listener, final Handler handler, + final String id, final int status) { + if (isAvailable(status)) { + handler.post( + new Runnable() { + @Override + public void run() { + listener.onCameraAvailable(id); + } + }); + } else { + handler.post( + new Runnable() { + @Override + public void run() { + listener.onCameraUnavailable(id); + } + }); + } + } + + /** + * Send the state of all known cameras to the provided listener, to initialize + * the listener's knowledge of camera state. + */ + public void updateListenerLocked(AvailabilityListener listener, Handler handler) { + for (int i = 0; i < mDeviceStatus.size(); i++) { + String id = mDeviceStatus.keyAt(i); + Integer status = mDeviceStatus.valueAt(i); + postSingleUpdate(listener, handler, id, status); + } + } + @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) { + if (DEBUG) { Log.v(TAG, - String.format("Camera id %d has status changed to 0x%x", cameraId, status)); - - final String id = String.valueOf(cameraId); + String.format("Camera id %s has status changed to 0x%x", id, status)); + } - if (!validStatus(status)) { - Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId, - status)); - return; - } + if (!validStatus(status)) { + Log.e(TAG, String.format("Ignoring invalid device %s status 0x%x", id, + status)); + return; + } - Integer oldStatus = mDeviceStatus.put(id, status); + Integer oldStatus = mDeviceStatus.put(id, status); - if (oldStatus != null && oldStatus == status) { + if (oldStatus != null && oldStatus == status) { + if (DEBUG) { Log.v(TAG, String.format( - "Device status changed to 0x%x, which is what it already was", - status)); - return; + "Device status changed to 0x%x, which is what it already was", + status)); } + return; + } - // TODO: consider abstracting out this state minimization + transition - // into a separate - // more easily testable class - // i.e. (new State()).addState(STATE_AVAILABLE) - // .addState(STATE_NOT_AVAILABLE) - // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), - // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) - // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); - // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); - - // Translate all the statuses to either 'available' or 'not available' - // available -> available => no new update - // not available -> not available => no new update - if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { - + // TODO: consider abstracting out this state minimization + transition + // into a separate + // more easily testable class + // i.e. (new State()).addState(STATE_AVAILABLE) + // .addState(STATE_NOT_AVAILABLE) + // .addTransition(STATUS_PRESENT, STATE_AVAILABLE), + // .addTransition(STATUS_NOT_PRESENT, STATE_NOT_AVAILABLE) + // .addTransition(STATUS_ENUMERATING, STATE_NOT_AVAILABLE); + // .addTransition(STATUS_NOT_AVAILABLE, STATE_NOT_AVAILABLE); + + // Translate all the statuses to either 'available' or 'not available' + // available -> available => no new update + // not available -> not available => no new update + if (oldStatus != null && isAvailable(status) == isAvailable(oldStatus)) { + if (DEBUG) { Log.v(TAG, String.format( - "Device status was previously available (%d), " + - " and is now again available (%d)" + - "so no new client visible update will be sent", - isAvailable(status), isAvailable(status))); - return; + "Device status was previously available (%d), " + + " and is now again available (%d)" + + "so no new client visible update will be sent", + isAvailable(status), isAvailable(status))); } + return; + } + + final int listenerCount = mListenerMap.size(); + for (int i = 0; i < listenerCount; i++) { + Handler handler = mListenerMap.valueAt(i); + final AvailabilityListener listener = mListenerMap.keyAt(i); + + postSingleUpdate(listener, handler, id, status); + } + } // onStatusChangedLocked - final int listenerCount = mListenerMap.size(); - for (int i = 0; i < listenerCount; i++) { - Handler handler = mListenerMap.valueAt(i); - final AvailabilityListener listener = mListenerMap.keyAt(i); - if (isAvailable(status)) { - handler.post( - new Runnable() { - @Override - public void run() { - listener.onCameraAvailable(id); - } - }); - } else { - handler.post( - new Runnable() { - @Override - public void run() { - listener.onCameraUnavailable(id); - } - }); - } - } // for - } // synchronized - } // onStatusChanged } // CameraServiceListener } // CameraManager diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index fb1bc15..ed4e457 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -294,8 +294,6 @@ public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice { final int code = failureCode; final boolean isError = failureIsError; synchronized(mInterfaceLock) { - if (mRemoteDevice == null) return; // Camera already closed, can't go to error state - mInError = true; mDeviceHandler.post(new Runnable() { @Override diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java index 898c746..83ebadd 100644 --- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java +++ b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java @@ -28,7 +28,7 @@ import android.os.RemoteException; import java.lang.reflect.Method; /** - * Translate camera service status_t return values into exceptions. + * Translate camera device status_t return values into exceptions. * * @see android.hardware.camera2.utils.CameraBinderDecorator#newInstance * @hide @@ -57,7 +57,7 @@ public class CameraBinderDecorator { public static final int EUSERS = -87; - private static class CameraBinderDecoratorListener implements Decorator.DecoratorListener { + static class CameraBinderDecoratorListener implements Decorator.DecoratorListener { @Override public void onBeforeInvocation(Method m, Object[] args) { @@ -76,10 +76,9 @@ public class CameraBinderDecorator { public boolean onCatchException(Method m, Object[] args, Throwable t) { if (t instanceof DeadObjectException) { - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISCONNECTED, + throw new CameraRuntimeException(CAMERA_DISCONNECTED, "Process hosting the camera service has died unexpectedly", - t)); + t); } else if (t instanceof RemoteException) { throw new UnsupportedOperationException("An unknown RemoteException was thrown" + " which should never happen.", t); @@ -112,26 +111,20 @@ public class CameraBinderDecorator { case BAD_VALUE: throw new IllegalArgumentException("Bad argument passed to camera service"); case DEAD_OBJECT: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISCONNECTED)); + throw new CameraRuntimeException(CAMERA_DISCONNECTED); case EACCES: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISABLED)); + throw new CameraRuntimeException(CAMERA_DISABLED); case EBUSY: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_IN_USE)); + throw new CameraRuntimeException(CAMERA_IN_USE); case EUSERS: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - MAX_CAMERAS_IN_USE)); + throw new CameraRuntimeException(MAX_CAMERAS_IN_USE); case ENODEV: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DISCONNECTED)); + throw new CameraRuntimeException(CAMERA_DISCONNECTED); case EOPNOTSUPP: - UncheckedThrow.throwAnyException(new CameraRuntimeException( - CAMERA_DEPRECATED_HAL)); + throw new CameraRuntimeException(CAMERA_DEPRECATED_HAL); case INVALID_OPERATION: - UncheckedThrow.throwAnyException(new IllegalStateException( - "Illegal state encountered in camera service.")); + throw new IllegalStateException( + "Illegal state encountered in camera service."); } /** diff --git a/core/java/android/hardware/camera2/utils/CameraServiceBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraServiceBinderDecorator.java new file mode 100644 index 0000000..c1fb6b1 --- /dev/null +++ b/core/java/android/hardware/camera2/utils/CameraServiceBinderDecorator.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.camera2.utils; + +import android.os.DeadObjectException; +import android.os.RemoteException; +import android.util.Log; + +import java.lang.reflect.Method; + +/** + * Translate camera service status_t return values into exceptions. + * + * @see android.hardware.camera2.utils.CameraBinderDecorator#newInstance + * @hide + */ +public class CameraServiceBinderDecorator extends CameraBinderDecorator { + + private static final String TAG = "CameraServiceBinderDecorator"; + + static class CameraServiceBinderDecoratorListener + extends CameraBinderDecorator.CameraBinderDecoratorListener { + + // Pass through remote exceptions, unlike CameraBinderDecorator + @Override + public boolean onCatchException(Method m, Object[] args, Throwable t) { + + if (t instanceof DeadObjectException) { + // Can sometimes happen (camera service died) + // Pass on silently + } else if (t instanceof RemoteException) { + // Some other kind of remote exception - this is not normal, so let's at least + // note it before moving on + Log.e(TAG, "Unexpected RemoteException from camera service call.", t); + } + // All other exceptions also get sent onward + return false; + } + + } + + /** + * <p> + * Wraps the type T with a proxy that will check 'status_t' return codes + * from the native side of the camera service, and throw Java exceptions + * automatically based on the code. + * </p> + * + * @param obj object that will serve as the target for all method calls + * @param <T> the type of the element you want to wrap. This must be an interface. + * @return a proxy that will intercept all invocations to obj + */ + public static <T> T newInstance(T obj) { + return Decorator.<T> newInstance(obj, new CameraServiceBinderDecoratorListener()); + } +} |