Camera: increase the exif tag test coverage

Add test for all the uncoverged Exif tags defined in ExifInterface.

Also make the log runtime switchable.

Bug: 7969995
Change-Id: Ia9d622924a0d6aa40706d8fce5c21aff263e068c
diff --git a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
index bb2d782..f70a763 100644
--- a/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/CameraTest.java
@@ -32,6 +32,7 @@
 import android.media.CamcorderProfile;
 import android.media.ExifInterface;
 import android.media.MediaRecorder;
+import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Environment;
 import android.os.Looper;
@@ -62,7 +63,7 @@
 public class CameraTest extends ActivityInstrumentationTestCase2<CameraStubActivity> {
     private static String TAG = "CameraTest";
     private static final String PACKAGE = "com.android.cts.stub";
-    private static final boolean LOGV = false;
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private final String JPEG_PATH = Environment.getExternalStorageDirectory().getPath() +
             "/test.jpg";
     private byte[] mJpegData;
@@ -107,7 +108,7 @@
 
     public CameraTest() {
         super(PACKAGE, CameraStubActivity.class);
-        if (LOGV) Log.v(TAG, "Camera Constructor");
+        if (VERBOSE) Log.v(TAG, "Camera Constructor");
     }
 
     @Override
@@ -150,7 +151,7 @@
                 Log.v(TAG, "camera is opened");
                 startDone.open();
                 Looper.loop(); // Blocks forever until Looper.quit() is called.
-                if (LOGV) Log.v(TAG, "initializeMessageLooper: quit.");
+                if (VERBOSE) Log.v(TAG, "initializeMessageLooper: quit.");
             }
         }.start();
 
@@ -186,7 +187,7 @@
     private static int calculateBufferSize(int width, int height,
                                            int format, int bpp) {
 
-        if (LOGV) {
+        if (VERBOSE) {
             Log.v(TAG, "calculateBufferSize: w=" + width + ",h=" + height
             + ",f=" + format + ",bpp=" + bpp);
         }
@@ -203,7 +204,7 @@
             int c_size = c_stride * height/2;
             int size = y_size + c_size * 2;
 
-            if (LOGV) {
+            if (VERBOSE) {
                 Log.v(TAG, "calculateBufferSize: YV12 size= " + size);
             }
 
@@ -237,9 +238,9 @@
             }
             mPreviewCallbackResult = PREVIEW_CALLBACK_RECEIVED;
             mCamera.stopPreview();
-            if (LOGV) Log.v(TAG, "notify the preview callback");
+            if (VERBOSE) Log.v(TAG, "notify the preview callback");
             mPreviewDone.open();
-            if (LOGV) Log.v(TAG, "Preview callback stop");
+            if (VERBOSE) Log.v(TAG, "Preview callback stop");
         }
     }
 
@@ -247,7 +248,7 @@
     private final class TestShutterCallback implements ShutterCallback {
         public void onShutter() {
             mShutterCallbackResult = true;
-            if (LOGV) Log.v(TAG, "onShutter called");
+            if (VERBOSE) Log.v(TAG, "onShutter called");
         }
     }
 
@@ -255,7 +256,7 @@
     private final class RawPictureCallback implements PictureCallback {
         public void onPictureTaken(byte [] rawData, Camera camera) {
             mRawPictureCallbackResult = true;
-            if (LOGV) Log.v(TAG, "RawPictureCallback callback");
+            if (VERBOSE) Log.v(TAG, "RawPictureCallback callback");
         }
     }
 
@@ -272,14 +273,14 @@
                     outStream.close();
                     mJpegPictureCallbackResult = true;
 
-                    if (LOGV) {
+                    if (VERBOSE) {
                         Log.v(TAG, "JpegPictureCallback rawDataLength = " + rawData.length);
                     }
                 } else {
                     mJpegPictureCallbackResult = false;
                 }
                 mSnapshotDone.open();
-                if (LOGV) Log.v(TAG, "Jpeg Picture callback");
+                if (VERBOSE) Log.v(TAG, "Jpeg Picture callback");
             } catch (IOException e) {
                 // no need to fail here; callback worked fine
                 Log.w(TAG, "Error writing picture to sd card.");
@@ -312,7 +313,7 @@
     }
 
     private void waitForPreviewDone() {
-        if (LOGV) Log.v(TAG, "Wait for preview callback");
+        if (VERBOSE) Log.v(TAG, "Wait for preview callback");
         if (!mPreviewDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
             // timeout could be expected or unexpected. The caller will decide.
             Log.v(TAG, "waitForPreviewDone: timeout");
@@ -339,7 +340,7 @@
     }
 
     private void checkPreviewCallback() throws Exception {
-        if (LOGV) Log.v(TAG, "check preview callback");
+        if (VERBOSE) Log.v(TAG, "check preview callback");
         mCamera.startPreview();
         waitForPreviewDone();
         mCamera.setPreviewCallback(null);
@@ -395,7 +396,7 @@
             assertEquals(pictureSize.height, bmpOptions.outHeight);
         } else {
             int realArea = bmpOptions.outWidth * bmpOptions.outHeight;
-            if (LOGV) Log.v(TAG, "Video snapshot is " +
+            if (VERBOSE) Log.v(TAG, "Video snapshot is " +
                     bmpOptions.outWidth + " x " + bmpOptions.outHeight +
                     ", video size is " + videoWidth + " x " + videoHeight);
             assertTrue ("Video snapshot too small! Expected at least " +
@@ -860,8 +861,10 @@
 
         // Test various exif tags.
         ExifInterface exif = new ExifInterface(JPEG_PATH);
-        assertNotNull(exif.getAttribute(ExifInterface.TAG_MAKE));
-        assertNotNull(exif.getAttribute(ExifInterface.TAG_MODEL));
+        StringBuffer failedCause = new StringBuffer("Jpeg exif test failed:\n");
+        boolean extraExiftestPassed = checkExtraExifTagsSucceeds(failedCause, exif, parameters);
+
+        if (VERBOSE) Log.v(TAG, "Testing exif tag TAG_DATETIME");
         String datetime = exif.getAttribute(ExifInterface.TAG_DATETIME);
         assertNotNull(datetime);
         // Datetime should be local time.
@@ -874,6 +877,7 @@
         assertBitmapAndJpegSizeEqual(mJpegData, exif);
 
         // Test gps exif tags.
+        if (VERBOSE) Log.v(TAG, "Testing exif GPS tags");
         testGpsExifValues(parameters, 37.736071, -122.441983, 21, 1199145600,
             "GPS NETWORK HYBRID ARE ALL FINE.");
         testGpsExifValues(parameters, 0.736071, 0.441983, 1, 1199145601, "GPS");
@@ -881,6 +885,7 @@
 
         // Test gps tags do not exist after calling removeGpsData. Also check if
         // image width and height exif match the jpeg when jpeg rotation is set.
+        if (VERBOSE) Log.v(TAG, "Testing exif GPS tag removal");
         if (!recording) mCamera.startPreview();
         parameters.removeGpsData();
         parameters.setRotation(90); // For testing image width and height exif.
@@ -890,11 +895,129 @@
         exif = new ExifInterface(JPEG_PATH);
         checkGpsDataNull(exif);
         assertBitmapAndJpegSizeEqual(mJpegData, exif);
+        assertTrue(failedCause.toString(), extraExiftestPassed);
         // Reset the rotation to prevent from affecting other tests.
         parameters.setRotation(0);
         mCamera.setParameters(parameters);
     }
 
+    /**
+     * Sanity check of some extra exif tags.
+     * <p>
+     * Sanity check some extra exif tags without asserting the check failures
+     * immediately. When a failure is detected, the failure cause is logged,
+     * the rest of the tests are still executed. The caller can assert with the
+     * failure cause based on the returned test status.
+     * </p>
+     *
+     * @param logBuf Log failure cause to this StringBuffer if there is
+     * any failure.
+     * @param exif The exif data associated with a jpeg image being tested.
+     * @return true if no test failure is found, false if there is any failure.
+     */
+    private boolean checkExtraExifTagsSucceeds(StringBuffer logBuf, ExifInterface exif,
+            Parameters parameters) {
+        if (logBuf == null || exif == null || parameters == null) {
+            throw new IllegalArgumentException("failureCause, exif and paremeters" +
+                    "shouldn't be null");
+        }
+
+        if (VERBOSE) Log.v(TAG, "Testing extra exif tags");
+        boolean allTestsPassed = true;
+        boolean passedSoFar = true;
+        String failureMsg;
+
+        // TAG_EXPOSURE_TIME
+        // ExifInterface API gives exposure time value in the form of float instead of rational
+        String exposureTime = exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
+        passedSoFar = expectNotNull("Exif TAG_EXPOSURE_TIME is null!", logBuf, exposureTime);
+        if (passedSoFar) {
+            double exposureTimeValue = Double.parseDouble(exposureTime);
+            failureMsg = "Exif exposure time " + exposureTime + " should be a positive value";
+            passedSoFar = expectTrue(failureMsg, logBuf, exposureTimeValue > 0);
+        }
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        // TAG_APERTURE
+        // ExifInterface API gives aperture value in the form of float instead of rational
+        String aperture = exif.getAttribute(ExifInterface.TAG_APERTURE);
+        passedSoFar = expectNotNull("Exif TAG_APERTURE is null!", logBuf, aperture);
+        if (passedSoFar) {
+            double apertureValue = Double.parseDouble(aperture);
+            passedSoFar = expectTrue("Exif TAG_APERTURE value " + aperture + " should be positive!",
+                    logBuf, apertureValue > 0);
+        }
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        // TAG_FLASH
+        String flash = exif.getAttribute(ExifInterface.TAG_FLASH);
+        passedSoFar = expectNotNull("Exif TAG_FLASH is null!", logBuf, flash);
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        // TAG_WHITE_BALANCE
+        String whiteBalance = exif.getAttribute(ExifInterface.TAG_WHITE_BALANCE);
+        passedSoFar = expectNotNull("Exif TAG_WHITE_BALANCE is null!", logBuf, whiteBalance);
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        // TAG_MAKE
+        String make = exif.getAttribute(ExifInterface.TAG_MAKE);
+        passedSoFar = expectNotNull("Exif TAG_MAKE is null!", logBuf, make);
+        if (passedSoFar) {
+            passedSoFar = expectTrue("Exif TAG_MODEL value: " + make
+                    + " should match build manufacturer: " + Build.MANUFACTURER, logBuf,
+                    make.equals(Build.MANUFACTURER));
+        }
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        // TAG_MODEL
+        String model = exif.getAttribute(ExifInterface.TAG_MODEL);
+        passedSoFar = expectNotNull("Exif TAG_MODEL is null!", logBuf, model);
+        if (passedSoFar) {
+            passedSoFar = expectTrue("Exif TAG_MODEL value: " + model
+                    + " should match build manufacturer: " + Build.MODEL, logBuf,
+                    model.equals(Build.MODEL));
+        }
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        // TAG_ISO
+        int iso = exif.getAttributeInt(ExifInterface.TAG_ISO, -1);
+        passedSoFar = expectTrue("Exif ISO value " + iso + " is invalid", logBuf, iso > 0);
+        allTestsPassed = allTestsPassed && passedSoFar;
+
+        return allTestsPassed;
+    }
+
+    /**
+     * Check if object is null and log failure msg.
+     *
+     * @param msg Failure msg.
+     * @param logBuffer StringBuffer to log the failure msg.
+     * @param obj Object to test.
+     * @return true if object is not null, otherwise return false.
+     */
+    private boolean expectNotNull(String msg, StringBuffer logBuffer, Object obj)
+    {
+        if (obj == null) {
+            logBuffer.append(msg + "\n");
+        }
+        return (obj != null);
+    }
+
+    /**
+     * Check if condition is false and log failure msg.
+     *
+     * @param msg Failure msg.
+     * @param logBuffer StringBuffer to log the failure msg.
+     * @param condition Condition to test.
+     * @return The value of the condition.
+     */
+    private boolean expectTrue(String msg, StringBuffer logBuffer, boolean condition) {
+        if (!condition) {
+            logBuffer.append(msg + "\n");
+        }
+        return condition;
+    }
+
     private void assertBitmapAndJpegSizeEqual(byte[] jpegData, ExifInterface exif) {
         int exifWidth = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
         int exifHeight = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
@@ -1821,7 +1944,7 @@
                 double intervalMargin = 0.9;
                 long lastArrivalTime = mFrames.get(mFrames.size() - 1);
                 double interval = arrivalTime - lastArrivalTime;
-                if (LOGV) Log.v(TAG, "Frame interval=" + interval);
+                if (VERBOSE) Log.v(TAG, "Frame interval=" + interval);
                 try {
                     assertTrue("Frame interval (" + interval + "ms) is too " +
                             "large. mMaxFrameInterval=" +
@@ -2099,7 +2222,7 @@
     @UiThreadTest
     public void testMultiCameraRelease() throws Exception {
         // Verify that multiple cameras exist, and that they can be opened at the same time
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Checking pre-conditions.");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Checking pre-conditions.");
         int nCameras = Camera.getNumberOfCameras();
         if (nCameras < 2) {
             Log.i(TAG, "Test multi-camera release: Skipping test because only 1 camera available");
@@ -2121,11 +2244,11 @@
         testCamera1.release();
 
         // Start first camera
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Opening camera 0");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening camera 0");
         initializeMessageLooper(0);
         SimplePreviewStreamCb callback0 = new SimplePreviewStreamCb(0);
         mCamera.setPreviewCallback(callback0);
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 0");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 0");
         mCamera.startPreview();
         // Run preview for a bit
         for (int f = 0; f < 100; f++) {
@@ -2133,7 +2256,7 @@
             assertTrue("testMultiCameraRelease: First camera preview timed out on frame " + f + "!",
                        mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
         }
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
         mCamera.stopPreview();
         // Save message looper and camera to deterministically release them, instead
         // of letting GC do it at some point.
@@ -2145,11 +2268,11 @@
 
         // Start second camera without releasing the first one (will
         // set mCamera and mLooper to new objects)
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Opening camera 1");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Opening camera 1");
         initializeMessageLooper(1);
         SimplePreviewStreamCb callback1 = new SimplePreviewStreamCb(1);
         mCamera.setPreviewCallback(callback1);
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 1");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Starting preview on camera 1");
         mCamera.startPreview();
         // Run preview for a bit - GC of first camera instance should not impact the second's
         // operation.
@@ -2159,11 +2282,11 @@
                        mPreviewDone.block( WAIT_FOR_COMMAND_TO_COMPLETE));
             if (f == 50) {
                 // Release first camera mid-preview, should cause no problems
-                if (LOGV) Log.v(TAG, "testMultiCameraRelease: Releasing camera 0");
+                if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Releasing camera 0");
                 firstCamera.release();
             }
         }
-        if (LOGV) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
+        if (VERBOSE) Log.v(TAG, "testMultiCameraRelease: Stopping preview on camera 0");
         mCamera.stopPreview();
 
         firstLooper.quit();
@@ -2179,7 +2302,7 @@
             mId = id;
         }
         public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
-            if (LOGV) Log.v(TAG, "Preview frame callback, id " + mId + ".");
+            if (VERBOSE) Log.v(TAG, "Preview frame callback, id " + mId + ".");
             mPreviewDone.open();
         }
     }