package android.hardware.photography {
- public class CameraAccessException extends java.lang.Exception {
+ public class CameraAccessException extends android.util.AndroidException {
ctor public CameraAccessException(int);
ctor public CameraAccessException(int, java.lang.String);
ctor public CameraAccessException(int, java.lang.String, java.lang.Throwable);
}
public final class CameraDevice implements java.lang.AutoCloseable {
- ctor public CameraDevice();
method public void capture(android.hardware.photography.CaptureRequest, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
method public void captureBurst(java.util.List<android.hardware.photography.CaptureRequest>, android.hardware.photography.CameraDevice.CaptureListener) throws android.hardware.photography.CameraAccessException;
method public void close();
return new AppOpsManager(ctx, service);
}});
- registerService(CAMERA_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- return new CameraManager();
+ registerService(CAMERA_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return new CameraManager(ctx);
}
});
package android.hardware.photography;
+import android.util.AndroidException;
+
/**
* <p><code>CameraAccessException</code> is thrown if a camera device could not
* be queried or opened by the {@link CameraManager}, or if the connection to an
* @see CameraManager
* @see CameraDevice
*/
-public class CameraAccessException extends Exception {
+public class CameraAccessException extends AndroidException {
/**
* The camera device is in use already
*/
*/
public static final int CAMERA_DISCONNECTED = 4;
- private int mReason;
+ // Make the eclipse warning about serializable exceptions go away
+ private static final long serialVersionUID = 5630338637471475675L; // randomly generated
+
+ private final int mReason;
/**
* The reason for the failure to access the camera.
}
public CameraAccessException(int problem) {
+ super(getDefaultMessage(problem));
mReason = problem;
}
}
public CameraAccessException(int problem, Throwable cause) {
- super(cause);
+ super(getDefaultMessage(problem), cause);
mReason = problem;
}
+
+ private static String getDefaultMessage(int problem) {
+ switch (problem) {
+ case CAMERA_IN_USE:
+ return "The camera device is in use already";
+ case MAX_CAMERAS_IN_USE:
+ return "The system-wide limit for number of open cameras has been reached, " +
+ "and more camera devices cannot be opened until previous instances " +
+ "are closed.";
+ case CAMERA_DISABLED:
+ return "The camera is disabled due to a device policy, and cannot be opened.";
+ case CAMERA_DISCONNECTED:
+ return "The camera device is removable and has been disconnected from the Android" +
+ " device, or the camera service has shut down the connection due to a " +
+ "higher-priority access request for the camera device.";
+ }
+ return null;
+ }
}
package android.hardware.photography;
import android.graphics.ImageFormat;
+import android.os.IBinder;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
+import android.util.Log;
import android.view.Surface;
import java.lang.AutoCloseable;
*/
public static final int TEMPLATE_MANUAL = 5;
+ private static final String TAG = "CameraDevice";
+
/**
* Get the static properties for this camera. These are identical to the
* properties returned by {@link CameraManager#getCameraProperties}.
* the camera device interface will throw a {@link IllegalStateException},
* except for calls to close().
*/
+ @Override
public void close() {
}
public void onCameraDeviceError(CameraDevice camera, int error);
}
+ /**
+ * @hide
+ */
+ public CameraDevice(IBinder binder) {
+ Log.e(TAG, "CameraDevice constructor not implemented yet");
+ }
+
}
package android.hardware.photography;
+import android.content.Context;
+import android.hardware.ICameraService;
+import android.hardware.ICameraServiceListener;
+import android.hardware.IProCameraUser;
+import android.hardware.photography.utils.CameraBinderDecorator;
+import android.hardware.photography.utils.CameraRuntimeException;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
/**
* <p>An interface for iterating, listing, and connecting to
* {@link CameraDevice CameraDevices}.</p>
public final class CameraManager {
/**
+ * This should match the ICameraService definition
+ */
+ private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
+ private static final int USE_CALLING_UID = -1;
+
+ private final ICameraService mCameraService;
+ private ArrayList<String> mDeviceIdList;
+ private HashSet<CameraListener> mListenerSet;
+ private final Context mContext;
+ private final Object mLock = new Object();
+
+ /**
* @hide
*/
- public CameraManager() {
+ 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 {
+ mCameraService.addListener(new CameraServiceListener());
+ } catch(CameraRuntimeException e) {
+ throw new IllegalStateException("Failed to register a camera service listener",
+ e.asChecked());
+ } catch (RemoteException e) {
+ // impossible
+ }
}
/**
*
* @return The list of currently connected camera devices.
*/
- public String[] getDeviceIdList() {
- return null;
+ public String[] getDeviceIdList() throws CameraAccessException {
+ synchronized (mLock) {
+ return (String[]) getOrCreateDeviceIdListLocked().toArray();
+ }
}
/**
* Register a listener to be notified about camera device availability.
*
- * @param listener the new listener to send camera availablity notices to.
+ * Registering a listener more than once has no effect.
+ *
+ * @param listener the new listener to send camera availability notices to.
*/
public void registerCameraListener(CameraListener listener) {
+ synchronized (mLock) {
+ mListenerSet.add(listener);
+ }
}
/**
* Remove a previously-added listener; the listener will no longer receive
* connection and disconnection callbacks.
*
+ * Removing a listener that isn't registered has no effect.
+ *
* @param listener the listener to remove from the notification list
*/
public void unregisterCameraListener(CameraListener listener) {
+ synchronized (mLock) {
+ mListenerSet.remove(listener);
+ }
}
/**
*/
public CameraProperties getCameraProperties(String cameraId)
throws CameraAccessException {
- throw new IllegalArgumentException();
+
+ synchronized (mLock) {
+ if (!getOrCreateDeviceIdListLocked().contains(cameraId)) {
+ throw new IllegalArgumentException(String.format("Camera id %s does not match any" +
+ " currently connected camera device", cameraId));
+ }
+ }
+
+ // TODO: implement and call a service function to get the capabilities on C++ side
+
+ // TODO: get properties from service
+ return new CameraProperties();
}
/**
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*/
public CameraDevice openCamera(String cameraId) throws CameraAccessException {
- throw new IllegalArgumentException();
+
+ try {
+ IProCameraUser cameraUser;
+
+ synchronized (mLock) {
+ // TODO: Use ICameraDevice or some such instead of this...
+ cameraUser = mCameraService.connectPro(null,
+ Integer.parseInt(cameraId),
+ mContext.getPackageName(), USE_CALLING_UID);
+
+ }
+
+ return new CameraDevice(cameraUser.asBinder());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
+ + cameraId);
+ } catch (CameraRuntimeException e) {
+ if (e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) {
+ throw new IllegalArgumentException("Invalid camera ID specified -- " +
+ "perhaps the camera was physically disconnected", e);
+ } else {
+ throw e.asChecked();
+ }
+ } catch (RemoteException e) {
+ // impossible
+ return null;
+ }
}
/**
*/
public void onCameraUnavailable(String cameraId);
}
-}
+
+ private ArrayList<String> getOrCreateDeviceIdListLocked() throws CameraAccessException {
+ if (mDeviceIdList == null) {
+ int numCameras = 0;
+
+ try {
+ numCameras = mCameraService.getNumberOfCameras();
+ } catch(CameraRuntimeException e) {
+ throw e.asChecked();
+ } catch (RemoteException e) {
+ // impossible
+ return null;
+ }
+
+ mDeviceIdList = new ArrayList<String>();
+ for (int i = 0; i < numCameras; ++i) {
+ // Non-removable cameras use integers starting at 0 for their
+ // identifiers
+ mDeviceIdList.add(String.valueOf(i));
+ }
+
+ }
+ return mDeviceIdList;
+ }
+
+ // TODO: this class needs unit tests
+ // TODO: extract class into top level
+ private class CameraServiceListener extends Binder implements ICameraServiceListener {
+
+ // Keep up-to-date with ICameraServiceListener.h
+
+ // Device physically unplugged
+ public static final int STATUS_NOT_PRESENT = 0;
+ // Device physically has been plugged in
+ // and the camera can be used exclusively
+ public static final int STATUS_PRESENT = 1;
+ // Device physically has been plugged in
+ // but it will not be connect-able until enumeration is complete
+ public static final int STATUS_ENUMERATING = 2;
+ // Camera is in use by another app and cannot be used exclusively
+ public static final int STATUS_NOT_AVAILABLE = 0x80000000;
+
+ // Camera ID -> Status map
+ private final HashMap<String, Integer> mDeviceStatus = new HashMap<String, Integer>();
+
+ private static final String TAG = "CameraServiceListener";
+
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
+
+ private boolean isAvailable(int status) {
+ switch (status) {
+ case STATUS_PRESENT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean validStatus(int status) {
+ switch (status) {
+ case STATUS_NOT_PRESENT:
+ case STATUS_PRESENT:
+ case STATUS_ENUMERATING:
+ case STATUS_NOT_AVAILABLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onStatusChanged(int status, int cameraId) throws RemoteException {
+ synchronized(CameraManager.this) {
+
+ Log.v(TAG,
+ String.format("Camera id %d has status changed to 0x%x", cameraId, status));
+
+ String id = String.valueOf(cameraId);
+
+ if (!validStatus(status)) {
+ Log.e(TAG, String.format("Ignoring invalid device %d status 0x%x", cameraId,
+ status));
+ return;
+ }
+
+ Integer oldStatus = mDeviceStatus.put(id, status);
+
+ if (oldStatus == status) {
+ Log.v(TAG, String.format(
+ "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)) {
+
+ 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;
+ }
+
+ for (CameraListener listener : mListenerSet) {
+ if (isAvailable(status)) {
+ listener.onCameraAvailable(id);
+ } else {
+ listener.onCameraUnavailable(id);
+ }
+ } // for
+ } // synchronized
+ } // onStatusChanged
+ } // CameraServiceListener
+} // CameraManager
--- /dev/null
+/*
+ * Copyright (C) 2013 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.photography.utils;
+
+import static android.hardware.photography.CameraAccessException.CAMERA_DISABLED;
+import static android.hardware.photography.CameraAccessException.CAMERA_DISCONNECTED;
+import static android.hardware.photography.CameraAccessException.CAMERA_IN_USE;
+
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+
+import java.lang.reflect.Method;
+
+/**
+ * Translate camera service status_t return values into exceptions.
+ *
+ * @see android.hardware.photography.utils.CameraBinderDecorator#newInstance
+ * @hide
+ */
+public class CameraBinderDecorator {
+
+ public static final int NO_ERROR = 0;
+ public static final int PERMISSION_DENIED = -1;
+ public static final int ALREADY_EXISTS = -17;
+ public static final int BAD_VALUE = -22;
+ public static final int DEAD_OBJECT = -32;
+
+ /**
+ * TODO: add as error codes in Errors.h
+ * - POLICY_PROHIBITS
+ * - RESOURCE_BUSY
+ * - NO_SUCH_DEVICE
+ */
+ public static final int EACCES = -13;
+ public static final int EBUSY = -16;
+ public static final int ENODEV = -19;
+
+ private static class CameraBinderDecoratorListener implements Decorator.DecoratorListener {
+
+ @Override
+ public void onBeforeInvocation(Method m, Object[] args) {
+ }
+
+ @Override
+ public void onAfterInvocation(Method m, Object[] args, Object result) {
+ // int return type => status_t => convert to exception
+ if (m.getReturnType() == Integer.TYPE) {
+ int returnValue = (Integer) result;
+
+ switch (returnValue) {
+ case NO_ERROR:
+ return;
+ case PERMISSION_DENIED:
+ throw new SecurityException("Lacking privileges to access camera service");
+ case ALREADY_EXISTS:
+ return;
+ case BAD_VALUE:
+ throw new IllegalArgumentException("Bad argument passed to camera service");
+ case DEAD_OBJECT:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISCONNECTED));
+ // TODO: Camera service (native side) should return
+ // EACCES error
+ // when there's a policy manager disabled causing this
+ case EACCES:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISABLED));
+ case EBUSY:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_IN_USE));
+ case ENODEV:
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISCONNECTED));
+ }
+
+ /**
+ * Trap the rest of the negative return values. If we have known
+ * error codes i.e. ALREADY_EXISTS that aren't really runtime
+ * errors, then add them to the top switch statement
+ */
+ if (returnValue < 0) {
+ throw new UnsupportedOperationException(String.format("Unknown error %d",
+ returnValue));
+ }
+ }
+ }
+
+ @Override
+ public boolean onCatchException(Method m, Object[] args, Throwable t) {
+
+ if (t instanceof DeadObjectException) {
+ UncheckedThrow.throwAnyException(new CameraRuntimeException(
+ CAMERA_DISCONNECTED,
+ "Process hosting the camera service has died unexpectedly",
+ t));
+ } else if (t instanceof RemoteException) {
+ throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
+ " which should never happen.", t);
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onFinally(Method m, Object[] args) {
+ }
+
+ }
+
+ /**
+ * <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>
+ * <p>
+ * In addition it also rewrites binder's RemoteException into either a
+ * CameraAccessException or an UnsupportedOperationException.
+ * </p>
+ * <p>
+ * As a result of calling any method on the proxy, RemoteException is
+ * guaranteed never to be thrown.
+ * </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 CameraBinderDecoratorListener());
+ }
+}
--- /dev/null
+package android.hardware.photography.utils;
+
+import android.hardware.photography.CameraAccessException;
+
+/**
+ * @hide
+ */
+public class CameraRuntimeException extends RuntimeException {
+
+ private final int mReason;
+ private String mMessage;
+ private Throwable mCause;
+
+ public final int getReason() {
+ return mReason;
+ }
+
+ public CameraRuntimeException(int problem) {
+ super();
+ mReason = problem;
+ }
+
+ public CameraRuntimeException(int problem, String message) {
+ super(message);
+ mReason = problem;
+ mMessage = message;
+ }
+
+ public CameraRuntimeException(int problem, String message, Throwable cause) {
+ super(message, cause);
+ mReason = problem;
+ mMessage = message;
+ mCause = cause;
+ }
+
+ public CameraRuntimeException(int problem, Throwable cause) {
+ super(cause);
+ mReason = problem;
+ mCause = cause;
+ }
+
+ /**
+ * Recreate this exception as the CameraAccessException equivalent.
+ * @return CameraAccessException
+ */
+ public CameraAccessException asChecked() {
+ CameraAccessException e;
+
+ if (mMessage != null && mCause != null) {
+ e = new CameraAccessException(mReason, mMessage, mCause);
+ } else if (mMessage != null) {
+ e = new CameraAccessException(mReason, mMessage);
+ } else if (mCause != null) {
+ e = new CameraAccessException(mReason, mCause);
+ } else {
+ e = new CameraAccessException(mReason);
+ }
+ // throw and catch, so java has a chance to fill out the stack trace
+ e.setStackTrace(this.getStackTrace());
+
+ return e;
+ }
+}
--- /dev/null
+
+package android.hardware.photography.utils;
+
+import java.lang.reflect.*;
+
+/**
+ * This is an implementation of the 'decorator' design pattern using Java's proxy mechanism.
+ *
+ * @see android.hardware.photography.utils.Decorator#newInstance
+ *
+ * @hide
+ */
+public class Decorator<T> implements InvocationHandler {
+
+ public interface DecoratorListener {
+ /**
+ * This method is called before the target method is invoked
+ * @param args arguments to target method
+ * @param m Method being called
+ */
+ void onBeforeInvocation(Method m, Object[] args);
+ /**
+ * This function is called after the target method is invoked
+ * if there were no uncaught exceptions
+ * @param args arguments to target method
+ * @param m Method being called
+ * @param result return value of target method
+ */
+ void onAfterInvocation(Method m, Object[] args, Object result);
+ /**
+ * This method is called only if there was an exception thrown by the target method
+ * during its invocation.
+ *
+ * @param args arguments to target method
+ * @param m Method being called
+ * @param t Throwable that was thrown
+ * @return false to rethrow exception, true if the exception was handled
+ */
+ boolean onCatchException(Method m, Object[] args, Throwable t);
+ /**
+ * This is called after the target method is invoked, regardless of whether or not
+ * there were any exceptions.
+ * @param args arguments to target method
+ * @param m Method being called
+ */
+ void onFinally(Method m, Object[] args);
+ }
+
+ private final T mObject;
+ private final DecoratorListener mListener;
+
+ /**
+ * Create a decorator wrapping the specified object's method calls.
+ *
+ * @param obj the object whose method calls you want to intercept
+ * @param listener the decorator handler for intercepted method calls
+ * @param <T> the type of the element you want to wrap. This must be an interface.
+ * @return a wrapped interface-compatible T
+ */
+ @SuppressWarnings("unchecked")
+ public static<T> T newInstance(T obj, DecoratorListener listener) {
+ return (T)java.lang.reflect.Proxy.newProxyInstance(
+ obj.getClass().getClassLoader(),
+ obj.getClass().getInterfaces(),
+ new Decorator<T>(obj, listener));
+ }
+
+ private Decorator(T obj, DecoratorListener listener) {
+ this.mObject = obj;
+ this.mListener = listener;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method m, Object[] args)
+ throws Throwable
+ {
+ Object result = null;
+ try {
+ mListener.onBeforeInvocation(m, args);
+ result = m.invoke(mObject, args);
+ mListener.onAfterInvocation(m, args, result);
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+ if (!mListener.onCatchException(m, args, t)) {
+ throw t;
+ }
+ } finally {
+ mListener.onFinally(m, args);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 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.photography.utils;
+
+/**
+ * @hide
+ */
+public class UncheckedThrow {
+
+ /**
+ * Throw any kind of exception without needing it to be checked
+ * @param e any instance of a Exception
+ */
+ public static void throwAnyException(Exception e) {
+ /**
+ * Abuse type erasure by making the compiler think we are throwing RuntimeException,
+ * which is unchecked, but then inserting any exception in there.
+ */
+ UncheckedThrow.<RuntimeException>throwAnyImpl(e);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static<T extends Exception> void throwAnyImpl(Exception e) throws T {
+ throw (T) e;
+ }
+}
LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := easymocklib
+LOCAL_STATIC_JAVA_LIBRARIES := easymocklib mockito-target
LOCAL_PACKAGE_NAME := mediaframeworktest
addMediaRecorderStateUnitTests(suite);
addMediaPlayerStateUnitTests(suite);
addMediaScannerUnitTests(suite);
+ addCameraUnitTests(suite);
return suite;
}
return MediaFrameworkUnitTestRunner.class.getClassLoader();
}
+ private void addCameraUnitTests(TestSuite suite) {
+ suite.addTestSuite(CameraUtilsDecoratorTest.class);
+ suite.addTestSuite(CameraUtilsRuntimeExceptionTest.class);
+ suite.addTestSuite(CameraUtilsUncheckedThrowTest.class);
+ suite.addTestSuite(CameraUtilsBinderDecoratorTest.class);
+ }
+
// Running all unit tests checking the state machine may be time-consuming.
private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) {
suite.addTestSuite(MediaMetadataRetrieverTest.class);
--- /dev/null
+/*
+ * Copyright (C) 2013 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 com.android.mediaframeworktest.unit;
+
+import android.hardware.photography.CameraAccessException;
+import android.hardware.photography.utils.CameraBinderDecorator;
+import android.hardware.photography.utils.CameraRuntimeException;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.os.TransactionTooLargeException;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static org.mockito.Mockito.*;
+import static android.hardware.photography.utils.CameraBinderDecorator.*;
+import static android.hardware.photography.CameraAccessException.*;
+
+import junit.framework.Assert;
+
+public class CameraUtilsBinderDecoratorTest extends junit.framework.TestCase {
+
+ private interface ICameraBinderStereotype {
+
+ double doNothing();
+
+ // int is a 'status_t'
+ int doSomethingPositive();
+
+ int doSomethingNoError();
+
+ int doSomethingPermissionDenied();
+
+ int doSomethingAlreadyExists();
+
+ int doSomethingBadValue();
+
+ int doSomethingDeadObject() throws CameraRuntimeException;
+
+ int doSomethingBadPolicy() throws CameraRuntimeException;
+
+ int doSomethingDeviceBusy() throws CameraRuntimeException;
+
+ int doSomethingNoSuchDevice() throws CameraRuntimeException;
+
+ int doSomethingUnknownErrorCode();
+
+ int doSomethingThrowDeadObjectException() throws RemoteException;
+
+ int doSomethingThrowTransactionTooLargeException() throws RemoteException;
+ }
+
+ private static final double SOME_ARBITRARY_DOUBLE = 1.0;
+ private static final int SOME_ARBITRARY_POSITIVE_INT = 5;
+ private static final int SOME_ARBITRARY_NEGATIVE_INT = -0xC0FFEE;
+
+ @SmallTest
+ public void testStereotypes() {
+
+ ICameraBinderStereotype mock = mock(ICameraBinderStereotype.class);
+ try {
+ when(mock.doNothing()).thenReturn(SOME_ARBITRARY_DOUBLE);
+ when(mock.doSomethingPositive()).thenReturn(SOME_ARBITRARY_POSITIVE_INT);
+ when(mock.doSomethingNoError()).thenReturn(NO_ERROR);
+ when(mock.doSomethingPermissionDenied()).thenReturn(PERMISSION_DENIED);
+ when(mock.doSomethingAlreadyExists()).thenReturn(ALREADY_EXISTS);
+ when(mock.doSomethingBadValue()).thenReturn(BAD_VALUE);
+ when(mock.doSomethingDeadObject()).thenReturn(DEAD_OBJECT);
+ when(mock.doSomethingBadPolicy()).thenReturn(EACCES);
+ when(mock.doSomethingDeviceBusy()).thenReturn(EBUSY);
+ when(mock.doSomethingNoSuchDevice()).thenReturn(ENODEV);
+ when(mock.doSomethingUnknownErrorCode()).thenReturn(SOME_ARBITRARY_NEGATIVE_INT);
+ when(mock.doSomethingThrowDeadObjectException()).thenThrow(new DeadObjectException());
+ when(mock.doSomethingThrowTransactionTooLargeException()).thenThrow(
+ new TransactionTooLargeException());
+ } catch (RemoteException e) {
+ Assert.fail("Unreachable");
+ }
+
+ ICameraBinderStereotype decoratedMock = CameraBinderDecorator.newInstance(mock);
+
+ // ignored by decorator because return type is double, not int
+ assertEquals(SOME_ARBITRARY_DOUBLE, decoratedMock.doNothing());
+
+ // pass through for positive values
+ assertEquals(SOME_ARBITRARY_POSITIVE_INT, decoratedMock.doSomethingPositive());
+
+ // pass through NO_ERROR
+ assertEquals(NO_ERROR, decoratedMock.doSomethingNoError());
+
+ try {
+ decoratedMock.doSomethingPermissionDenied();
+ Assert.fail("Should've thrown SecurityException");
+ } catch (SecurityException e) {
+ }
+
+ assertEquals(ALREADY_EXISTS, decoratedMock.doSomethingAlreadyExists());
+
+ try {
+ decoratedMock.doSomethingBadValue();
+ Assert.fail("Should've thrown IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ }
+
+ try {
+ decoratedMock.doSomethingDeadObject();
+ Assert.fail("Should've thrown CameraRuntimeException");
+ } catch (CameraRuntimeException e) {
+ assertEquals(CAMERA_DISCONNECTED, e.getReason());
+ }
+
+ try {
+ decoratedMock.doSomethingBadPolicy();
+ Assert.fail("Should've thrown CameraRuntimeException");
+ } catch (CameraRuntimeException e) {
+ assertEquals(CAMERA_DISABLED, e.getReason());
+ }
+
+ try {
+ decoratedMock.doSomethingDeviceBusy();
+ Assert.fail("Should've thrown CameraRuntimeException");
+ } catch (CameraRuntimeException e) {
+ assertEquals(CAMERA_IN_USE, e.getReason());
+ }
+
+ try {
+ decoratedMock.doSomethingNoSuchDevice();
+ Assert.fail("Should've thrown CameraRuntimeException");
+ } catch (CameraRuntimeException e) {
+ assertEquals(CAMERA_DISCONNECTED, e.getReason());
+ }
+
+ try {
+ decoratedMock.doSomethingUnknownErrorCode();
+ Assert.fail("Should've thrown UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ assertEquals(String.format("Unknown error %d",
+ SOME_ARBITRARY_NEGATIVE_INT), e.getMessage());
+ }
+
+ try {
+ decoratedMock.doSomethingThrowDeadObjectException();
+ Assert.fail("Should've thrown CameraRuntimeException");
+ } catch (CameraRuntimeException e) {
+ assertEquals(CAMERA_DISCONNECTED, e.getReason());
+ } catch (RemoteException e) {
+ Assert.fail("Should not throw a DeadObjectException directly, but rethrow");
+ }
+
+ try {
+ decoratedMock.doSomethingThrowTransactionTooLargeException();
+ Assert.fail("Should've thrown UnsupportedOperationException");
+ } catch (UnsupportedOperationException e) {
+ assertTrue(e.getCause() instanceof TransactionTooLargeException);
+ } catch (RemoteException e) {
+ Assert.fail("Should not throw a TransactionTooLargeException directly, but rethrow");
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 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 com.android.mediaframeworktest.unit;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.hardware.photography.utils.*;
+import android.hardware.photography.utils.Decorator.DecoratorListener;
+
+import junit.framework.Assert;
+
+import java.lang.reflect.Method;
+
+/**
+ * adb shell am instrument -e class 'com.android.mediaframeworktest.unit.CameraUtilsDecoratorTest' \
+ * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
+ */
+public class CameraUtilsDecoratorTest extends junit.framework.TestCase {
+ private DummyListener mDummyListener;
+ private DummyInterface mIface;
+
+ @Override
+ public void setUp() {
+ mDummyListener = new DummyListener();
+ mIface = Decorator.newInstance(new DummyImpl(), mDummyListener);
+ }
+
+ interface DummyInterface {
+ int addValues(int x, int y, int z);
+
+ void raiseException() throws Exception;
+
+ void raiseUnsupportedOperationException() throws UnsupportedOperationException;
+ }
+
+ class DummyImpl implements DummyInterface {
+ @Override
+ public int addValues(int x, int y, int z) {
+ return x + y + z;
+ }
+
+ @Override
+ public void raiseException() throws Exception {
+ throw new Exception("Test exception");
+ }
+
+ @Override
+ public void raiseUnsupportedOperationException() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Test exception");
+ }
+ }
+
+ class DummyListener implements DecoratorListener {
+
+ public boolean beforeCalled = false;
+ public boolean afterCalled = false;
+ public boolean catchCalled = false;
+ public boolean finallyCalled = false;
+ public Object resultValue = null;
+
+ public boolean raiseException = false;
+
+ @Override
+ public void onBeforeInvocation(Method m, Object[] args) {
+ beforeCalled = true;
+ }
+
+ @Override
+ public void onAfterInvocation(Method m, Object[] args, Object result) {
+ afterCalled = true;
+ resultValue = result;
+
+ if (raiseException) {
+ throw new UnsupportedOperationException("Test exception");
+ }
+ }
+
+ @Override
+ public boolean onCatchException(Method m, Object[] args, Throwable t) {
+ catchCalled = true;
+ return false;
+ }
+
+ @Override
+ public void onFinally(Method m, Object[] args) {
+ finallyCalled = true;
+ }
+
+ };
+
+ @SmallTest
+ public void testDecorator() {
+
+ // TODO rewrite this using mocks
+
+ assertTrue(mIface.addValues(1, 2, 3) == 6);
+ assertTrue(mDummyListener.beforeCalled);
+ assertTrue(mDummyListener.afterCalled);
+
+ int resultValue = (Integer)mDummyListener.resultValue;
+ assertTrue(resultValue == 6);
+ assertTrue(mDummyListener.finallyCalled);
+ assertFalse(mDummyListener.catchCalled);
+ }
+
+ @SmallTest
+ public void testDecoratorExceptions() {
+
+ boolean gotExceptions = false;
+ try {
+ mIface.raiseException();
+ } catch (Exception e) {
+ gotExceptions = true;
+ assertTrue(e.getMessage() == "Test exception");
+ }
+ assertTrue(gotExceptions);
+ assertTrue(mDummyListener.beforeCalled);
+ assertFalse(mDummyListener.afterCalled);
+ assertTrue(mDummyListener.catchCalled);
+ assertTrue(mDummyListener.finallyCalled);
+ }
+
+ @SmallTest
+ public void testDecoratorUnsupportedOperationException() {
+
+ boolean gotExceptions = false;
+ try {
+ mIface.raiseUnsupportedOperationException();
+ } catch (UnsupportedOperationException e) {
+ gotExceptions = true;
+ assertTrue(e.getMessage() == "Test exception");
+ }
+ assertTrue(gotExceptions);
+ assertTrue(mDummyListener.beforeCalled);
+ assertFalse(mDummyListener.afterCalled);
+ assertTrue(mDummyListener.catchCalled);
+ assertTrue(mDummyListener.finallyCalled);
+ }
+
+ @SmallTest
+ public void testDecoratorRaisesException() {
+
+ boolean gotExceptions = false;
+ try {
+ mDummyListener.raiseException = true;
+ mIface.addValues(1, 2, 3);
+ Assert.fail("unreachable");
+ } catch (UnsupportedOperationException e) {
+ gotExceptions = true;
+ assertTrue(e.getMessage() == "Test exception");
+ }
+ assertTrue(gotExceptions);
+ assertTrue(mDummyListener.beforeCalled);
+ assertTrue(mDummyListener.afterCalled);
+ assertFalse(mDummyListener.catchCalled);
+ assertTrue(mDummyListener.finallyCalled);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 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 com.android.mediaframeworktest.unit;
+
+import android.hardware.photography.CameraAccessException;
+import android.hardware.photography.utils.CameraRuntimeException;
+import android.hardware.photography.utils.UncheckedThrow;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.Assert;
+
+public class CameraUtilsRuntimeExceptionTest extends junit.framework.TestCase {
+
+ @SmallTest
+ public void testCameraRuntimeException1() {
+ try {
+ CameraRuntimeException runtimeExc = new CameraRuntimeException(12345);
+ throw runtimeExc.asChecked();
+ } catch (CameraAccessException e) {
+ assertEquals(12345, e.getReason());
+ assertNull(e.getMessage());
+ assertNull(e.getCause());
+ }
+ }
+
+ @SmallTest
+ public void testCameraRuntimeException2() {
+ try {
+ CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello");
+ throw runtimeExc.asChecked();
+ } catch (CameraAccessException e) {
+ assertEquals(12345, e.getReason());
+ assertEquals("Hello", e.getMessage());
+ assertNull(e.getCause());
+ }
+ }
+
+ @SmallTest
+ public void testCameraRuntimeException3() {
+ Throwable cause = new IllegalStateException("For great justice");
+ try {
+ CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, cause);
+ throw runtimeExc.asChecked();
+ } catch (CameraAccessException e) {
+ assertEquals(12345, e.getReason());
+ assertNull(e.getMessage());
+ assertEquals(cause, e.getCause());
+ }
+ }
+
+ @SmallTest
+ public void testCameraRuntimeException4() {
+ Throwable cause = new IllegalStateException("For great justice");
+ try {
+ CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello", cause);
+ throw runtimeExc.asChecked();
+ } catch (CameraAccessException e) {
+ assertEquals(12345, e.getReason());
+ assertEquals("Hello", e.getMessage());
+ assertEquals(cause, e.getCause());
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 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 com.android.mediaframeworktest.unit;
+
+import android.hardware.photography.CameraAccessException;
+import android.hardware.photography.utils.UncheckedThrow;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.Assert;
+
+public class CameraUtilsUncheckedThrowTest extends junit.framework.TestCase {
+
+ private void fakeNeverThrowsCameraAccess() throws CameraAccessException {
+ }
+
+ @SmallTest
+ public void testUncheckedThrow() {
+ try {
+ UncheckedThrow.throwAnyException(new CameraAccessException(
+ CameraAccessException.CAMERA_DISCONNECTED));
+ Assert.fail("unreachable");
+ fakeNeverThrowsCameraAccess();
+ } catch (CameraAccessException e) {
+ }
+ }
+}