From a4fe117a76c47b208287bdf6079a903d15776f5f Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Wed, 1 Jun 2016 14:14:15 +0900 Subject: [PATCH] Add takePicture Change-Id: I5a185904708305b6affeb148743582063ca76e44 --- demo/build.gradle | 3 +- demo/src/main/AndroidManifest.xml | 1 + .../android/cameraview/demo/MainActivity.java | 169 ++++++++++++++++-- demo/src/main/res/drawable/ic_camera.xml | 25 +++ .../main/res/layout-land/activity_main.xml | 20 ++- demo/src/main/res/layout/activity_main.xml | 21 ++- demo/src/main/res/layout/include_camera.xml | 22 +++ demo/src/main/res/layout/include_control.xml | 29 +++ demo/src/main/res/values/colors.xml | 4 +- demo/src/main/res/values/strings.xml | 7 +- demo/src/main/res/values/styles.xml | 2 + .../android/cameraview/CameraViewTest.java | 110 ++++++++---- .../android/cameraview/CameraViewImpl.java | 9 +- .../google/android/cameraview/SizeMap.java | 2 +- .../google/android/cameraview/Camera1.java | 64 ++++++- .../google/android/cameraview/CameraView.java | 49 ++++- 16 files changed, 458 insertions(+), 79 deletions(-) create mode 100644 demo/src/main/res/drawable/ic_camera.xml create mode 100644 demo/src/main/res/layout/include_camera.xml create mode 100644 demo/src/main/res/layout/include_control.xml diff --git a/demo/build.gradle b/demo/build.gradle index d81d16d8..17730712 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -26,6 +26,7 @@ android { versionCode 1 versionName '1.0' testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' + vectorDrawables.useSupportLibrary = true } buildTypes { release { @@ -37,7 +38,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:appcompat-v7:$supportLibraryVersion" + compile "com.android.support:design:$supportLibraryVersion" compile project(':library') // Tests diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 8bb04098..d5aeda42 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ + diff --git a/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java b/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java index e44ee0c5..0e6f33f6 100644 --- a/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java +++ b/demo/src/main/java/com/google/android/cameraview/demo/MainActivity.java @@ -22,25 +22,72 @@ import android.app.Dialog; import android.content.DialogInterface; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; +import android.view.View; import android.widget.Toast; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + + public class MainActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { private static final String TAG = "MainActivity"; + private static final int REQUEST_CAMERA_PERMISSION = 1; + private static final int REQUEST_STORAGE_PERMISSION = 2; + private static final String FRAGMENT_DIALOG = "dialog"; private CameraView mCameraView; + private Handler mBackgroundHandler; + + private View.OnClickListener mOnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.take_picture: + if (ContextCompat.checkSelfPermission(MainActivity.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED) { + if (mCameraView != null) { + mCameraView.takePicture(); + } + } else if (ActivityCompat.shouldShowRequestPermissionRationale( + MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + ConfirmationDialogFragment + .newInstance(R.string.storage_permission_confirmation, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + REQUEST_STORAGE_PERMISSION, + R.string.storage_permission_not_granted) + .show(getSupportFragmentManager(), FRAGMENT_DIALOG); + } else { + ActivityCompat.requestPermissions(MainActivity.this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + REQUEST_STORAGE_PERMISSION); + } + break; + } + } + }; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -49,6 +96,10 @@ protected void onCreate(Bundle savedInstanceState) { if (mCameraView != null) { mCameraView.addCallback(mCallback); } + FloatingActionButton takePicture = (FloatingActionButton) findViewById(R.id.take_picture); + if (takePicture != null) { + takePicture.setOnClickListener(mOnClickListener); + } } @Override @@ -59,7 +110,12 @@ protected void onResume() { mCameraView.start(); } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { - new ConfirmationDialogFragment().show(getSupportFragmentManager(), FRAGMENT_DIALOG); + ConfirmationDialogFragment + .newInstance(R.string.camera_permission_confirmation, + new String[]{Manifest.permission.CAMERA}, + REQUEST_CAMERA_PERMISSION, + R.string.camera_permission_not_granted) + .show(getSupportFragmentManager(), FRAGMENT_DIALOG); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); @@ -72,18 +128,52 @@ protected void onPause() { super.onPause(); } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mBackgroundHandler != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mBackgroundHandler.getLooper().quitSafely(); + } else { + mBackgroundHandler.getLooper().quit(); + } + mBackgroundHandler = null; + } + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == REQUEST_CAMERA_PERMISSION) { - if (permissions.length != 1 || grantResults.length != 1) { - throw new RuntimeException("Error on requesting permission."); - } - if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, R.string.permission_not_granted, Toast.LENGTH_SHORT).show(); + switch (requestCode) { + case REQUEST_CAMERA_PERMISSION: + if (permissions.length != 1 || grantResults.length != 1) { + throw new RuntimeException("Error on requesting camera permission."); + } + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, R.string.camera_permission_not_granted, + Toast.LENGTH_SHORT).show(); + } // No need to start camera here; it is handled by onResume - } + break; + case REQUEST_STORAGE_PERMISSION: + if (permissions.length != 1 || grantResults.length != 1) { + throw new RuntimeException("Error on requesting storage permission."); + } + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, R.string.storage_permission_not_granted, + Toast.LENGTH_SHORT).show(); + } + break; + } + } + + private Handler getBackgroundHandler() { + if (mBackgroundHandler == null) { + HandlerThread thread = new HandlerThread("background"); + thread.start(); + mBackgroundHandler = new Handler(thread.getLooper()); } + return mBackgroundHandler; } private CameraView.Callback mCallback @@ -99,29 +189,84 @@ public void onCameraClosed(CameraView cameraView) { Log.d(TAG, "onCameraClosed"); } + @Override + public void onPictureTaken(CameraView cameraView, final byte[] data) { + Log.d(TAG, "onPictureTaken " + data.length); + Toast.makeText(cameraView.getContext(), R.string.picture_taken, Toast.LENGTH_SHORT) + .show(); + getBackgroundHandler().post(new Runnable() { + @Override + public void run() { + // This demo app saves the taken picture to a constant file. + // $ adb pull /sdcard/Android/data/com.google.android.cameraview.demo/files/Pictures/picture.jpg + File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), + "picture.jpg"); + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(data); + os.close(); + } catch (IOException e) { + Log.w(TAG, "Cannot write to " + file, e); + } finally { + if (os != null) { + try { + os.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + }); + } + }; public static class ConfirmationDialogFragment extends DialogFragment { + private static final String ARG_MESSAGE = "message"; + private static final String ARG_PERMISSIONS = "permissions"; + private static final String ARG_REQUEST_CODE = "request_code"; + private static final String ARG_NOT_GRANTED_MESSAGE = "not_granted_message"; + + public static ConfirmationDialogFragment newInstance(@StringRes int message, + String[] permissions, int requestCode, + @StringRes int notGrantedMessage) { + ConfirmationDialogFragment fragment = new ConfirmationDialogFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_MESSAGE, message); + args.putStringArray(ARG_PERMISSIONS, permissions); + args.putInt(ARG_REQUEST_CODE, requestCode); + args.putInt(ARG_NOT_GRANTED_MESSAGE, notGrantedMessage); + fragment.setArguments(args); + return fragment; + } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + final Bundle args = getArguments(); return new AlertDialog.Builder(getActivity()) - .setMessage(R.string.permission_confirmation) + .setMessage(args.getInt(ARG_MESSAGE)) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { + String[] permissions = args.getStringArray(ARG_PERMISSIONS); + if (permissions == null) { + throw new IllegalArgumentException(); + } ActivityCompat.requestPermissions(getActivity(), - new String[]{Manifest.permission.CAMERA}, - REQUEST_CAMERA_PERMISSION); + permissions, args.getInt(ARG_REQUEST_CODE)); } }) .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - Toast.makeText(getActivity(), R.string.permission_not_granted, + Toast.makeText(getActivity(), + args.getInt(ARG_NOT_GRANTED_MESSAGE), Toast.LENGTH_SHORT).show(); } }) diff --git a/demo/src/main/res/drawable/ic_camera.xml b/demo/src/main/res/drawable/ic_camera.xml new file mode 100644 index 00000000..14584648 --- /dev/null +++ b/demo/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/demo/src/main/res/layout-land/activity_main.xml b/demo/src/main/res/layout-land/activity_main.xml index 04f4d95b..af24ba0a 100644 --- a/demo/src/main/res/layout-land/activity_main.xml +++ b/demo/src/main/res/layout-land/activity_main.xml @@ -11,20 +11,24 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + + android:layout_weight="1"/> - + diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index c8c3a3e7..7cebfbd7 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -11,20 +11,23 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + android:layout_height="wrap_content"/> - + + + diff --git a/demo/src/main/res/layout/include_camera.xml b/demo/src/main/res/layout/include_camera.xml new file mode 100644 index 00000000..3ea2090c --- /dev/null +++ b/demo/src/main/res/layout/include_camera.xml @@ -0,0 +1,22 @@ + + + diff --git a/demo/src/main/res/layout/include_control.xml b/demo/src/main/res/layout/include_control.xml new file mode 100644 index 00000000..8638dd78 --- /dev/null +++ b/demo/src/main/res/layout/include_control.xml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/demo/src/main/res/values/colors.xml b/demo/src/main/res/values/colors.xml index 6ee01f97..9bbf0348 100644 --- a/demo/src/main/res/values/colors.xml +++ b/demo/src/main/res/values/colors.xml @@ -14,5 +14,7 @@ #3F51B5 #303F9F - #FF4081 + #4CAF50 + #212121 + #727272 diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index dab9081e..c799a82f 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -13,6 +13,9 @@ --> CameraView Demo - This app demonstrates the usage of CameraView. In order to do that, it needs permission to access camera. - Camera app cannot do anything without camera permission. + This app demonstrates the usage of CameraView. In order to do that, it needs permission to access camera. + Camera app cannot do anything without camera permission. + This app saves taken photos to external storage. + The app does not have permission to save a photo. + Picture taken diff --git a/demo/src/main/res/values/styles.xml b/demo/src/main/res/values/styles.xml index e4ea791d..3bc78ff2 100644 --- a/demo/src/main/res/values/styles.xml +++ b/demo/src/main/res/values/styles.xml @@ -19,6 +19,8 @@ @color/primary @color/primaryDark @color/accent + @color/primaryText + @color/secondaryText diff --git a/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java b/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java index 64eee88f..32da3396 100644 --- a/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java +++ b/library/src/androidTest/java/com/google/android/cameraview/CameraViewTest.java @@ -100,23 +100,6 @@ public void preview_isShowing() throws Exception { .check(showingPreview()); } - @Test - public void getSupportedPreviewSizes() { - onView(withId(R.id.camera)) - .check(new ViewAssertion() { - @Override - public void check(View view, NoMatchingViewException noViewFoundException) { - CameraView cameraView = (CameraView) view; - SizeMap map = cameraView.getSupportedPreviewSizes(); - assertThat(map.ratios().size(), is(greaterThanOrEqualTo(1))); - for (AspectRatio ratio : map.ratios()) { - final Set sizes = map.sizes(ratio); - assertThat(sizes.size(), is(is(greaterThanOrEqualTo(1)))); - } - } - }); - } - @Test public void testAspectRatio() { onView(withId(R.id.camera)) @@ -126,19 +109,6 @@ public void check(View view, NoMatchingViewException noViewFoundException) { CameraView cameraView = (CameraView) view; AspectRatio ratio = cameraView.getAspectRatio(); assertThat(ratio, is(notNullValue())); - SizeMap map = cameraView.getSupportedPreviewSizes(); - assertThat(map.ratios(), hasItem(ratio)); - AspectRatio otherRatio = null; - for (AspectRatio r : map.ratios()) { - if (!r.equals(ratio)) { - otherRatio = r; - break; - } - } - if (otherRatio != null) { - cameraView.setAspectRatio(otherRatio); - assertThat(cameraView.getAspectRatio(), is(equalTo(otherRatio))); - } } }); } @@ -223,6 +193,30 @@ public void check(View view, NoMatchingViewException noViewFoundException) { .check(showingPreview()); } + @Test + public void testTakePicture() throws Exception { + TakePictureIdlingResource resource = new TakePictureIdlingResource( + (CameraView) rule.getActivity().findViewById(R.id.camera)); + onView(withId(R.id.camera)) + .perform(new AnythingAction("take picture") { + @Override + public void perform(UiController uiController, View view) { + CameraView cameraView = (CameraView) view; + cameraView.takePicture(); + } + }); + try { + registerIdlingResources(resource); + onView(withId(R.id.camera)) + .perform(waitFor(1000)) + .check(showingPreview()); + assertThat("Didn't receive valid JPEG data.", resource.receivedValidJpeg(), is(true)); + } finally { + unregisterIdlingResources(resource); + resource.close(); + } + } + private static ViewAction waitFor(final long ms) { return new AnythingAction("wait") { @Override @@ -307,6 +301,62 @@ public void registerIdleTransitionCallback(ResourceCallback callback) { } + private static class TakePictureIdlingResource implements IdlingResource, Closeable { + + private final CameraView.Callback mCallback + = new CameraView.Callback() { + @Override + public void onPictureTaken(CameraView cameraView, byte[] data) { + if (!mIsIdleNow) { + mIsIdleNow = true; + mValidJpeg = data.length > 2 && + data[0] == (byte) 0xFF && data[1] == (byte) 0xD8; + if (mResourceCallback != null) { + mResourceCallback.onTransitionToIdle(); + } + } + } + }; + + private final CameraView mCameraView; + + private ResourceCallback mResourceCallback; + + private boolean mIsIdleNow; + + private boolean mValidJpeg; + + public TakePictureIdlingResource(CameraView cameraView) { + mCameraView = cameraView; + mCameraView.addCallback(mCallback); + } + + @Override + public void close() throws IOException { + mCameraView.removeCallback(mCallback); + } + + @Override + public String getName() { + return TakePictureIdlingResource.class.getSimpleName(); + } + + @Override + public boolean isIdleNow() { + return mIsIdleNow; + } + + @Override + public void registerIdleTransitionCallback(ResourceCallback callback) { + mResourceCallback = callback; + } + + public boolean receivedValidJpeg() { + return mValidJpeg; + } + + } + private static abstract class AnythingAction implements ViewAction { private final String mDescription; diff --git a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java index 8c765daf..5fb9fa25 100644 --- a/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java +++ b/library/src/main/base/com/google/android/cameraview/CameraViewImpl.java @@ -32,8 +32,6 @@ public CameraViewImpl(Callback callback) { abstract void stop(); - abstract SizeMap getSupportedPreviewSizes(); - abstract boolean isCameraOpened(); abstract void setAspectRatio(AspectRatio ratio); @@ -50,11 +48,18 @@ public CameraViewImpl(Callback callback) { abstract int getFacing(); + abstract void takePicture(); + + abstract void setOrientation(int orientation); + interface Callback { void onCameraOpened(); void onCameraClosed(); + void onPictureTaken(byte[] data); + } + } diff --git a/library/src/main/base/com/google/android/cameraview/SizeMap.java b/library/src/main/base/com/google/android/cameraview/SizeMap.java index 8f90a4c6..39f70612 100644 --- a/library/src/main/base/com/google/android/cameraview/SizeMap.java +++ b/library/src/main/base/com/google/android/cameraview/SizeMap.java @@ -25,7 +25,7 @@ /** * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s. */ -public class SizeMap { +class SizeMap { private final ArrayMap> mRatios = new ArrayMap<>(); diff --git a/library/src/main/camera1/com/google/android/cameraview/Camera1.java b/library/src/main/camera1/com/google/android/cameraview/Camera1.java index d5b0567a..2289d120 100644 --- a/library/src/main/camera1/com/google/android/cameraview/Camera1.java +++ b/library/src/main/camera1/com/google/android/cameraview/Camera1.java @@ -50,6 +50,8 @@ class Camera1 extends CameraViewImpl { private final SizeMap mPreviewSizes = new SizeMap(); + private final SizeMap mPictureSizes = new SizeMap(); + private AspectRatio mAspectRatio; private int mDisplayOrientation; @@ -60,6 +62,8 @@ class Camera1 extends CameraViewImpl { private int mFacing; + private int mOrientation; + private static class PreviewInfo { SurfaceTexture surface; int width; @@ -79,7 +83,7 @@ private void reconfigurePreview(SurfaceTexture surface, int width, int height) { mPreviewInfo.configure(surface, width, height); if (mCamera != null) { setUpPreview(); - adjustPreviewSize(); + adjustCameraParameters(); } } @@ -142,11 +146,6 @@ private void setUpPreview() { } } - @Override - SizeMap getSupportedPreviewSizes() { - return mPreviewSizes; - } - @Override boolean isCameraOpened() { return mCamera != null; @@ -163,7 +162,7 @@ void setAspectRatio(AspectRatio ratio) { throw new UnsupportedOperationException(ratio + " is not supported"); } else { mAspectRatio = ratio; - adjustPreviewSize(); + adjustCameraParameters(); } } } @@ -210,6 +209,34 @@ int getFacing() { return mFacing; } + @Override + void takePicture() { + if (!isCameraOpened()) { + throw new IllegalStateException("Camera is not ready. Call start() before takePicture()."); + } + mCamera.takePicture(null, null, null, new Camera.PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + mCallback.onPictureTaken(data); + camera.startPreview(); + } + }); + } + + @Override + void setOrientation(int orientation) { + if (mOrientation != orientation) { + mOrientation = orientation; + if (isCameraOpened()) { + calcRotation(mCameraParameters); + mCamera.setParameters(mCameraParameters); + + mDisplayOrientation = calcDisplayOrientation(); + mCamera.setDisplayOrientation(mDisplayOrientation); + } + } + } + /** * This rewrites {@link #mCameraId} and {@link #mCameraInfo}. */ @@ -235,11 +262,16 @@ private void openCamera() { for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) { mPreviewSizes.add(new Size(size.width, size.height)); } + // Supported picture sizes; + mPictureSizes.clear(); + for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) { + mPictureSizes.add(new Size(size.width, size.height)); + } // AspectRatio if (mAspectRatio == null) { mAspectRatio = DEFAULT_ASPECT_RATIO; } - adjustPreviewSize(); + adjustCameraParameters(); // Display orientation mDisplayOrientation = calcDisplayOrientation(); mCamera.setDisplayOrientation(mDisplayOrientation); @@ -257,7 +289,7 @@ private AspectRatio chooseAspectRatio() { return r; } - private void adjustPreviewSize() { + private void adjustCameraParameters() { final SortedSet sizes = mPreviewSizes.sizes(mAspectRatio); if (sizes == null) { // Not supported mAspectRatio = chooseAspectRatio(); @@ -265,10 +297,14 @@ private void adjustPreviewSize() { Size size = chooseOptimalSize(sizes); final Camera.Size currentSize = mCameraParameters.getPictureSize(); if (currentSize.width != size.getWidth() || currentSize.height != size.getHeight()) { + // Largest picture size in this ratio + final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last(); if (mShowingPreview) { mCamera.stopPreview(); } mCameraParameters.setPreviewSize(size.getWidth(), size.getHeight()); + mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); + calcRotation(mCameraParameters); mCameraParameters.setFocusMode(Camera1Constants.convertFocusMode(mFocusMode)); mCamera.setParameters(mCameraParameters); if (mShowingPreview) { @@ -277,6 +313,16 @@ private void adjustPreviewSize() { } } + private void calcRotation(Camera.Parameters parameters) { + int rotation; + if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + rotation = (mCameraInfo.orientation - mOrientation + 360) % 360; + } else { // back-facing camera + rotation = (mCameraInfo.orientation + mOrientation) % 360; + } + parameters.setRotation(rotation); + } + @SuppressWarnings("SuspiciousNameCombination") private Size chooseOptimalSize(SortedSet sizes) { if (mPreviewInfo.width == 0 || mPreviewInfo.height == 0) { // Not yet laid out diff --git a/library/src/main/java/com/google/android/cameraview/CameraView.java b/library/src/main/java/com/google/android/cameraview/CameraView.java index 30d682c3..72996d83 100644 --- a/library/src/main/java/com/google/android/cameraview/CameraView.java +++ b/library/src/main/java/com/google/android/cameraview/CameraView.java @@ -24,6 +24,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.AttributeSet; +import android.view.OrientationEventListener; import android.view.TextureView; import android.widget.FrameLayout; @@ -63,6 +64,8 @@ public class CameraView extends FrameLayout { private TextureView mTextureView; + private OrientationEventListener mOrientationEventListener; + public CameraView(Context context) { this(context, null); } @@ -92,6 +95,22 @@ public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { setFocusMode(a.getInt(R.styleable.CameraView_focusMode, FOCUS_MODE_OFF)); setFacing(a.getInt(R.styleable.CameraView_facing, FACING_BACK)); a.recycle(); + // Orientation listener + mOrientationEventListener = new OrientationEventListener(context) { + private int mLastOrientation; + @Override + public void onOrientationChanged(int orientation) { + if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { + return; + } + // Round the value to one of 0, 90, 180, and 270 + orientation = ((orientation + 45) / 90 * 90) % 360; + if (mLastOrientation != orientation) { + mLastOrientation = orientation; + mImpl.setOrientation(orientation); + } + } + }; } @Override @@ -156,6 +175,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { * {@link Activity#onResume()}. */ public void start() { + mOrientationEventListener.enable(); mImpl.start(); } @@ -165,10 +185,7 @@ public void start() { */ public void stop() { mImpl.stop(); - } - - public SizeMap getSupportedPreviewSizes() { - return mImpl.getSupportedPreviewSizes(); + mOrientationEventListener.disable(); } /** @@ -282,6 +299,14 @@ public int getFacing() { return mImpl.getFacing(); } + /** + * Take a picture. The result will be returned to + * {@link Callback#onPictureTaken(CameraView, byte[])}. + */ + public void takePicture() { + mImpl.takePicture(); + } + private class CallbackBridge implements CameraViewImpl.Callback { private final ArrayList mCallbacks = new ArrayList<>(); @@ -314,6 +339,13 @@ public void onCameraClosed() { } } + @Override + public void onPictureTaken(byte[] data) { + for (Callback callback : mCallbacks) { + callback.onPictureTaken(CameraView.this, data); + } + } + public void reserveRequestLayoutOnOpen() { mRequestLayoutOnOpen = true; } @@ -340,6 +372,15 @@ public void onCameraOpened(CameraView cameraView) { */ public void onCameraClosed(CameraView cameraView) { } + + /** + * Called when a picture is taken. + * + * @param cameraView The associated {@link CameraView}. + * @param data JPEG data. + */ + public void onPictureTaken(CameraView cameraView, byte[] data) { + } } }