6/n: Add camera preview to FaceEnrollEnrolling
Bug: 112005540
Test: manual
Change-Id: Ie4f810dffecdec9731e20d5756854d9c9f420f4b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0a5f24b..51de802 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -94,6 +94,7 @@
<uses-permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD" />
<uses-permission android:name="android.permission.USE_RESERVED_DISK" />
<uses-permission android:name="android.permission.MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS" />
+ <uses-permission android:name="android.permission.CAMERA" />
<application android:label="@string/settings_label"
android:icon="@drawable/ic_launcher_settings"
@@ -1576,9 +1577,17 @@
android:windowSoftInputMode="stateHidden|adjustResize"
android:theme="@style/GlifTheme.Light"/>
- <activity android:name=".biometrics.face.FaceEnrollIntroduction" android:exported="false" />
- <activity android:name=".biometrics.face.FaceEnrollEnrolling" android:exported="false" />
- <activity android:name=".biometrics.face.FaceEnrollFinish" android:exported="false" />
+ <activity android:name=".biometrics.face.FaceEnrollIntroduction"
+ android:exported="false"
+ android:screenOrientation="portrait"/>
+
+ <activity android:name=".biometrics.face.FaceEnrollEnrolling"
+ android:exported="false"
+ android:screenOrientation="portrait"/>
+
+ <activity android:name=".biometrics.face.FaceEnrollFinish"
+ android:exported="false"
+ android:screenOrientation="portrait"/>
<activity android:name=".biometrics.fingerprint.FingerprintSettings" android:exported="false"/>
<activity android:name=".biometrics.fingerprint.FingerprintEnrollFindSensor" android:exported="false"/>
diff --git a/res/layout/face_enroll_enrolling.xml b/res/layout/face_enroll_enrolling.xml
index 6ced80f..2208cc2 100644
--- a/res/layout/face_enroll_enrolling.xml
+++ b/res/layout/face_enroll_enrolling.xml
@@ -39,20 +39,23 @@
android:gravity="center"
android:orientation="vertical">
- <com.android.setupwizardlib.view.FillContentLayout
+ <com.android.settings.biometrics.face.FaceSquareFrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_weight="1">
- <!-- TODO: replace this with actual content-->
- <ImageView
- style="@style/SuwContentIllustration"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@null"
- android:src="@drawable/face_enroll_introduction" />
+ <com.android.settings.biometrics.face.FaceSquareTextureView
+ android:id="@+id/texture_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@null" />
- </com.android.setupwizardlib.view.FillContentLayout>
+ <ImageView
+ android:id="@+id/circle_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </com.android.settings.biometrics.face.FaceSquareFrameLayout>
<TextView
style="@style/TextAppearance.FaceErrorText"
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java
new file mode 100644
index 0000000..0da666c
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java
@@ -0,0 +1,85 @@
+/*
+ * 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.biometrics.face;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A drawable containing the circle cutout.
+ */
+public class FaceEnrollAnimationDrawable extends Drawable {
+
+ private Rect mBounds;
+ private final Paint mSquarePaint;
+ private final Paint mCircleCutoutPaint;
+
+ public FaceEnrollAnimationDrawable() {
+ mSquarePaint = new Paint();
+ mSquarePaint.setColor(Color.WHITE);
+ mSquarePaint.setAntiAlias(true);
+
+ mCircleCutoutPaint = new Paint();
+ mCircleCutoutPaint.setColor(Color.TRANSPARENT);
+ mCircleCutoutPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+ mCircleCutoutPaint.setAntiAlias(true);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ mBounds = bounds;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (mBounds == null) {
+ return;
+ }
+ canvas.save();
+
+ // Draw a rectangle covering the whole view
+ canvas.drawRect(0, 0, mBounds.width(), mBounds.height(), mSquarePaint);
+
+ // Clear a circle in the middle for the camera preview
+ canvas.drawCircle(mBounds.exactCenterX(), mBounds.exactCenterY(),
+ mBounds.height() / 2, mCircleCutoutPaint);
+
+ canvas.restore();
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
index 9786363..7fac9f6 100644
--- a/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/face/FaceEnrollEnrolling.java
@@ -40,10 +40,12 @@
private static final String TAG = "FaceEnrollEnrolling";
private static final boolean DEBUG = true;
+ private static final String TAG_FACE_PREVIEW = "tag_preview";
private TextView mErrorText;
private Interpolator mLinearOutSlowInInterpolator;
private boolean mShouldFinishOnStop = true;
+ private FaceEnrollPreviewFragment mFaceCameraPreview;
public static class FaceErrorDialog extends BiometricErrorDialog {
static FaceErrorDialog newInstance(CharSequence msg, int msgId) {
@@ -93,6 +95,18 @@
}
@Override
+ public void startEnrollment() {
+ super.startEnrollment();
+ mFaceCameraPreview = (FaceEnrollPreviewFragment) getSupportFragmentManager()
+ .findFragmentByTag(TAG_FACE_PREVIEW);
+ if (mFaceCameraPreview == null) {
+ mFaceCameraPreview = new FaceEnrollPreviewFragment();
+ getSupportFragmentManager().beginTransaction().add(mFaceCameraPreview, TAG_FACE_PREVIEW)
+ .commitAllowingStateLoss();
+ }
+ }
+
+ @Override
protected Intent getFinishIntent() {
return new Intent(this, FaceEnrollFinish.class);
}
diff --git a/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java
new file mode 100644
index 0000000..2b92ccb
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceEnrollPreviewFragment.java
@@ -0,0 +1,347 @@
+/*
+ * 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.biometrics.face;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.InstrumentedPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Fragment that contains the logic for showing and controlling the camera preview, circular
+ * overlay, as well as the enrollment animations.
+ */
+public class FaceEnrollPreviewFragment extends InstrumentedPreferenceFragment {
+
+ private static final String TAG = "FaceEnrollPreviewFragment";
+
+ private static final int MAX_PREVIEW_WIDTH = 1920;
+ private static final int MAX_PREVIEW_HEIGHT = 1080;
+
+ private Handler mHandler = new Handler(Looper.getMainLooper());
+ private CameraManager mCameraManager;
+ private String mCameraId;
+ private CameraDevice mCameraDevice;
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+ private CameraCaptureSession mCaptureSession;
+ private CaptureRequest mPreviewRequest;
+ private Size mPreviewSize;
+
+ // View used to contain the circular cutout and enrollment animation drawable
+ private ImageView mCircleView;
+
+ // Drawable containing the circular cutout and enrollment animations
+ private FaceEnrollAnimationDrawable mAnimationDrawable;
+
+ // Texture used for showing the camera preview
+ private FaceSquareTextureView mTextureView;
+
+ private final TextureView.SurfaceTextureListener mSurfaceTextureListener =
+ new TextureView.SurfaceTextureListener() {
+
+ @Override
+ public void onSurfaceTextureAvailable(
+ SurfaceTexture surfaceTexture, int width, int height) {
+ openCamera(width, height);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(
+ SurfaceTexture surfaceTexture, int width, int height) {
+ // Shouldn't be called, but do this for completeness.
+ configureTransform(width, height);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+
+ }
+ };
+
+ private final CameraDevice.StateCallback mCameraStateCallback =
+ new CameraDevice.StateCallback() {
+
+ @Override
+ public void onOpened(CameraDevice cameraDevice) {
+ mCameraDevice = cameraDevice;
+
+ try {
+ // Configure the size of default buffer
+ SurfaceTexture texture = mTextureView.getSurfaceTexture();
+ texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+
+ // This is the output Surface we need to start preview
+ Surface surface = new Surface(texture);
+
+ // Set up a CaptureRequest.Builder with the output Surface
+ mPreviewRequestBuilder =
+ mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ mPreviewRequestBuilder.addTarget(surface);
+
+ // Create a CameraCaptureSession for camera preview
+ mCameraDevice.createCaptureSession(Arrays.asList(surface),
+ new CameraCaptureSession.StateCallback() {
+
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ // The camera is already closed
+ if (null == mCameraDevice) {
+ return;
+ }
+ // When the session is ready, we start displaying the preview.
+ mCaptureSession = cameraCaptureSession;
+ try {
+ // Auto focus should be continuous for camera preview.
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+ // Finally, we start displaying the camera preview.
+ mPreviewRequest = mPreviewRequestBuilder.build();
+ mCaptureSession.setRepeatingRequest(mPreviewRequest,
+ null /* listener */, mHandler);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to access camera", e);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
+ Log.e(TAG, "Unable to configure camera");
+ }
+ }, null /* handler */);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice cameraDevice) {
+ cameraDevice.close();
+ mCameraDevice = null;
+ }
+
+ @Override
+ public void onError(CameraDevice cameraDevice, int error) {
+ cameraDevice.close();
+ mCameraDevice = null;
+ }
+ };
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.FACE_ENROLL_PREVIEW;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mTextureView = getActivity().findViewById(R.id.texture_view);
+ mCircleView = getActivity().findViewById(R.id.circle_view);
+
+ // Must disable hardware acceleration for this view, otherwise transparency breaks
+ mCircleView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+
+ mAnimationDrawable = new FaceEnrollAnimationDrawable();
+ mCircleView.setImageDrawable(mAnimationDrawable);
+
+ mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // When the screen is turned off and turned back on, the SurfaceTexture is already
+ // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
+ // a camera and start preview from here (otherwise, we wait until the surface is ready in
+ // the SurfaceTextureListener).
+ if (mTextureView.isAvailable()) {
+ openCamera(mTextureView.getWidth(), mTextureView.getHeight());
+ } else {
+ mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ closeCamera();
+ }
+
+ /**
+ * Sets up member variables related to camera.
+ *
+ * @param width The width of available size for camera preview
+ * @param height The height of available size for camera preview
+ */
+ private void setUpCameraOutputs(int width, int height) {
+ try {
+ for (String cameraId : mCameraManager.getCameraIdList()) {
+ CameraCharacteristics characteristics =
+ mCameraManager.getCameraCharacteristics(cameraId);
+
+ // Find front facing camera
+ Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+ if (facing == null || facing != CameraCharacteristics.LENS_FACING_FRONT) {
+ continue;
+ }
+ mCameraId = cameraId;
+
+ // Get the stream configurations
+ StreamConfigurationMap map = characteristics.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
+ width, height, MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT);
+ }
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to access camera", e);
+ }
+ }
+
+ /**
+ * Opens the camera specified by mCameraId.
+ * @param width The width of the texture view
+ * @param height The height of the texture view
+ */
+ private void openCamera(int width, int height) {
+ try {
+ setUpCameraOutputs(width, height);
+ mCameraManager.openCamera(mCameraId, mCameraStateCallback, mHandler);
+ configureTransform(width, height);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Unable to open camera", e);
+ }
+ }
+
+ /**
+ * Chooses the optimal resolution for the camera to open.
+ */
+ private Size chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight,
+ int maxWidth, int maxHeight) {
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ List<Size> bigEnough = new ArrayList<>();
+ // Collect the supported resolutions that are smaller than the preview Surface
+ List<Size> notBigEnough = new ArrayList<>();
+
+ for (Size option : choices) {
+ if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
+ option.getHeight() == option.getWidth()) {
+ if (option.getWidth() >= textureViewWidth &&
+ option.getHeight() >= textureViewHeight) {
+ bigEnough.add(option);
+ } else {
+ notBigEnough.add(option);
+ }
+ }
+ }
+
+ // Pick the smallest of those big enough. If there is no one big enough, pick the
+ // largest of those not big enough.
+ if (bigEnough.size() > 0) {
+ return Collections.min(bigEnough, new CompareSizesByArea());
+ } else if (notBigEnough.size() > 0) {
+ return Collections.max(notBigEnough, new CompareSizesByArea());
+ } else {
+ Log.e(TAG, "Couldn't find any suitable preview size");
+ return choices[0];
+ }
+ }
+
+ /**
+ * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
+ * This method should be called after the camera preview size is determined in
+ * setUpCameraOutputs and also the size of `mTextureView` is fixed.
+ *
+ * @param viewWidth The width of `mTextureView`
+ * @param viewHeight The height of `mTextureView`
+ */
+ private void configureTransform(int viewWidth, int viewHeight) {
+ if (mTextureView == null) {
+ return;
+ }
+
+ // Fix the aspect ratio
+ Matrix matrix = new Matrix();
+ float scaleX = (float) viewWidth / mPreviewSize.getWidth();
+ float scaleY = (float) viewHeight / mPreviewSize.getHeight();
+
+ // Now divide by smaller one so it fills up the original space
+ float smaller = Math.min(scaleX, scaleY);
+ scaleX = scaleX / smaller;
+ scaleY = scaleY / smaller;
+
+ // Apply the scale
+ matrix.setScale(scaleX, scaleY);
+
+ mTextureView.setTransform(matrix);
+ }
+
+ private void closeCamera() {
+ if (mCaptureSession != null) {
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ if (mCameraDevice != null) {
+ mCameraDevice.close();
+ mCameraDevice = null;
+ }
+ }
+
+ /**
+ * Compares two {@code Size}s based on their areas.
+ */
+ private static class CompareSizesByArea implements Comparator<Size> {
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ // We cast here to ensure the multiplications won't overflow
+ return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
+ (long) rhs.getWidth() * rhs.getHeight());
+ }
+
+ }
+
+}
diff --git a/src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java b/src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java
new file mode 100644
index 0000000..3aed524
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceSquareFrameLayout.java
@@ -0,0 +1,60 @@
+/*
+ * 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.biometrics.face;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * Square layout that sets the height to be the same as width.
+ */
+public class FaceSquareFrameLayout extends FrameLayout {
+
+ public FaceSquareFrameLayout(Context context) {
+ super(context);
+ }
+
+ public FaceSquareFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FaceSquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public FaceSquareFrameLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Don't call super, manually set their size below
+ int size = MeasureSpec.getSize(widthMeasureSpec);
+
+ // Set this frame layout to be a square
+ setMeasuredDimension(size, size);
+
+ // Set the children to be the same size (square) as well
+ final int numChildren = getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ int spec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+ this.getChildAt(i).measure(spec, spec);
+ }
+ }
+}
diff --git a/src/com/android/settings/biometrics/face/FaceSquareTextureView.java b/src/com/android/settings/biometrics/face/FaceSquareTextureView.java
new file mode 100644
index 0000000..ebbbc27
--- /dev/null
+++ b/src/com/android/settings/biometrics/face/FaceSquareTextureView.java
@@ -0,0 +1,52 @@
+/*
+ * 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.biometrics.face;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.TextureView;
+
+/**
+ * A square {@link TextureView}.
+ */
+public class FaceSquareTextureView extends TextureView {
+
+ public FaceSquareTextureView(Context context) {
+ this(context, null);
+ }
+
+ public FaceSquareTextureView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FaceSquareTextureView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (width < height) {
+ setMeasuredDimension(width, width);
+ } else {
+ setMeasuredDimension(height, height);
+ }
+ }
+}