From 06fd1374cf3e6e10fac6e012b9291756da2adc2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Mon, 6 Apr 2020 18:05:44 -0400 Subject: [PATCH] CameraActivity: make saving picture async, don't reencode Change-Id: I11a5c6ce5ccdc16ae44cb768c61df75d3b6e0002 --- .../java/cx/ring/tv/camera/CameraPreview.java | 36 ++++---- .../ring/tv/camera/CustomCameraActivity.java | 89 ++++++++++--------- .../conversation/TvConversationFragment.java | 4 +- .../app/src/main/res/layout/camerapicker.xml | 19 ++-- 4 files changed, 75 insertions(+), 73 deletions(-) diff --git a/ring-android/app/src/main/java/cx/ring/tv/camera/CameraPreview.java b/ring-android/app/src/main/java/cx/ring/tv/camera/CameraPreview.java index 1191fa32e..27f1a15b9 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/camera/CameraPreview.java +++ b/ring-android/app/src/main/java/cx/ring/tv/camera/CameraPreview.java @@ -24,21 +24,31 @@ import android.hardware.Camera; import android.view.SurfaceHolder; import android.view.SurfaceView; +import androidx.annotation.NonNull; + import java.io.IOException; -public class CameraPreview extends SurfaceView implements - SurfaceHolder.Callback { - private SurfaceHolder mSurfaceHolder; +public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private Camera mCamera; // Constructor that obtains context and camera @SuppressWarnings("deprecation") - public CameraPreview(Context context, Camera camera) { + public CameraPreview(Context context, @NonNull Camera camera) { super(context); - this.mCamera = camera; - this.mSurfaceHolder = this.getHolder(); - this.mSurfaceHolder.addCallback(this); - this.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + mCamera = camera; + getHolder().addCallback(this); + } + + public void stop() { + if (mCamera == null) + return; + try { + mCamera.stopPreview(); + } catch (Exception e) { + // intentionally left blank + } + mCamera.release(); + mCamera = null; } @Override @@ -48,22 +58,18 @@ public class CameraPreview extends SurfaceView implements try { mCamera.setPreviewDisplay(surfaceHolder); mCamera.startPreview(); - } catch (IOException e) { + } catch (Exception e) { // left blank for now } } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - if (mCamera == null) - return; - mCamera.stopPreview(); - mCamera.release(); + stop(); } @Override - public void surfaceChanged(SurfaceHolder surfaceHolder, int format, - int width, int height) { + public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { if (mCamera == null) return; try { diff --git a/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java b/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java index 02a0da152..d647c4306 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java +++ b/ring-android/app/src/main/java/cx/ring/tv/camera/CustomCameraActivity.java @@ -25,17 +25,15 @@ package cx.ring.tv.camera; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.hardware.Camera; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraManager; import android.media.CamcorderProfile; import android.media.MediaRecorder; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; +import android.util.Log; import android.view.View; import android.widget.FrameLayout; import android.widget.Toast; @@ -53,14 +51,18 @@ import butterknife.OnClick; import cx.ring.R; import cx.ring.utils.AndroidFileUtils; import cx.ring.utils.ContentUriHandler; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; public class CustomCameraActivity extends Activity { - public static final String TYPE_IMAGE = "image/png"; + private static final String TAG = "CustomCameraActivity"; + public static final String TYPE_IMAGE = "image/jpeg"; public static final String TYPE_VIDEO = "video"; - int cameraFront = -1; - int cameraBack = -1; - int currentCamera = 0; + private int cameraFront = -1; + private int cameraBack = -1; + private int currentCamera = 0; private MediaRecorder recorder; private boolean mRecording = false; @@ -75,53 +77,52 @@ public class CustomCameraActivity extends Activity { private Camera mCamera; private CameraPreview mCameraPreview; - private final Camera.PictureCallback mPicture = (input, camera) -> { - if (mRecording) { - releaseMediaRecorder(); - } - Intent intent = new Intent(); - int result = RESULT_OK; - Bitmap photo = BitmapFactory.decodeByteArray(input, 0, input.length); - try { - File tempFile = AndroidFileUtils.createImageFile(this); - try (OutputStream outStream = new FileOutputStream(tempFile)) { - photo.compress(Bitmap.CompressFormat.PNG, 100, outStream); - outStream.flush(); + private final Camera.PictureCallback mPicture = (input, camera) -> Single.fromCallable(() -> { + mCameraPreview.stop(); + File file = AndroidFileUtils.createImageFile(this); + try (OutputStream out = new FileOutputStream(file)) { + out.write(input); + out.flush(); } - intent.putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, tempFile)); - intent.setType(TYPE_IMAGE); - } catch (IOException e) { - e.printStackTrace(); - result = RESULT_CANCELED; - } - if (getParent() == null) { - setResult(result, intent); - } else { - getParent().setResult(result, intent); - } - finish(); - }; + return ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, file); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(uri -> { + setResult(RESULT_OK, new Intent() + .putExtra(MediaStore.EXTRA_OUTPUT, uri) + .setType(TYPE_IMAGE)); + finish(); + }, e -> { + Log.e(TAG, "Error saving picture", e); + setResult(RESULT_CANCELED); + finish(); + }); @OnClick(R.id.button_picture) public void takePicture() { + if (mRecording) + releaseMediaRecorder(); if (mCamera != null) { - mCamera.takePicture(null, null, mPicture); + mButtonPicture.setEnabled(false); + mButtonVideo.setVisibility(View.GONE); + try { + mCamera.takePicture(null, null, mPicture); + } catch (Exception e) { + Log.w(TAG, "Error taking picture", e); + } } } @OnClick(R.id.button_video) public void takeVideo() { - if (mRecording){ + if (mRecording) { releaseMediaRecorder(); - Intent intent = new Intent(); - Uri uri = ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, mVideoFile); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - intent.setType(TYPE_VIDEO); - if (getParent() == null) { - setResult(RESULT_OK, intent); - } else { - getParent().setResult(RESULT_OK, intent); - } + mCameraPreview.stop(); + Intent intent = new Intent() + .putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(this, ContentUriHandler.AUTHORITY_FILES, mVideoFile)) + .setType(TYPE_VIDEO); + setResult(RESULT_OK, intent); finish(); mButtonVideo.setImageResource(R.drawable.baseline_videocam_24); return; @@ -155,7 +156,7 @@ public class CustomCameraActivity extends Activity { } mCameraPreview = new CameraPreview(this, mCamera); FrameLayout preview = findViewById(R.id.camera_preview); - preview.addView(mCameraPreview); + preview.addView(mCameraPreview, 0); if (mActionVideo) { mButtonVideo.setVisibility(View.VISIBLE); diff --git a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java index 81f271dd7..4e8eb6141 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java @@ -187,8 +187,8 @@ public class TvConversationFragment extends BaseSupportFragment<TvConversationPr ImageButton video = view.findViewById(R.id.button_video); video.setOnClickListener(v -> { - Intent intent = new Intent(getActivity(), CustomCameraActivity.class); - intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); + Intent intent = new Intent(getActivity(), CustomCameraActivity.class) + .setAction(MediaStore.ACTION_VIDEO_CAPTURE); startActivityForResult(intent, REQUEST_CODE_PHOTO); }); diff --git a/ring-android/app/src/main/res/layout/camerapicker.xml b/ring-android/app/src/main/res/layout/camerapicker.xml index eb79dfb2a..6313c3309 100644 --- a/ring-android/app/src/main/res/layout/camerapicker.xml +++ b/ring-android/app/src/main/res/layout/camerapicker.xml @@ -1,19 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent"> - <FrameLayout - android:id="@+id/camera_preview" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - <LinearLayout - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:gravity="center" + android:layout_gravity="bottom|center_horizontal" android:orientation="horizontal"> <com.google.android.material.floatingactionbutton.FloatingActionButton @@ -33,12 +28,12 @@ android:layout_height="wrap_content" android:contentDescription="@string/action_call_decline" android:padding="16dp" + android:visibility="gone" app:backgroundTint="@color/green_500" app:rippleColor="@android:color/white" app:srcCompat="@drawable/baseline_videocam_24" - app:useCompatPadding="true" - android:visibility="gone" /> + app:useCompatPadding="true" /> </LinearLayout> -</RelativeLayout> \ No newline at end of file +</FrameLayout> \ No newline at end of file -- GitLab