Fix portrait-orientation assumptions
am: 3855bb499f

* commit '3855bb499fbfc073771fa951babfe1922d90ffc9':
  Fix portrait-orientation assumptions

Change-Id: Iadd126e2d0fc383e17234e9363cb2fc6d8c79380
diff --git a/Android.mk b/Android.mk
index 6433b95..d3deaac 100644
--- a/Android.mk
+++ b/Android.mk
@@ -19,6 +19,7 @@
 LOCAL_MODULE_TAGS := optional
 
 LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 21
 
 LOCAL_SRC_FILES := \
 	$(call all-java-files-under, src)
diff --git a/src/com/android/devcamera/Api2Camera.java b/src/com/android/devcamera/Api2Camera.java
index 65308a5..73e5c87 100644
--- a/src/com/android/devcamera/Api2Camera.java
+++ b/src/com/android/devcamera/Api2Camera.java
@@ -305,6 +305,11 @@
     }
 
     @Override
+    public int getOrientation() {
+        return mCameraInfoCache.sensorOrientation();
+    }
+
+    @Override
     public void openCamera() {
         // If API2 FULL mode is not available, display toast
         if (!mCameraInfoCache.isCamera2FullModeAvailable()) {
@@ -556,8 +561,8 @@
         Log.v(TAG, "  Sent YUV1 image to ImageWriter.queueInputImage()");
         try {
             CaptureRequest.Builder b1 = mCameraDevice.createReprocessCaptureRequest(mLastTotalCaptureResult);
-            // Portrait.
-            b1.set(CaptureRequest.JPEG_ORIENTATION, 90);
+            // Todo: Read current orientation instead of just assuming device is in native orientation
+            b1.set(CaptureRequest.JPEG_ORIENTATION, mCameraInfoCache.sensorOrientation());
             b1.set(CaptureRequest.JPEG_QUALITY, (byte) 95);
             b1.set(CaptureRequest.NOISE_REDUCTION_MODE, mReprocessingNoiseMode);
             b1.set(CaptureRequest.EDGE_MODE, mReprocessingEdgeMode);
diff --git a/src/com/android/devcamera/CameraInfoCache.java b/src/com/android/devcamera/CameraInfoCache.java
index 1a5b0b1..699fd97 100644
--- a/src/com/android/devcamera/CameraInfoCache.java
+++ b/src/com/android/devcamera/CameraInfoCache.java
@@ -186,11 +186,21 @@
             Log.e(TAG, "No physical sensor dimensions reported by camera device, assuming default "
                     + physicalSize);
         }
+
+        // Only active array is actually visible, so calculate fraction of physicalSize that it takes up
+        Size pixelArraySize = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
+        Rect activeArraySize = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+
+        float activeWidthFraction = activeArraySize.width() / (float) pixelArraySize.getWidth();
+        float activeHeightFraction = activeArraySize.height() / (float) pixelArraySize.getHeight();
+
         // Simple rectilinear lens field of view formula:
-        //   angle of view = 2 * arctan ( sensor size / (2 * focal length) )
+        //   angle of view = 2 * arctan ( active size / (2 * focal length) )
         float[] fieldOfView = new float[2];
-        fieldOfView[0] = (float) Math.toDegrees(2 * Math.atan(physicalSize.getWidth() / 2 / focalLength));
-        fieldOfView[1] = (float) Math.toDegrees(2 * Math.atan(physicalSize.getHeight() / 2 / focalLength));
+        fieldOfView[0] = (float) Math.toDegrees(
+                2 * Math.atan(physicalSize.getWidth() * activeWidthFraction / 2 / focalLength));
+        fieldOfView[1] = (float) Math.toDegrees(
+                2 * Math.atan(physicalSize.getHeight() * activeHeightFraction / 2 / focalLength));
 
         return fieldOfView;
     }
diff --git a/src/com/android/devcamera/CameraInterface.java b/src/com/android/devcamera/CameraInterface.java
index e16c9e5..e6df5ce 100644
--- a/src/com/android/devcamera/CameraInterface.java
+++ b/src/com/android/devcamera/CameraInterface.java
@@ -33,6 +33,13 @@
     float[] getFieldOfView();
 
     /**
+     * Get the camera sensor orientation relative to device native orientation
+     * Typically 90 or 270 for phones, 0 or 180 for tablets, though many tables are also
+     * portrait-native.
+     */
+    int getOrientation();
+
+    /**
      * Open the camera. Call startPreview() to actually see something.
      */
     void openCamera();
diff --git a/src/com/android/devcamera/DevCameraActivity.java b/src/com/android/devcamera/DevCameraActivity.java
index 6194915..869e065 100644
--- a/src/com/android/devcamera/DevCameraActivity.java
+++ b/src/com/android/devcamera/DevCameraActivity.java
@@ -542,8 +542,9 @@
 
         float[] fovs = mCamera.getFieldOfView();
         mPreviewOverlay.setFieldOfView(fovs[0], fovs[1]);
-        mPreviewOverlay.setFacing(mToggleFrontCam.isChecked() ?
-                CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK);
+        mPreviewOverlay.setFacingAndOrientation(mToggleFrontCam.isChecked() ?
+                CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK,
+                mCamera.getOrientation());
         if (mGyroOperations == null) {
             SensorManager sensorManager = (SensorManager) getSystemService(this.SENSOR_SERVICE);
             mGyroOperations = new GyroOperations(sensorManager);
diff --git a/src/com/android/devcamera/PreviewOverlay.java b/src/com/android/devcamera/PreviewOverlay.java
index 7a4dd02..5909b2d 100644
--- a/src/com/android/devcamera/PreviewOverlay.java
+++ b/src/com/android/devcamera/PreviewOverlay.java
@@ -43,8 +43,11 @@
     private float mFovLargeDegrees;
     private float mFovSmallDegrees;
     private int mFacing = CameraCharacteristics.LENS_FACING_BACK;
+    private int mOrientation = 0;  // degrees
+
     float[] mAngles = new float[2];
 
+
     public PreviewOverlay(Context context, AttributeSet attrs) {
         super(context, attrs);
         Resources res = getResources();
@@ -75,8 +78,9 @@
      * Set the facing of the current camera, for correct coordinate mapping.
      * One of the CameraCharacteristics.LENS_INFO_FACING_* constants
      */
-    public void setFacing(int facing) {
+    public void setFacingAndOrientation(int facing, int orientation) {
         mFacing = facing;
+        mOrientation = orientation;
     }
 
     public void show3AInfo(boolean show) {
@@ -86,13 +90,39 @@
     }
 
     public void setGyroAngles(float[] angles) {
-        if (mFacing == CameraCharacteristics.LENS_FACING_BACK) {
+        boolean front = (mFacing == CameraCharacteristics.LENS_FACING_BACK);
+        // Rotate gyro coordinates to match camera orientation
+        // Gyro data is always presented in the device native coordinate system, which
+        // is either portrait or landscape depending on device.
+        // (http://developer.android.com/reference/android/hardware/SensorEvent.html)
+        // DevCamera locks itself to portrait, and the camera sensor long edge is always aligned
+        // with the long edge of the device.
+        // mOrientation is the relative orientation of the camera sensor and the device native
+        // orientation, so it can be used to decide if the gyro data is meant to be interpreted
+        // in landscape or portrait and flip coordinates/sign accordingly.
+        // Additionally, front-facing cameras are mirrored, so an additional sign flip is needed.
+        switch (mOrientation) {
+            case 0:
+                mAngles[1] = -angles[0];
+                mAngles[0] = angles[1];
+                break;
+            case 90:
+                mAngles[0] = angles[0];
+                mAngles[1] = angles[1];
+                break;
+            case 180:
+                mAngles[1] = -angles[0];
+                mAngles[0] = angles[1];
+                break;
+            case 270:
+                mAngles[0] = angles[0];
+                mAngles[1] = angles[1];
+                break;
+        }
+        if (mFacing != CameraCharacteristics.LENS_FACING_BACK) {
             // Reverse sensor readout for front/external facing cameras
-            mAngles[0] = angles[0];
-            mAngles[1] = angles[1];
-        } else {
-            mAngles[0] = -angles[0];
-            mAngles[1] = -angles[1];
+            mAngles[0] = -mAngles[0];
+            mAngles[1] = -mAngles[1];
         }
     }