| /* |
| * 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.os.Parcel; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.graphics.ImageFormat; |
| import android.hardware.photography.CameraMetadata; |
| import android.hardware.photography.Rational; |
| |
| import static android.hardware.photography.CameraMetadata.*; |
| |
| import java.lang.reflect.Array; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.IntBuffer; |
| |
| import static org.junit.Assert.assertArrayEquals; |
| |
| /** |
| * <pre> |
| * adb shell am instrument \ |
| * -e class 'com.android.mediaframeworktest.unit.CameraMetadataTest' \ |
| * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner |
| * </pre> |
| */ |
| public class CameraMetadataTest extends junit.framework.TestCase { |
| |
| CameraMetadata mMetadata; |
| Parcel mParcel; |
| |
| // Sections |
| static final int ANDROID_COLOR_CORRECTION = 0; |
| static final int ANDROID_CONTROL = 1; |
| |
| // Section starts |
| static final int ANDROID_COLOR_CORRECTION_START = ANDROID_COLOR_CORRECTION << 16; |
| static final int ANDROID_CONTROL_START = ANDROID_CONTROL << 16; |
| |
| // Tags |
| static final int ANDROID_COLOR_CORRECTION_MODE = ANDROID_COLOR_CORRECTION_START; |
| static final int ANDROID_COLOR_CORRECTION_TRANSFORM = ANDROID_COLOR_CORRECTION_START + 1; |
| |
| static final int ANDROID_CONTROL_AE_ANTIBANDING_MODE = ANDROID_CONTROL_START; |
| static final int ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION = ANDROID_CONTROL_START + 1; |
| |
| @Override |
| public void setUp() { |
| mMetadata = new CameraMetadata(); |
| mParcel = Parcel.obtain(); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| mMetadata.close(); |
| mMetadata = null; |
| |
| mParcel.recycle(); |
| mParcel = null; |
| } |
| |
| @SmallTest |
| public void testNew() { |
| assertEquals(0, mMetadata.getEntryCount()); |
| assertTrue(mMetadata.isEmpty()); |
| } |
| |
| @SmallTest |
| public void testClose() throws Exception { |
| mMetadata.isEmpty(); // no throw |
| |
| assertFalse(mMetadata.isClosed()); |
| |
| mMetadata.close(); |
| |
| assertTrue(mMetadata.isClosed()); |
| |
| // OK: second close should not throw |
| mMetadata.close(); |
| |
| assertTrue(mMetadata.isClosed()); |
| |
| // All other calls after close should throw IllegalStateException |
| |
| try { |
| mMetadata.isEmpty(); |
| fail("Unreachable -- isEmpty after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| |
| try { |
| mMetadata.getEntryCount(); |
| fail("Unreachable -- getEntryCount after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| |
| |
| try { |
| mMetadata.swap(mMetadata); |
| fail("Unreachable -- swap after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| |
| try { |
| mMetadata.readFromParcel(mParcel); |
| fail("Unreachable -- readFromParcel after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| |
| try { |
| mMetadata.writeToParcel(mParcel, /*flags*/0); |
| fail("Unreachable -- writeToParcel after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| |
| try { |
| mMetadata.readValues(/*tag*/0); |
| fail("Unreachable -- readValues after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| |
| try { |
| mMetadata.writeValues(/*tag*/0, /*source*/new byte[] { 1,2,3 }); |
| fail("Unreachable -- readValues after close should throw IllegalStateException"); |
| } catch (IllegalStateException e) { |
| // good: we expect calling this method after close to fail |
| } |
| } |
| |
| @SmallTest |
| public void testGetTagFromKey() { |
| |
| // Test success |
| |
| assertEquals(ANDROID_COLOR_CORRECTION_MODE, |
| CameraMetadata.getTag("android.colorCorrection.mode")); |
| assertEquals(ANDROID_COLOR_CORRECTION_TRANSFORM, |
| CameraMetadata.getTag("android.colorCorrection.transform")); |
| assertEquals(ANDROID_CONTROL_AE_ANTIBANDING_MODE, |
| CameraMetadata.getTag("android.control.aeAntibandingMode")); |
| assertEquals(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, |
| CameraMetadata.getTag("android.control.aeExposureCompensation")); |
| |
| // Test failures |
| |
| try { |
| CameraMetadata.getTag(null); |
| fail("A null key should throw NPE"); |
| } catch(NullPointerException e) { |
| } |
| |
| try { |
| CameraMetadata.getTag("android.control"); |
| fail("A section name only should not be a valid key"); |
| } catch(IllegalArgumentException e) { |
| } |
| |
| try { |
| CameraMetadata.getTag("android.control.thisTagNameIsFakeAndDoesNotExist"); |
| fail("A valid section with an invalid tag name should not be a valid key"); |
| } catch(IllegalArgumentException e) { |
| } |
| |
| try { |
| CameraMetadata.getTag("android"); |
| fail("A namespace name only should not be a valid key"); |
| } catch(IllegalArgumentException e) { |
| } |
| |
| try { |
| CameraMetadata.getTag("this.key.is.definitely.invalid"); |
| fail("A completely fake key name should not be valid"); |
| } catch(IllegalArgumentException e) { |
| } |
| } |
| |
| @SmallTest |
| public void testGetTypeFromTag() { |
| assertEquals(TYPE_BYTE, CameraMetadata.getNativeType(ANDROID_COLOR_CORRECTION_MODE)); |
| assertEquals(TYPE_FLOAT, CameraMetadata.getNativeType(ANDROID_COLOR_CORRECTION_TRANSFORM)); |
| assertEquals(TYPE_BYTE, CameraMetadata.getNativeType(ANDROID_CONTROL_AE_ANTIBANDING_MODE)); |
| assertEquals(TYPE_INT32, |
| CameraMetadata.getNativeType(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION)); |
| |
| try { |
| CameraMetadata.getNativeType(0xDEADF00D); |
| fail("No type should exist for invalid tag 0xDEADF00D"); |
| } catch(IllegalArgumentException e) { |
| } |
| } |
| |
| @SmallTest |
| public void testReadWriteValues() { |
| final byte ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; |
| byte[] valueResult; |
| |
| assertEquals(0, mMetadata.getEntryCount()); |
| assertEquals(true, mMetadata.isEmpty()); |
| |
| // |
| // android.colorCorrection.mode (single enum byte) |
| // |
| |
| assertEquals(null, mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE)); |
| |
| // Write 0 values |
| mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] {}); |
| |
| // Read 0 values |
| valueResult = mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE); |
| assertNotNull(valueResult); |
| assertEquals(0, valueResult.length); |
| |
| assertEquals(1, mMetadata.getEntryCount()); |
| assertEquals(false, mMetadata.isEmpty()); |
| |
| // Write 1 value |
| mMetadata.writeValues(ANDROID_COLOR_CORRECTION_MODE, new byte[] { |
| ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY |
| }); |
| |
| // Read 1 value |
| valueResult = mMetadata.readValues(ANDROID_COLOR_CORRECTION_MODE); |
| assertNotNull(valueResult); |
| assertEquals(1, valueResult.length); |
| assertEquals(ANDROID_COLOR_CORRECTION_MODE_HIGH_QUALITY, valueResult[0]); |
| |
| assertEquals(1, mMetadata.getEntryCount()); |
| assertEquals(false, mMetadata.isEmpty()); |
| |
| // |
| // android.colorCorrection.transform (3x3 matrix) |
| // |
| |
| final float[] transformMatrix = new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; |
| byte[] transformMatrixAsByteArray = new byte[transformMatrix.length * 4]; |
| ByteBuffer transformMatrixByteBuffer = |
| ByteBuffer.wrap(transformMatrixAsByteArray).order(ByteOrder.nativeOrder()); |
| for (float f : transformMatrix) |
| transformMatrixByteBuffer.putFloat(f); |
| |
| // Read |
| assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); |
| mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, transformMatrixAsByteArray); |
| |
| // Write |
| assertArrayEquals(transformMatrixAsByteArray, |
| mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); |
| |
| assertEquals(2, mMetadata.getEntryCount()); |
| assertEquals(false, mMetadata.isEmpty()); |
| |
| // Erase |
| mMetadata.writeValues(ANDROID_COLOR_CORRECTION_TRANSFORM, null); |
| assertNull(mMetadata.readValues(ANDROID_COLOR_CORRECTION_TRANSFORM)); |
| assertEquals(1, mMetadata.getEntryCount()); |
| } |
| |
| private static <T> void assertArrayEquals(T expected, T actual) { |
| assertEquals(Array.getLength(expected), Array.getLength(actual)); |
| |
| int len = Array.getLength(expected); |
| for (int i = 0; i < len; ++i) { |
| assertEquals(Array.get(expected, i), Array.get(actual, i)); |
| } |
| } |
| |
| private <T> void checkKeyGetAndSet(String keyStr, Class<T> type, T value) { |
| Key<T> key = new Key<T>(keyStr, type); |
| assertNull(mMetadata.get(key)); |
| mMetadata.set(key, value); |
| assertEquals(value, mMetadata.get(key)); |
| } |
| |
| private <T> void checkKeyGetAndSetArray(String keyStr, Class<T> type, T value) { |
| Key<T> key = new Key<T>(keyStr, type); |
| assertNull(mMetadata.get(key)); |
| mMetadata.set(key, value); |
| assertArrayEquals(value, mMetadata.get(key)); |
| } |
| |
| @SmallTest |
| public void testReadWritePrimitive() { |
| // int32 (single) |
| checkKeyGetAndSet("android.control.aeExposureCompensation", Integer.TYPE, 0xC0FFEE); |
| |
| // byte (single) |
| checkKeyGetAndSet("android.flash.maxEnergy", Byte.TYPE, (byte)6); |
| |
| // int64 (single) |
| checkKeyGetAndSet("android.flash.firingTime", Long.TYPE, 0xABCD12345678FFFFL); |
| |
| // float (single) |
| checkKeyGetAndSet("android.lens.aperture", Float.TYPE, Float.MAX_VALUE); |
| |
| // double (single) -- technically double x 3, but we fake it |
| checkKeyGetAndSet("android.jpeg.gpsCoordinates", Double.TYPE, Double.MAX_VALUE); |
| |
| // rational (single) |
| checkKeyGetAndSet("android.sensor.baseGainFactor", Rational.class, new Rational(1, 2)); |
| |
| /** |
| * Weirder cases, that don't map 1:1 with the native types |
| */ |
| |
| // bool (single) -- with TYPE_BYTE |
| checkKeyGetAndSet("android.control.aeLock", Boolean.TYPE, true); |
| |
| // integer (single) -- with TYPE_BYTE |
| checkKeyGetAndSet("android.control.aePrecaptureTrigger", Integer.TYPE, 6); |
| } |
| |
| @SmallTest |
| public void testReadWritePrimitiveArray() { |
| // int32 (n) |
| checkKeyGetAndSetArray("android.sensor.info.availableSensitivities", int[].class, |
| new int[] { |
| 0xC0FFEE, 0xDEADF00D |
| }); |
| |
| // byte (n) |
| checkKeyGetAndSetArray("android.statistics.faceScores", byte[].class, new byte[] { |
| 1, 2, 3, 4 |
| }); |
| |
| // int64 (n) |
| checkKeyGetAndSetArray("android.scaler.availableProcessedMinDurations", long[].class, |
| new long[] { |
| 0xABCD12345678FFFFL, 0x1234ABCD5678FFFFL, 0xFFFF12345678ABCDL |
| }); |
| |
| // float (n) |
| checkKeyGetAndSetArray("android.lens.info.availableApertures", float[].class, |
| new float[] { |
| Float.MAX_VALUE, Float.MIN_NORMAL, Float.MIN_VALUE |
| }); |
| |
| // double (n) -- in particular double x 3 |
| checkKeyGetAndSetArray("android.jpeg.gpsCoordinates", double[].class, |
| new double[] { |
| Double.MAX_VALUE, Double.MIN_NORMAL, Double.MIN_VALUE |
| }); |
| |
| // rational (n) -- in particular rational x 9 |
| checkKeyGetAndSetArray("android.sensor.calibrationTransform1", Rational[].class, |
| new Rational[] { |
| new Rational(1, 2), new Rational(3, 4), new Rational(5, 6), |
| new Rational(7, 8), new Rational(9, 10), new Rational(10, 11), |
| new Rational(12, 13), new Rational(14, 15), new Rational(15, 16) |
| }); |
| |
| /** |
| * Weirder cases, that don't map 1:1 with the native types |
| */ |
| |
| // bool (n) -- with TYPE_BYTE |
| checkKeyGetAndSetArray("android.control.aeLock", boolean[].class, new boolean[] { |
| true, false, true |
| }); |
| |
| |
| // integer (n) -- with TYPE_BYTE |
| checkKeyGetAndSetArray("android.control.aeAvailableModes", int[].class, new int[] { |
| 1, 2, 3, 4 |
| }); |
| } |
| |
| private enum ColorCorrectionMode { |
| TRANSFORM_MATRIX, |
| FAST, |
| HIGH_QUALITY |
| } |
| |
| private enum AeAntibandingMode { |
| OFF, |
| _50HZ, |
| _60HZ, |
| AUTO |
| } |
| |
| // TODO: special values for the enum. |
| private enum AvailableFormat { |
| RAW_SENSOR, |
| YV12, |
| YCrCb_420_SP, |
| IMPLEMENTATION_DEFINED, |
| YCbCr_420_888, |
| BLOB |
| } |
| |
| @SmallTest |
| public void testReadWriteEnum() { |
| // byte (single) |
| checkKeyGetAndSet("android.colorCorrection.mode", ColorCorrectionMode.class, |
| ColorCorrectionMode.HIGH_QUALITY); |
| |
| // byte (single) |
| checkKeyGetAndSet("android.control.aeAntibandingMode", AeAntibandingMode.class, |
| AeAntibandingMode.AUTO); |
| |
| // byte (n) |
| checkKeyGetAndSetArray("android.control.aeAvailableAntibandingModes", |
| AeAntibandingMode[].class, new AeAntibandingMode[] { |
| AeAntibandingMode.OFF, AeAntibandingMode._50HZ, AeAntibandingMode._60HZ, |
| AeAntibandingMode.AUTO |
| }); |
| |
| /** |
| * Stranger cases that don't use byte enums |
| */ |
| // int (n) |
| checkKeyGetAndSetArray("android.scaler.availableFormats", AvailableFormat[].class, |
| new AvailableFormat[] { |
| AvailableFormat.RAW_SENSOR, |
| AvailableFormat.YV12, |
| AvailableFormat.IMPLEMENTATION_DEFINED |
| }); |
| |
| } |
| |
| @SmallTest |
| public void testReadWriteEnumWithCustomValues() { |
| CameraMetadata.registerEnumValues(AeAntibandingMode.class, new int[] { |
| 0, |
| 10, |
| 20, |
| 30 |
| }); |
| |
| // byte (single) |
| checkKeyGetAndSet("android.control.aeAntibandingMode", AeAntibandingMode.class, |
| AeAntibandingMode.AUTO); |
| |
| // byte (n) |
| checkKeyGetAndSetArray("android.control.aeAvailableAntibandingModes", |
| AeAntibandingMode[].class, new AeAntibandingMode[] { |
| AeAntibandingMode.OFF, AeAntibandingMode._50HZ, AeAntibandingMode._60HZ, |
| AeAntibandingMode.AUTO |
| }); |
| |
| Key<AeAntibandingMode[]> aeAntibandingModeKey = |
| new Key<AeAntibandingMode[]>("android.control.aeAvailableAntibandingModes", |
| AeAntibandingMode[].class); |
| byte[] aeAntibandingModeValues = mMetadata.readValues(CameraMetadata |
| .getTag("android.control.aeAvailableAntibandingModes")); |
| byte[] expectedValues = new byte[] { 0, 10, 20, 30 }; |
| assertArrayEquals(expectedValues, aeAntibandingModeValues); |
| |
| |
| /** |
| * Stranger cases that don't use byte enums |
| */ |
| // int (n) |
| CameraMetadata.registerEnumValues(AvailableFormat.class, new int[] { |
| 0x20, |
| 0x32315659, |
| 0x11, |
| 0x22, |
| 0x23, |
| 0x21, |
| }); |
| |
| checkKeyGetAndSetArray("android.scaler.availableFormats", AvailableFormat[].class, |
| new AvailableFormat[] { |
| AvailableFormat.RAW_SENSOR, |
| AvailableFormat.YV12, |
| AvailableFormat.IMPLEMENTATION_DEFINED, |
| AvailableFormat.YCbCr_420_888 |
| }); |
| |
| Key<AeAntibandingMode> availableFormatsKey = |
| new Key<AeAntibandingMode>("android.scaler.availableFormats", |
| AeAntibandingMode.class); |
| byte[] availableFormatValues = mMetadata.readValues(CameraMetadata |
| .getTag(availableFormatsKey.getName())); |
| |
| int[] expectedIntValues = new int[] { |
| 0x20, |
| 0x32315659, |
| 0x22, |
| 0x23 |
| }; |
| |
| ByteBuffer bf = ByteBuffer.wrap(availableFormatValues).order(ByteOrder.nativeOrder()); |
| |
| assertEquals(expectedIntValues.length * 4, availableFormatValues.length); |
| for (int i = 0; i < expectedIntValues.length; ++i) { |
| assertEquals(expectedIntValues[i], bf.getInt()); |
| } |
| } |
| } |