Merge "Adjust camera preview area to square"
diff --git a/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml b/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml
index 0c938f8..fb50def 100644
--- a/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml
+++ b/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml
@@ -24,20 +24,19 @@
 
     <include layout="@layout/wifi_dpp_fragment_header"/>
 
-    <FrameLayout
+    <com.android.settings.wifi.qrcode.QrPreviewLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-        <SurfaceView
+        android:layout_height="match_parent"
+        android:layout_gravity="center">
+        <TextureView
             android:id="@+id/preview_view"
-            android:layout_width="426dp"
-            android:layout_height="320dp"
-            android:layout_gravity="center"/>
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"/>
         <com.android.settings.wifi.qrcode.QrDecorateView
             android:id="@+id/decorate_view"
-            android:layout_width="426dp"
-            android:layout_height="320dp"
-            android:layout_gravity="center"/>
-    </FrameLayout>
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"/>
+    </com.android.settings.wifi.qrcode.QrPreviewLayout>
 
     <TextView android:id="@+id/error_message"
         android:layout_width="wrap_content"
diff --git a/res/layout/wifi_dpp_qrcode_scanner_fragment.xml b/res/layout/wifi_dpp_qrcode_scanner_fragment.xml
index 0beacff..913998f 100644
--- a/res/layout/wifi_dpp_qrcode_scanner_fragment.xml
+++ b/res/layout/wifi_dpp_qrcode_scanner_fragment.xml
@@ -24,20 +24,20 @@
 
     <include layout="@layout/wifi_dpp_fragment_header"/>
 
-    <FrameLayout
+    <com.android.settings.wifi.qrcode.QrPreviewLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-        <SurfaceView
+        android:layout_height="match_parent">
+        <TextureView
             android:id="@+id/preview_view"
-            android:layout_width="320dp"
-            android:layout_height="426dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
             android:layout_gravity="center"/>
         <com.android.settings.wifi.qrcode.QrDecorateView
             android:id="@+id/decorate_view"
-            android:layout_width="320dp"
-            android:layout_height="426dp"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
             android:layout_gravity="center"/>
-    </FrameLayout>
+    </com.android.settings.wifi.qrcode.QrPreviewLayout>
 
     <TextView android:id="@+id/error_message"
         android:layout_width="wrap_content"
diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java
index e6427d9..cfdb40a 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeBaseFragment.java
@@ -18,7 +18,6 @@
 
 import android.os.Bundle;
 import android.view.LayoutInflater;
-import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
@@ -30,7 +29,6 @@
 import com.android.internal.logging.nano.MetricsProto;
 
 import com.android.settings.core.InstrumentedFragment;
-import com.android.settings.wifi.qrcode.QrDecorateView;
 import com.android.settings.R;
 
 /**
@@ -50,14 +48,8 @@
     private TextView mTitle;
     private TextView mDescription;
 
-    private SurfaceView mPreviewView;       //optional, for WifiDppQrCodeScannerFragment
-    private QrDecorateView mDecorateViiew;  //optional, for WifiDppQrCodeScannerFragment
     private TextView mErrorMessage;         //optional, for WifiDppQrCodeScannerFragment
-
-    private ImageView mBarcodeView;         //optional, for WifiDppQrCodeGeneratorFragment
-
     private ListView mSavedWifiNetworkList; //optional, for WifiDppChooseSavedWifiNetworkFragment
-
     private ProgressBar mProgressBar;       //optional, for WifiDppAddDeviceFragment
     private ImageView mWifiApPictureView;   //optional, for WifiDppAddDeviceFragment
     private TextView mChooseDifferentNetwork;//optional, for WifiDppAddDeviceFragment
@@ -91,13 +83,8 @@
     private void initView(View view) {
         mTitle = view.findViewById(R.id.title);
         mDescription = view.findViewById(R.id.description);
-
-        mPreviewView = view.findViewById(R.id.preview_view);
-        mDecorateViiew = view.findViewById(R.id.decorate_view);
         mErrorMessage = view.findViewById(R.id.error_message);
 
-        mBarcodeView = view.findViewById(R.id.barcode_view);
-
         mSavedWifiNetworkList = view.findViewById(R.id.saved_wifi_network_list);
 
         mProgressBar = view.findViewById(R.id.progress_bar);
diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java
index 342e693..e7124a5 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeScannerFragment.java
@@ -21,14 +21,16 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.Size;
 import android.view.Menu;
 import android.view.MenuInflater;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
 import android.view.View;
 
 import com.android.settings.R;
@@ -36,10 +38,10 @@
 import com.android.settings.wifi.qrcode.QrDecorateView;
 
 public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment implements
-        SurfaceHolder.Callback,
+        SurfaceTextureListener,
         QrCamera.ScannerCallback {
     private QrCamera mCamera;
-    private SurfaceView mSurfaceView;
+    private TextureView mTextureView;
     private QrDecorateView mDecorateView;
 
     /** true if the fragment working for configurator, false enrollee*/
@@ -109,23 +111,13 @@
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
-        mSurfaceView = (SurfaceView) view.findViewById(R.id.preview_view);
-        final SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
-        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
-        surfaceHolder.addCallback(this);
+        mTextureView = (TextureView) view.findViewById(R.id.preview_view);
+        mTextureView.setSurfaceTextureListener(this);
 
         mDecorateView = (QrDecorateView) view.findViewById(R.id.decorate_view);
     }
 
     @Override
-    public void onDestroyView() {
-        SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
-        surfaceHolder.removeCallback(this);
-
-        super.onDestroyView();
-    }
-
-    @Override
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         menu.removeItem(Menu.FIRST);
 
@@ -133,23 +125,29 @@
     }
 
     @Override
-    public void surfaceCreated(final SurfaceHolder holder) {
-        initCamera(holder);
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        initCamera(surface);
     }
 
     @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Do nothing
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
         destroyCamera();
+        return true;
     }
 
     @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
         // Do nothing
     }
 
     @Override
     public Size getViewSize() {
-        return new Size(mSurfaceView.getWidth(), mSurfaceView.getHeight());
+        return new Size(mTextureView.getWidth(), mTextureView.getHeight());
     }
 
     @Override
@@ -158,6 +156,11 @@
     }
 
     @Override
+    public void setTransform(Matrix transform) {
+        mTextureView.setTransform(transform);
+    }
+
+    @Override
     public void handleSuccessfulResult(String qrCode) {
         destroyCamera();
         mDecorateView.setFocused(true);
@@ -169,11 +172,11 @@
         destroyCamera();
     }
 
-    private void initCamera(SurfaceHolder holder) {
+    private void initCamera(SurfaceTexture surface) {
         // Check if the camera has already created.
         if (mCamera == null) {
             mCamera = new QrCamera(getContext(), this);
-            mCamera.start(holder);
+            mCamera.start(surface);
         }
     }
 
diff --git a/src/com/android/settings/wifi/qrcode/QrCamera.java b/src/com/android/settings/wifi/qrcode/QrCamera.java
index dc650b9..c60c30e 100644
--- a/src/com/android/settings/wifi/qrcode/QrCamera.java
+++ b/src/com/android/settings/wifi/qrcode/QrCamera.java
@@ -17,7 +17,10 @@
 package com.android.settings.wifi.qrcode;
 
 import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
@@ -29,7 +32,6 @@
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
-import android.view.SurfaceHolder;
 import android.view.WindowManager;
 import com.google.zxing.BarcodeFormat;
 import com.google.zxing.BinaryBitmap;
@@ -50,7 +52,7 @@
 
 /**
  * Manage the camera for the QR scanner and help the decoder to get the image inside the scanning
- * frame. Caller prepares a {@link SurfaceHolder} then call {@link #start(SurfaceHolder)} to
+ * frame. Caller prepares a {@link SurfaceTexture} then call {@link #start(SurfaceTexture)} to
  * start QR Code scanning. The scanning result will return by ScannerCallback interface. Caller
  * can also call {@link #stop()} to halt QR Code scanning before the result returned.
  */
@@ -90,12 +92,11 @@
      * The function start camera preview and capture pictures to decode QR code continuously in a
      * background task.
      *
-     * @param surfaceHolder the Surface to be used for live preview, must already contain a surface
-     *                      when this method is called.
+     * @param surface The surface to be used for live preview.
      */
-    public void start(SurfaceHolder surfaceHolder) {
+    public void start(SurfaceTexture surface) {
         if (mDecodeTask == null) {
-            mDecodeTask = new DecodingTask(surfaceHolder);
+            mDecodeTask = new DecodingTask(surface);
             // Execute in the separate thread pool to prevent block other AsyncTask.
             mDecodeTask.executeOnExecutor(Executors.newSingleThreadExecutor());
         }
@@ -144,6 +145,13 @@
          * @return The rectangle would like to crop from the camera preview shot.
          */
         Rect getFramePosition(Size previewSize, int cameraOrientation);
+
+        /**
+         * Sets the transform to associate with preview area.
+         *
+         * @param transform The transform to apply to the content of preview
+         */
+        void setTransform(Matrix transform);
     }
 
     private void setCameraParameter() {
@@ -200,15 +208,15 @@
 
     private class DecodingTask extends AsyncTask<Void, Void, String> {
         private QrYuvLuminanceSource mImage;
-        private SurfaceHolder mSurfaceHolder;
+        private SurfaceTexture mSurface;
 
-        private DecodingTask(SurfaceHolder surfaceHolder) {
-            mSurfaceHolder = surfaceHolder;
+        private DecodingTask(SurfaceTexture surface) {
+            mSurface = surface;
         }
 
         @Override
         protected String doInBackground(Void... tmp) {
-            if (!initCamera(mSurfaceHolder)) {
+            if (!initCamera(mSurface)) {
                 return null;
             }
 
@@ -253,7 +261,7 @@
             }
         }
 
-        private boolean initCamera(SurfaceHolder surfaceHolder) {
+        private boolean initCamera(SurfaceTexture surface) {
             final int numberOfCameras = Camera.getNumberOfCameras();
             Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
             try {
@@ -261,7 +269,7 @@
                     Camera.getCameraInfo(i, cameraInfo);
                     if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                         mCamera = Camera.open(i);
-                        mCamera.setPreviewDisplay(surfaceHolder);
+                        mCamera.setPreviewTexture(surface);
                         mCameraOrientation = cameraInfo.orientation;
                         break;
                     }
@@ -272,6 +280,7 @@
                     return false;
                 }
                 setCameraParameter();
+                setTransformationMatrix(mScannerCallback.getViewSize());
                 if (!startPreview()) {
                     Log.e(TAG, "Error to init Camera");
                     mCamera = null;
@@ -288,6 +297,36 @@
         }
     }
 
+    /** Set transfom matrix to crop and center the preview picture */
+    private void setTransformationMatrix(Size viewSize) {
+        // Check aspect ratio, can only handle square view.
+        final int viewRatio = (int)getRatio(viewSize.getWidth(), viewSize.getHeight());
+        if (viewRatio != 1) {
+            throw new IllegalArgumentException("Preview area should be square");
+        }
+
+        final boolean isPortrait = mContext.get().getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_PORTRAIT ? true : false;
+
+        final int previewWidth = isPortrait ? mPreviewSize.getWidth() : mPreviewSize.getHeight();
+        final int previewHeight = isPortrait ? mPreviewSize.getHeight() : mPreviewSize.getWidth();
+        final float ratioPreview = (float) getRatio(previewWidth, previewHeight);
+
+        // Calculate transformation matrix.
+        float scaleX = 1.0f;
+        float scaleY = 1.0f;
+        if (previewWidth > previewHeight) {
+            scaleY = scaleX / ratioPreview;
+        } else {
+            scaleX = scaleY / ratioPreview;
+        }
+
+        // Set the transform matrix.
+        final Matrix matrix = new Matrix();
+        matrix.setScale(scaleX, scaleY);
+        mScannerCallback.setTransform(matrix);
+    }
+
     private QrYuvLuminanceSource getFrameImage(byte[] imageData) {
         final Rect frame = mScannerCallback.getFramePosition(mPreviewSize, mCameraOrientation);
         final Camera.Size size = mParameters.getPictureSize();
diff --git a/src/com/android/settings/wifi/qrcode/QrPreviewLayout.java b/src/com/android/settings/wifi/qrcode/QrPreviewLayout.java
new file mode 100644
index 0000000..56566ae
--- /dev/null
+++ b/src/com/android/settings/wifi/qrcode/QrPreviewLayout.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2018 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.settings.wifi.qrcode;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.FrameLayout;
+
+/**
+ * A customize square {@link FrameLayout}.
+ * This is used for camera preview. Choose the smaller size of both dimensions as length and width.
+ */
+public class QrPreviewLayout extends FrameLayout {
+    public QrPreviewLayout(Context context) {
+        super(context);
+    }
+
+    public QrPreviewLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public QrPreviewLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Choose the smaller size of the two dimensions.
+        if (MeasureSpec.getSize(widthMeasureSpec) > MeasureSpec.getSize(heightMeasureSpec)) {
+            super.onMeasure(heightMeasureSpec, heightMeasureSpec);
+        } else {
+            super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java b/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java
index e32ac6b..0ef0273 100644
--- a/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/qrcode/QrCameraTest.java
@@ -22,9 +22,11 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
 import android.util.Size;
-import android.view.SurfaceHolder;
 
 import com.android.settings.R;
 
@@ -48,7 +50,7 @@
 public class QrCameraTest {
 
     @Mock
-    private SurfaceHolder mSurfaceHolder;
+    private SurfaceTexture mSurfaceTexture;
 
     private QrCamera mCamera;
     private Context mContext;
@@ -78,6 +80,11 @@
             mCameraCallbacked = true;
             mCallbackSignal.countDown();
         }
+
+        @Override
+        public void setTransform(Matrix transform) {
+            // Do nothing
+        }
     }
 
     private ScannerTestCallback mScannerCallback;
@@ -87,7 +94,7 @@
         mContext = RuntimeEnvironment.application;
         mScannerCallback = new ScannerTestCallback();
         mCamera = new QrCamera(mContext, mScannerCallback);
-        mSurfaceHolder = mock(SurfaceHolder.class);
+        mSurfaceTexture = mock(SurfaceTexture.class);
         mQrCode = "";
         mCameraCallbacked = false;
         mCallbackSignal = null;
@@ -96,7 +103,7 @@
     @Test
     public void testCamera_Init_Callback() throws InterruptedException {
         mCallbackSignal = new CountDownLatch(1);
-        mCamera.start(mSurfaceHolder);
+        mCamera.start(mSurfaceTexture);
         mCallbackSignal.await(5000, TimeUnit.MILLISECONDS);
         assertThat(mCameraCallbacked).isTrue();
     }