DO NOT MERGE - Merge Android 10 into master

Bug: 139893257
Change-Id: I67a7040a90c369abf5a0c3e7cf0e16f351addb99
diff --git a/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java b/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
index d743443..f796dee 100644
--- a/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
+++ b/apps/TestingCamera/src/com/android/testingcamera/TestingCamera.java
@@ -34,6 +34,7 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.view.OrientationEventListener;
 import android.view.View;
 import android.view.Surface;
 import android.view.SurfaceHolder;
@@ -179,6 +180,7 @@
     private static final int PERMISSIONS_REQUEST_CAMERA = 1;
     private static final int PERMISSIONS_REQUEST_RECORDING = 2;
     static final int PERMISSIONS_REQUEST_SNAPSHOT = 3;
+    private OrientationEventHandler mOrientationHandler;
 
     /** Activity lifecycle */
 
@@ -331,6 +333,34 @@
         }
 
         mRS = RenderScript.create(this);
+
+        mOrientationHandler = new OrientationEventHandler(this);
+    }
+
+    private static class OrientationEventHandler extends OrientationEventListener {
+        private TestingCamera mActivity;
+        private int mCurrentRotation = -1;
+        OrientationEventHandler(TestingCamera activity) {
+            super(activity);
+            mActivity = activity;
+        }
+
+        @Override
+        public void onOrientationChanged(int orientation) {
+            if (mActivity != null) {
+                int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+                if (mCurrentRotation != rotation) {
+                    mCurrentRotation = rotation;
+                    mActivity.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            mActivity.setCameraDisplayOrientation();
+                            mActivity.resizePreview();
+                        }
+                    });
+                }
+            }
+        }
     }
 
     @Override
@@ -338,6 +368,7 @@
         super.onResume();
         log("onResume: Setting up");
         setUpCamera();
+        mOrientationHandler.enable();
     }
 
     @Override
@@ -361,6 +392,7 @@
             }
             mState = CAMERA_UNINITIALIZED;
         }
+        mOrientationHandler.disable();
     }
 
     @Override
@@ -418,7 +450,7 @@
                 }
             }
 
-            if (mPreviewHolder != null) {
+            if (mPreviewHolder != null || mState == CAMERA_UNINITIALIZED) {
                 return;
             }
             log("Surface holder available: " + width + " x " + height);
@@ -1037,7 +1069,9 @@
         updateColorEffects(mParams);
 
         // Trigger updating video record size to match camcorder profile
-        mCamcorderProfileSpinner.setSelection(mCamcorderProfile);
+        if (mCamcorderProfile >= 0) {
+            mCamcorderProfileSpinner.setSelection(mCamcorderProfile);
+        }
 
         if (mParams.isVideoStabilizationSupported()) {
             log("Video stabilization is supported");
@@ -1317,11 +1351,18 @@
                 mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i]));
             }
         }
+
         String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]);
         mCamcorderProfileSpinner.setAdapter(
                 new ArrayAdapter<String>(
                         this, R.layout.spinner_item, nameArray));
 
+        if (availableCamcorderProfileNames.size() == 0) {
+            log("Camera " +  cameraId + " doesn't support camcorder profile");
+            mCamcorderProfile = -1;
+            return;
+        }
+
         mCamcorderProfile = 0;
         log("Setting camcorder profile to " + nameArray[mCamcorderProfile]);
 
@@ -1388,8 +1429,33 @@
     }
 
     void layoutPreview() {
+        int rotation = getWindowManager().getDefaultDisplay().getRotation();
         int width = mPreviewSizes.get(mPreviewSize).width;
         int height = mPreviewSizes.get(mPreviewSize).height;
+        switch (rotation) {
+            case Surface.ROTATION_0:
+            case Surface.ROTATION_180:
+                // Portrait
+                // Switch the preview size so that the longer edge aligns with the taller
+                // dimension.
+                if (width > height) {
+                    int tmp = height;
+                    height = width;
+                    width = tmp;
+                }
+                break;
+            case Surface.ROTATION_90:
+            case Surface.ROTATION_270:
+                // Landscape
+                // Possibly somewhat unlikely case but we should try to handle it too.
+                if (height > width) {
+                    int tmp = height;
+                    height = width;
+                    width = tmp;
+                }
+                break;
+        }
+
         float previewAspect = ((float) width) / height;
 
         int viewHeight = mPreviewView.getHeight();
@@ -1402,6 +1468,8 @@
         }
         mPreviewView.setLayoutParams(
                 new LayoutParams(viewWidth, viewHeight));
+        log("Setting layout params viewWidth: " + viewWidth + " viewHeight: " + viewHeight +
+                " display rotation: " + rotation);
 
         if (mCallbacksEnabled) {
             int callbackHeight = mCallbackView.getHeight();
@@ -1569,6 +1637,19 @@
         }
     }
 
+    private static final int BIT_RATE_1080P = 16000000;
+    private static final int BIT_RATE_MIN = 64000;
+    private static final int BIT_RATE_MAX = 40000000;
+
+    private int getVideoBitRate(Camera.Size sz) {
+        int rate = BIT_RATE_1080P;
+        float scaleFactor = sz.height * sz.width / (float)(1920 * 1080);
+        rate = (int)(rate * scaleFactor);
+
+        // Clamp to the MIN, MAX range.
+        return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
+    }
+
     private void startRecording() {
         log("Starting recording");
 
@@ -1608,8 +1689,17 @@
 
         mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
         mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
+
         Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize);
+        if (mCamcorderProfile >= 0) {
+            mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
+        } else {
+            mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+            mRecorder.setVideoEncodingBitRate(getVideoBitRate(videoRecordSize));
+            mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+            mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        }
+
         if (videoRecordSize.width > 0 && videoRecordSize.height > 0) {
             mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height);
         }
diff --git a/apps/TestingCamera2/AndroidManifest.xml b/apps/TestingCamera2/AndroidManifest.xml
index 79c1b7f..430d8ba 100644
--- a/apps/TestingCamera2/AndroidManifest.xml
+++ b/apps/TestingCamera2/AndroidManifest.xml
@@ -37,8 +37,6 @@
         android:required="false" />
 
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
     <application
         android:icon="@mipmap/launcher_testingcamera2"
diff --git a/apps/TestingCamera2/res/layout/main.xml b/apps/TestingCamera2/res/layout/main.xml
index 488265d..acc8594 100644
--- a/apps/TestingCamera2/res/layout/main.xml
+++ b/apps/TestingCamera2/res/layout/main.xml
@@ -66,7 +66,13 @@
             android:spinnerMode="dropdown"
             android:prompt="@string/camera_id_spinner_prompt"
             />
-
+        <Spinner
+            android:id="@+id/still_format_spinner"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:spinnerMode="dropdown"
+            android:prompt="@string/still_format_spinner_prompt"
+            />
         <Button
             android:id="@+id/info_button"
             android:text="@string/info_button_label"
diff --git a/apps/TestingCamera2/res/values/strings.xml b/apps/TestingCamera2/res/values/strings.xml
index c3bc711..71c88dc 100644
--- a/apps/TestingCamera2/res/values/strings.xml
+++ b/apps/TestingCamera2/res/values/strings.xml
@@ -87,7 +87,8 @@
 
     <string name="v1_app_name">TestingCam2</string>
     <string name="camera_id_spinner_prompt">Camera Id</string>
-    <string name="info_button_label">JPEG!</string>
+    <string name="still_format_spinner_prompt">Still Capture Format</string>
+    <string name="info_button_label">Capture!</string>
     <string name="flush_button_label">Flush device</string>
     <string name="focus_lock_button_label">Focus Lock</string>
     <string name="focus_unlock_button_label">Focus Unlock</string>
diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java b/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java
index ddf0098..01ba6b6 100644
--- a/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java
+++ b/apps/TestingCamera2/src/com/android/testingcamera2/CameraOps2.java
@@ -109,18 +109,14 @@
                 return false;
             }
         }
-        if ((mActivity.checkSelfPermission(Manifest.permission.CAMERA)
-                != PackageManager.PERMISSION_GRANTED)
-            || (mActivity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
-                != PackageManager.PERMISSION_GRANTED)) {
-            TLog.i("Requesting camera/storage permissions");
+        if (mActivity.checkSelfPermission(Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            TLog.i("Requesting camera permissions");
 
             mDelayedOpenId = cameraId;
             mDelayedOpenListener = listener;
 
-            mActivity.requestPermissions(new String[] {
-                        Manifest.permission.CAMERA,
-                        Manifest.permission.WRITE_EXTERNAL_STORAGE },
+            mActivity.requestPermissions(new String[] {Manifest.permission.CAMERA},
                     PERMISSIONS_REQUEST_CAMERA);
             return false;
         }
diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java b/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java
index 18534f7..9e82012 100644
--- a/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java
+++ b/apps/TestingCamera2/src/com/android/testingcamera2/RequestControlPane.java
@@ -23,6 +23,7 @@
 import android.view.LayoutInflater;
 import android.view.Surface;
 import android.view.View;
+import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ListView;
@@ -159,6 +160,8 @@
         mTemplateSpinner = (Spinner) findViewById(R.id.request_pane_template_spinner);
         mOutputListView = (ListView) findViewById(R.id.request_pane_output_listview);
 
+        mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
+
         mOutputAdapter = new CheckableListAdapter(context, R.layout.checkable_list_item,
                 new ArrayList<CheckableListAdapter.CheckableItem>());
         mOutputListView.setAdapter(mOutputAdapter);
@@ -193,6 +196,18 @@
         }
     }
 
+    private AdapterView.OnItemSelectedListener mCameraSpinnerListener = new AdapterView.OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            updateOutputList();
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+            updateOutputList();
+        }
+    };
+
     private OnClickListener mCaptureButtonListener = new OnClickListener() {
         @Override
         public void onClick(View v) {
diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java b/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java
index 753fa00..486c245 100644
--- a/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java
+++ b/apps/TestingCamera2/src/com/android/testingcamera2/v1/CameraOps.java
@@ -31,6 +31,7 @@
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.MediaCodec;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.util.Log;
@@ -84,8 +85,8 @@
     List<Surface> mOutputSurfaces = new ArrayList<Surface>(2);
     private Surface mPreviewSurface;
     private Surface mPreviewSurface2;
-    // How many JPEG buffers do we want to hold on to at once
-    private static final int MAX_CONCURRENT_JPEGS = 2;
+    // How many still capture buffers do we want to hold on to at once
+    private static final int MAX_CONCURRENT_STILL_CAPTURES = 2;
 
     private static final int STATUS_ERROR = 0;
     private static final int STATUS_UNINITIALIZED = 1;
@@ -408,33 +409,65 @@
         }
     }
 
-    public void minimalJpegCapture(final CaptureCallback listener, CaptureResultListener l,
-            Handler h, CameraControls cameraControl) throws ApiFailureException {
+    private static class SimpleImageListener implements ImageReader.OnImageAvailableListener {
+        private final ConditionVariable imageAvailable = new ConditionVariable();
+        private final CaptureCallback mListener;
+
+        SimpleImageListener(final CaptureCallback listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            Image i = null;
+            try {
+                i = reader.acquireNextImage();
+                mListener.onCaptureAvailable(i);
+            } finally {
+                if (i != null) {
+                    i.close();
+                }
+                imageAvailable.open();
+            }
+        }
+
+        public void waitForImageAvailable(long timeout) {
+            if (imageAvailable.block(timeout)) {
+                imageAvailable.close();
+            } else {
+                Log.e(TAG, "wait for image available timed out after " + timeout + "ms");
+            }
+        }
+    }
+
+    public void minimalStillCapture(final CaptureCallback listener, CaptureResultListener l,
+            Handler h, CameraControls cameraControl, int format) throws ApiFailureException {
         minimalOpenCamera();
 
         try {
             CameraCharacteristics properties =
                     mCameraManager.getCameraCharacteristics(mCamera.getId());
-            Size[] jpegSizes = null;
+            Size[] stillSizes = null;
             if (properties != null) {
-                jpegSizes = properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).
-                        getOutputSizes(ImageFormat.JPEG);
+                stillSizes = properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).
+                        getOutputSizes(format);
             }
             int width = 640;
             int height = 480;
 
-            if (jpegSizes != null && jpegSizes.length > 0) {
-                width = jpegSizes[0].getWidth();
-                height = jpegSizes[0].getHeight();
+            if (stillSizes != null && stillSizes.length > 0) {
+                width = stillSizes[0].getWidth();
+                height = stillSizes[0].getHeight();
             }
 
             if (mCaptureReader == null || mCaptureReader.getWidth() != width ||
-                    mCaptureReader.getHeight() != height) {
+                    mCaptureReader.getHeight() != height ||
+                    mCaptureReader.getImageFormat() != format) {
                 if (mCaptureReader != null) {
                     mCaptureReader.close();
                 }
                 mCaptureReader = ImageReader.newInstance(width, height,
-                        ImageFormat.JPEG, MAX_CONCURRENT_JPEGS);
+                        format, MAX_CONCURRENT_STILL_CAPTURES);
             }
 
             List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
@@ -450,26 +483,15 @@
 
             updateCaptureRequest(captureBuilder, cameraControl);
 
-            ImageReader.OnImageAvailableListener readerListener =
-                    new ImageReader.OnImageAvailableListener() {
-                @Override
-                public void onImageAvailable(ImageReader reader) {
-                    Image i = null;
-                    try {
-                        i = reader.acquireNextImage();
-                        listener.onCaptureAvailable(i);
-                    } finally {
-                        if (i != null) {
-                            i.close();
-                        }
-                    }
-                }
-            };
+            SimpleImageListener readerListener = new SimpleImageListener(listener);
+
             mCaptureReader.setOnImageAvailableListener(readerListener, h);
 
             mSession.capture(captureBuilder.build(), l, mOpsHandler);
+
+            readerListener.waitForImageAvailable(1000L/*timeout*/);
         } catch (CameraAccessException e) {
-            throw new ApiFailureException("Error in minimal JPEG capture", e);
+            throw new ApiFailureException("Error in minimal still capture", e);
         }
     }
 
diff --git a/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java b/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java
index 7a4a9b5..27273a8 100644
--- a/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java
+++ b/apps/TestingCamera2/src/com/android/testingcamera2/v1/TestingCamera2.java
@@ -29,6 +29,7 @@
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.media.MediaMuxer;
@@ -73,6 +74,7 @@
     private CameraOps mCameraOps;
     private String[] mAvailableCameraIds;
     private String mCameraId;
+    private int mCaptureFormat = ImageFormat.JPEG;
     private static final int mSeekBarMax = 100;
     private static final long MAX_EXPOSURE = 200000000L; // 200ms
     private static final long MIN_EXPOSURE = 100000L; // 100us
@@ -94,7 +96,9 @@
     private SurfaceHolder mCurrentPreviewHolder2 = null;
 
     private Spinner mCameraIdSpinner;
+    private Spinner mCaptureFormatSpinner;
     private OnItemSelectedListener mCameraIdSelectionListener;
+    private OnItemSelectedListener mCaptureFormatSelectionListener;
     private Button mInfoButton;
     private Button mFlushButton;
     private ToggleButton mFocusLockToggle;
@@ -139,6 +143,7 @@
         mStillView = (ImageView) findViewById(R.id.still_view);
 
         mCameraIdSpinner = (Spinner) findViewById(R.id.camera_id_spinner);
+        mCaptureFormatSpinner = (Spinner) findViewById(R.id.still_format_spinner);
         mInfoButton  = (Button) findViewById(R.id.info_button);
         mInfoButton.setOnClickListener(mInfoButtonListener);
         mFlushButton  = (Button) findViewById(R.id.flush_button);
@@ -232,14 +237,11 @@
         if ((checkSelfPermission(Manifest.permission.CAMERA)
                 != PackageManager.PERMISSION_GRANTED )
             || (checkSelfPermission(Manifest.permission.RECORD_AUDIO)
-                != PackageManager.PERMISSION_GRANTED)
-            || (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                 != PackageManager.PERMISSION_GRANTED)) {
             Log.i(TAG, "Requested camera/video permissions");
             requestPermissions(new String[] {
                         Manifest.permission.CAMERA,
-                        Manifest.permission.RECORD_AUDIO,
-                        Manifest.permission.WRITE_EXTERNAL_STORAGE},
+                        Manifest.permission.RECORD_AUDIO },
                     PERMISSIONS_REQUEST_CAMERA);
             return;
         }
@@ -402,14 +404,14 @@
                 @Override
                 public void run() {
                     try {
-                        mCameraOps.minimalJpegCapture(mCaptureCallback, mCaptureResultListener,
-                                uiHandler, mCameraControl);
+                        mCameraOps.minimalStillCapture(mCaptureCallback, mCaptureResultListener,
+                                uiHandler, mCameraControl, mCaptureFormat);
                         if (mCurrentPreviewHolder != null && mCurrentPreviewHolder2 != null) {
                             mCameraOps.minimalPreview(mCurrentPreviewHolder,
                                     mCurrentPreviewHolder2, mCameraControl);
                         }
                     } catch (ApiFailureException e) {
-                        logException("Can't take a JPEG! ", e);
+                        logException("Can't take a still capture! ", e);
                     }
                 }
             });
@@ -448,15 +450,16 @@
     private final CameraOps.CaptureCallback mCaptureCallback = new CameraOps.CaptureCallback() {
         @Override
         public void onCaptureAvailable(Image capture) {
-            if (capture.getFormat() != ImageFormat.JPEG) {
+            if (capture.getFormat() != ImageFormat.JPEG &&
+                    capture.getFormat() != ImageFormat.HEIC) {
                 Log.e(TAG, "Unexpected format: " + capture.getFormat());
                 return;
             }
-            ByteBuffer jpegBuffer = capture.getPlanes()[0].getBuffer();
-            byte[] jpegData = new byte[jpegBuffer.capacity()];
-            jpegBuffer.get(jpegData);
+            ByteBuffer encodedBuffer = capture.getPlanes()[0].getBuffer();
+            byte[] encodedData = new byte[encodedBuffer.capacity()];
+            encodedBuffer.get(encodedData);
 
-            Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+            Bitmap b = BitmapFactory.decodeByteArray(encodedData, 0, encodedData.length);
             mStillView.setImageBitmap(b);
         }
     };
@@ -822,6 +825,45 @@
                     // Do nothing
                 }
             });
+
+            // Map available still capture formats -> capture format spinner dropdown list of
+            // strings
+            StreamConfigurationMap streamConfigMap =
+                    characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+            final List<String> captureFormatList = new ArrayList<>();
+
+            captureFormatList.add("JPEG");
+            if (streamConfigMap.isOutputSupportedFor(ImageFormat.HEIC)) {
+                captureFormatList.add("HEIC");
+            }
+
+            dataAdapter = new ArrayAdapter<>(TestingCamera2.this,
+                    android.R.layout.simple_spinner_item, captureFormatList);
+            dataAdapter.setDropDownViewResource(
+                    android.R.layout.simple_spinner_dropdown_item);
+            mCaptureFormatSpinner.setAdapter(dataAdapter);
+
+            /*
+             * Change the capture format and update preview when format spinner's selected item changes
+             */
+            mCaptureFormatSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+
+                @Override
+                public void onItemSelected(AdapterView<?> parent, View view, int position,
+                        long id) {
+                    int format = (position == 0 ? ImageFormat.JPEG : ImageFormat.HEIC);
+
+                    Log.i(TAG, "Change image capture format to " + captureFormatList.get(position)
+                            + " " + format);
+
+                    mCaptureFormat = format;
+                }
+
+                @Override
+                public void onNothingSelected(AdapterView<?> parent) {
+                    // Do nothing
+                }
+            });
         }
     };
 }