|  |  |  | @ -1,8 +1,20 @@ | 
			
		
	
		
			
				
					|  |  |  |  | package com.otaliastudios.cameraview.engine; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import android.annotation.SuppressLint; | 
			
		
	
		
			
				
					|  |  |  |  | import android.content.Context; | 
			
		
	
		
			
				
					|  |  |  |  | import android.graphics.PointF; | 
			
		
	
		
			
				
					|  |  |  |  | import android.graphics.SurfaceTexture; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.Camera; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.CameraAccessException; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.CameraCaptureSession; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.CameraCharacteristics; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.CameraDevice; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.CameraManager; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.CaptureRequest; | 
			
		
	
		
			
				
					|  |  |  |  | import android.hardware.camera2.params.StreamConfigurationMap; | 
			
		
	
		
			
				
					|  |  |  |  | import android.location.Location; | 
			
		
	
		
			
				
					|  |  |  |  | import android.os.Build; | 
			
		
	
		
			
				
					|  |  |  |  | import android.view.Surface; | 
			
		
	
		
			
				
					|  |  |  |  | import android.view.SurfaceHolder; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.CameraException; | 
			
		
	
	
		
			
				
					|  |  |  | @ -18,13 +30,20 @@ import com.otaliastudios.cameraview.controls.Hdr; | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.controls.Mode; | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.controls.WhiteBalance; | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.gesture.Gesture; | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.internal.utils.Task; | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.size.AspectRatio; | 
			
		
	
		
			
				
					|  |  |  |  | import com.otaliastudios.cameraview.size.Size; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import java.io.File; | 
			
		
	
		
			
				
					|  |  |  |  | import java.io.IOException; | 
			
		
	
		
			
				
					|  |  |  |  | import java.util.ArrayList; | 
			
		
	
		
			
				
					|  |  |  |  | import java.util.Arrays; | 
			
		
	
		
			
				
					|  |  |  |  | import java.util.List; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import androidx.annotation.NonNull; | 
			
		
	
		
			
				
					|  |  |  |  | import androidx.annotation.Nullable; | 
			
		
	
		
			
				
					|  |  |  |  | import androidx.annotation.RequiresApi; | 
			
		
	
		
			
				
					|  |  |  |  | import androidx.annotation.WorkerThread; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) | 
			
		
	
	
		
			
				
					|  |  |  | @ -33,36 +52,391 @@ public class Camera2Engine extends CameraEngine { | 
			
		
	
		
			
				
					|  |  |  |  |     private static final String TAG = Camera2Engine.class.getSimpleName(); | 
			
		
	
		
			
				
					|  |  |  |  |     private static final CameraLogger LOG = CameraLogger.create(TAG); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private final CameraManager mManager; | 
			
		
	
		
			
				
					|  |  |  |  |     private String mCameraId; | 
			
		
	
		
			
				
					|  |  |  |  |     private CameraDevice mCamera; | 
			
		
	
		
			
				
					|  |  |  |  |     private CameraCaptureSession mSession; // TODO must be released and nulled
 | 
			
		
	
		
			
				
					|  |  |  |  |     private CaptureRequest.Builder mPreviewStreamRequestBuilder; // TODO must be nulled
 | 
			
		
	
		
			
				
					|  |  |  |  |     private CaptureRequest mPreviewStreamRequest; // TODO must be nulled
 | 
			
		
	
		
			
				
					|  |  |  |  |     private boolean mIsBound = false; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     public Camera2Engine(Callback callback) { | 
			
		
	
		
			
				
					|  |  |  |  |         super(callback); | 
			
		
	
		
			
				
					|  |  |  |  |         mMapper = Mapper.get(Engine.CAMERA2); | 
			
		
	
		
			
				
					|  |  |  |  |         mManager = (CameraManager) mCallback.getContext().getSystemService(Context.CAMERA_SERVICE); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceAvailable() { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void schedule(@Nullable final Task<Void> task, final boolean ensureAvailable, final Runnable action) { | 
			
		
	
		
			
				
					|  |  |  |  |         mHandler.post(new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 if (ensureAvailable && !isCameraAvailable()) { | 
			
		
	
		
			
				
					|  |  |  |  |                     if (task != null) task.end(null); | 
			
		
	
		
			
				
					|  |  |  |  |                 } else { | 
			
		
	
		
			
				
					|  |  |  |  |                     action.run(); | 
			
		
	
		
			
				
					|  |  |  |  |                     if (task != null) task.end(null); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceChanged() { | 
			
		
	
		
			
				
					|  |  |  |  |     @NonNull | 
			
		
	
		
			
				
					|  |  |  |  |     private <T> T readCharacteristic(@NonNull CameraCharacteristics characteristics, | 
			
		
	
		
			
				
					|  |  |  |  |                                      @NonNull CameraCharacteristics.Key<T> key, | 
			
		
	
		
			
				
					|  |  |  |  |                                      @NonNull T fallback) { | 
			
		
	
		
			
				
					|  |  |  |  |         T value = characteristics.get(key); | 
			
		
	
		
			
				
					|  |  |  |  |         return value == null ? fallback : value; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @NonNull | 
			
		
	
		
			
				
					|  |  |  |  |     private Size computePreviewStreamSize() throws CameraAccessException { | 
			
		
	
		
			
				
					|  |  |  |  |         CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId); | 
			
		
	
		
			
				
					|  |  |  |  |         StreamConfigurationMap streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | 
			
		
	
		
			
				
					|  |  |  |  |         if (streamMap == null) throw new RuntimeException("StreamConfigurationMap is null. Should not happen."); | 
			
		
	
		
			
				
					|  |  |  |  |         // This works because our previews return either a SurfaceTexture or a SurfaceHolder, which are
 | 
			
		
	
		
			
				
					|  |  |  |  |         // accepted class types by the getOutputSizes method.
 | 
			
		
	
		
			
				
					|  |  |  |  |         android.util.Size[] sizes = streamMap.getOutputSizes(mPreview.getOutputClass()); | 
			
		
	
		
			
				
					|  |  |  |  |         List<Size> candidates = new ArrayList<>(sizes.length); | 
			
		
	
		
			
				
					|  |  |  |  |         for (android.util.Size size : sizes) { | 
			
		
	
		
			
				
					|  |  |  |  |             Size add = new Size(size.getWidth(), size.getHeight()); | 
			
		
	
		
			
				
					|  |  |  |  |             if (!candidates.contains(add)) candidates.add(add); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         return computePreviewStreamSize(candidates); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceDestroyed() { | 
			
		
	
		
			
				
					|  |  |  |  |     private boolean collectCameraId() throws CameraAccessException { | 
			
		
	
		
			
				
					|  |  |  |  |         int internalFacing = mMapper.map(mFacing); | 
			
		
	
		
			
				
					|  |  |  |  |         int cameras = mManager.getCameraIdList().length; | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i("collectCameraId", "Facing:", mFacing, "Internal:", internalFacing, "Cameras:", cameras); | 
			
		
	
		
			
				
					|  |  |  |  |         for (String cameraId : mManager.getCameraIdList()) { | 
			
		
	
		
			
				
					|  |  |  |  |             CameraCharacteristics characteristics = mManager.getCameraCharacteristics(cameraId); | 
			
		
	
		
			
				
					|  |  |  |  |             if (internalFacing == readCharacteristic(characteristics, CameraCharacteristics.LENS_FACING, -99)) { | 
			
		
	
		
			
				
					|  |  |  |  |                 mCameraId = cameraId; | 
			
		
	
		
			
				
					|  |  |  |  |                 mSensorOffset = readCharacteristic(characteristics, CameraCharacteristics.SENSOR_ORIENTATION, 0); | 
			
		
	
		
			
				
					|  |  |  |  |                 return true; | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         return false; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private boolean isCameraAvailable() { | 
			
		
	
		
			
				
					|  |  |  |  |         switch (getState()) { | 
			
		
	
		
			
				
					|  |  |  |  |             // If we are stopped, don't.
 | 
			
		
	
		
			
				
					|  |  |  |  |             case STATE_STOPPED: | 
			
		
	
		
			
				
					|  |  |  |  |                 return false; | 
			
		
	
		
			
				
					|  |  |  |  |             // If we are going to be closed, don't act on camera.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Even if mCamera != null, it might have been released.
 | 
			
		
	
		
			
				
					|  |  |  |  |             case STATE_STOPPING: | 
			
		
	
		
			
				
					|  |  |  |  |                 return false; | 
			
		
	
		
			
				
					|  |  |  |  |             // If we are started, mCamera should never be null.
 | 
			
		
	
		
			
				
					|  |  |  |  |             case STATE_STARTED: | 
			
		
	
		
			
				
					|  |  |  |  |                 return true; | 
			
		
	
		
			
				
					|  |  |  |  |             // If we are starting, theoretically we could act.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Just check that camera is available.
 | 
			
		
	
		
			
				
					|  |  |  |  |             case STATE_STARTING: | 
			
		
	
		
			
				
					|  |  |  |  |                 return mCamera != null; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         return false; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     protected void onStart() { | 
			
		
	
		
			
				
					|  |  |  |  |         if (isCameraAvailable()) { | 
			
		
	
		
			
				
					|  |  |  |  |             LOG.w("onStart:", "Camera not available. Should not happen."); | 
			
		
	
		
			
				
					|  |  |  |  |             onStop(); // Should not happen.
 | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         try { | 
			
		
	
		
			
				
					|  |  |  |  |             if (collectCameraId()) { | 
			
		
	
		
			
				
					|  |  |  |  |                 createCamera(); | 
			
		
	
		
			
				
					|  |  |  |  |                 LOG.i("onStart:", "Ended"); | 
			
		
	
		
			
				
					|  |  |  |  |             } else { | 
			
		
	
		
			
				
					|  |  |  |  |                 LOG.e("onStart:", "No camera available for facing", mFacing); | 
			
		
	
		
			
				
					|  |  |  |  |                 throw new CameraException(CameraException.REASON_NO_CAMERA); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |             // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @SuppressLint("MissingPermission") | 
			
		
	
		
			
				
					|  |  |  |  |     private void createCamera() throws CameraAccessException { | 
			
		
	
		
			
				
					|  |  |  |  |         mManager.openCamera(mCameraId, new CameraDevice.StateCallback() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void onOpened(@NonNull CameraDevice camera) { | 
			
		
	
		
			
				
					|  |  |  |  |                 mCamera = camera; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                 // TODO Set parameters that might have been set before the camera was opened.
 | 
			
		
	
		
			
				
					|  |  |  |  |                 try { | 
			
		
	
		
			
				
					|  |  |  |  |                     LOG.i("createCamera:", "Applying default parameters."); | 
			
		
	
		
			
				
					|  |  |  |  |                     CameraCharacteristics characteristics = mManager.getCameraCharacteristics(mCameraId); | 
			
		
	
		
			
				
					|  |  |  |  |                     mCameraOptions = new CameraOptions(mManager, characteristics, flip(REF_SENSOR, REF_VIEW)); | 
			
		
	
		
			
				
					|  |  |  |  |                     // applyDefaultFocus(params);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // applyFlash(params, Flash.OFF);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // applyLocation(params, null);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // applyWhiteBalance(params, WhiteBalance.AUTO);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // applyHdr(params, Hdr.OFF);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // applyPlaySounds(mPlaySounds);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // params.setRecordingHint(mMode == Mode.VIDEO);
 | 
			
		
	
		
			
				
					|  |  |  |  |                     // mCamera.setParameters(params);
 | 
			
		
	
		
			
				
					|  |  |  |  |                 } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |                     // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |                     throw new RuntimeException(e); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                 // Set display orientation, not allowed during preview
 | 
			
		
	
		
			
				
					|  |  |  |  |                 // TODO not needed anymore? mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW));
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                 try { | 
			
		
	
		
			
				
					|  |  |  |  |                     if (shouldBindToSurface()) bindToSurface("onStart"); | 
			
		
	
		
			
				
					|  |  |  |  |                 } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |                     // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |                     throw new RuntimeException(e); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void onDisconnected(@NonNull CameraDevice camera) { | 
			
		
	
		
			
				
					|  |  |  |  |                 // TODO not sure what to do here. maybe stop(). Read docs.
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void onError(@NonNull CameraDevice camera, int error) { | 
			
		
	
		
			
				
					|  |  |  |  |                 // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }, null); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private boolean shouldBindToSurface() { | 
			
		
	
		
			
				
					|  |  |  |  |         return isCameraAvailable() && mPreview != null && mPreview.hasSurface() && !mIsBound; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * The act of binding an "open" camera to a "ready" preview. | 
			
		
	
		
			
				
					|  |  |  |  |      * These can happen at different times but we want to end up here. | 
			
		
	
		
			
				
					|  |  |  |  |      * At this point we are sure that mPreview is not null. | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     @SuppressLint("Recycle") | 
			
		
	
		
			
				
					|  |  |  |  |     @WorkerThread | 
			
		
	
		
			
				
					|  |  |  |  |     private void bindToSurface(final @NonNull String trigger) throws CameraAccessException { | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i("bindToSurface:", "Started"); | 
			
		
	
		
			
				
					|  |  |  |  |         Object output = mPreview.getOutput(); | 
			
		
	
		
			
				
					|  |  |  |  |         Surface previewSurface; | 
			
		
	
		
			
				
					|  |  |  |  |         if (output instanceof SurfaceHolder) { | 
			
		
	
		
			
				
					|  |  |  |  |             previewSurface = ((SurfaceHolder) output).getSurface(); | 
			
		
	
		
			
				
					|  |  |  |  |         } else if (output instanceof SurfaceTexture) { | 
			
		
	
		
			
				
					|  |  |  |  |             previewSurface = new Surface((SurfaceTexture) output); | 
			
		
	
		
			
				
					|  |  |  |  |         } else { | 
			
		
	
		
			
				
					|  |  |  |  |             throw new RuntimeException("Unknown CameraPreview output class."); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         //noinspection ArraysAsListWithZeroOrOneArgument
 | 
			
		
	
		
			
				
					|  |  |  |  |         List<Surface> outputSurfaces = Arrays.asList(previewSurface); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamRequestBuilder.addTarget(previewSurface); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // null handler means using the current looper which is totally ok.
 | 
			
		
	
		
			
				
					|  |  |  |  |         mCamera.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void onConfigured(@NonNull CameraCaptureSession session) { | 
			
		
	
		
			
				
					|  |  |  |  |                 try { | 
			
		
	
		
			
				
					|  |  |  |  |                     mCaptureSize = computeCaptureSize(); | 
			
		
	
		
			
				
					|  |  |  |  |                     mPreviewStreamSize = computePreviewStreamSize(); | 
			
		
	
		
			
				
					|  |  |  |  |                     mSession = session; | 
			
		
	
		
			
				
					|  |  |  |  |                     mIsBound = true; | 
			
		
	
		
			
				
					|  |  |  |  |                     if (shouldStartPreview()) startPreview(trigger); | 
			
		
	
		
			
				
					|  |  |  |  |                 } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |                     // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |                     throw new RuntimeException(e); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void onConfigureFailed(@NonNull CameraCaptureSession session) { | 
			
		
	
		
			
				
					|  |  |  |  |                 // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         }, null); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private boolean shouldStartPreview() { | 
			
		
	
		
			
				
					|  |  |  |  |         return isCameraAvailable() && mIsBound; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * To be called when the preview size is setup or changed. | 
			
		
	
		
			
				
					|  |  |  |  |      * @param trigger a log helper | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     private void startPreview(@NonNull String trigger) { | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i(trigger, "Dispatching onCameraPreviewStreamSizeChanged."); | 
			
		
	
		
			
				
					|  |  |  |  |         mCallback.onCameraPreviewStreamSizeChanged(); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         Size previewSize = getPreviewStreamSize(REF_VIEW); | 
			
		
	
		
			
				
					|  |  |  |  |         if (previewSize == null) { | 
			
		
	
		
			
				
					|  |  |  |  |             throw new IllegalStateException("previewStreamSize should not be null at this point."); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // TODO mPreviewStreamFormat = params.getPreviewFormat();
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // TODO: previewSize and captureSize
 | 
			
		
	
		
			
				
					|  |  |  |  |         /* params.setPreviewSize(mPreviewStreamSize.getWidth(), mPreviewStreamSize.getHeight()); // <- not allowed during preview
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (mMode == Mode.PICTURE) { | 
			
		
	
		
			
				
					|  |  |  |  |             params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed
 | 
			
		
	
		
			
				
					|  |  |  |  |         } else { | 
			
		
	
		
			
				
					|  |  |  |  |             // mCaptureSize in this case is a video size. The available video sizes are not necessarily
 | 
			
		
	
		
			
				
					|  |  |  |  |             // a subset of the picture sizes, so we can't use the mCaptureSize value: it might crash.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // However, the setPictureSize() passed here is useless : we don't allow HQ pictures in video mode.
 | 
			
		
	
		
			
				
					|  |  |  |  |             // While this might be lifted in the future, for now, just use a picture capture size.
 | 
			
		
	
		
			
				
					|  |  |  |  |             Size pictureSize = computeCaptureSize(Mode.PICTURE); | 
			
		
	
		
			
				
					|  |  |  |  |             params.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); | 
			
		
	
		
			
				
					|  |  |  |  |         } */ | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // TODO mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
 | 
			
		
	
		
			
				
					|  |  |  |  |         // TODO mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
 | 
			
		
	
		
			
				
					|  |  |  |  |         // TODO mFrameManager.allocateBuffers(ImageFormat.getBitsPerPixel(mPreviewStreamFormat), mPreviewStreamSize);
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i(trigger, "Starting preview with startPreview()."); | 
			
		
	
		
			
				
					|  |  |  |  |         try { | 
			
		
	
		
			
				
					|  |  |  |  |             mPreviewStreamRequest = mPreviewStreamRequestBuilder.build(); | 
			
		
	
		
			
				
					|  |  |  |  |             mSession.setRepeatingRequest(mPreviewStreamRequest, null, null); | 
			
		
	
		
			
				
					|  |  |  |  |         } catch (Exception e) { | 
			
		
	
		
			
				
					|  |  |  |  |             LOG.e(trigger, "Failed to start preview.", e); | 
			
		
	
		
			
				
					|  |  |  |  |             throw new CameraException(e, CameraException.REASON_FAILED_TO_START_PREVIEW); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i(trigger, "Started preview."); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     protected void onStop() { | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i("onStop:", "About to clean up."); | 
			
		
	
		
			
				
					|  |  |  |  |         if (mVideoRecorder != null) { | 
			
		
	
		
			
				
					|  |  |  |  |             mVideoRecorder.stop(); | 
			
		
	
		
			
				
					|  |  |  |  |             mVideoRecorder = null; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         if (mCamera != null) { | 
			
		
	
		
			
				
					|  |  |  |  |             stopPreview(); | 
			
		
	
		
			
				
					|  |  |  |  |             if (mIsBound) unbindFromSurface(); | 
			
		
	
		
			
				
					|  |  |  |  |             destroyCamera(); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         mCameraOptions = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mCamera = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamSize = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mCaptureSize = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mIsBound = false; | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.w("onStop:", "Clean up.", "Returning."); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void stopPreview() { | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamFormat = 0; | 
			
		
	
		
			
				
					|  |  |  |  |         mFrameManager.release(); | 
			
		
	
		
			
				
					|  |  |  |  |         // TODO mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
 | 
			
		
	
		
			
				
					|  |  |  |  |         try { | 
			
		
	
		
			
				
					|  |  |  |  |             mSession.stopRepeating(); | 
			
		
	
		
			
				
					|  |  |  |  |             // TODO should wait for onReady?
 | 
			
		
	
		
			
				
					|  |  |  |  |         } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |             LOG.w("stopRepeating failed!", e); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamRequest = null; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void unbindFromSurface() { | 
			
		
	
		
			
				
					|  |  |  |  |         mIsBound = false; | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamRequestBuilder = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mPreviewStreamSize = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mCaptureSize = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mSession.close(); | 
			
		
	
		
			
				
					|  |  |  |  |         mSession = null; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private void destroyCamera() { | 
			
		
	
		
			
				
					|  |  |  |  |         try { | 
			
		
	
		
			
				
					|  |  |  |  |             LOG.i("destroyCamera:", "Clean up.", "Releasing camera."); | 
			
		
	
		
			
				
					|  |  |  |  |             mCamera.close(); | 
			
		
	
		
			
				
					|  |  |  |  |             LOG.i("destroyCamera:", "Clean up.", "Released camera."); | 
			
		
	
		
			
				
					|  |  |  |  |         } catch (Exception e) { | 
			
		
	
		
			
				
					|  |  |  |  |             LOG.w("destroyCamera:", "Clean up.", "Exception while releasing camera.", e); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         mCamera = null; | 
			
		
	
		
			
				
					|  |  |  |  |         mCameraOptions = null; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * Preview surface is now available. If camera is open, set up. | 
			
		
	
		
			
				
					|  |  |  |  |      * At this point we are sure that mPreview is not null. | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceAvailable() { | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i("onSurfaceAvailable:", "Size is", getPreviewSurfaceSize(REF_VIEW)); | 
			
		
	
		
			
				
					|  |  |  |  |         schedule(null, false, new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 LOG.i("onSurfaceAvailable:", "Inside handler. About to bind."); | 
			
		
	
		
			
				
					|  |  |  |  |                 try { | 
			
		
	
		
			
				
					|  |  |  |  |                     if (shouldBindToSurface()) bindToSurface("onSurfaceAvailable"); | 
			
		
	
		
			
				
					|  |  |  |  |                 } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |                     // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |                     throw new RuntimeException(e); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * Preview surface did change its size. Compute a new preview size. | 
			
		
	
		
			
				
					|  |  |  |  |      * This requires stopping and restarting the preview. | 
			
		
	
		
			
				
					|  |  |  |  |      * At this point we are sure that mPreview is not null. | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceChanged() { | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i("onSurfaceChanged, size is", getPreviewSurfaceSize(REF_VIEW)); | 
			
		
	
		
			
				
					|  |  |  |  |         schedule(null, true, new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 if (!mIsBound) return; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |                 // Compute a new camera preview size and apply.
 | 
			
		
	
		
			
				
					|  |  |  |  |                 try { | 
			
		
	
		
			
				
					|  |  |  |  |                     Size newSize = computePreviewStreamSize(); | 
			
		
	
		
			
				
					|  |  |  |  |                     if (newSize.equals(mPreviewStreamSize)) return; | 
			
		
	
		
			
				
					|  |  |  |  |                     LOG.i("onSurfaceChanged:", "Computed a new preview size. Going on."); | 
			
		
	
		
			
				
					|  |  |  |  |                     mPreviewStreamSize = newSize; | 
			
		
	
		
			
				
					|  |  |  |  |                 } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |                     // TODO
 | 
			
		
	
		
			
				
					|  |  |  |  |                     throw new RuntimeException(e); | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |                 stopPreview(); | 
			
		
	
		
			
				
					|  |  |  |  |                 startPreview("onSurfaceChanged:"); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onSurfaceDestroyed() { | 
			
		
	
		
			
				
					|  |  |  |  |         LOG.i("onSurfaceDestroyed"); | 
			
		
	
		
			
				
					|  |  |  |  |         schedule(null, true, new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |             @Override | 
			
		
	
		
			
				
					|  |  |  |  |             public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                 stopPreview(); | 
			
		
	
		
			
				
					|  |  |  |  |                 if (mIsBound) unbindFromSurface(); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |         }); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void onBufferAvailable(@NonNull byte[] buffer) { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -75,7 +449,26 @@ public class Camera2Engine extends CameraEngine { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
		
			
				
					|  |  |  |  |     public void setFacing(@NonNull Facing facing) { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         final Facing old = mFacing; | 
			
		
	
		
			
				
					|  |  |  |  |         if (facing != old) { | 
			
		
	
		
			
				
					|  |  |  |  |             mFacing = facing; | 
			
		
	
		
			
				
					|  |  |  |  |             schedule(null, true, new Runnable() { | 
			
		
	
		
			
				
					|  |  |  |  |                 @Override | 
			
		
	
		
			
				
					|  |  |  |  |                 public void run() { | 
			
		
	
		
			
				
					|  |  |  |  |                     boolean success; | 
			
		
	
		
			
				
					|  |  |  |  |                     try { | 
			
		
	
		
			
				
					|  |  |  |  |                         success = collectCameraId(); | 
			
		
	
		
			
				
					|  |  |  |  |                     } catch (CameraAccessException e) { | 
			
		
	
		
			
				
					|  |  |  |  |                         success = false; | 
			
		
	
		
			
				
					|  |  |  |  |                     } | 
			
		
	
		
			
				
					|  |  |  |  |                     if (success) { | 
			
		
	
		
			
				
					|  |  |  |  |                         restart(); | 
			
		
	
		
			
				
					|  |  |  |  |                     } else { | 
			
		
	
		
			
				
					|  |  |  |  |                         mFacing = old; | 
			
		
	
		
			
				
					|  |  |  |  |                     } | 
			
		
	
		
			
				
					|  |  |  |  |                 } | 
			
		
	
		
			
				
					|  |  |  |  |             }); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     @Override | 
			
		
	
	
		
			
				
					|  |  |  | 
 |