Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 1 | /* |
| 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 Wu | 393daa9 | 2019-02-20 11:05:19 -0800 | [diff] [blame] | 17 | package androidx.camera.integration.core; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 18 | |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 19 | import static androidx.camera.core.ImageCapture.FLASH_MODE_AUTO; |
| 20 | import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF; |
| 21 | import static androidx.camera.core.ImageCapture.FLASH_MODE_ON; |
| 22 | |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 23 | import android.content.ContentValues; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 24 | import android.content.pm.PackageInfo; |
| 25 | import android.content.pm.PackageManager; |
| 26 | import android.content.pm.PackageManager.NameNotFoundException; |
| 27 | import android.graphics.Color; |
| 28 | import android.graphics.Matrix; |
| 29 | import android.graphics.SurfaceTexture; |
| 30 | import android.os.Bundle; |
| 31 | import android.os.Environment; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 32 | import android.os.StrictMode; |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 33 | import android.os.SystemClock; |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 34 | import android.provider.MediaStore; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 35 | import android.util.Log; |
| 36 | import android.util.Size; |
| 37 | import android.view.Surface; |
| 38 | import android.view.TextureView; |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 39 | import android.view.View; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 40 | import android.widget.Button; |
Wenhung Teng | 7b61dd0 | 2019-02-25 10:00:03 -0800 | [diff] [blame] | 41 | import android.widget.ImageButton; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 42 | import android.widget.TextView; |
| 43 | import android.widget.Toast; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 44 | |
leo huang | dc156bc | 2019-07-25 13:07:12 +0800 | [diff] [blame] | 45 | import androidx.annotation.NonNull; |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 46 | import androidx.annotation.Nullable; |
wegin lee | 7c98792 | 2019-02-28 10:05:25 -0800 | [diff] [blame] | 47 | import androidx.annotation.VisibleForTesting; |
Franklin Wu | 87eadea | 2019-02-15 14:01:24 -0800 | [diff] [blame] | 48 | import androidx.appcompat.app.AppCompatActivity; |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 49 | import androidx.camera.core.Camera; |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 50 | import androidx.camera.core.CameraControl; |
| 51 | import androidx.camera.core.CameraInfo; |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 52 | import androidx.camera.core.CameraSelector; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 53 | import androidx.camera.core.ImageAnalysis; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 54 | import androidx.camera.core.ImageCapture; |
husaynhakeem | 175db56 | 2020-01-17 11:26:51 -0800 | [diff] [blame] | 55 | import androidx.camera.core.ImageCaptureException; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 56 | import androidx.camera.core.Preview; |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 57 | import androidx.camera.core.SurfaceRequest; |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 58 | import androidx.camera.core.TorchState; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 59 | import androidx.camera.core.UseCase; |
| 60 | import androidx.camera.core.VideoCapture; |
charcoalchen | d67a03d | 2019-12-17 19:48:09 +0800 | [diff] [blame] | 61 | import androidx.camera.core.impl.VideoCaptureConfig; |
Sergey | 3f890dd | 2019-12-30 19:51:47 +0400 | [diff] [blame] | 62 | import androidx.camera.core.impl.utils.executor.CameraXExecutors; |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 63 | import androidx.camera.lifecycle.ProcessCameraProvider; |
Franklin Wu | 76c2f74 | 2019-11-07 11:01:11 -0800 | [diff] [blame] | 64 | import androidx.concurrent.futures.CallbackToFutureAdapter; |
Franklin Wu | 393daa9 | 2019-02-20 11:05:19 -0800 | [diff] [blame] | 65 | import androidx.core.app.ActivityCompat; |
Franklin Wu | 87eadea | 2019-02-15 14:01:24 -0800 | [diff] [blame] | 66 | import androidx.core.content.ContextCompat; |
Franklin Wu | 87eadea | 2019-02-15 14:01:24 -0800 | [diff] [blame] | 67 | import androidx.lifecycle.MutableLiveData; |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 68 | import androidx.lifecycle.Observer; |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 69 | import androidx.test.espresso.IdlingResource; |
wegin lee | 7c98792 | 2019-02-28 10:05:25 -0800 | [diff] [blame] | 70 | import androidx.test.espresso.idling.CountingIdlingResource; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 71 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 72 | import com.google.common.base.Preconditions; |
| 73 | import com.google.common.util.concurrent.FutureCallback; |
| 74 | import com.google.common.util.concurrent.Futures; |
Franklin Wu | 76c2f74 | 2019-11-07 11:01:11 -0800 | [diff] [blame] | 75 | import com.google.common.util.concurrent.ListenableFuture; |
| 76 | |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 77 | import java.io.File; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 78 | import java.math.BigDecimal; |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 79 | import java.util.List; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 80 | import java.util.concurrent.atomic.AtomicLong; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 81 | |
| 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 | */ |
| 92 | public class CameraXActivity extends AppCompatActivity |
AL Ho | 8883e6e | 2019-11-06 18:44:14 +0800 | [diff] [blame] | 93 | implements ActivityCompat.OnRequestPermissionsResultCallback { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 94 | 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 McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 98 | static final CameraSelector BACK_SELECTOR = |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 99 | new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build(); |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 100 | static final CameraSelector FRONT_SELECTOR = |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 101 | new CameraSelector.Builder().requireLensFacing( |
| 102 | CameraSelector.LENS_FACING_FRONT).build(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 103 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 104 | private boolean mPermissionsGranted = false; |
| 105 | private CallbackToFutureAdapter.Completer<Boolean> mPermissionsCompleter; |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 106 | private final AtomicLong mImageAnalysisFrameCount = new AtomicLong(0); |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 107 | private final AtomicLong mPreviewFrameCount = new AtomicLong(0); |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 108 | private final MutableLiveData<String> mImageAnalysisResult = new MutableLiveData<>(); |
| 109 | private VideoFileSaver mVideoFileSaver; |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 110 | /** The camera to use */ |
| 111 | CameraSelector mCurrentCameraSelector = BACK_SELECTOR; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 112 | @CameraSelector.LensFacing |
| 113 | int mCurrentCameraLensFacing = CameraSelector.LENS_FACING_BACK; |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 114 | ProcessCameraProvider mCameraProvider; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 115 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 116 | // TODO: Move the analysis processing, capture processing to separate threads, so |
| 117 | // there is smaller impact on the preview. |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 118 | private String mCurrentCameraDirection = "BACKWARD"; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 119 | private Preview mPreview; |
| 120 | private ImageAnalysis mImageAnalysis; |
| 121 | private ImageCapture mImageCapture; |
| 122 | private VideoCapture mVideoCapture; |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 123 | private Camera mCamera; |
husaynhakeem | 352a087 | 2019-11-19 15:19:57 -0800 | [diff] [blame] | 124 | @ImageCapture.CaptureMode |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 125 | private int mCaptureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY; |
Xi Zhang | a3762c5 | 2019-10-22 08:09:32 -0700 | [diff] [blame] | 126 | // Synthetic Accessor |
| 127 | @SuppressWarnings("WeakerAccess") |
| 128 | TextureView mTextureView; |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 129 | @SuppressWarnings("WeakerAccess") |
| 130 | SurfaceTexture mSurfaceTexture; |
| 131 | @SuppressWarnings("WeakerAccess") |
| 132 | private Size mResolution; |
| 133 | @SuppressWarnings("WeakerAccess") |
| 134 | ListenableFuture<Void> mSurfaceReleaseFuture; |
| 135 | @SuppressWarnings("WeakerAccess") |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 136 | SurfaceRequest mSurfaceRequest; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 137 | |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 138 | |
wegin lee | 7c98792 | 2019-02-28 10:05:25 -0800 | [diff] [blame] | 139 | // Espresso testing variables |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 140 | private final CountingIdlingResource mViewIdlingResource = new CountingIdlingResource("view"); |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 141 | private static final int FRAMES_UNTIL_VIEW_IS_READY = 5; |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 142 | private final CountingIdlingResource mAnalysisIdlingResource = |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 143 | new CountingIdlingResource("analysis"); |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 144 | private final CountingIdlingResource mImageSavedIdlingResource = |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 145 | new CountingIdlingResource("imagesaved"); |
Xi Zhang | c52e4f3 | 2019-09-16 10:52:27 +0800 | [diff] [blame] | 146 | |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 147 | /** |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 148 | * Retrieve idling resource that waits for image received by analyzer). |
husaynhakeem | 175db56 | 2020-01-17 11:26:51 -0800 | [diff] [blame] | 149 | * |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 150 | * @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. |
husaynhakeem | 175db56 | 2020-01-17 11:26:51 -0800 | [diff] [blame] | 160 | * |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 161 | * @return idline resource for image capture |
| 162 | */ |
| 163 | @VisibleForTesting |
| 164 | @NonNull |
| 165 | public IdlingResource getViewIdlingResource() { |
| 166 | return mViewIdlingResource; |
| 167 | } |
| 168 | |
| 169 | /** |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 170 | * Retrieve idling resource that waits for capture to complete (save or error). |
Xi Zhang | c52e4f3 | 2019-09-16 10:52:27 +0800 | [diff] [blame] | 171 | * |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 172 | * @return idline resource for image capture |
| 173 | */ |
| 174 | @VisibleForTesting |
| 175 | @NonNull |
| 176 | public IdlingResource getImageSavedIdlingResource() { |
| 177 | return mImageSavedIdlingResource; |
| 178 | } |
wegin lee | 7c98792 | 2019-02-28 10:05:25 -0800 | [diff] [blame] | 179 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 180 | /** |
fungja | 8875bfd | 2020-01-30 15:18:40 -0800 | [diff] [blame] | 181 | * Retrieve idling resource that waits for view to display frames before proceeding. |
husaynhakeem | 175db56 | 2020-01-17 11:26:51 -0800 | [diff] [blame] | 182 | */ |
weginlee | 692104b | 2019-12-11 20:17:36 +0800 | [diff] [blame] | 183 | @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 McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 192 | * 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 huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 197 | private void createPreview() { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 198 | Button button = this.findViewById(R.id.PreviewToggle); |
| 199 | button.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 200 | enablePreview(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 201 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 202 | button.setOnClickListener( |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 203 | new View.OnClickListener() { |
| 204 | @Override |
| 205 | public void onClick(View view) { |
| 206 | Button buttonView = (Button) view; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 207 | if (mPreview != null) { |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 208 | // Remove the use case |
| 209 | buttonView.setBackgroundColor(Color.RED); |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 210 | mCameraProvider.unbind(mPreview); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 211 | mPreview = null; |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 212 | } else { |
| 213 | // Add the use case |
| 214 | buttonView.setBackgroundColor(Color.LTGRAY); |
Franklin Wu | 76c2f74 | 2019-11-07 11:01:11 -0800 | [diff] [blame] | 215 | enablePreview(); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 216 | } |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 217 | } |
| 218 | }); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 219 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 220 | Log.i(TAG, "Got UseCase: " + mPreview); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 221 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 222 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 223 | void enablePreview() { |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame] | 224 | mPreview = new Preview.Builder() |
| 225 | .setTargetName("Preview") |
| 226 | .build(); |
Xi Zhang | c52e4f3 | 2019-09-16 10:52:27 +0800 | [diff] [blame] | 227 | Log.d(TAG, "enablePreview"); |
Xi Zhang | 9d84866 | 2019-10-08 13:00:40 -0700 | [diff] [blame] | 228 | |
Trevor McGuire | 7ded947 | 2020-01-29 16:26:14 -0800 | [diff] [blame] | 229 | mPreview.setSurfaceProvider( |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 230 | (surfaceRequest) -> { |
| 231 | mResolution = surfaceRequest.getResolution(); |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 232 | |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 233 | 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 Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 241 | mSurfaceReleaseFuture = null; |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 242 | } |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 243 | }); |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 244 | tryToProvidePreviewSurface(); |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 245 | }); |
| 246 | |
weginlee | 692104b | 2019-12-11 20:17:36 +0800 | [diff] [blame] | 247 | resetViewIdlingResource(); |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 248 | |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 249 | if (bindToLifecycleSafely(mPreview, R.id.PreviewToggle) == null) { |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 250 | mPreview = null; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 251 | return; |
| 252 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 253 | } |
| 254 | |
AL Ho | 8883e6e | 2019-11-06 18:44:14 +0800 | [diff] [blame] | 255 | void transformPreview(@NonNull Size resolution) { |
| 256 | if (resolution.getWidth() == 0 || resolution.getHeight() == 0) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 257 | return; |
| 258 | } |
| 259 | |
Xi Zhang | 9d84866 | 2019-10-08 13:00:40 -0700 | [diff] [blame] | 260 | if (mTextureView.getWidth() == 0 || mTextureView.getHeight() == 0) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 261 | return; |
| 262 | } |
| 263 | |
| 264 | Matrix matrix = new Matrix(); |
| 265 | |
Xi Zhang | 9d84866 | 2019-10-08 13:00:40 -0700 | [diff] [blame] | 266 | int left = mTextureView.getLeft(); |
| 267 | int right = mTextureView.getRight(); |
| 268 | int top = mTextureView.getTop(); |
| 269 | int bottom = mTextureView.getBottom(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 270 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 271 | // Compute the preview ui size based on the available width, height, and ui orientation. |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 272 | int viewWidth = (right - left); |
| 273 | int viewHeight = (bottom - top); |
| 274 | |
| 275 | int displayRotation = getDisplayRotation(); |
| 276 | Size scaled = |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 277 | calculatePreviewViewDimens( |
AL Ho | 8883e6e | 2019-11-06 18:44:14 +0800 | [diff] [blame] | 278 | resolution, viewWidth, viewHeight, displayRotation); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 279 | |
| 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 Zhang | 9d84866 | 2019-10-08 13:00:40 -0700 | [diff] [blame] | 306 | mTextureView.setTransform(matrix); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 307 | } |
| 308 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 309 | /** @return One of 0, 90, 180, 270. */ |
Chris Banes | ed18608 | 2020-02-07 21:36:02 -0800 | [diff] [blame] | 310 | @SuppressWarnings("deprecation") /* defaultDisplay */ |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 311 | private int getDisplayRotation() { |
| 312 | int displayRotation = getWindowManager().getDefaultDisplay().getRotation(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 313 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 314 | 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 McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 331 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 332 | return displayRotation; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 333 | } |
| 334 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 335 | private Size calculatePreviewViewDimens( |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 336 | 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 McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 344 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 345 | 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 McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 350 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 351 | // 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 McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 360 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 361 | return new Size(outWidth, outHeight); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 362 | } |
| 363 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 364 | /** |
| 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 huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 369 | private void createImageAnalysis() { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 370 | Button button = this.findViewById(R.id.AnalysisToggle); |
| 371 | button.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 372 | enableImageAnalysis(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 373 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 374 | button.setOnClickListener( |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 375 | new View.OnClickListener() { |
| 376 | @Override |
| 377 | public void onClick(View view) { |
| 378 | Button buttonView = (Button) view; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 379 | if (mImageAnalysis != null) { |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 380 | // Remove the use case |
| 381 | buttonView.setBackgroundColor(Color.RED); |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 382 | mCameraProvider.unbind(mImageAnalysis); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 383 | mImageAnalysis = null; |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 384 | } else { |
| 385 | // Add the use case |
| 386 | buttonView.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 387 | CameraXActivity.this.enableImageAnalysis(); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 388 | } |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 389 | } |
| 390 | }); |
| 391 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 392 | Log.i(TAG, "Got UseCase: " + mImageAnalysis); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 393 | } |
| 394 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 395 | void enableImageAnalysis() { |
husaynhakeem | 43c180d | 2019-11-18 12:01:20 -0800 | [diff] [blame] | 396 | mImageAnalysis = new ImageAnalysis.Builder() |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame] | 397 | .setTargetName("ImageAnalysis") |
| 398 | .build(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 399 | TextView textView = this.findViewById(R.id.textView); |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 400 | |
| 401 | // Make the analysis idling resource non-idle, until a frame received. |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 402 | mAnalysisIdlingResource.increment(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 403 | |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 404 | if (bindToLifecycleSafely(mImageAnalysis, R.id.AnalysisToggle) == null) { |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 405 | mImageAnalysis = null; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 406 | return; |
| 407 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 408 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 409 | mImageAnalysis.setAnalyzer( |
Franklin Wu | d688b2d | 2019-11-26 15:49:01 -0800 | [diff] [blame] | 410 | ContextCompat.getMainExecutor(this), |
Franklin Wu | f9c0040 | 2019-12-03 11:31:49 -0800 | [diff] [blame] | 411 | (image) -> { |
TY Chang | 2707b4d | 2019-11-11 17:59:53 +0800 | [diff] [blame] | 412 | // 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())); |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 417 | try { |
| 418 | if (!mAnalysisIdlingResource.isIdleNow()) { |
| 419 | mAnalysisIdlingResource.decrement(); |
| 420 | } |
| 421 | } catch (IllegalStateException e) { |
| 422 | Log.e(TAG, "Unexpected counter decrement"); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 423 | } |
TY Chang | 2707b4d | 2019-11-11 17:59:53 +0800 | [diff] [blame] | 424 | image.close(); |
| 425 | } |
| 426 | ); |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 427 | mImageAnalysisResult.observe( |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 428 | this, |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 429 | 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 McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 437 | } |
| 438 | }); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 439 | } |
| 440 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 441 | /** |
| 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 huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 446 | private void createImageCapture() { |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 447 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 448 | Button button = this.findViewById(R.id.PhotoToggle); |
| 449 | button.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 450 | enableImageCapture(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 451 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 452 | button.setOnClickListener( |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 453 | new View.OnClickListener() { |
| 454 | @Override |
| 455 | public void onClick(View view) { |
| 456 | Button buttonView = (Button) view; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 457 | if (mImageCapture != null) { |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 458 | // Remove the use case |
| 459 | buttonView.setBackgroundColor(Color.RED); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 460 | CameraXActivity.this.disableImageCapture(); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 461 | } else { |
| 462 | // Add the use case |
| 463 | buttonView.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 464 | CameraXActivity.this.enableImageCapture(); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 465 | } |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 466 | } |
| 467 | }); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 468 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 469 | Log.i(TAG, "Got UseCase: " + mImageCapture); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 470 | } |
| 471 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 472 | void enableImageCapture() { |
husaynhakeem | bf6c2f9 | 2019-11-18 14:49:02 -0800 | [diff] [blame] | 473 | mImageCapture = new ImageCapture.Builder() |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame] | 474 | .setCaptureMode(mCaptureMode) |
| 475 | .setTargetName("ImageCapture") |
| 476 | .build(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 477 | |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 478 | Camera camera = bindToLifecycleSafely(mImageCapture, R.id.PhotoToggle); |
| 479 | if (camera == null) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 480 | Button button = this.findViewById(R.id.Picture); |
| 481 | button.setOnClickListener(null); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 482 | mImageCapture = null; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 483 | return; |
| 484 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 485 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 486 | Button button = this.findViewById(R.id.Picture); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 487 | button.setOnClickListener( |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 488 | new View.OnClickListener() { |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 489 | long mStartCaptureTime = 0; |
| 490 | |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 491 | @Override |
| 492 | public void onClick(View view) { |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 493 | mImageSavedIdlingResource.increment(); |
| 494 | |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 495 | mStartCaptureTime = SystemClock.elapsedRealtime(); |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 496 | createDefaultPictureFolderIfNotExist(); |
| 497 | ContentValues contentValues = new ContentValues(); |
| 498 | contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg"); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 499 | ImageCapture.OutputFileOptions outputFileOptions = |
| 500 | new ImageCapture.OutputFileOptions.Builder( |
| 501 | getContentResolver(), |
| 502 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, |
Xi Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 503 | contentValues).build(); |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 504 | mImageCapture.takePicture(outputFileOptions, |
Franklin Wu | d688b2d | 2019-11-26 15:49:01 -0800 | [diff] [blame] | 505 | ContextCompat.getMainExecutor(CameraXActivity.this), |
Franklin Wu | 3b4ed3e | 2019-09-13 13:43:29 -0700 | [diff] [blame] | 506 | new ImageCapture.OnImageSavedCallback() { |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 507 | @Override |
Xi Zhang | ec5384d | 2020-01-27 15:01:28 -0800 | [diff] [blame] | 508 | public void onImageSaved( |
| 509 | @NonNull ImageCapture.OutputFileResults |
| 510 | outputFileResults) { |
| 511 | Log.d(TAG, "Saved image to " |
| 512 | + outputFileResults.getSavedUri()); |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 513 | try { |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 514 | mImageSavedIdlingResource.decrement(); |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 515 | } catch (IllegalStateException e) { |
| 516 | Log.e(TAG, "Error: unexpected onImageSaved " |
| 517 | + "callback received. Continuing."); |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 518 | } |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 519 | |
| 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 Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 530 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 531 | |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 532 | @Override |
husaynhakeem | 175db56 | 2020-01-17 11:26:51 -0800 | [diff] [blame] | 533 | public void onError(@NonNull ImageCaptureException exception) { |
| 534 | Log.e(TAG, "Failed to save image.", exception.getCause()); |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 535 | try { |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 536 | mImageSavedIdlingResource.decrement(); |
fungja | 5e3d86b | 2019-09-13 11:31:54 -0700 | [diff] [blame] | 537 | } catch (IllegalStateException e) { |
| 538 | Log.e(TAG, "Error: unexpected onImageSaved " |
| 539 | + "callback received. Continuing."); |
weginlee | 7e7afa0 | 2019-03-05 17:18:06 +0800 | [diff] [blame] | 540 | } |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 541 | } |
| 542 | }); |
| 543 | } |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 544 | }); |
WenHung_Teng | 4b5372c | 2019-03-04 16:58:21 +0800 | [diff] [blame] | 545 | |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 546 | refreshFlashButton(); |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 547 | |
| 548 | |
| 549 | Button btnCaptureQuality = this.findViewById(R.id.capture_quality); |
| 550 | btnCaptureQuality.setVisibility(View.VISIBLE); |
| 551 | btnCaptureQuality.setText( |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 552 | mCaptureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY ? "MAX" : "MIN"); |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 553 | btnCaptureQuality.setOnClickListener(new View.OnClickListener() { |
| 554 | @Override |
| 555 | public void onClick(View view) { |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 556 | mCaptureMode = (mCaptureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY |
| 557 | ? ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY |
| 558 | : ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY); |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 559 | rebindUseCases(); |
| 560 | } |
| 561 | }); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 562 | } |
| 563 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 564 | void disableImageCapture() { |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 565 | mCameraProvider.unbind(mImageCapture); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 566 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 567 | mImageCapture = null; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 568 | Button button = this.findViewById(R.id.Picture); |
| 569 | button.setOnClickListener(null); |
WenHung_Teng | 4b5372c | 2019-03-04 16:58:21 +0800 | [diff] [blame] | 570 | |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 571 | Button btnCaptureQuality = this.findViewById(R.id.capture_quality); |
| 572 | btnCaptureQuality.setVisibility(View.GONE); |
| 573 | |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 574 | 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) { |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 586 | @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 huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 593 | } |
| 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_Teng | 4b5372c | 2019-03-04 16:58:21 +0800 | [diff] [blame] | 606 | } |
| 607 | |
| 608 | private void refreshFlashButtonIcon() { |
| 609 | ImageButton flashToggle = findViewById(R.id.flash_toggle); |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 610 | @ImageCapture.FlashMode int flashMode = mImageCapture.getFlashMode(); |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 611 | switch (flashMode) { |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 612 | case FLASH_MODE_ON: |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 613 | flashToggle.setImageResource(R.drawable.ic_flash_on); |
| 614 | break; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 615 | case FLASH_MODE_OFF: |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 616 | flashToggle.setImageResource(R.drawable.ic_flash_off); |
| 617 | break; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 618 | case FLASH_MODE_AUTO: |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 619 | 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); |
Sergey | 3f890dd | 2019-12-30 19:51:47 +0400 | [diff] [blame] | 635 | 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_Teng | 4b5372c | 2019-03-04 16:58:21 +0800 | [diff] [blame] | 647 | } |
| 648 | }); |
WenHung_Teng | 4b5372c | 2019-03-04 16:58:21 +0800 | [diff] [blame] | 649 | } else { |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 650 | Log.d(TAG, "No flash unit"); |
| 651 | torchToggle.setVisibility(View.INVISIBLE); |
| 652 | torchToggle.setOnClickListener(null); |
WenHung_Teng | 4b5372c | 2019-03-04 16:58:21 +0800 | [diff] [blame] | 653 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 654 | } |
| 655 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 656 | /** |
| 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 huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 662 | private void createVideoCapture() { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 663 | Button button = this.findViewById(R.id.VideoToggle); |
| 664 | button.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 665 | enableVideoCapture(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 666 | |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 667 | mVideoFileSaver = new VideoFileSaver(); |
| 668 | mVideoFileSaver.setRootDirectory( |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 669 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)); |
| 670 | |
| 671 | button.setOnClickListener( |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 672 | new View.OnClickListener() { |
| 673 | @Override |
| 674 | public void onClick(View view) { |
| 675 | Button buttonView = (Button) view; |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 676 | if (mVideoCapture != null) { |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 677 | // Remove the use case |
| 678 | buttonView.setBackgroundColor(Color.RED); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 679 | CameraXActivity.this.disableVideoCapture(); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 680 | } else { |
| 681 | // Add the use case |
| 682 | buttonView.setBackgroundColor(Color.LTGRAY); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 683 | CameraXActivity.this.enableVideoCapture(); |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 684 | } |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 685 | } |
| 686 | }); |
| 687 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 688 | Log.i(TAG, "Got UseCase: " + mVideoCapture); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 689 | } |
| 690 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 691 | void enableVideoCapture() { |
husaynhakeem | 687d79a | 2019-11-13 16:33:20 -0800 | [diff] [blame] | 692 | mVideoCapture = new VideoCaptureConfig.Builder() |
| 693 | .setTargetName("VideoCapture") |
| 694 | .build(); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 695 | |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 696 | if (bindToLifecycleSafely(mVideoCapture, R.id.VideoToggle) == null) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 697 | Button button = this.findViewById(R.id.Video); |
| 698 | button.setOnClickListener(null); |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 699 | mVideoCapture = null; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 700 | return; |
| 701 | } |
| 702 | |
| 703 | Button button = this.findViewById(R.id.Video); |
Franklin Wu | d688b2d | 2019-11-26 15:49:01 -0800 | [diff] [blame] | 704 | 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 McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 724 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 725 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 726 | void disableVideoCapture() { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 727 | Button button = this.findViewById(R.id.Video); |
| 728 | button.setOnClickListener(null); |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 729 | mCameraProvider.unbind(mVideoCapture); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 730 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 731 | mVideoCapture = null; |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 732 | } |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 733 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 734 | /** Creates all the use cases. */ |
| 735 | private void createUseCases() { |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 736 | createImageCapture(); |
| 737 | createPreview(); |
| 738 | createImageAnalysis(); |
| 739 | createVideoCapture(); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 740 | } |
| 741 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 742 | @SuppressWarnings("UnstableApiUsage") |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 743 | @Override |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 744 | protected void onCreate(@Nullable Bundle savedInstanceState) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 745 | super.onCreate(savedInstanceState); |
| 746 | setContentView(R.layout.activity_camera_xmain); |
Xi Zhang | 9d84866 | 2019-10-08 13:00:40 -0700 | [diff] [blame] | 747 | mTextureView = findViewById(R.id.textureView); |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 748 | 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 McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 763 | * {@link CameraXActivity#mSurfaceRequest} is null), but the camera |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 764 | * 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 McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 775 | if (mSurfaceRequest == null && mSurfaceReleaseFuture != null) { |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 776 | 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. |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 787 | if (mPreviewFrameCount.getAndIncrement() >= FRAMES_UNTIL_VIEW_IS_READY) { |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 788 | try { |
| 789 | if (!mViewIdlingResource.isIdleNow()) { |
weginlee | 692104b | 2019-12-11 20:17:36 +0800 | [diff] [blame] | 790 | Log.d(TAG, FRAMES_UNTIL_VIEW_IS_READY + " or more counted on preview." |
| 791 | + " Make IdlingResource idle."); |
fungja | f3530f7 | 2019-09-17 14:18:19 -0700 | [diff] [blame] | 792 | mViewIdlingResource.decrement(); |
| 793 | } |
| 794 | } catch (IllegalStateException e) { |
| 795 | Log.e(TAG, "Unexpected decrement. Continuing"); |
| 796 | } |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 797 | } |
| 798 | } |
| 799 | }); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 800 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 801 | 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) { |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 810 | mCurrentCameraDirection = newCameraDirection; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 811 | } |
| 812 | } |
| 813 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 814 | 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 Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 831 | @Override |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 832 | public void onSuccess(@Nullable List<Void> ignored) { |
Scott Nien | 3d89b90 | 2019-02-21 14:42:18 +0800 | [diff] [blame] | 833 | CameraXActivity.this.setupCamera(); |
| 834 | } |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 835 | |
| 836 | @Override |
| 837 | public void onFailure(@NonNull Throwable t) { |
| 838 | throw new RuntimeException("Initialization failed.", t); |
| 839 | } |
| 840 | }, ContextCompat.getMainExecutor(this)); |
Trevor McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 841 | } |
| 842 | |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 843 | @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 McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 853 | if (mResolution == null || mSurfaceTexture == null || mSurfaceRequest == null) { |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 854 | return; |
| 855 | } |
| 856 | |
| 857 | mSurfaceTexture.setDefaultBufferSize(mResolution.getWidth(), mResolution.getHeight()); |
| 858 | |
| 859 | final Surface surface = new Surface(mSurfaceTexture); |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 860 | final ListenableFuture<Void> surfaceReleaseFuture = mSurfaceRequest.setSurface(surface); |
| 861 | mSurfaceReleaseFuture = surfaceReleaseFuture; |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 862 | mSurfaceReleaseFuture.addListener(() -> { |
| 863 | surface.release(); |
| 864 | if (mSurfaceReleaseFuture == surfaceReleaseFuture) { |
| 865 | mSurfaceReleaseFuture = null; |
| 866 | } |
| 867 | }, ContextCompat.getMainExecutor(mTextureView.getContext())); |
Trevor McGuire | 049c918 | 2020-01-31 11:15:33 -0800 | [diff] [blame] | 868 | mSurfaceRequest = null; |
Scott Nien | 80b49e7 | 2019-12-11 23:05:18 +0800 | [diff] [blame] | 869 | |
| 870 | transformPreview(mResolution); |
| 871 | } |
| 872 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 873 | void setupCamera() { |
| 874 | // Check for permissions before proceeding. |
| 875 | if (!mPermissionsGranted) { |
| 876 | Log.d(TAG, "Permissions denied."); |
| 877 | return; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 878 | } |
| 879 | |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 880 | Log.d(TAG, "Camera direction: " + mCurrentCameraDirection); |
| 881 | if (mCurrentCameraDirection.equalsIgnoreCase("BACKWARD")) { |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 882 | mCurrentCameraSelector = BACK_SELECTOR; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 883 | mCurrentCameraLensFacing = CameraSelector.LENS_FACING_BACK; |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 884 | } else if (mCurrentCameraDirection.equalsIgnoreCase("FORWARD")) { |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 885 | mCurrentCameraSelector = FRONT_SELECTOR; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 886 | mCurrentCameraLensFacing = CameraSelector.LENS_FACING_FRONT; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 887 | } else { |
weginlee | 8bfd064 | 2019-02-14 15:15:00 +0800 | [diff] [blame] | 888 | throw new RuntimeException("Invalid camera direction: " + mCurrentCameraDirection); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 889 | } |
Trevor McGuire | a743e1f | 2019-10-28 10:42:09 -0700 | [diff] [blame] | 890 | Log.d(TAG, "Using camera lens facing: " + mCurrentCameraSelector); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 891 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 892 | CameraXActivity.this.createUseCases(); |
| 893 | refreshTorchButton(); |
Wenhung Teng | 7b61dd0 | 2019-02-25 10:00:03 -0800 | [diff] [blame] | 894 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 895 | 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) { |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 900 | if (mCurrentCameraLensFacing == CameraSelector.LENS_FACING_BACK) { |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 901 | mCurrentCameraSelector = FRONT_SELECTOR; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 902 | mCurrentCameraLensFacing = CameraSelector.LENS_FACING_FRONT; |
| 903 | } else if (mCurrentCameraLensFacing == CameraSelector.LENS_FACING_FRONT) { |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 904 | mCurrentCameraSelector = BACK_SELECTOR; |
husaynhakeem | 661b7e9 | 2019-11-22 16:39:36 -0800 | [diff] [blame] | 905 | mCurrentCameraLensFacing = CameraSelector.LENS_FACING_BACK; |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 906 | } |
Wenhung Teng | 7b61dd0 | 2019-02-25 10:00:03 -0800 | [diff] [blame] | 907 | |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 908 | Log.d(TAG, "Change camera direction: " + mCurrentCameraSelector); |
| 909 | rebindUseCases(); |
| 910 | refreshTorchButton(); |
| 911 | |
| 912 | } |
| 913 | }); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 914 | } |
| 915 | |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 916 | private void rebindUseCases() { |
| 917 | // Rebind all use cases. |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 918 | mCameraProvider.unbindAll(); |
Scott Nien | fb8affc | 2019-06-14 16:41:18 +0800 | [diff] [blame] | 919 | 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 McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 933 | 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 McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 944 | } |
| 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 Zhang | 6f28a57 | 2020-02-10 10:50:01 -0800 | [diff] [blame] | 961 | private void createDefaultPictureFolderIfNotExist() { |
| 962 | File pictureFolder = Environment.getExternalStoragePublicDirectory( |
| 963 | Environment.DIRECTORY_PICTURES); |
| 964 | if (!pictureFolder.exists()) { |
| 965 | pictureFolder.mkdir(); |
| 966 | } |
| 967 | } |
| 968 | |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 969 | /** 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_Teng | 1d12f20 | 2019-08-14 14:06:08 +0800 | [diff] [blame] | 990 | int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 991 | 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 McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 997 | mPermissionsCompleter.set(true); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 998 | } else { |
| 999 | Log.d(TAG, "Permissions Denied."); |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 1000 | mPermissionsCompleter.set(false); |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 1001 | } |
| 1002 | return; |
| 1003 | } |
| 1004 | default: |
| 1005 | // No-op |
| 1006 | } |
| 1007 | } |
| 1008 | |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 1009 | @Nullable |
| 1010 | private Camera bindToLifecycleSafely(UseCase useCase, int buttonViewId) { |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 1011 | try { |
Trevor McGuire | cf8e22b | 2019-11-14 22:12:36 -0800 | [diff] [blame] | 1012 | mCamera = mCameraProvider.bindToLifecycle(this, mCurrentCameraSelector, |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 1013 | useCase); |
| 1014 | return mCamera; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 1015 | } 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 McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 1021 | } |
WenHung_Teng | 60fffca | 2019-11-05 14:27:35 +0800 | [diff] [blame] | 1022 | return null; |
Trevor McGuire | 470a44f | 2019-02-11 13:19:37 -0800 | [diff] [blame] | 1023 | } |
| 1024 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 1025 | Preview getPreview() { |
| 1026 | return mPreview; |
WenHung_Teng | af9c6fa | 2019-03-05 23:38:06 +0800 | [diff] [blame] | 1027 | } |
| 1028 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 1029 | ImageAnalysis getImageAnalysis() { |
| 1030 | return mImageAnalysis; |
WenHung_Teng | af9c6fa | 2019-03-05 23:38:06 +0800 | [diff] [blame] | 1031 | } |
| 1032 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 1033 | ImageCapture getImageCapture() { |
| 1034 | return mImageCapture; |
WenHung_Teng | af9c6fa | 2019-03-05 23:38:06 +0800 | [diff] [blame] | 1035 | } |
| 1036 | |
leo huang | 3a95a1d | 2019-03-28 13:35:17 +0800 | [diff] [blame] | 1037 | VideoCapture getVideoCapture() { |
| 1038 | return mVideoCapture; |
WenHung_Teng | af9c6fa | 2019-03-05 23:38:06 +0800 | [diff] [blame] | 1039 | } |
Franklin Wu | 76c2f74 | 2019-11-07 11:01:11 -0800 | [diff] [blame] | 1040 | |
leo huang | 6193f93 | 2019-11-20 17:43:52 +0800 | [diff] [blame] | 1041 | @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 McGuire | c63cd73 | 2019-02-06 13:12:50 -0800 | [diff] [blame] | 1052 | } |