[go: nahoru, domu]

blob: 024e8b250f1d36a524501e59da978bbcfd600573 [file] [log] [blame]
Trevor McGuirec63cd732019-02-06 13:12:50 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Franklin Wu393daa92019-02-20 11:05:19 -080017package androidx.camera.integration.core;
Trevor McGuirec63cd732019-02-06 13:12:50 -080018
husaynhakeem661b7e92019-11-22 16:39:36 -080019import static androidx.camera.core.ImageCapture.FLASH_MODE_AUTO;
20import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
21import static androidx.camera.core.ImageCapture.FLASH_MODE_ON;
22
Xi Zhangec5384d2020-01-27 15:01:28 -080023import android.content.ContentValues;
Trevor McGuirec63cd732019-02-06 13:12:50 -080024import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.graphics.Color;
28import android.graphics.Matrix;
29import android.graphics.SurfaceTexture;
30import android.os.Bundle;
31import android.os.Environment;
Trevor McGuirec63cd732019-02-06 13:12:50 -080032import android.os.StrictMode;
Scott Nienfb8affc2019-06-14 16:41:18 +080033import android.os.SystemClock;
Xi Zhangec5384d2020-01-27 15:01:28 -080034import android.provider.MediaStore;
Trevor McGuirec63cd732019-02-06 13:12:50 -080035import android.util.Log;
36import android.util.Size;
37import android.view.Surface;
38import android.view.TextureView;
Scott Nien3d89b902019-02-21 14:42:18 +080039import android.view.View;
Trevor McGuirec63cd732019-02-06 13:12:50 -080040import android.widget.Button;
Wenhung Teng7b61dd02019-02-25 10:00:03 -080041import android.widget.ImageButton;
Trevor McGuirec63cd732019-02-06 13:12:50 -080042import android.widget.TextView;
43import android.widget.Toast;
Trevor McGuire470a44f2019-02-11 13:19:37 -080044
leo huangdc156bc2019-07-25 13:07:12 +080045import androidx.annotation.NonNull;
WenHung_Teng60fffca2019-11-05 14:27:35 +080046import androidx.annotation.Nullable;
wegin lee7c987922019-02-28 10:05:25 -080047import androidx.annotation.VisibleForTesting;
Franklin Wu87eadea2019-02-15 14:01:24 -080048import androidx.appcompat.app.AppCompatActivity;
WenHung_Teng60fffca2019-11-05 14:27:35 +080049import androidx.camera.core.Camera;
leo huang6193f932019-11-20 17:43:52 +080050import androidx.camera.core.CameraControl;
51import androidx.camera.core.CameraInfo;
Trevor McGuirea743e1f2019-10-28 10:42:09 -070052import androidx.camera.core.CameraSelector;
leo huang3a95a1d2019-03-28 13:35:17 +080053import androidx.camera.core.ImageAnalysis;
leo huang3a95a1d2019-03-28 13:35:17 +080054import androidx.camera.core.ImageCapture;
husaynhakeem175db562020-01-17 11:26:51 -080055import androidx.camera.core.ImageCaptureException;
leo huang3a95a1d2019-03-28 13:35:17 +080056import androidx.camera.core.Preview;
Trevor McGuire049c9182020-01-31 11:15:33 -080057import androidx.camera.core.SurfaceRequest;
leo huang6193f932019-11-20 17:43:52 +080058import androidx.camera.core.TorchState;
leo huang3a95a1d2019-03-28 13:35:17 +080059import androidx.camera.core.UseCase;
60import androidx.camera.core.VideoCapture;
charcoalchend67a03d2019-12-17 19:48:09 +080061import androidx.camera.core.impl.VideoCaptureConfig;
Sergey3f890dd2019-12-30 19:51:47 +040062import androidx.camera.core.impl.utils.executor.CameraXExecutors;
Trevor McGuirecf8e22b2019-11-14 22:12:36 -080063import androidx.camera.lifecycle.ProcessCameraProvider;
Franklin Wu76c2f742019-11-07 11:01:11 -080064import androidx.concurrent.futures.CallbackToFutureAdapter;
Franklin Wu393daa92019-02-20 11:05:19 -080065import androidx.core.app.ActivityCompat;
Franklin Wu87eadea2019-02-15 14:01:24 -080066import androidx.core.content.ContextCompat;
Franklin Wu87eadea2019-02-15 14:01:24 -080067import androidx.lifecycle.MutableLiveData;
Scott Nien3d89b902019-02-21 14:42:18 +080068import androidx.lifecycle.Observer;
fungja5e3d86b2019-09-13 11:31:54 -070069import androidx.test.espresso.IdlingResource;
wegin lee7c987922019-02-28 10:05:25 -080070import androidx.test.espresso.idling.CountingIdlingResource;
Trevor McGuire470a44f2019-02-11 13:19:37 -080071
Trevor McGuirecf8e22b2019-11-14 22:12:36 -080072import com.google.common.base.Preconditions;
73import com.google.common.util.concurrent.FutureCallback;
74import com.google.common.util.concurrent.Futures;
Franklin Wu76c2f742019-11-07 11:01:11 -080075import com.google.common.util.concurrent.ListenableFuture;
76
Xi Zhang6f28a572020-02-10 10:50:01 -080077import java.io.File;
Trevor McGuirec63cd732019-02-06 13:12:50 -080078import java.math.BigDecimal;
Trevor McGuirecf8e22b2019-11-14 22:12:36 -080079import java.util.List;
Trevor McGuirec63cd732019-02-06 13:12:50 -080080import java.util.concurrent.atomic.AtomicLong;
Trevor McGuirec63cd732019-02-06 13:12:50 -080081
82/**
83 * An activity with four use cases: (1) view finder, (2) image capture, (3) image analysis, (4)
84 * video capture.
85 *
86 * <p>All four use cases are created with CameraX and tied to the activity's lifecycle. CameraX
87 * automatically connects and disconnects the use cases from the camera in response to changes in
88 * the activity's lifecycle. Therefore, the use cases function properly when the app is paused and
89 * resumed and when the device is rotated. The complex interactions between the camera and these
90 * lifecycle events are handled internally by CameraX.
91 */
92public class CameraXActivity extends AppCompatActivity
AL Ho8883e6e2019-11-06 18:44:14 +080093 implements ActivityCompat.OnRequestPermissionsResultCallback {
Trevor McGuire470a44f2019-02-11 13:19:37 -080094 private static final String TAG = "CameraXActivity";
95 private static final int PERMISSIONS_REQUEST_CODE = 42;
96 // Possible values for this intent key: "backward" or "forward".
97 private static final String INTENT_EXTRA_CAMERA_DIRECTION = "camera_direction";
Trevor McGuirea743e1f2019-10-28 10:42:09 -070098 static final CameraSelector BACK_SELECTOR =
husaynhakeem661b7e92019-11-22 16:39:36 -080099 new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
Trevor McGuirea743e1f2019-10-28 10:42:09 -0700100 static final CameraSelector FRONT_SELECTOR =
husaynhakeem661b7e92019-11-22 16:39:36 -0800101 new CameraSelector.Builder().requireLensFacing(
102 CameraSelector.LENS_FACING_FRONT).build();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800103
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800104 private boolean mPermissionsGranted = false;
105 private CallbackToFutureAdapter.Completer<Boolean> mPermissionsCompleter;
weginlee8bfd0642019-02-14 15:15:00 +0800106 private final AtomicLong mImageAnalysisFrameCount = new AtomicLong(0);
fungjaf3530f72019-09-17 14:18:19 -0700107 private final AtomicLong mPreviewFrameCount = new AtomicLong(0);
weginlee8bfd0642019-02-14 15:15:00 +0800108 private final MutableLiveData<String> mImageAnalysisResult = new MutableLiveData<>();
109 private VideoFileSaver mVideoFileSaver;
Trevor McGuirea743e1f2019-10-28 10:42:09 -0700110 /** The camera to use */
111 CameraSelector mCurrentCameraSelector = BACK_SELECTOR;
husaynhakeem661b7e92019-11-22 16:39:36 -0800112 @CameraSelector.LensFacing
113 int mCurrentCameraLensFacing = CameraSelector.LENS_FACING_BACK;
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800114 ProcessCameraProvider mCameraProvider;
Trevor McGuirec63cd732019-02-06 13:12:50 -0800115
Trevor McGuire470a44f2019-02-11 13:19:37 -0800116 // TODO: Move the analysis processing, capture processing to separate threads, so
117 // there is smaller impact on the preview.
weginlee8bfd0642019-02-14 15:15:00 +0800118 private String mCurrentCameraDirection = "BACKWARD";
leo huang3a95a1d2019-03-28 13:35:17 +0800119 private Preview mPreview;
120 private ImageAnalysis mImageAnalysis;
121 private ImageCapture mImageCapture;
122 private VideoCapture mVideoCapture;
leo huang6193f932019-11-20 17:43:52 +0800123 private Camera mCamera;
husaynhakeem352a0872019-11-19 15:19:57 -0800124 @ImageCapture.CaptureMode
husaynhakeem661b7e92019-11-22 16:39:36 -0800125 private int mCaptureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY;
Xi Zhanga3762c52019-10-22 08:09:32 -0700126 // Synthetic Accessor
127 @SuppressWarnings("WeakerAccess")
128 TextureView mTextureView;
Scott Nien80b49e72019-12-11 23:05:18 +0800129 @SuppressWarnings("WeakerAccess")
130 SurfaceTexture mSurfaceTexture;
131 @SuppressWarnings("WeakerAccess")
132 private Size mResolution;
133 @SuppressWarnings("WeakerAccess")
134 ListenableFuture<Void> mSurfaceReleaseFuture;
135 @SuppressWarnings("WeakerAccess")
Trevor McGuire049c9182020-01-31 11:15:33 -0800136 SurfaceRequest mSurfaceRequest;
Trevor McGuirec63cd732019-02-06 13:12:50 -0800137
fungjaf3530f72019-09-17 14:18:19 -0700138
wegin lee7c987922019-02-28 10:05:25 -0800139 // Espresso testing variables
fungjaf3530f72019-09-17 14:18:19 -0700140 private final CountingIdlingResource mViewIdlingResource = new CountingIdlingResource("view");
weginlee7e7afa02019-03-05 17:18:06 +0800141 private static final int FRAMES_UNTIL_VIEW_IS_READY = 5;
fungjaf3530f72019-09-17 14:18:19 -0700142 private final CountingIdlingResource mAnalysisIdlingResource =
weginlee7e7afa02019-03-05 17:18:06 +0800143 new CountingIdlingResource("analysis");
fungjaf3530f72019-09-17 14:18:19 -0700144 private final CountingIdlingResource mImageSavedIdlingResource =
weginlee7e7afa02019-03-05 17:18:06 +0800145 new CountingIdlingResource("imagesaved");
Xi Zhangc52e4f32019-09-16 10:52:27 +0800146
fungja5e3d86b2019-09-13 11:31:54 -0700147 /**
fungjaf3530f72019-09-17 14:18:19 -0700148 * Retrieve idling resource that waits for image received by analyzer).
husaynhakeem175db562020-01-17 11:26:51 -0800149 *
fungjaf3530f72019-09-17 14:18:19 -0700150 * @return idline resource for image capture
151 */
152 @VisibleForTesting
153 @NonNull
154 public IdlingResource getAnalysisIdlingResource() {
155 return mAnalysisIdlingResource;
156 }
157
158 /**
159 * Retrieve idling resource that waits view to get texture update.
husaynhakeem175db562020-01-17 11:26:51 -0800160 *
fungjaf3530f72019-09-17 14:18:19 -0700161 * @return idline resource for image capture
162 */
163 @VisibleForTesting
164 @NonNull
165 public IdlingResource getViewIdlingResource() {
166 return mViewIdlingResource;
167 }
168
169 /**
fungja5e3d86b2019-09-13 11:31:54 -0700170 * Retrieve idling resource that waits for capture to complete (save or error).
Xi Zhangc52e4f32019-09-16 10:52:27 +0800171 *
fungja5e3d86b2019-09-13 11:31:54 -0700172 * @return idline resource for image capture
173 */
174 @VisibleForTesting
175 @NonNull
176 public IdlingResource getImageSavedIdlingResource() {
177 return mImageSavedIdlingResource;
178 }
wegin lee7c987922019-02-28 10:05:25 -0800179
Trevor McGuire470a44f2019-02-11 13:19:37 -0800180 /**
fungja8875bfd2020-01-30 15:18:40 -0800181 * Retrieve idling resource that waits for view to display frames before proceeding.
husaynhakeem175db562020-01-17 11:26:51 -0800182 */
weginlee692104b2019-12-11 20:17:36 +0800183 @VisibleForTesting
184 public void resetViewIdlingResource() {
185 mPreviewFrameCount.set(0);
186 // Make the view idling resource non-idle, until required framecount achieved.
187 mViewIdlingResource.increment();
188 }
189
190
191 /**
Trevor McGuire470a44f2019-02-11 13:19:37 -0800192 * Creates a view finder use case.
193 *
194 * <p>This use case observes a {@link SurfaceTexture}. The texture is connected to a {@link
195 * TextureView} to display a camera preview.
196 */
leo huang3a95a1d2019-03-28 13:35:17 +0800197 private void createPreview() {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800198 Button button = this.findViewById(R.id.PreviewToggle);
199 button.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800200 enablePreview();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800201
Trevor McGuire470a44f2019-02-11 13:19:37 -0800202 button.setOnClickListener(
Scott Nien3d89b902019-02-21 14:42:18 +0800203 new View.OnClickListener() {
204 @Override
205 public void onClick(View view) {
206 Button buttonView = (Button) view;
leo huang3a95a1d2019-03-28 13:35:17 +0800207 if (mPreview != null) {
Scott Nien3d89b902019-02-21 14:42:18 +0800208 // Remove the use case
209 buttonView.setBackgroundColor(Color.RED);
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800210 mCameraProvider.unbind(mPreview);
leo huang3a95a1d2019-03-28 13:35:17 +0800211 mPreview = null;
Scott Nien3d89b902019-02-21 14:42:18 +0800212 } else {
213 // Add the use case
214 buttonView.setBackgroundColor(Color.LTGRAY);
Franklin Wu76c2f742019-11-07 11:01:11 -0800215 enablePreview();
Scott Nien3d89b902019-02-21 14:42:18 +0800216 }
Trevor McGuire470a44f2019-02-11 13:19:37 -0800217 }
218 });
Trevor McGuirec63cd732019-02-06 13:12:50 -0800219
leo huang3a95a1d2019-03-28 13:35:17 +0800220 Log.i(TAG, "Got UseCase: " + mPreview);
Trevor McGuire470a44f2019-02-11 13:19:37 -0800221 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800222
leo huang3a95a1d2019-03-28 13:35:17 +0800223 void enablePreview() {
husaynhakeem687d79a2019-11-13 16:33:20 -0800224 mPreview = new Preview.Builder()
225 .setTargetName("Preview")
226 .build();
Xi Zhangc52e4f32019-09-16 10:52:27 +0800227 Log.d(TAG, "enablePreview");
Xi Zhang9d848662019-10-08 13:00:40 -0700228
Trevor McGuire7ded9472020-01-29 16:26:14 -0800229 mPreview.setSurfaceProvider(
Trevor McGuire049c9182020-01-31 11:15:33 -0800230 (surfaceRequest) -> {
231 mResolution = surfaceRequest.getResolution();
Scott Nien80b49e72019-12-11 23:05:18 +0800232
Trevor McGuire049c9182020-01-31 11:15:33 -0800233 if (mSurfaceRequest != null) {
234 mSurfaceRequest.setWillNotComplete();
235 }
236 mSurfaceRequest = surfaceRequest;
237 mSurfaceRequest.addRequestCancellationListener(
238 ContextCompat.getMainExecutor(mTextureView.getContext()), () -> {
239 if (mSurfaceRequest != null && mSurfaceRequest == surfaceRequest) {
240 mSurfaceRequest = null;
Scott Nien80b49e72019-12-11 23:05:18 +0800241 mSurfaceReleaseFuture = null;
Trevor McGuire049c9182020-01-31 11:15:33 -0800242 }
Scott Nien80b49e72019-12-11 23:05:18 +0800243 });
Trevor McGuire049c9182020-01-31 11:15:33 -0800244 tryToProvidePreviewSurface();
Scott Nien80b49e72019-12-11 23:05:18 +0800245 });
246
weginlee692104b2019-12-11 20:17:36 +0800247 resetViewIdlingResource();
weginlee7e7afa02019-03-05 17:18:06 +0800248
WenHung_Teng60fffca2019-11-05 14:27:35 +0800249 if (bindToLifecycleSafely(mPreview, R.id.PreviewToggle) == null) {
leo huang3a95a1d2019-03-28 13:35:17 +0800250 mPreview = null;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800251 return;
252 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800253 }
254
AL Ho8883e6e2019-11-06 18:44:14 +0800255 void transformPreview(@NonNull Size resolution) {
256 if (resolution.getWidth() == 0 || resolution.getHeight() == 0) {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800257 return;
258 }
259
Xi Zhang9d848662019-10-08 13:00:40 -0700260 if (mTextureView.getWidth() == 0 || mTextureView.getHeight() == 0) {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800261 return;
262 }
263
264 Matrix matrix = new Matrix();
265
Xi Zhang9d848662019-10-08 13:00:40 -0700266 int left = mTextureView.getLeft();
267 int right = mTextureView.getRight();
268 int top = mTextureView.getTop();
269 int bottom = mTextureView.getBottom();
Trevor McGuire470a44f2019-02-11 13:19:37 -0800270
leo huang3a95a1d2019-03-28 13:35:17 +0800271 // Compute the preview ui size based on the available width, height, and ui orientation.
Trevor McGuire470a44f2019-02-11 13:19:37 -0800272 int viewWidth = (right - left);
273 int viewHeight = (bottom - top);
274
275 int displayRotation = getDisplayRotation();
276 Size scaled =
leo huang3a95a1d2019-03-28 13:35:17 +0800277 calculatePreviewViewDimens(
AL Ho8883e6e2019-11-06 18:44:14 +0800278 resolution, viewWidth, viewHeight, displayRotation);
Trevor McGuire470a44f2019-02-11 13:19:37 -0800279
280 // Compute the center of the view.
281 int centerX = viewWidth / 2;
282 int centerY = viewHeight / 2;
283
284 // Do corresponding rotation to correct the preview direction
285 matrix.postRotate(-getDisplayRotation(), centerX, centerY);
286
287 // Compute the scale value for center crop mode
288 float xScale = scaled.getWidth() / (float) viewWidth;
289 float yScale = scaled.getHeight() / (float) viewHeight;
290
291 if (getDisplayRotation() == 90 || getDisplayRotation() == 270) {
292 xScale = scaled.getWidth() / (float) viewHeight;
293 yScale = scaled.getHeight() / (float) viewWidth;
294 }
295
296 // Only two digits after the decimal point are valid for postScale. Need to get ceiling of
297 // two
298 // digits floating value to do the scale operation. Otherwise, the result may be scaled not
299 // large enough and will have some blank lines on the screen.
300 xScale = new BigDecimal(xScale).setScale(2, BigDecimal.ROUND_CEILING).floatValue();
301 yScale = new BigDecimal(yScale).setScale(2, BigDecimal.ROUND_CEILING).floatValue();
302
303 // Do corresponding scale to resolve the deformation problem
304 matrix.postScale(xScale, yScale, centerX, centerY);
305
Xi Zhang9d848662019-10-08 13:00:40 -0700306 mTextureView.setTransform(matrix);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800307 }
308
Trevor McGuire470a44f2019-02-11 13:19:37 -0800309 /** @return One of 0, 90, 180, 270. */
Chris Banesed186082020-02-07 21:36:02 -0800310 @SuppressWarnings("deprecation") /* defaultDisplay */
Trevor McGuire470a44f2019-02-11 13:19:37 -0800311 private int getDisplayRotation() {
312 int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800313
Trevor McGuire470a44f2019-02-11 13:19:37 -0800314 switch (displayRotation) {
315 case Surface.ROTATION_0:
316 displayRotation = 0;
317 break;
318 case Surface.ROTATION_90:
319 displayRotation = 90;
320 break;
321 case Surface.ROTATION_180:
322 displayRotation = 180;
323 break;
324 case Surface.ROTATION_270:
325 displayRotation = 270;
326 break;
327 default:
328 throw new UnsupportedOperationException(
329 "Unsupported display rotation: " + displayRotation);
330 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800331
Trevor McGuire470a44f2019-02-11 13:19:37 -0800332 return displayRotation;
Trevor McGuirec63cd732019-02-06 13:12:50 -0800333 }
334
leo huang3a95a1d2019-03-28 13:35:17 +0800335 private Size calculatePreviewViewDimens(
Trevor McGuire470a44f2019-02-11 13:19:37 -0800336 Size srcSize, int parentWidth, int parentHeight, int displayRotation) {
337 int inWidth = srcSize.getWidth();
338 int inHeight = srcSize.getHeight();
339 if (displayRotation == 0 || displayRotation == 180) {
340 // Need to reverse the width and height since we're in landscape orientation.
341 inWidth = srcSize.getHeight();
342 inHeight = srcSize.getWidth();
343 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800344
Trevor McGuire470a44f2019-02-11 13:19:37 -0800345 int outWidth = parentWidth;
346 int outHeight = parentHeight;
347 if (inWidth != 0 && inHeight != 0) {
348 float vfRatio = inWidth / (float) inHeight;
349 float parentRatio = parentWidth / (float) parentHeight;
Trevor McGuirec63cd732019-02-06 13:12:50 -0800350
Trevor McGuire470a44f2019-02-11 13:19:37 -0800351 // Match shortest sides together.
352 if (vfRatio < parentRatio) {
353 outWidth = parentWidth;
354 outHeight = Math.round(parentWidth / vfRatio);
355 } else {
356 outWidth = Math.round(parentHeight * vfRatio);
357 outHeight = parentHeight;
358 }
359 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800360
Trevor McGuire470a44f2019-02-11 13:19:37 -0800361 return new Size(outWidth, outHeight);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800362 }
363
Trevor McGuire470a44f2019-02-11 13:19:37 -0800364 /**
365 * Creates an image analysis use case.
366 *
367 * <p>This use case observes a stream of analysis results computed from the frames.
368 */
leo huang3a95a1d2019-03-28 13:35:17 +0800369 private void createImageAnalysis() {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800370 Button button = this.findViewById(R.id.AnalysisToggle);
371 button.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800372 enableImageAnalysis();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800373
Trevor McGuire470a44f2019-02-11 13:19:37 -0800374 button.setOnClickListener(
Scott Nien3d89b902019-02-21 14:42:18 +0800375 new View.OnClickListener() {
376 @Override
377 public void onClick(View view) {
378 Button buttonView = (Button) view;
leo huang3a95a1d2019-03-28 13:35:17 +0800379 if (mImageAnalysis != null) {
Scott Nien3d89b902019-02-21 14:42:18 +0800380 // Remove the use case
381 buttonView.setBackgroundColor(Color.RED);
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800382 mCameraProvider.unbind(mImageAnalysis);
leo huang3a95a1d2019-03-28 13:35:17 +0800383 mImageAnalysis = null;
Scott Nien3d89b902019-02-21 14:42:18 +0800384 } else {
385 // Add the use case
386 buttonView.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800387 CameraXActivity.this.enableImageAnalysis();
Scott Nien3d89b902019-02-21 14:42:18 +0800388 }
Trevor McGuire470a44f2019-02-11 13:19:37 -0800389 }
390 });
391
leo huang3a95a1d2019-03-28 13:35:17 +0800392 Log.i(TAG, "Got UseCase: " + mImageAnalysis);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800393 }
394
leo huang3a95a1d2019-03-28 13:35:17 +0800395 void enableImageAnalysis() {
husaynhakeem43c180d2019-11-18 12:01:20 -0800396 mImageAnalysis = new ImageAnalysis.Builder()
husaynhakeem687d79a2019-11-13 16:33:20 -0800397 .setTargetName("ImageAnalysis")
398 .build();
Trevor McGuire470a44f2019-02-11 13:19:37 -0800399 TextView textView = this.findViewById(R.id.textView);
fungjaf3530f72019-09-17 14:18:19 -0700400
401 // Make the analysis idling resource non-idle, until a frame received.
weginlee7e7afa02019-03-05 17:18:06 +0800402 mAnalysisIdlingResource.increment();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800403
WenHung_Teng60fffca2019-11-05 14:27:35 +0800404 if (bindToLifecycleSafely(mImageAnalysis, R.id.AnalysisToggle) == null) {
leo huang3a95a1d2019-03-28 13:35:17 +0800405 mImageAnalysis = null;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800406 return;
407 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800408
leo huang3a95a1d2019-03-28 13:35:17 +0800409 mImageAnalysis.setAnalyzer(
Franklin Wud688b2d2019-11-26 15:49:01 -0800410 ContextCompat.getMainExecutor(this),
Franklin Wuf9c00402019-12-03 11:31:49 -0800411 (image) -> {
TY Chang2707b4d2019-11-11 17:59:53 +0800412 // Since we set the callback handler to a main thread handler, we can call
413 // setValue() here. If we weren't on the main thread, we would have to call
414 // postValue() instead.
415 mImageAnalysisResult.setValue(
416 Long.toString(image.getImageInfo().getTimestamp()));
fungjaf3530f72019-09-17 14:18:19 -0700417 try {
418 if (!mAnalysisIdlingResource.isIdleNow()) {
419 mAnalysisIdlingResource.decrement();
420 }
421 } catch (IllegalStateException e) {
422 Log.e(TAG, "Unexpected counter decrement");
Scott Nien3d89b902019-02-21 14:42:18 +0800423 }
TY Chang2707b4d2019-11-11 17:59:53 +0800424 image.close();
425 }
426 );
weginlee8bfd0642019-02-14 15:15:00 +0800427 mImageAnalysisResult.observe(
Trevor McGuire470a44f2019-02-11 13:19:37 -0800428 this,
Scott Nien3d89b902019-02-21 14:42:18 +0800429 new Observer<String>() {
430 @Override
431 public void onChanged(String text) {
432 if (mImageAnalysisFrameCount.getAndIncrement() % 30 == 0) {
433 textView.setText(
434 "ImgCount: " + mImageAnalysisFrameCount.get() + " @ts: "
435 + text);
436 }
Trevor McGuire470a44f2019-02-11 13:19:37 -0800437 }
438 });
Trevor McGuirec63cd732019-02-06 13:12:50 -0800439 }
440
Trevor McGuire470a44f2019-02-11 13:19:37 -0800441 /**
442 * Creates an image capture use case.
443 *
444 * <p>This use case takes a picture and saves it to a file, whenever the user clicks a button.
445 */
leo huang3a95a1d2019-03-28 13:35:17 +0800446 private void createImageCapture() {
Trevor McGuirec63cd732019-02-06 13:12:50 -0800447
Trevor McGuire470a44f2019-02-11 13:19:37 -0800448 Button button = this.findViewById(R.id.PhotoToggle);
449 button.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800450 enableImageCapture();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800451
Trevor McGuire470a44f2019-02-11 13:19:37 -0800452 button.setOnClickListener(
Scott Nien3d89b902019-02-21 14:42:18 +0800453 new View.OnClickListener() {
454 @Override
455 public void onClick(View view) {
456 Button buttonView = (Button) view;
leo huang3a95a1d2019-03-28 13:35:17 +0800457 if (mImageCapture != null) {
Scott Nien3d89b902019-02-21 14:42:18 +0800458 // Remove the use case
459 buttonView.setBackgroundColor(Color.RED);
leo huang3a95a1d2019-03-28 13:35:17 +0800460 CameraXActivity.this.disableImageCapture();
Scott Nien3d89b902019-02-21 14:42:18 +0800461 } else {
462 // Add the use case
463 buttonView.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800464 CameraXActivity.this.enableImageCapture();
Scott Nien3d89b902019-02-21 14:42:18 +0800465 }
Trevor McGuire470a44f2019-02-11 13:19:37 -0800466 }
467 });
Trevor McGuirec63cd732019-02-06 13:12:50 -0800468
leo huang3a95a1d2019-03-28 13:35:17 +0800469 Log.i(TAG, "Got UseCase: " + mImageCapture);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800470 }
471
leo huang3a95a1d2019-03-28 13:35:17 +0800472 void enableImageCapture() {
husaynhakeembf6c2f92019-11-18 14:49:02 -0800473 mImageCapture = new ImageCapture.Builder()
husaynhakeem687d79a2019-11-13 16:33:20 -0800474 .setCaptureMode(mCaptureMode)
475 .setTargetName("ImageCapture")
476 .build();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800477
WenHung_Teng60fffca2019-11-05 14:27:35 +0800478 Camera camera = bindToLifecycleSafely(mImageCapture, R.id.PhotoToggle);
479 if (camera == null) {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800480 Button button = this.findViewById(R.id.Picture);
481 button.setOnClickListener(null);
leo huang3a95a1d2019-03-28 13:35:17 +0800482 mImageCapture = null;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800483 return;
484 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800485
Trevor McGuire470a44f2019-02-11 13:19:37 -0800486 Button button = this.findViewById(R.id.Picture);
Trevor McGuire470a44f2019-02-11 13:19:37 -0800487 button.setOnClickListener(
Scott Nien3d89b902019-02-21 14:42:18 +0800488 new View.OnClickListener() {
Scott Nienfb8affc2019-06-14 16:41:18 +0800489 long mStartCaptureTime = 0;
490
Scott Nien3d89b902019-02-21 14:42:18 +0800491 @Override
492 public void onClick(View view) {
weginlee7e7afa02019-03-05 17:18:06 +0800493 mImageSavedIdlingResource.increment();
494
Scott Nienfb8affc2019-06-14 16:41:18 +0800495 mStartCaptureTime = SystemClock.elapsedRealtime();
Xi Zhang6f28a572020-02-10 10:50:01 -0800496 createDefaultPictureFolderIfNotExist();
497 ContentValues contentValues = new ContentValues();
498 contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
Xi Zhangec5384d2020-01-27 15:01:28 -0800499 ImageCapture.OutputFileOptions outputFileOptions =
500 new ImageCapture.OutputFileOptions.Builder(
501 getContentResolver(),
502 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
Xi Zhang6f28a572020-02-10 10:50:01 -0800503 contentValues).build();
Xi Zhangec5384d2020-01-27 15:01:28 -0800504 mImageCapture.takePicture(outputFileOptions,
Franklin Wud688b2d2019-11-26 15:49:01 -0800505 ContextCompat.getMainExecutor(CameraXActivity.this),
Franklin Wu3b4ed3e2019-09-13 13:43:29 -0700506 new ImageCapture.OnImageSavedCallback() {
Scott Nien3d89b902019-02-21 14:42:18 +0800507 @Override
Xi Zhangec5384d2020-01-27 15:01:28 -0800508 public void onImageSaved(
509 @NonNull ImageCapture.OutputFileResults
510 outputFileResults) {
511 Log.d(TAG, "Saved image to "
512 + outputFileResults.getSavedUri());
fungja5e3d86b2019-09-13 11:31:54 -0700513 try {
weginlee7e7afa02019-03-05 17:18:06 +0800514 mImageSavedIdlingResource.decrement();
fungja5e3d86b2019-09-13 11:31:54 -0700515 } catch (IllegalStateException e) {
516 Log.e(TAG, "Error: unexpected onImageSaved "
517 + "callback received. Continuing.");
weginlee7e7afa02019-03-05 17:18:06 +0800518 }
Scott Nienfb8affc2019-06-14 16:41:18 +0800519
520 long duration =
521 SystemClock.elapsedRealtime() - mStartCaptureTime;
522 runOnUiThread(new Runnable() {
523 @Override
524 public void run() {
525 Toast.makeText(CameraXActivity.this,
526 "Image captured in " + duration + " ms",
527 Toast.LENGTH_SHORT).show();
528 }
529 });
Scott Nien3d89b902019-02-21 14:42:18 +0800530 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800531
Scott Nien3d89b902019-02-21 14:42:18 +0800532 @Override
husaynhakeem175db562020-01-17 11:26:51 -0800533 public void onError(@NonNull ImageCaptureException exception) {
534 Log.e(TAG, "Failed to save image.", exception.getCause());
fungja5e3d86b2019-09-13 11:31:54 -0700535 try {
weginlee7e7afa02019-03-05 17:18:06 +0800536 mImageSavedIdlingResource.decrement();
fungja5e3d86b2019-09-13 11:31:54 -0700537 } catch (IllegalStateException e) {
538 Log.e(TAG, "Error: unexpected onImageSaved "
539 + "callback received. Continuing.");
weginlee7e7afa02019-03-05 17:18:06 +0800540 }
Scott Nien3d89b902019-02-21 14:42:18 +0800541 }
542 });
543 }
Trevor McGuire470a44f2019-02-11 13:19:37 -0800544 });
WenHung_Teng4b5372c2019-03-04 16:58:21 +0800545
leo huang6193f932019-11-20 17:43:52 +0800546 refreshFlashButton();
Scott Nienfb8affc2019-06-14 16:41:18 +0800547
548
549 Button btnCaptureQuality = this.findViewById(R.id.capture_quality);
550 btnCaptureQuality.setVisibility(View.VISIBLE);
551 btnCaptureQuality.setText(
husaynhakeem661b7e92019-11-22 16:39:36 -0800552 mCaptureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY ? "MAX" : "MIN");
Scott Nienfb8affc2019-06-14 16:41:18 +0800553 btnCaptureQuality.setOnClickListener(new View.OnClickListener() {
554 @Override
555 public void onClick(View view) {
husaynhakeem661b7e92019-11-22 16:39:36 -0800556 mCaptureMode = (mCaptureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
557 ? ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
558 : ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY);
Scott Nienfb8affc2019-06-14 16:41:18 +0800559 rebindUseCases();
560 }
561 });
Trevor McGuirec63cd732019-02-06 13:12:50 -0800562 }
563
leo huang3a95a1d2019-03-28 13:35:17 +0800564 void disableImageCapture() {
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800565 mCameraProvider.unbind(mImageCapture);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800566
leo huang3a95a1d2019-03-28 13:35:17 +0800567 mImageCapture = null;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800568 Button button = this.findViewById(R.id.Picture);
569 button.setOnClickListener(null);
WenHung_Teng4b5372c2019-03-04 16:58:21 +0800570
Scott Nienfb8affc2019-06-14 16:41:18 +0800571 Button btnCaptureQuality = this.findViewById(R.id.capture_quality);
572 btnCaptureQuality.setVisibility(View.GONE);
573
leo huang6193f932019-11-20 17:43:52 +0800574 refreshFlashButton();
575 }
576
577 private void refreshFlashButton() {
578 ImageButton flashToggle = findViewById(R.id.flash_toggle);
579 if (mImageCapture != null) {
580 CameraInfo cameraInfo = getCameraInfo();
581 if (cameraInfo != null && cameraInfo.hasFlashUnit()) {
582 flashToggle.setVisibility(View.VISIBLE);
583 flashToggle.setOnClickListener(new View.OnClickListener() {
584 @Override
585 public void onClick(View v) {
husaynhakeem661b7e92019-11-22 16:39:36 -0800586 @ImageCapture.FlashMode int flashMode = mImageCapture.getFlashMode();
587 if (flashMode == FLASH_MODE_ON) {
588 mImageCapture.setFlashMode(FLASH_MODE_OFF);
589 } else if (flashMode == FLASH_MODE_OFF) {
590 mImageCapture.setFlashMode(FLASH_MODE_AUTO);
591 } else if (flashMode == FLASH_MODE_AUTO) {
592 mImageCapture.setFlashMode(FLASH_MODE_ON);
leo huang6193f932019-11-20 17:43:52 +0800593 }
594 refreshFlashButtonIcon();
595 }
596 });
597 refreshFlashButtonIcon();
598 } else {
599 flashToggle.setVisibility(View.INVISIBLE);
600 flashToggle.setOnClickListener(null);
601 }
602 } else {
603 flashToggle.setVisibility(View.GONE);
604 flashToggle.setOnClickListener(null);
605 }
WenHung_Teng4b5372c2019-03-04 16:58:21 +0800606 }
607
608 private void refreshFlashButtonIcon() {
609 ImageButton flashToggle = findViewById(R.id.flash_toggle);
husaynhakeem661b7e92019-11-22 16:39:36 -0800610 @ImageCapture.FlashMode int flashMode = mImageCapture.getFlashMode();
leo huang6193f932019-11-20 17:43:52 +0800611 switch (flashMode) {
husaynhakeem661b7e92019-11-22 16:39:36 -0800612 case FLASH_MODE_ON:
leo huang6193f932019-11-20 17:43:52 +0800613 flashToggle.setImageResource(R.drawable.ic_flash_on);
614 break;
husaynhakeem661b7e92019-11-22 16:39:36 -0800615 case FLASH_MODE_OFF:
leo huang6193f932019-11-20 17:43:52 +0800616 flashToggle.setImageResource(R.drawable.ic_flash_off);
617 break;
husaynhakeem661b7e92019-11-22 16:39:36 -0800618 case FLASH_MODE_AUTO:
leo huang6193f932019-11-20 17:43:52 +0800619 flashToggle.setImageResource(R.drawable.ic_flash_auto);
620 break;
621 }
622 }
623
624 private void refreshTorchButton() {
625 ImageButton torchToggle = findViewById(R.id.torch_toggle);
626 CameraInfo cameraInfo = getCameraInfo();
627 if (cameraInfo != null && cameraInfo.hasFlashUnit()) {
628 torchToggle.setVisibility(View.VISIBLE);
629 torchToggle.setOnClickListener(v -> {
630 Integer torchState = cameraInfo.getTorchState().getValue();
631 boolean toggledState = !(torchState == TorchState.ON);
632 CameraControl cameraControl = getCameraControl();
633 if (cameraControl != null) {
634 Log.d(TAG, "Set camera torch: " + toggledState);
Sergey3f890dd2019-12-30 19:51:47 +0400635 ListenableFuture<Void> future = cameraControl.enableTorch(toggledState);
636 Futures.addCallback(future, new FutureCallback<Void>() {
637 @Override
638 public void onSuccess(@Nullable Void result) {
639 }
640
641 @Override
642 public void onFailure(@NonNull Throwable t) {
643 // Throw the unexpected error.
644 throw new RuntimeException(t);
645 }
646 }, CameraXExecutors.directExecutor());
WenHung_Teng4b5372c2019-03-04 16:58:21 +0800647 }
648 });
WenHung_Teng4b5372c2019-03-04 16:58:21 +0800649 } else {
leo huang6193f932019-11-20 17:43:52 +0800650 Log.d(TAG, "No flash unit");
651 torchToggle.setVisibility(View.INVISIBLE);
652 torchToggle.setOnClickListener(null);
WenHung_Teng4b5372c2019-03-04 16:58:21 +0800653 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800654 }
655
Trevor McGuire470a44f2019-02-11 13:19:37 -0800656 /**
657 * Creates a video capture use case.
658 *
659 * <p>This use case records a video segment and saves it to a file, in response to user button
660 * clicks.
661 */
leo huang3a95a1d2019-03-28 13:35:17 +0800662 private void createVideoCapture() {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800663 Button button = this.findViewById(R.id.VideoToggle);
664 button.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800665 enableVideoCapture();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800666
weginlee8bfd0642019-02-14 15:15:00 +0800667 mVideoFileSaver = new VideoFileSaver();
668 mVideoFileSaver.setRootDirectory(
Trevor McGuire470a44f2019-02-11 13:19:37 -0800669 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
670
671 button.setOnClickListener(
Scott Nien3d89b902019-02-21 14:42:18 +0800672 new View.OnClickListener() {
673 @Override
674 public void onClick(View view) {
675 Button buttonView = (Button) view;
leo huang3a95a1d2019-03-28 13:35:17 +0800676 if (mVideoCapture != null) {
Scott Nien3d89b902019-02-21 14:42:18 +0800677 // Remove the use case
678 buttonView.setBackgroundColor(Color.RED);
leo huang3a95a1d2019-03-28 13:35:17 +0800679 CameraXActivity.this.disableVideoCapture();
Scott Nien3d89b902019-02-21 14:42:18 +0800680 } else {
681 // Add the use case
682 buttonView.setBackgroundColor(Color.LTGRAY);
leo huang3a95a1d2019-03-28 13:35:17 +0800683 CameraXActivity.this.enableVideoCapture();
Scott Nien3d89b902019-02-21 14:42:18 +0800684 }
Trevor McGuire470a44f2019-02-11 13:19:37 -0800685 }
686 });
687
leo huang3a95a1d2019-03-28 13:35:17 +0800688 Log.i(TAG, "Got UseCase: " + mVideoCapture);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800689 }
690
leo huang3a95a1d2019-03-28 13:35:17 +0800691 void enableVideoCapture() {
husaynhakeem687d79a2019-11-13 16:33:20 -0800692 mVideoCapture = new VideoCaptureConfig.Builder()
693 .setTargetName("VideoCapture")
694 .build();
Trevor McGuire470a44f2019-02-11 13:19:37 -0800695
WenHung_Teng60fffca2019-11-05 14:27:35 +0800696 if (bindToLifecycleSafely(mVideoCapture, R.id.VideoToggle) == null) {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800697 Button button = this.findViewById(R.id.Video);
698 button.setOnClickListener(null);
leo huang3a95a1d2019-03-28 13:35:17 +0800699 mVideoCapture = null;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800700 return;
701 }
702
703 Button button = this.findViewById(R.id.Video);
Franklin Wud688b2d2019-11-26 15:49:01 -0800704 button.setOnClickListener((view) -> {
705 Button buttonView = (Button) view;
706 String text = button.getText().toString();
707 if (text.equals("Record") && !mVideoFileSaver.isSaving()) {
708 mVideoCapture.startRecording(
709 mVideoFileSaver.getNewVideoFile(),
710 ContextCompat.getMainExecutor(CameraXActivity.this),
711 mVideoFileSaver);
712 mVideoFileSaver.setSaving();
713 buttonView.setText("Stop");
714 } else if (text.equals("Stop") && mVideoFileSaver.isSaving()) {
715 buttonView.setText("Record");
716 mVideoCapture.stopRecording();
717 } else if (text.equals("Record") && mVideoFileSaver.isSaving()) {
718 buttonView.setText("Stop");
719 mVideoFileSaver.setSaving();
720 } else if (text.equals("Stop") && !mVideoFileSaver.isSaving()) {
721 buttonView.setText("Record");
722 }
723 });
Trevor McGuirec63cd732019-02-06 13:12:50 -0800724 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800725
leo huang3a95a1d2019-03-28 13:35:17 +0800726 void disableVideoCapture() {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800727 Button button = this.findViewById(R.id.Video);
728 button.setOnClickListener(null);
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800729 mCameraProvider.unbind(mVideoCapture);
Trevor McGuirec63cd732019-02-06 13:12:50 -0800730
leo huang3a95a1d2019-03-28 13:35:17 +0800731 mVideoCapture = null;
Trevor McGuirec63cd732019-02-06 13:12:50 -0800732 }
Trevor McGuirec63cd732019-02-06 13:12:50 -0800733
Trevor McGuire470a44f2019-02-11 13:19:37 -0800734 /** Creates all the use cases. */
735 private void createUseCases() {
leo huang3a95a1d2019-03-28 13:35:17 +0800736 createImageCapture();
737 createPreview();
738 createImageAnalysis();
739 createVideoCapture();
Trevor McGuirec63cd732019-02-06 13:12:50 -0800740 }
741
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800742 @SuppressWarnings("UnstableApiUsage")
Trevor McGuirec63cd732019-02-06 13:12:50 -0800743 @Override
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800744 protected void onCreate(@Nullable Bundle savedInstanceState) {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800745 super.onCreate(savedInstanceState);
746 setContentView(R.layout.activity_camera_xmain);
Xi Zhang9d848662019-10-08 13:00:40 -0700747 mTextureView = findViewById(R.id.textureView);
Scott Nien80b49e72019-12-11 23:05:18 +0800748 mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
749 @Override
750 public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture,
751 final int width, final int height) {
752 mSurfaceTexture = surfaceTexture;
753 tryToProvidePreviewSurface();
754 }
755
756 @Override
757 public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture,
758 final int width, final int height) {
759 }
760
761 /**
762 * If a surface has been provided to the camera (meaning
Trevor McGuire049c9182020-01-31 11:15:33 -0800763 * {@link CameraXActivity#mSurfaceRequest} is null), but the camera
Scott Nien80b49e72019-12-11 23:05:18 +0800764 * is still using it (meaning {@link CameraXActivity#mSurfaceReleaseFuture} is
765 * not null), a listener must be added to
766 * {@link CameraXActivity#mSurfaceReleaseFuture} to ensure the surface
767 * is properly released after the camera is done using it.
768 *
769 * @param surfaceTexture The {@link SurfaceTexture} about to be destroyed.
770 * @return false if the camera is not done with the surface, true otherwise.
771 */
772 @Override
773 public boolean onSurfaceTextureDestroyed(final SurfaceTexture surfaceTexture) {
774 mSurfaceTexture = null;
Trevor McGuire049c9182020-01-31 11:15:33 -0800775 if (mSurfaceRequest == null && mSurfaceReleaseFuture != null) {
Scott Nien80b49e72019-12-11 23:05:18 +0800776 mSurfaceReleaseFuture.addListener(surfaceTexture::release,
777 ContextCompat.getMainExecutor(mTextureView.getContext()));
778 return false;
779 } else {
780 return true;
781 }
782 }
783
784 @Override
785 public void onSurfaceTextureUpdated(final SurfaceTexture surfaceTexture) {
786 // Wait until surface texture receives enough updates. This is for testing.
fungjaf3530f72019-09-17 14:18:19 -0700787 if (mPreviewFrameCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY) {
fungjaf3530f72019-09-17 14:18:19 -0700788 try {
789 if (!mViewIdlingResource.isIdleNow()) {
weginlee692104b2019-12-11 20:17:36 +0800790 Log.d(TAG, FRAMES_UNTIL_VIEW_IS_READY + " or more counted on preview."
791 + " Make IdlingResource idle.");
fungjaf3530f72019-09-17 14:18:19 -0700792 mViewIdlingResource.decrement();
793 }
794 } catch (IllegalStateException e) {
795 Log.e(TAG, "Unexpected decrement. Continuing");
796 }
Scott Nien80b49e72019-12-11 23:05:18 +0800797 }
798 }
799 });
Trevor McGuirec63cd732019-02-06 13:12:50 -0800800
Trevor McGuire470a44f2019-02-11 13:19:37 -0800801 StrictMode.VmPolicy policy =
802 new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build();
803 StrictMode.setVmPolicy(policy);
804
805 // Get params from adb extra string
806 Bundle bundle = this.getIntent().getExtras();
807 if (bundle != null) {
808 String newCameraDirection = bundle.getString(INTENT_EXTRA_CAMERA_DIRECTION);
809 if (newCameraDirection != null) {
weginlee8bfd0642019-02-14 15:15:00 +0800810 mCurrentCameraDirection = newCameraDirection;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800811 }
812 }
813
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800814 ListenableFuture<Void> cameraProviderFuture =
815 Futures.transform(ProcessCameraProvider.getInstance(this),
816 provider -> {
817 mCameraProvider = provider;
818 return null;
819 },
820 ContextCompat.getMainExecutor(this));
821
822 ListenableFuture<Void> permissionFuture = Futures.transform(setupPermissions(),
823 permissionGranted -> {
824 mPermissionsGranted = Preconditions.checkNotNull(permissionGranted);
825 return null;
826 }, ContextCompat.getMainExecutor(this));
827
828 Futures.addCallback(
829 Futures.allAsList(cameraProviderFuture, permissionFuture),
830 new FutureCallback<List<Void>>() {
Scott Nien3d89b902019-02-21 14:42:18 +0800831 @Override
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800832 public void onSuccess(@Nullable List<Void> ignored) {
Scott Nien3d89b902019-02-21 14:42:18 +0800833 CameraXActivity.this.setupCamera();
834 }
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800835
836 @Override
837 public void onFailure(@NonNull Throwable t) {
838 throw new RuntimeException("Initialization failed.", t);
839 }
840 }, ContextCompat.getMainExecutor(this));
Trevor McGuirec63cd732019-02-06 13:12:50 -0800841 }
842
Scott Nien80b49e72019-12-11 23:05:18 +0800843 @SuppressWarnings("WeakerAccess")
844 void tryToProvidePreviewSurface() {
845 /*
846 Should only continue if:
847 - The preview size has been specified.
848 - The textureView's surfaceTexture is available (after TextureView
849 .SurfaceTextureListener#onSurfaceTextureAvailable is invoked)
850 - The surfaceCompleter has been set (after CallbackToFutureAdapter
851 .Resolver#attachCompleter is invoked).
852 */
Trevor McGuire049c9182020-01-31 11:15:33 -0800853 if (mResolution == null || mSurfaceTexture == null || mSurfaceRequest == null) {
Scott Nien80b49e72019-12-11 23:05:18 +0800854 return;
855 }
856
857 mSurfaceTexture.setDefaultBufferSize(mResolution.getWidth(), mResolution.getHeight());
858
859 final Surface surface = new Surface(mSurfaceTexture);
Trevor McGuire049c9182020-01-31 11:15:33 -0800860 final ListenableFuture<Void> surfaceReleaseFuture = mSurfaceRequest.setSurface(surface);
861 mSurfaceReleaseFuture = surfaceReleaseFuture;
Scott Nien80b49e72019-12-11 23:05:18 +0800862 mSurfaceReleaseFuture.addListener(() -> {
863 surface.release();
864 if (mSurfaceReleaseFuture == surfaceReleaseFuture) {
865 mSurfaceReleaseFuture = null;
866 }
867 }, ContextCompat.getMainExecutor(mTextureView.getContext()));
Trevor McGuire049c9182020-01-31 11:15:33 -0800868 mSurfaceRequest = null;
Scott Nien80b49e72019-12-11 23:05:18 +0800869
870 transformPreview(mResolution);
871 }
872
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800873 void setupCamera() {
874 // Check for permissions before proceeding.
875 if (!mPermissionsGranted) {
876 Log.d(TAG, "Permissions denied.");
877 return;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800878 }
879
weginlee8bfd0642019-02-14 15:15:00 +0800880 Log.d(TAG, "Camera direction: " + mCurrentCameraDirection);
881 if (mCurrentCameraDirection.equalsIgnoreCase("BACKWARD")) {
Trevor McGuirea743e1f2019-10-28 10:42:09 -0700882 mCurrentCameraSelector = BACK_SELECTOR;
husaynhakeem661b7e92019-11-22 16:39:36 -0800883 mCurrentCameraLensFacing = CameraSelector.LENS_FACING_BACK;
weginlee8bfd0642019-02-14 15:15:00 +0800884 } else if (mCurrentCameraDirection.equalsIgnoreCase("FORWARD")) {
Trevor McGuirea743e1f2019-10-28 10:42:09 -0700885 mCurrentCameraSelector = FRONT_SELECTOR;
husaynhakeem661b7e92019-11-22 16:39:36 -0800886 mCurrentCameraLensFacing = CameraSelector.LENS_FACING_FRONT;
Trevor McGuire470a44f2019-02-11 13:19:37 -0800887 } else {
weginlee8bfd0642019-02-14 15:15:00 +0800888 throw new RuntimeException("Invalid camera direction: " + mCurrentCameraDirection);
Trevor McGuire470a44f2019-02-11 13:19:37 -0800889 }
Trevor McGuirea743e1f2019-10-28 10:42:09 -0700890 Log.d(TAG, "Using camera lens facing: " + mCurrentCameraSelector);
Trevor McGuire470a44f2019-02-11 13:19:37 -0800891
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800892 CameraXActivity.this.createUseCases();
893 refreshTorchButton();
Wenhung Teng7b61dd02019-02-25 10:00:03 -0800894
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800895 ImageButton directionToggle = findViewById(R.id.direction_toggle);
896 directionToggle.setVisibility(View.VISIBLE);
897 directionToggle.setOnClickListener(new View.OnClickListener() {
898 @Override
899 public void onClick(View v) {
husaynhakeem661b7e92019-11-22 16:39:36 -0800900 if (mCurrentCameraLensFacing == CameraSelector.LENS_FACING_BACK) {
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800901 mCurrentCameraSelector = FRONT_SELECTOR;
husaynhakeem661b7e92019-11-22 16:39:36 -0800902 mCurrentCameraLensFacing = CameraSelector.LENS_FACING_FRONT;
903 } else if (mCurrentCameraLensFacing == CameraSelector.LENS_FACING_FRONT) {
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800904 mCurrentCameraSelector = BACK_SELECTOR;
husaynhakeem661b7e92019-11-22 16:39:36 -0800905 mCurrentCameraLensFacing = CameraSelector.LENS_FACING_BACK;
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800906 }
Wenhung Teng7b61dd02019-02-25 10:00:03 -0800907
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800908 Log.d(TAG, "Change camera direction: " + mCurrentCameraSelector);
909 rebindUseCases();
910 refreshTorchButton();
911
912 }
913 });
Trevor McGuire470a44f2019-02-11 13:19:37 -0800914 }
915
Scott Nienfb8affc2019-06-14 16:41:18 +0800916 private void rebindUseCases() {
917 // Rebind all use cases.
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800918 mCameraProvider.unbindAll();
Scott Nienfb8affc2019-06-14 16:41:18 +0800919 if (mImageCapture != null) {
920 enableImageCapture();
921 }
922 if (mPreview != null) {
923 enablePreview();
924 }
925 if (mImageAnalysis != null) {
926 enableImageAnalysis();
927 }
928 if (mVideoCapture != null) {
929 enableVideoCapture();
930 }
931 }
932
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800933 private ListenableFuture<Boolean> setupPermissions() {
934 return CallbackToFutureAdapter.getFuture(completer -> {
935 mPermissionsCompleter = completer;
936 if (!allPermissionsGranted()) {
937 makePermissionRequest();
938 } else {
939 mPermissionsCompleter.set(true);
940 }
941
942 return "get_permissions";
943 });
Trevor McGuire470a44f2019-02-11 13:19:37 -0800944 }
945
946 private void makePermissionRequest() {
947 ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSIONS_REQUEST_CODE);
948 }
949
950 /** Returns true if all the necessary permissions have been granted already. */
951 private boolean allPermissionsGranted() {
952 for (String permission : getRequiredPermissions()) {
953 if (ContextCompat.checkSelfPermission(this, permission)
954 != PackageManager.PERMISSION_GRANTED) {
955 return false;
956 }
957 }
958 return true;
959 }
960
Xi Zhang6f28a572020-02-10 10:50:01 -0800961 private void createDefaultPictureFolderIfNotExist() {
962 File pictureFolder = Environment.getExternalStoragePublicDirectory(
963 Environment.DIRECTORY_PICTURES);
964 if (!pictureFolder.exists()) {
965 pictureFolder.mkdir();
966 }
967 }
968
Trevor McGuire470a44f2019-02-11 13:19:37 -0800969 /** Tries to acquire all the necessary permissions through a dialog. */
970 private String[] getRequiredPermissions() {
971 PackageInfo info;
972 try {
973 info =
974 getPackageManager()
975 .getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS);
976 } catch (NameNotFoundException exception) {
977 Log.e(TAG, "Failed to obtain all required permissions.", exception);
978 return new String[0];
979 }
980 String[] permissions = info.requestedPermissions;
981 if (permissions != null && permissions.length > 0) {
982 return permissions;
983 } else {
984 return new String[0];
985 }
986 }
987
988 @Override
989 public void onRequestPermissionsResult(
WenHung_Teng1d12f202019-08-14 14:06:08 +0800990 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Trevor McGuire470a44f2019-02-11 13:19:37 -0800991 switch (requestCode) {
992 case PERMISSIONS_REQUEST_CODE: {
993 // If request is cancelled, the result arrays are empty.
994 if (grantResults.length > 0
995 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
996 Log.d(TAG, "Permissions Granted.");
Trevor McGuirecf8e22b2019-11-14 22:12:36 -0800997 mPermissionsCompleter.set(true);
Trevor McGuire470a44f2019-02-11 13:19:37 -0800998 } else {
999 Log.d(TAG, "Permissions Denied.");
Trevor McGuirecf8e22b2019-11-14 22:12:36 -08001000 mPermissionsCompleter.set(false);
Trevor McGuire470a44f2019-02-11 13:19:37 -08001001 }
1002 return;
1003 }
1004 default:
1005 // No-op
1006 }
1007 }
1008
WenHung_Teng60fffca2019-11-05 14:27:35 +08001009 @Nullable
1010 private Camera bindToLifecycleSafely(UseCase useCase, int buttonViewId) {
Trevor McGuire470a44f2019-02-11 13:19:37 -08001011 try {
Trevor McGuirecf8e22b2019-11-14 22:12:36 -08001012 mCamera = mCameraProvider.bindToLifecycle(this, mCurrentCameraSelector,
leo huang6193f932019-11-20 17:43:52 +08001013 useCase);
1014 return mCamera;
Trevor McGuire470a44f2019-02-11 13:19:37 -08001015 } catch (IllegalArgumentException e) {
1016 Log.e(TAG, e.getMessage());
1017 Toast.makeText(getApplicationContext(), "Bind too many use cases.", Toast.LENGTH_SHORT)
1018 .show();
1019 Button button = this.findViewById(buttonViewId);
1020 button.setBackgroundColor(Color.RED);
Trevor McGuire470a44f2019-02-11 13:19:37 -08001021 }
WenHung_Teng60fffca2019-11-05 14:27:35 +08001022 return null;
Trevor McGuire470a44f2019-02-11 13:19:37 -08001023 }
1024
leo huang3a95a1d2019-03-28 13:35:17 +08001025 Preview getPreview() {
1026 return mPreview;
WenHung_Tengaf9c6fa2019-03-05 23:38:06 +08001027 }
1028
leo huang3a95a1d2019-03-28 13:35:17 +08001029 ImageAnalysis getImageAnalysis() {
1030 return mImageAnalysis;
WenHung_Tengaf9c6fa2019-03-05 23:38:06 +08001031 }
1032
leo huang3a95a1d2019-03-28 13:35:17 +08001033 ImageCapture getImageCapture() {
1034 return mImageCapture;
WenHung_Tengaf9c6fa2019-03-05 23:38:06 +08001035 }
1036
leo huang3a95a1d2019-03-28 13:35:17 +08001037 VideoCapture getVideoCapture() {
1038 return mVideoCapture;
WenHung_Tengaf9c6fa2019-03-05 23:38:06 +08001039 }
Franklin Wu76c2f742019-11-07 11:01:11 -08001040
leo huang6193f932019-11-20 17:43:52 +08001041 @VisibleForTesting
1042 @Nullable
1043 CameraInfo getCameraInfo() {
1044 return mCamera != null ? mCamera.getCameraInfo() : null;
1045 }
1046
1047 @VisibleForTesting
1048 @Nullable
1049 CameraControl getCameraControl() {
1050 return mCamera != null ? mCamera.getCameraControl() : null;
1051 }
Trevor McGuirec63cd732019-02-06 13:12:50 -08001052}