From f695668db85ce29f537d7c4c54dbf50a48c6c124 Mon Sep 17 00:00:00 2001 From: Gonzalo Martin Date: Tue, 4 Oct 2016 12:28:11 -0300 Subject: [PATCH 1/4] Updated references to camera2 android feature Updated on Camera2Basic --- android5.0/Camera2Basic/Camera2Basic.sln | 11 +- .../Camera2Basic/Camera2Basic.csproj | 40 +- .../Camera2Basic/Camera2BasicFragment.cs | 1534 +++++++++++------ .../Properties/AndroidManifest.xml | 5 +- .../Camera2Basic/Resources/values/strings.xml | 9 +- .../Camera2Basic/Camera2Basic/packages.config | 9 + 6 files changed, 1091 insertions(+), 517 deletions(-) create mode 100644 android5.0/Camera2Basic/Camera2Basic/packages.config diff --git a/android5.0/Camera2Basic/Camera2Basic.sln b/android5.0/Camera2Basic/Camera2Basic.sln index c903df94b..e5bd1803e 100644 --- a/android5.0/Camera2Basic/Camera2Basic.sln +++ b/android5.0/Camera2Basic/Camera2Basic.sln @@ -1,6 +1,7 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2012 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Camera2Basic", "Camera2Basic\Camera2Basic.csproj", "{7AB960BF-C0FF-47C8-8B1D-6AB9B45C97A9}" EndProject Global @@ -11,9 +12,13 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {7AB960BF-C0FF-47C8-8B1D-6AB9B45C97A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7AB960BF-C0FF-47C8-8B1D-6AB9B45C97A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AB960BF-C0FF-47C8-8B1D-6AB9B45C97A9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {7AB960BF-C0FF-47C8-8B1D-6AB9B45C97A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {7AB960BF-C0FF-47C8-8B1D-6AB9B45C97A9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = Camera2Basic\Camera2Basic.csproj EndGlobalSection diff --git a/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj b/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj index 344685a31..ada744c72 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj +++ b/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj @@ -1,4 +1,4 @@ - + Debug @@ -18,6 +18,8 @@ Properties\AndroidManifest.xml 8.0.30703 2.0 + + true @@ -44,6 +46,30 @@ + + ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.23.4.0.1\lib\MonoAndroid403\Xamarin.Android.Support.Animated.Vector.Drawable.dll + True + + + ..\packages\Xamarin.Android.Support.v13.23.4.0.1\lib\MonoAndroid403\Xamarin.Android.Support.v13.dll + True + + + ..\packages\Xamarin.Android.Support.v4.23.4.0.1\lib\MonoAndroid403\Xamarin.Android.Support.v4.dll + True + + + ..\packages\Xamarin.Android.Support.v7.AppCompat.23.4.0.1\lib\MonoAndroid403\Xamarin.Android.Support.v7.AppCompat.dll + True + + + ..\packages\Xamarin.Android.Support.v7.CardView.23.4.0.1\lib\MonoAndroid403\Xamarin.Android.Support.v7.CardView.dll + True + + + ..\packages\Xamarin.Android.Support.Vector.Drawable.23.4.0.1\lib\MonoAndroid403\Xamarin.Android.Support.Vector.Drawable.dll + True + @@ -53,6 +79,7 @@ + @@ -83,7 +110,12 @@ - - - + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs index 5ef65218e..8d5631d3a 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs +++ b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs @@ -4,524 +4,1050 @@ using System.Collections.Generic; using System.Linq; using System.Text; - +using Android; using Android.App; using Android.Content; +using Android.Content.PM; +using Android.Content.Res; using Android.OS; -using Android.Runtime; using Android.Util; using Android.Views; using Android.Widget; -using Android.Hardware; using Android.Hardware.Camera2; -using Android.Hardware.Camera2.Params; using Android.Graphics; -using Android.Content.Res; +using Android.Hardware.Camera2.Params; using Android.Media; +using Android.Support.V13.App; +using Android.Support.V4.Content; using Java.IO; -using Java.Nio; using Java.Lang; - +using Java.Nio; +using Java.Util; +using Java.Util.Concurrent; +using Boolean = Java.Lang.Boolean; using CameraError = Android.Hardware.Camera2.CameraError; +using Math = Java.Lang.Math; +using Object = Java.Lang.Object; +using Orientation = Android.Content.Res.Orientation; namespace Camera2Basic { - public class Camera2BasicFragment : Fragment, View.IOnClickListener - { - private static readonly SparseIntArray ORIENTATIONS = new SparseIntArray(); - // An AutoFitTextureView for camera preview - private AutoFitTextureView mTextureView; - - // A CameraRequest.Builder for camera preview - private CaptureRequest.Builder mPreviewBuilder; - - // A CameraCaptureSession for camera preview - private CameraCaptureSession mPreviewSession; - - // A reference to the opened CameraDevice - private CameraDevice mCameraDevice; - - // TextureView.ISurfaceTextureListener handles several lifecycle events on a TextureView - private Camera2BasicSurfaceTextureListener mSurfaceTextureListener; - private class Camera2BasicSurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener - { - private Camera2BasicFragment Fragment; - public Camera2BasicSurfaceTextureListener(Camera2BasicFragment fragment) - { - Fragment = fragment; - } - public void OnSurfaceTextureAvailable (Android.Graphics.SurfaceTexture surface, int width, int height) - { - Fragment.ConfigureTransform(width, height); - Fragment.StartPreview(); - } - - public bool OnSurfaceTextureDestroyed (Android.Graphics.SurfaceTexture surface) - { - return true; - } - - public void OnSurfaceTextureSizeChanged (Android.Graphics.SurfaceTexture surface, int width, int height) - { - Fragment.ConfigureTransform(width, height); - Fragment.StartPreview(); - } - - public void OnSurfaceTextureUpdated (Android.Graphics.SurfaceTexture surface) - { - - } - } - - // The size of the camera preview - private Size mPreviewSize; - - // True if the app is currently trying to open the camera - private bool mOpeningCamera; - - // CameraDevice.StateListener is called when a CameraDevice changes its state - private CameraStateListener mStateListener; - private class CameraStateListener : CameraDevice.StateCallback - { - public Camera2BasicFragment Fragment; - public override void OnOpened (CameraDevice camera) - { - - if (Fragment != null) { - Fragment.mCameraDevice = camera; - Fragment.StartPreview (); - Fragment.mOpeningCamera = false; - } - } - - public override void OnDisconnected (CameraDevice camera) - { - if (Fragment != null) { - camera.Close (); - Fragment.mCameraDevice = null; - Fragment.mOpeningCamera = false; - } - } - - public override void OnError (CameraDevice camera, CameraError error) - { - camera.Close(); - if (Fragment != null) { - Fragment.mCameraDevice = null; - Activity activity = Fragment.Activity; - Fragment.mOpeningCamera = false; - if (activity != null) { - activity.Finish (); - } - } - - } - } - - private class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener - { - public File File; - public void OnImageAvailable (ImageReader reader) - { - Image image = null; - try - { - image = reader.AcquireLatestImage(); - ByteBuffer buffer = image.GetPlanes()[0].Buffer; - byte[] bytes = new byte[buffer.Capacity()]; - buffer.Get(bytes); - Save(bytes); - } - catch (FileNotFoundException ex) { - Log.WriteLine (LogPriority.Info, "Camera capture session", ex.StackTrace); - } - catch (IOException ex) { - Log.WriteLine (LogPriority.Info, "Camera capture session", ex.StackTrace); - } - finally { - if (image != null) - image.Close (); - } - } - - private void Save(byte[] bytes) - { - OutputStream output = null; - try - { - if (File != null) - { - output = new FileOutputStream(File); - output.Write(bytes); - } - } - finally { - if (output != null) - output.Close (); - } - } - } - - private class CameraCaptureListener : CameraCaptureSession.CaptureCallback - { - public Camera2BasicFragment Fragment; - public File File; - public override void OnCaptureCompleted (CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) - { - if (Fragment != null && File != null) - { - Activity activity = Fragment.Activity; - if (activity != null) - { - Toast.MakeText(activity, "Saved: " + File.ToString(), ToastLength.Short).Show(); - Fragment.StartPreview (); - } - } - } - } - - // This CameraCaptureSession.StateListener uses Action delegates to allow the methods to be defined inline, as they are defined more than once - private class CameraCaptureStateListener : CameraCaptureSession.StateCallback - { - public Action OnConfigureFailedAction; - public override void OnConfigureFailed (CameraCaptureSession session) - { - if (OnConfigureFailedAction != null) { - OnConfigureFailedAction (session); - } - } - - public Action OnConfiguredAction; - public override void OnConfigured (CameraCaptureSession session) - { - if (OnConfiguredAction != null) { - OnConfiguredAction (session); - } - } - - } - - public override void OnCreate (Bundle savedInstanceState) - { - base.OnCreate (savedInstanceState); - mStateListener = new CameraStateListener () { Fragment = this }; - mSurfaceTextureListener = new Camera2BasicSurfaceTextureListener (this); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation0, 90); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation90, 0); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation180, 270); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation270, 180); - } - - public static Camera2BasicFragment NewInstance() - { - Camera2BasicFragment fragment = new Camera2BasicFragment (); - fragment.RetainInstance = true; - return fragment; - } - - public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) - { - return inflater.Inflate (Resource.Layout.fragment_camera2_basic, container, false); - } - - public override void OnViewCreated (View view, Bundle savedInstanceState) - { - mTextureView = (AutoFitTextureView)view.FindViewById (Resource.Id.texture); - mTextureView.SurfaceTextureListener = mSurfaceTextureListener; - view.FindViewById (Resource.Id.picture).SetOnClickListener (this); - view.FindViewById (Resource.Id.info).SetOnClickListener (this); - } - - public override void OnResume () - { - base.OnResume (); - OpenCamera (); - } - - public override void OnPause () - { - base.OnPause (); - if (mCameraDevice != null) { - mCameraDevice.Close (); - mCameraDevice = null; - } - } - - // Opens a CameraDevice. The result is listened to by 'mStateListener'. - private void OpenCamera() - { - Activity activity = Activity; - if (activity == null || activity.IsFinishing || mOpeningCamera) { - return; - } - mOpeningCamera = true; - CameraManager manager = (CameraManager)activity.GetSystemService (Context.CameraService); - try - { - string cameraId = manager.GetCameraIdList()[0]; - - // To get a list of available sizes of camera preview, we retrieve an instance of - // StreamConfigurationMap from CameraCharacteristics - CameraCharacteristics characteristics = manager.GetCameraCharacteristics(cameraId); - StreamConfigurationMap map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap); - mPreviewSize = map.GetOutputSizes(Java.Lang.Class.FromType(typeof(SurfaceTexture)))[0]; - Android.Content.Res.Orientation orientation = Resources.Configuration.Orientation; - if (orientation == Android.Content.Res.Orientation.Landscape) - { - mTextureView.SetAspectRatio(mPreviewSize.Width, mPreviewSize.Height); - } - else - { - mTextureView.SetAspectRatio(mPreviewSize.Height, mPreviewSize.Width); - } - - // We are opening the camera with a listener. When it is ready, OnOpened of mStateListener is called. - manager.OpenCamera(cameraId, mStateListener, null); - } - catch (CameraAccessException ex) { - Toast.MakeText (activity, "Cannot access the camera.", ToastLength.Short).Show (); - Activity.Finish (); - } catch (NullPointerException) { - var dialog = new ErrorDialog (); - dialog.Show (FragmentManager, "dialog"); - } - } - - /// - /// Starts the camera previe - /// - private void StartPreview() - { - if (mCameraDevice == null || !mTextureView.IsAvailable || mPreviewSize == null) { - return; - } - try - { - SurfaceTexture texture = mTextureView.SurfaceTexture; - System.Diagnostics.Debug.Assert( texture != null ); - - // We configure the size of the default buffer to be the size of the camera preview we want - texture.SetDefaultBufferSize(mPreviewSize.Width, mPreviewSize.Height); - - // This is the output Surface we need to start the preview - Surface surface = new Surface(texture); - - // We set up a CaptureRequest.Builder with the output Surface - mPreviewBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.Preview); - mPreviewBuilder.AddTarget(surface); - - // Here, we create a CameraCaptureSession for camera preview. - mCameraDevice.CreateCaptureSession(new List() { surface }, - new CameraCaptureStateListener() - { - OnConfigureFailedAction = (CameraCaptureSession session) => - { - Activity activity = Activity; - if (activity != null) - { - Toast.MakeText(activity, "Failed", ToastLength.Short).Show(); - } - }, - OnConfiguredAction = (CameraCaptureSession session) => - { - mPreviewSession = session; - UpdatePreview (); - } - }, - null); - - - } - catch (CameraAccessException ex) { - Log.WriteLine (LogPriority.Info, "Camera2BasicFragment", ex.StackTrace); - } - } - - /// - /// Updates the camera preview, StartPreview() needs to be called in advance - /// - private void UpdatePreview() - { - if (mCameraDevice == null) { - return; - } - - try - { - // The camera preview can be run in a background thread. This is a Handler for the camere preview - SetUpCaptureRequestBuilder(mPreviewBuilder); - HandlerThread thread = new HandlerThread("CameraPreview"); - thread.Start(); - Handler backgroundHandler = new Handler(thread.Looper); - - // Finally, we start displaying the camera preview - mPreviewSession.SetRepeatingRequest(mPreviewBuilder.Build(), null, backgroundHandler); - } - catch (CameraAccessException ex) { - Log.WriteLine (LogPriority.Info, "Camera2BasicFragment", ex.StackTrace); - } - } - - /// - /// Sets up capture request builder. - /// - /// Builder. - private void SetUpCaptureRequestBuilder(CaptureRequest.Builder builder) - { - // In this sample, w just let the camera device pick the automatic settings - builder.Set (CaptureRequest.ControlMode, new Java.Lang.Integer((int)ControlMode.Auto)); - } - - /// - /// Configures the necessary transformation to mTextureView. - /// This method should be called after the camera preciew size is determined in openCamera, and also the size of mTextureView is fixed - /// - /// The width of mTextureView - /// VThe height of mTextureView - private void ConfigureTransform(int viewWidth, int viewHeight) - { - Activity activity = Activity; - if (mTextureView == null || mPreviewSize == null || activity == null) { - return; - } - - SurfaceOrientation rotation = activity.WindowManager.DefaultDisplay.Rotation; - Matrix matrix = new Matrix (); - RectF viewRect = new RectF (0, 0, viewWidth, viewHeight); - RectF bufferRect = new RectF (0, 0, mPreviewSize.Width, mPreviewSize.Height); - float centerX = viewRect.CenterX(); - float centerY = viewRect.CenterY(); - if (rotation == SurfaceOrientation.Rotation90 || rotation == SurfaceOrientation.Rotation270) { - bufferRect.Offset (centerX - bufferRect.CenterX(), centerY - bufferRect.CenterY()); - matrix.SetRectToRect (viewRect, bufferRect, Matrix.ScaleToFit.Fill); - float scale = System.Math.Max ((float)viewHeight / mPreviewSize.Height, (float)viewWidth / mPreviewSize.Width); - matrix.PostScale (scale, scale, centerX, centerY); - matrix.PostRotate (90 * ((int)rotation - 2), centerX, centerY); - } - mTextureView.SetTransform (matrix); - } - - /// - /// Takes a picture. - /// - private void TakePicture() - { - try - { - Activity activity = Activity; - if (activity == null || mCameraDevice == null) { - return; - } - CameraManager manager = (CameraManager) activity.GetSystemService(Context.CameraService); - - // Pick the best JPEG size that can be captures with this CameraDevice - CameraCharacteristics characteristics = manager.GetCameraCharacteristics(mCameraDevice.Id); - Size[] jpegSizes = null; - if (characteristics != null) - { - jpegSizes = ((StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap)).GetOutputSizes((int)ImageFormatType.Jpeg); - } - int width = 640; - int height = 480; - if (jpegSizes != null && jpegSizes.Length > 0) - { - width = jpegSizes[0].Width; - height = jpegSizes[0].Height; - } - - // We use an ImageReader to get a JPEG from CameraDevice - // Here, we create a new ImageReader and prepare its Surface as an output from the camera - ImageReader reader = ImageReader.NewInstance(width, height, ImageFormatType.Jpeg, 1); - List outputSurfaces = new List(2); - outputSurfaces.Add(reader.Surface); - outputSurfaces.Add(new Surface(mTextureView.SurfaceTexture)); - - CaptureRequest.Builder captureBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.StillCapture); - captureBuilder.AddTarget(reader.Surface); - SetUpCaptureRequestBuilder(captureBuilder); - // Orientation - SurfaceOrientation rotation = activity.WindowManager.DefaultDisplay.Rotation; - captureBuilder.Set(CaptureRequest.JpegOrientation, new Java.Lang.Integer(ORIENTATIONS.Get((int)rotation))); - - // Output file - File file = new File(activity.GetExternalFilesDir(null), "pic.jpg"); - - // This listener is called when an image is ready in ImageReader - // Right click on ImageAvailableListener in your IDE and go to its definition - ImageAvailableListener readerListener = new ImageAvailableListener() { File = file }; - - // We create a Handler since we want to handle the resulting JPEG in a background thread - HandlerThread thread = new HandlerThread("CameraPicture"); - thread.Start(); - Handler backgroundHandler = new Handler(thread.Looper); - reader.SetOnImageAvailableListener(readerListener, backgroundHandler); - - //This listener is called when the capture is completed - // Note that the JPEG data is not available in this listener, but in the ImageAvailableListener we created above - // Right click on CameraCaptureListener in your IDE and go to its definition - CameraCaptureListener captureListener = new CameraCaptureListener() { Fragment = this, File = file }; - - mCameraDevice.CreateCaptureSession(outputSurfaces, new CameraCaptureStateListener() - { - OnConfiguredAction = (CameraCaptureSession session) => { - try - { - session.Capture(captureBuilder.Build(), captureListener, backgroundHandler); - } - catch (CameraAccessException ex) - { - Log.WriteLine(LogPriority.Info, "Capture Session error: ", ex.ToString()); - } - } - }, backgroundHandler ); - } - catch (CameraAccessException ex) { - Log.WriteLine(LogPriority.Info, "Taking picture error: ", ex.StackTrace); - } - } - - public void OnClick (View v) - { - switch (v.Id) { - case Resource.Id.picture: - TakePicture (); - break; - case Resource.Id.info: - EventHandler nullHandler = null; - Activity activity = Activity; - if (activity != null) { - new AlertDialog.Builder (activity) - .SetMessage ("This sample demonstrates the basic use of the Camera2 API. ...") - .SetPositiveButton (Android.Resource.String.Ok, nullHandler) - .Show (); - } - break; - } - } - - public class ErrorDialog : DialogFragment { - public override Dialog OnCreateDialog (Bundle savedInstanceState) - { - var alert = new AlertDialog.Builder (Activity); - alert.SetMessage ("This device doesn't support Camera2 API."); - alert.SetPositiveButton (Android.Resource.String.Ok, new MyDialogOnClickListener (this)); - return alert.Show(); - - } - } - - private class MyDialogOnClickListener : Java.Lang.Object,IDialogInterfaceOnClickListener - { - ErrorDialog er; - public MyDialogOnClickListener(ErrorDialog e) - { - er = e; - } - public void OnClick(IDialogInterface dialogInterface, int i) - { - er.Activity.Finish (); - } - } - } + public class Camera2BasicFragment : Fragment, View.IOnClickListener, FragmentCompat.IOnRequestPermissionsResultCallback + { + private static readonly SparseIntArray ORIENTATIONS = new SparseIntArray(); + private static readonly int REQUEST_CAMERA_PERMISSION = 1; + private static readonly string FRAGMENT_DIALOG = "dialog"; + + // Tag for the {@link Log}. + private static readonly string TAG = "Camera2BasicFragment"; + + // Camera state: Showing camera preview. + private const int STATE_PREVIEW = 0; + + // Camera state: Waiting for the focus to be locked. + private const int STATE_WAITING_LOCK = 1; + + // Camera state: Waiting for the exposure to be precapture state. + private const int STATE_WAITING_PRECAPTURE = 2; + + //Camera state: Waiting for the exposure state to be something other than precapture. + private const int STATE_WAITING_NON_PRECAPTURE = 3; + + // Camera state: Picture was taken. + private const int STATE_PICTURE_TAKEN = 4; + + // Max preview width that is guaranteed by Camera2 API + private static readonly int MAX_PREVIEW_WIDTH = 1920; + + // Max preview height that is guaranteed by Camera2 API + private static readonly int MAX_PREVIEW_HEIGHT = 1080; + + // TextureView.ISurfaceTextureListener handles several lifecycle events on a TextureView + private Camera2BasicSurfaceTextureListener mSurfaceTextureListener; + private class Camera2BasicSurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener + { + public Camera2BasicFragment owner; + + public Camera2BasicSurfaceTextureListener(Camera2BasicFragment owner) + { + this.owner = owner; + } + + public void OnSurfaceTextureAvailable(Android.Graphics.SurfaceTexture surface, int width, int height) + { + owner.OpenCamera(width, height); + } + + public bool OnSurfaceTextureDestroyed(Android.Graphics.SurfaceTexture surface) + { + return true; + } + + public void OnSurfaceTextureSizeChanged(Android.Graphics.SurfaceTexture surface, int width, int height) + { + owner.ConfigureTransform(width, height); + } + + public void OnSurfaceTextureUpdated(Android.Graphics.SurfaceTexture surface) + { + + } + } + + + // ID of the current {@link CameraDevice}. + private string mCameraId; + + // An AutoFitTextureView for camera preview + private AutoFitTextureView mTextureView; + + // A {@link CameraCaptureSession } for camera preview. + private CameraCaptureSession mCaptureSession; + + // A reference to the opened CameraDevice + private CameraDevice mCameraDevice; + + // The size of the camera preview + private Size mPreviewSize; + + // CameraDevice.StateListener is called when a CameraDevice changes its state + private CameraStateListener mStateCallback; + private class CameraStateListener : CameraDevice.StateCallback + { + public Camera2BasicFragment owner; + public override void OnOpened(CameraDevice cameraDevice) + { + // This method is called when the camera is opened. We start camera preview here. + owner.mCameraOpenCloseLock.Release(); + owner.mCameraDevice = cameraDevice; + owner.CreateCameraPreviewSession(); + } + + public override void OnDisconnected(CameraDevice cameraDevice) + { + owner.mCameraOpenCloseLock.Release(); + cameraDevice.Close(); + owner.mCameraDevice = null; + } + + public override void OnError(CameraDevice cameraDevice, CameraError error) + { + owner.mCameraOpenCloseLock.Release(); + cameraDevice.Close(); + owner.mCameraDevice = null; + if (owner != null) + { + Activity activity = owner.Activity; + if (activity != null) + { + activity.Finish(); + } + } + + } + } + + // An additional thread for running tasks that shouldn't block the UI. + private HandlerThread mBackgroundThread; + + + // A {@link Handler} for running tasks in the background. + private Handler mBackgroundHandler; + + + // An {@link ImageReader} that handles still image capture. + private ImageReader mImageReader; + + // This is the output file for our picture. + private File mFile; + + // This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a + // still image is ready to be saved. + private ImageAvailableListener mOnImageAvailableListener; + private class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener + { + public File File; + public Camera2BasicFragment owner; + public void OnImageAvailable(ImageReader reader) + { + owner.mBackgroundHandler.Post(new ImageSaver(reader.AcquireNextImage(), File)); + } + } + + + //{@link CaptureRequest.Builder} for the camera preview + private CaptureRequest.Builder mPreviewRequestBuilder; + + // {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} + private CaptureRequest mPreviewRequest; + + // The current state of camera state for taking pictures. + private int mState = STATE_PREVIEW; + + // A {@link Semaphore} to prevent the app from exiting before closing the camera. + private Semaphore mCameraOpenCloseLock = new Semaphore(1); + + // Whether the current camera device supports Flash or not. + private bool mFlashSupported; + + // Orientation of the camera sensor + private int mSensorOrientation; + + // A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. + private CameraCaptureListener mCaptureCallback; + private class CameraCaptureListener : CameraCaptureSession.CaptureCallback + { + public Camera2BasicFragment owner; + public File File; + public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) + { + Process(result); + } + + public override void OnCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) + { + Process(partialResult); + } + + private void Process(CaptureResult result) + { + switch (owner.mState) + { + case STATE_PREVIEW: + { + // We have nothing to do when the camera preview is working normally. + break; + } + case STATE_WAITING_LOCK: + { + Integer afState = (Integer)result.Get(CaptureResult.ControlAfState); + if (afState == null) + { + owner.CaptureStillPicture(); + } + + else if ((((int)ControlAFState.FocusedLocked) == afState.IntValue()) || + (((int)ControlAFState.NotFocusedLocked) == afState.IntValue())) + { + // ControlAeState can be null on some devices + Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); + if (aeState == null || + aeState.IntValue() == ((int)ControlAEState.Converged)) + { + owner.mState = STATE_PICTURE_TAKEN; + owner.CaptureStillPicture(); + } + else + { + owner.RunPrecaptureSequence(); + } + } + break; + } + case STATE_WAITING_PRECAPTURE: + { + // ControlAeState can be null on some devices + Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); + if (aeState == null || + aeState.IntValue() == ((int)ControlAEState.Precapture) || + aeState.IntValue() == ((int)ControlAEState.FlashRequired)) + { + owner.mState = STATE_WAITING_NON_PRECAPTURE; + } + break; + } + case STATE_WAITING_NON_PRECAPTURE: + { + // ControlAeState can be null on some devices + Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); + if (aeState == null || aeState.IntValue() != ((int)ControlAEState.Precapture)) + { + owner.mState = STATE_PICTURE_TAKEN; + owner.CaptureStillPicture(); + } + break; + } + } + } + } + + // Shows a {@link Toast} on the UI thread. + private void ShowToast(string text) + { + if (Activity != null) + { + Activity.RunOnUiThread(new ShowToastRunnable(Activity.ApplicationContext, text)); + } + } + + private class ShowToastRunnable : Java.Lang.Object, IRunnable + { + private string text; + private Context context; + + public ShowToastRunnable(Context context, string text) + { + this.context = context; + this.text = text; + } + + public void Run() + { + Toast.MakeText(context, text, ToastLength.Short).Show(); + } + } + + private static Size ChooseOptimalSize(Size[] choices, int textureViewWidth, + int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) + { + // Collect the supported resolutions that are at least as big as the preview Surface + var bigEnough = new List(); + // Collect the supported resolutions that are smaller than the preview Surface + var notBigEnough = new List(); + int w = aspectRatio.Width; + int h = aspectRatio.Height; + + for (var i = 0; i < choices.Length; i++) + { + Size option = choices[i]; + if ((option.Width <= maxWidth) && (option.Height <= maxHeight) && + option.Height == option.Width * h / w) + { + if (option.Width >= textureViewWidth && + option.Height >= textureViewHeight) + { + bigEnough.Add(option); + } + else + { + notBigEnough.Add(option); + } + } + } + + // Pick the smallest of those big enough. If there is no one big enough, pick the + // largest of those not big enough. + if (bigEnough.Count > 0) + { + return (Size) Collections.Min(bigEnough, new CompareSizesByArea()); + } + else if (notBigEnough.Count > 0) + { + return (Size) Collections.Max(notBigEnough, new CompareSizesByArea()); + } + else + { + Log.Error(TAG, "Couldn't find any suitable preview size"); + return choices[0]; + } + } + + public static Camera2BasicFragment NewInstance() + { + return new Camera2BasicFragment(); + } + + public override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + mStateCallback = new CameraStateListener() { owner = this }; + mSurfaceTextureListener = new Camera2BasicSurfaceTextureListener(this); + + // fill ORIENTATIONS list + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation0, 90); + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation90, 0); + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation180, 270); + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation270, 180); + } + + public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + return inflater.Inflate(Resource.Layout.fragment_camera2_basic, container, false); + } + + public override void OnViewCreated(View view, Bundle savedInstanceState) + { + mTextureView = (AutoFitTextureView)view.FindViewById(Resource.Id.texture); + view.FindViewById(Resource.Id.picture).SetOnClickListener(this); + view.FindViewById(Resource.Id.info).SetOnClickListener(this); + } + + public override void OnActivityCreated(Bundle savedInstanceState) + { + base.OnActivityCreated(savedInstanceState); + mFile = new File(Activity.GetExternalFilesDir(null), "pic.jpg"); + } + + public override void OnResume() + { + base.OnResume(); + StartBackgroundThread(); + + // When the screen is turned off and turned back on, the SurfaceTexture is already + // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open + // a camera and start preview from here (otherwise, we wait until the surface is ready in + // the SurfaceTextureListener). + if (mTextureView.IsAvailable) + { + OpenCamera(mTextureView.Width, mTextureView.Height); + } + else + { + mTextureView.SurfaceTextureListener = mSurfaceTextureListener; + } + } + + public override void OnPause() + { + CloseCamera(); + StopBackgroundThread(); + base.OnPause(); + } + + private void RequestCameraPermission() + { + if (FragmentCompat.ShouldShowRequestPermissionRationale(this, Manifest.Permission.Camera)) + { + new ConfirmationDialog().Show(ChildFragmentManager, FRAGMENT_DIALOG); + } + else + { + FragmentCompat.RequestPermissions(this, new string[] { Manifest.Permission.Camera }, + REQUEST_CAMERA_PERMISSION); + } + } + + public void OnRequestPermissionsResult(int requestCode, string[] permissions, int[] grantResults) + { + if (requestCode == REQUEST_CAMERA_PERMISSION) + { + if (grantResults.Length != 1 || grantResults[0] != (int)Permission.Granted) + { + ErrorDialog.NewInstance(GetString(Resource.String.request_permission)) + .Show(ChildFragmentManager, FRAGMENT_DIALOG); + } + } + else + { + // it should call to base.. + //base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + + // Sets up member variables related to camera. + private void SetUpCameraOutputs(int width, int height) + { + var activity = Activity; + var manager = (CameraManager)activity.GetSystemService(Context.CameraService); + try + { + for (var i = 0; i < manager.GetCameraIdList().Length; i++) + { + var cameraId = manager.GetCameraIdList()[i]; + CameraCharacteristics characteristics = manager.GetCameraCharacteristics(cameraId); + + // We don't use a front facing camera in this sample. + var facing = (Integer)characteristics.Get(CameraCharacteristics.LensFacing); + if (facing != null && facing == (Integer.ValueOf((int)LensFacing.Front))) + { + continue; + } + + var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap); + if (map == null) + { + continue; + } + + // For still image captures, we use the largest available size. + Size largest = (Size) Collections.Max(Arrays.AsList(map.GetOutputSizes((int)ImageFormatType.Jpeg)), + new CompareSizesByArea()); + mImageReader = ImageReader.NewInstance(largest.Width, largest.Height, ImageFormatType.Jpeg, /*maxImages*/2); + mImageReader.SetOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); + + // Find out if we need to swap dimension to get the preview size relative to sensor + // coordinate. + var displayRotation = activity.WindowManager.DefaultDisplay.Rotation; + //noinspection ConstantConditions + mSensorOrientation = (int)characteristics.Get(CameraCharacteristics.SensorOrientation); + bool swappedDimensions = false; + switch (displayRotation) + { + case SurfaceOrientation.Rotation0: + case SurfaceOrientation.Rotation180: + if (mSensorOrientation == 90 || mSensorOrientation == 270) + { + swappedDimensions = true; + } + break; + case SurfaceOrientation.Rotation90: + case SurfaceOrientation.Rotation270: + if (mSensorOrientation == 0 || mSensorOrientation == 180) + { + swappedDimensions = true; + } + break; + default: + Log.Error(TAG, "Display rotation is invalid: " + displayRotation); + break; + } + + Point displaySize = new Point(); + activity.WindowManager.DefaultDisplay.GetSize(displaySize); + var rotatedPreviewWidth = width; + var rotatedPreviewHeight = height; + var maxPreviewWidth = displaySize.X; + var maxPreviewHeight = displaySize.Y; + + if (swappedDimensions) + { + rotatedPreviewWidth = height; + rotatedPreviewHeight = width; + maxPreviewWidth = displaySize.Y; + maxPreviewHeight = displaySize.X; + } + + if (maxPreviewWidth > MAX_PREVIEW_WIDTH) + { + maxPreviewWidth = MAX_PREVIEW_WIDTH; + } + + if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) + { + maxPreviewHeight = MAX_PREVIEW_HEIGHT; + } + + // Danger, W.R.! Attempting to use too large a preview size could exceed the camera + // bus' bandwidth limitation, resulting in gorgeous previews but the storage of + // garbage capture data. + mPreviewSize = ChooseOptimalSize(map.GetOutputSizes(Class.FromType(typeof(SurfaceTexture))), + rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, + maxPreviewHeight, largest); + + // We fit the aspect ratio of TextureView to the size of preview we picked. + var orientation = Resources.Configuration.Orientation; + if (orientation == Orientation.Landscape) + { + mTextureView.SetAspectRatio(mPreviewSize.Width, mPreviewSize.Height); + } + else + { + mTextureView.SetAspectRatio(mPreviewSize.Height, mPreviewSize.Width); + } + + // Check if the flash is supported. + var available = (Boolean)characteristics.Get(CameraCharacteristics.FlashInfoAvailable); + if (available == null) + { + mFlashSupported = false; + } + else + { + mFlashSupported = (bool)available; + } + + mCameraId = cameraId; + return; + } + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + catch (NullPointerException e) + { + // Currently an NPE is thrown when the Camera2API is used but not supported on the + // device this code runs. + ErrorDialog.NewInstance(GetString(Resource.String.camera_error)).Show(ChildFragmentManager, FRAGMENT_DIALOG); + } + } + + // Opens the camera specified by {@link Camera2BasicFragment#mCameraId}. + private void OpenCamera(int width, int height) + { + if (ContextCompat.CheckSelfPermission(Activity, Manifest.Permission.Camera) != Permission.Granted) + { + RequestCameraPermission(); + return; + } + SetUpCameraOutputs(width, height); + ConfigureTransform(width, height); + var activity = Activity; + var manager = (CameraManager)activity.GetSystemService(Context.CameraService); + try + { + if (!mCameraOpenCloseLock.TryAcquire(2500, TimeUnit.Microseconds)) + { + throw new RuntimeException("Time out waiting to lock camera opening."); + } + manager.OpenCamera(mCameraId, mStateCallback, mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + catch (InterruptedException e) + { + throw new RuntimeException("Interrupted while trying to lock camera opening.", e); + } + } + + // Closes the current {@link CameraDevice}. + private void CloseCamera() + { + try + { + mCameraOpenCloseLock.Acquire(); + if (null != mCaptureSession) + { + mCaptureSession.Close(); + mCaptureSession = null; + } + if (null != mCameraDevice) + { + mCameraDevice.Close(); + mCameraDevice = null; + } + if (null != mImageReader) + { + mImageReader.Close(); + mImageReader = null; + } + } + catch (InterruptedException e) + { + throw new RuntimeException("Interrupted while trying to lock camera closing.", e); + } + finally + { + mCameraOpenCloseLock.Release(); + } + } + + // Starts a background thread and its {@link Handler}. + private void StartBackgroundThread() + { + mBackgroundThread = new HandlerThread("CameraBackground"); + mBackgroundThread.Start(); + mBackgroundHandler = new Handler(mBackgroundThread.Looper); + } + + // Stops the background thread and its {@link Handler}. + private void StopBackgroundThread() + { + mBackgroundThread.QuitSafely(); + try + { + mBackgroundThread.Join(); + mBackgroundThread = null; + mBackgroundHandler = null; + } + catch (InterruptedException e) + { + e.PrintStackTrace(); + } + } + + // Creates a new {@link CameraCaptureSession} for camera preview. + private void CreateCameraPreviewSession() + { + try + { + SurfaceTexture texture = mTextureView.SurfaceTexture; + if (texture == null) + { + throw new IllegalStateException("texture is null"); + } + + // We configure the size of default buffer to be the size of camera preview we want. + texture.SetDefaultBufferSize(mPreviewSize.Width, mPreviewSize.Height); + + // This is the output Surface we need to start preview. + Surface surface = new Surface(texture); + + // We set up a CaptureRequest.Builder with the output Surface. + mPreviewRequestBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.Preview); + mPreviewRequestBuilder.AddTarget(surface); + + // Here, we create a CameraCaptureSession for camera preview. + List surfaces = new List(); + surfaces.Add(surface); + surfaces.Add(mImageReader.Surface); + mCameraDevice.CreateCaptureSession(surfaces, new CameraCaptureSessionCallback(this), null); + + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + private class CameraCaptureSessionCallback : CameraCaptureSession.StateCallback + { + + private Camera2BasicFragment owner; + + public CameraCaptureSessionCallback(Camera2BasicFragment owner) + { + this.owner = owner; + } + + public override void OnConfigureFailed(CameraCaptureSession session) + { + owner.ShowToast("Failed"); + } + + public override void OnConfigured(CameraCaptureSession session) + { + // The camera is already closed + if (null == owner.mCameraDevice) + { + return; + } + + // When the session is ready, we start displaying the preview. + owner.mCaptureSession = session; + try + { + // Auto focus should be continuous for camera preview. + owner.mPreviewRequestBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture); + // Flash is automatically enabled when necessary. + owner.SetAutoFlash(owner.mPreviewRequestBuilder); + + // Finally, we start displaying the camera preview. + owner.mPreviewRequest = owner.mPreviewRequestBuilder.Build(); + owner.mCaptureSession.SetRepeatingRequest(owner.mPreviewRequest, + owner.mCaptureCallback, owner.mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + } + + public static T Cast(Java.Lang.Object obj) where T : class + { + var propertyInfo = obj.GetType().GetProperty("Instance"); + return propertyInfo == null ? null : propertyInfo.GetValue(obj, null) as T; + } + + // Configures the necessary {@link android.graphics.Matrix} + // transformation to `mTextureView`. + // This method should be called after the camera preview size is determined in + // setUpCameraOutputs and also the size of `mTextureView` is fixed. + + private void ConfigureTransform(int viewWidth, int viewHeight) + { + Activity activity = Activity; + if (null == mTextureView || null == mPreviewSize || null == activity) + { + return; + } + var rotation = (int)activity.WindowManager.DefaultDisplay.Rotation; + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + RectF bufferRect = new RectF(0, 0, mPreviewSize.Height, mPreviewSize.Width); + float centerX = viewRect.CenterX(); + float centerY = viewRect.CenterY(); + if ((int)SurfaceOrientation.Rotation90 == rotation || (int)SurfaceOrientation.Rotation270 == rotation) + { + bufferRect.Offset(centerX - bufferRect.CenterX(), centerY - bufferRect.CenterY()); + matrix.SetRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.Fill); + float scale = Math.Max((float)viewHeight / mPreviewSize.Height, (float)viewWidth / mPreviewSize.Width); + matrix.PostScale(scale, scale, centerX, centerY); + matrix.PostRotate(90 * (rotation - 2), centerX, centerY); + } + else if ((int)SurfaceOrientation.Rotation180 == rotation) + { + matrix.PostRotate(180, centerX, centerY); + } + mTextureView.SetTransform(matrix); + } + + // Initiate a still image capture. + private void TakePicture() + { + LockFocus(); + } + + // Lock the focus as the first step for a still image capture. + private void LockFocus() + { + try + { + // This is how to tell the camera to lock focus. + + mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Start); + // Tell #mCaptureCallback to wait for the lock. + mState = STATE_WAITING_LOCK; + mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mCaptureCallback, + mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + // Run the precapture sequence for capturing a still image. This method should be called when + // we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. + private void RunPrecaptureSequence() + { + try + { + // This is how to tell the camera to trigger. + mPreviewRequestBuilder.Set(CaptureRequest.ControlAePrecaptureTrigger, (int)ControlAEPrecaptureTrigger.Start); + // Tell #mCaptureCallback to wait for the precapture sequence to be set. + mState = STATE_WAITING_PRECAPTURE; + mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mCaptureCallback, mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + // Capture a still picture. This method should be called when we get a response in + // {@link #mCaptureCallback} from both {@link #lockFocus()}. + private void CaptureStillPicture() + { + try + { + var activity = Activity; + if (null == activity || null == mCameraDevice) + { + return; + } + // This is the CaptureRequest.Builder that we use to take a picture. + CaptureRequest.Builder captureBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.StillCapture); + captureBuilder.AddTarget(mImageReader.Surface); + + // Use the same AE and AF modes as the preview. + captureBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture); + SetAutoFlash(captureBuilder); + + // Orientation + int rotation = (int)activity.WindowManager.DefaultDisplay.Rotation; + captureBuilder.Set(CaptureRequest.JpegOrientation, GetOrientation(rotation)); + + mCaptureSession.StopRepeating(); + mCaptureSession.Capture(captureBuilder.Build(), new CameraCaptureStillPictureSessionCallback(this), null); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + private class CameraCaptureStillPictureSessionCallback : CameraCaptureSession.CaptureCallback + { + private Camera2BasicFragment owner; + + public CameraCaptureStillPictureSessionCallback(Camera2BasicFragment owner) + { + this.owner = owner; + } + + public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) + { + owner.ShowToast("Saved: " + owner.mFile); + Log.Debug(TAG, owner.mFile.ToString()); + owner.UnlockFocus(); + } + } + + // Retrieves the JPEG orientation from the specified screen rotation. + private int GetOrientation(int rotation) + { + // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) + // We have to take that into account and rotate JPEG properly. + // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. + // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. + return (ORIENTATIONS.Get(rotation) + mSensorOrientation + 270) % 360; + } + + // Unlock the focus. This method should be called when still image capture sequence is + // finished. + private void UnlockFocus() + { + try + { + // Reset the auto-focus trigger + mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Cancel); + SetAutoFlash(mPreviewRequestBuilder); + mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mCaptureCallback, + mBackgroundHandler); + // After this, the camera will go back to the normal state of preview. + mState = STATE_PREVIEW; + mCaptureSession.SetRepeatingRequest(mPreviewRequest, mCaptureCallback, + mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + public void OnClick(View v) + { + switch (v.Id) + { + case Resource.Id.picture: + TakePicture(); + break; + case Resource.Id.info: + EventHandler nullHandler = null; + Activity activity = Activity; + if (activity != null) + { + new AlertDialog.Builder(activity) + .SetMessage("This sample demonstrates the basic use of the Camera2 API. ...") + .SetPositiveButton(Android.Resource.String.Ok, nullHandler) + .Show(); + } + break; + } + } + + private void SetAutoFlash(CaptureRequest.Builder requestBuilder) + { + if (mFlashSupported) + { + requestBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash); + } + } + + // Saves a JPEG {@link Image} into the specified {@link File}. + private class ImageSaver : Java.Lang.Object, IRunnable + { + // The JPEG image + private Image mImage; + + // The file we save the image into. + private File mFile; + + public ImageSaver(Image image, File file) + { + mImage = image; + mFile = file; + } + + public void Run() + { + ByteBuffer buffer = mImage.GetPlanes()[0].Buffer; + byte[] bytes = new byte[buffer.Remaining()]; + buffer.Get(bytes); + FileOutputStream output = null; + try + { + output = new FileOutputStream(mFile); + output.Write(bytes); + } + catch (IOException e) + { + e.PrintStackTrace(); + } + finally + { + mImage.Close(); + if (null != output) + { + try + { + output.Close(); + } + catch (IOException e) + { + e.PrintStackTrace(); + } + } + } + } + } + + // Compares two {@code Size}s based on their areas. + public class CompareSizesByArea : Java.Lang.Object, IComparator + { + public int Compare(Object lhs, Object rhs) + { + var lhsSize = (Size)lhs; + var rhsSize = (Size)rhs; + // We cast here to ensure the multiplications won't overflow + return Long.Signum((long)lhsSize.Width * lhsSize.Height - (long)rhsSize.Width * rhsSize.Height); + } + } + + // Shows an error message dialog. + public class ErrorDialog : DialogFragment + { + private static readonly string ARG_MESSAGE = "message"; + private static Activity mActivity; + + private class PositiveListener : Java.Lang.Object, IDialogInterfaceOnClickListener + { + public void OnClick(IDialogInterface dialog, int which) + { + mActivity.Finish(); + } + } + + public static ErrorDialog NewInstance(string message) + { + ErrorDialog dialog = new ErrorDialog(); + var args = new Bundle(); + args.PutString(ARG_MESSAGE, message); + dialog.Arguments = args; + return dialog; + } + + public override Dialog OnCreateDialog(Bundle savedInstanceState) + { + mActivity = Activity; + return new AlertDialog.Builder(mActivity) + .SetMessage(Arguments.GetString(ARG_MESSAGE)) + .SetPositiveButton(Android.Resource.String.Ok, new PositiveListener()) + .Create(); + } + } + + // Shows OK/Cancel confirmation dialog about camera permission. + public class ConfirmationDialog : DialogFragment + { + private static Fragment mParent; + private class PositiveListener : Java.Lang.Object, IDialogInterfaceOnClickListener + { + public void OnClick(IDialogInterface dialog, int which) + { + FragmentCompat.RequestPermissions(mParent, + new string[] { Manifest.Permission.Camera }, REQUEST_CAMERA_PERMISSION); + } + } + + private class NegativeListener : Java.Lang.Object, IDialogInterfaceOnClickListener + { + public void OnClick(IDialogInterface dialog, int which) + { + Activity activity = mParent.Activity; + if (activity != null) + { + activity.Finish(); + } + } + } + + public override Dialog OnCreateDialog(Bundle savedInstanceState) + { + mParent = ParentFragment; + return new AlertDialog.Builder(Activity) + .SetMessage(Resource.String.request_permission) + .SetPositiveButton(Android.Resource.String.Ok, new PositiveListener()) + .SetNegativeButton(Android.Resource.String.Cancel, new NegativeListener()) + .Create(); + } + } + + } } diff --git a/android5.0/Camera2Basic/Camera2Basic/Properties/AndroidManifest.xml b/android5.0/Camera2Basic/Camera2Basic/Properties/AndroidManifest.xml index 9f57c1267..9ef69bdd1 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Properties/AndroidManifest.xml +++ b/android5.0/Camera2Basic/Camera2Basic/Properties/AndroidManifest.xml @@ -1,7 +1,6 @@  - - - + + \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Resources/values/strings.xml b/android5.0/Camera2Basic/Camera2Basic/Resources/values/strings.xml index 66f10008a..f0d6bc0e8 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Resources/values/strings.xml +++ b/android5.0/Camera2Basic/Camera2Basic/Resources/values/strings.xml @@ -1,4 +1,5 @@ - - Picture - Info + Picture + Info + This sample needs camera permission. + This device doesn\'t support Camera2 API. diff --git a/android5.0/Camera2Basic/Camera2Basic/packages.config b/android5.0/Camera2Basic/Camera2Basic/packages.config new file mode 100644 index 000000000..20b82f838 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file From cfbf47ef98cd3deed342d4c861a886976efd78ab Mon Sep 17 00:00:00 2001 From: Gonzalo Martin Date: Wed, 5 Oct 2016 16:03:45 -0300 Subject: [PATCH 2/4] Updated Camera2Raw --- android5.0/Camera2Raw/Camera2Raw.sln | 10 +- android5.0/Camera2Raw/Camera2RawFragment.cs | 3463 +++++++++-------- .../Camera2Raw/Properties/AndroidManifest.xml | 3 +- 3 files changed, 1819 insertions(+), 1657 deletions(-) diff --git a/android5.0/Camera2Raw/Camera2Raw.sln b/android5.0/Camera2Raw/Camera2Raw.sln index 8a222730b..6b039a43f 100644 --- a/android5.0/Camera2Raw/Camera2Raw.sln +++ b/android5.0/Camera2Raw/Camera2Raw.sln @@ -1,6 +1,8 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Camera2Raw", "Camera2Raw.csproj", "{7522C011-FDFE-418D-B8E5-432116C510E0}" EndProject Global @@ -11,7 +13,11 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {7522C011-FDFE-418D-B8E5-432116C510E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7522C011-FDFE-418D-B8E5-432116C510E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7522C011-FDFE-418D-B8E5-432116C510E0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU {7522C011-FDFE-418D-B8E5-432116C510E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {7522C011-FDFE-418D-B8E5-432116C510E0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection EndGlobal diff --git a/android5.0/Camera2Raw/Camera2RawFragment.cs b/android5.0/Camera2Raw/Camera2RawFragment.cs index d90dce83e..e3458a918 100644 --- a/android5.0/Camera2Raw/Camera2RawFragment.cs +++ b/android5.0/Camera2Raw/Camera2RawFragment.cs @@ -1,4 +1,20 @@ -using System; +/* + * Copyright 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -22,1660 +38,1801 @@ using Java.Nio; using Java.Text; using System.IO; +using Android; namespace Camera2Raw { - public class Camera2RawFragment : Fragment, View.IOnClickListener - { - static readonly SparseIntArray ORIENTATIONS = new SparseIntArray (); - - static Camera2RawFragment () - { - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation0, 0); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation90, 90); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation180, 180); - ORIENTATIONS.Append ((int)SurfaceOrientation.Rotation270, 270); - } - - public Camera2RawFragment () : base () - { - } - - /// - /// Timeout for the pre-capture sequence. - /// - const long PRECAPTURE_TIMEOUT_MS = 1000; - - /// - /// Tolerance when comparing aspect ratios. - /// - const double ASPECT_RATIO_TOLERANCE = 0.005; - - /// - /// Tag for the {@link Log}. - /// - const string TAG = "Camera2RawFragment"; - - /// - /// Camera state: Device is closed. - /// - const int STATE_CLOSED = 0; - - /// - /// Camera state: Device is opened, but is not capturing. - /// - const int STATE_OPENED = 1; - - /// - /// Camera state: Showing camera preview. - /// - const int STATE_PREVIEW = 2; - - /// - /// Camera state: Waiting for 3A convergence before capturing a photo. - /// - const int STATE_WAITING_FOR_3A_CONVERGENCE = 3; - - /// - /// An {@link OrientationEventListener} used to determine when device rotation has occurred. - /// This is mainly necessary for when the device is rotated by 180 degrees, in which case - /// onCreate or onConfigurationChanged is not called as the view dimensions remain the same, - /// but the orientation of the has changed, and thus the preview rotation must be updated.. - /// - OrientationEventListener mOrientationListener; - - class SurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener - { - Activity Activity { get; set; } - - public SurfaceTextureListener (Activity activity) - { - Activity = activity; - } - - public void OnSurfaceTextureAvailable (Android.Graphics.SurfaceTexture surface, int width, int height) - { - ConfigureTransform (width, height, Activity); - } - - public bool OnSurfaceTextureDestroyed (Android.Graphics.SurfaceTexture surface) - { - lock (mCameraStateLock) { - mPreviewSize = null; - } - return true; - } - - public void OnSurfaceTextureSizeChanged (Android.Graphics.SurfaceTexture surface, int width, int height) - { - ConfigureTransform (width, height, Activity); - } - - public void OnSurfaceTextureUpdated (Android.Graphics.SurfaceTexture surface) - { - } - } - - /// - /// {@link TextureView.SurfaceTextureListener} handles several lifecycle events of a - /// {@link TextureView}. - /// - TextureView.ISurfaceTextureListener mSurfaceTextureListener; - - /// - /// An {@link AutoFitTextureView} for camera preview. - /// - static AutoFitTextureView mTextureView; - - /// - /// An additional thread for running tasks that shouldn't block the UI. This is used for all - /// callbacks from the {@link CameraDevice} and {@link CameraCaptureSession}s. - /// - HandlerThread mBackgroundThread; - - /// - /// A counter for tracking corresponding {@link CaptureRequest}s and {@link CaptureResult}s - /// across the {@link CameraCaptureSession} capture callbacks. - /// - static readonly AtomicInteger mRequestCounter = new AtomicInteger (); - - /// - /// A {@link Semaphore} to prevent the app from exiting before closing the camera. - /// - static readonly Semaphore mCameraOpenCloseLock = new Semaphore (1); - - /// - /// A lock protecting camera state. - /// - static readonly object mCameraStateLock = new object (); - - // ********************************************************************************************* - // State protected by mCameraStateLock. - // - // The following state is used across both the UI and background threads. Methods with "Locked" - // in the name expect mCameraStateLock to be held while calling. - - /// - /// ID of the current {@link CameraDevice}. - /// - string mCameraId; - - /// - /// A {@link CameraCaptureSession } for camera preview. - /// - static CameraCaptureSession mCaptureSession; - - /// - /// A reference to the open {@link CameraDevice}. - /// - static CameraDevice mCameraDevice; - - /// - /// The {@link Size} of camera preview. - /// - static Size mPreviewSize; - - /// - /// The {@link CameraCharacteristics} for the currently configured camera device. - /// - static CameraCharacteristics mCharacteristics; - - /// - /// A {@link Handler} for running tasks in the background. - /// - static Handler mBackgroundHandler; - - /// - /// A reference counted holder wrapping the {@link ImageReader} that handles JPEG image captures. - /// This is used to allow us to clean up the {@link ImageReader} when all background tasks using - /// its {@link Image}s have completed. - /// - static RefCountedAutoCloseable mJpegImageReader; - - /// - /// A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures. - /// This is used to allow us to clean up the {@link ImageReader} when all background tasks using - /// its {@link Image}s have completed. - /// - static RefCountedAutoCloseable mRawImageReader; - - /// - /// Whether or not the currently configured camera device is fixed-focus. - /// - static bool mNoAFRun = false; - - /// - /// Number of pending user requests to capture a photo. - /// - static int mPendingUserCaptures = 0; - - /// - /// Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress JPEG captures. - /// - static readonly TreeMap mJpegResultQueue = new TreeMap (); - - /// - /// Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress RAW captures. - /// - static readonly TreeMap mRawResultQueue = new TreeMap (); - - /// - /// {@link CaptureRequest.Builder} for the camera preview - /// - static CaptureRequest.Builder mPreviewRequestBuilder; - - /// - /// The state of the camera device. - /// - /// @see #mPreCaptureCallback - /// - static int mState = STATE_CLOSED; - - /// - /// Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is taking - /// too long. - /// - static long mCaptureTimer; - - //********************************************************************************************** - - class StateCallback : CameraDevice.StateCallback - { - Activity Activity { get; set; } - - public StateCallback (Activity activity) - { - Activity = activity; - } - - public override void OnDisconnected (CameraDevice camera) - { - lock (mCameraStateLock) { - mState = STATE_CLOSED; - mCameraOpenCloseLock.Release (); - camera.Close (); - mCameraDevice = null; - } - } - - public override void OnError (CameraDevice camera, Android.Hardware.Camera2.CameraError error) - { - Log.Error (TAG, "Received camera device error: " + error); - lock (mCameraStateLock) { - mState = STATE_CLOSED; - mCameraOpenCloseLock.Release (); - camera.Close (); - mCameraDevice = null; - } - var activity = Activity; - if (null != activity) { - activity.Finish (); - } - } - - public override void OnOpened (CameraDevice camera) - { - // This method is called when the camera is opened. We start camera preview here if - // the TextureView displaying this has been set up. - lock (mCameraStateLock) { - mState = STATE_OPENED; - mCameraOpenCloseLock.Release (); - mCameraDevice = camera; - - // Start the preview session if the TextureView has been set up already. - if (mPreviewSize != null && mTextureView.IsAvailable) { - CreateCameraPreviewSessionLocked (); - } - } - } - } - - /// - /// {@link CameraDevice.StateCallback} is called when the currently active {@link CameraDevice} - /// changes its state. - /// - CameraDevice.StateCallback mStateCallback; - - class OnJpegImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener - { - public void OnImageAvailable (ImageReader reader) - { - DequeueAndSaveImage (mJpegResultQueue, mJpegImageReader); - } - } - - /// - /// This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a - /// JPEG image is ready to be saved. - /// - readonly ImageReader.IOnImageAvailableListener mOnJpegImageAvailableListener = new OnJpegImageAvailableListener (); - - class OnRawImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener - { - public void OnImageAvailable (ImageReader reader) - { - DequeueAndSaveImage (mRawResultQueue, mRawImageReader); - } - } - - /// - /// This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a - /// RAW image is ready to be saved. - /// - readonly ImageReader.IOnImageAvailableListener mOnRawImageAvailableListener = new OnRawImageAvailableListener (); - - - class PreCameraCaptureCallback : CameraCaptureSession.CaptureCallback - { - Activity Activity { get; set; } - - public PreCameraCaptureCallback (Activity activity) - { - Activity = activity; - } - - void Process (CaptureResult result) - { - lock (mCameraStateLock) { - switch (mState) { - case STATE_PREVIEW: - // We have nothing to do when the camera preview is running normally. - break; - case STATE_WAITING_FOR_3A_CONVERGENCE: - bool readyToCapture = true; - if (!mNoAFRun) { - int afState = (int)result.Get (CaptureResult.ControlAfState); - - // If auto-focus has reached locked state, we are ready to capture - readyToCapture = (afState == (int)ControlAFState.FocusedLocked || - afState == (int)ControlAFState.NotFocusedLocked); - } - - // If we are running on an non-legacy device, we should also wait until - // auto-exposure and auto-white-balance have converged as well before - // taking a picture. - if (!IsLegacyLocked ()) { - var aeState = (int)result.Get (CaptureResult.ControlAeState); - var awbState = (int)result.Get (CaptureResult.ControlAwbState); - - readyToCapture = readyToCapture && - aeState == (int)ControlAEState.Converged && - awbState == (int)ControlAwbState.Converged; - } - - // If we haven't finished the pre-capture sequence but have hit our maximum - // wait timeout, too bad! Begin capture anyway. - if (!readyToCapture && HitTimeoutLocked ()) { - Log.Warn (TAG, "Timed out waiting for pre-capture sequence to complete."); - readyToCapture = true; - } - - if (readyToCapture && mPendingUserCaptures > 0) { - // Capture once for each user tap of the "Picture" button. - while (mPendingUserCaptures > 0) { - CaptureStillPictureLocked (Activity); - mPendingUserCaptures--; - } - // After this, the camera will go back to the normal state of preview. - mState = STATE_PREVIEW; - } - break; - } - } - } - - public override void OnCaptureProgressed (CameraCaptureSession session, CaptureRequest request, - CaptureResult partialResult) - { - Process (partialResult); - } - - public override void OnCaptureCompleted (CameraCaptureSession session, CaptureRequest request, - TotalCaptureResult result) - { - Process (result); - } - } - - /// > - /// A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and - /// pre-capture sequence. - /// - static CameraCaptureSession.CaptureCallback mPreCaptureCallback; - - class CaptureCallback : CameraCaptureSession.CaptureCallback - { - public override void OnCaptureStarted (CameraCaptureSession session, CaptureRequest request, - long timestamp, long frameNumber) - { - string currentDateTime = GenerateTimestamp (); - var path = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal); - var rawFilePath = System.IO.Path.Combine (path, "RAW_" + currentDateTime + ".dng"); - var jpegFilePath = System.IO.Path.Combine (path, "JPEG_" + currentDateTime + ".jpg"); - var rawFile = new FileInfo (rawFilePath); - var jpegFile = new FileInfo (jpegFilePath); - - // Look up the ImageSaverBuilder for this request and update it with the file name - // based on the capture start time. - ImageSaver.ImageSaverBuilder jpegBuilder; - ImageSaver.ImageSaverBuilder rawBuilder; - int requestId = (int)request.Tag; - lock (mCameraStateLock) { - jpegBuilder = (ImageSaver.ImageSaverBuilder)mJpegResultQueue.Get (requestId); - rawBuilder = (ImageSaver.ImageSaverBuilder)mRawResultQueue.Get (requestId); - } - - if (jpegBuilder != null) - jpegBuilder.SetFile (jpegFile); - - if (rawBuilder != null) - rawBuilder.SetFile (rawFile); - } - - public override void OnCaptureCompleted (CameraCaptureSession session, CaptureRequest request, - TotalCaptureResult result) - { - int requestId = (int)request.Tag; - ImageSaver.ImageSaverBuilder jpegBuilder; - ImageSaver.ImageSaverBuilder rawBuilder; - var sb = new System.Text.StringBuilder (); - - // Look up the ImageSaverBuilder for this request and update it with the CaptureResult - lock (mCameraStateLock) { - jpegBuilder = (ImageSaver.ImageSaverBuilder)mJpegResultQueue.Get (requestId); - rawBuilder = (ImageSaver.ImageSaverBuilder)mRawResultQueue.Get (requestId); - - // If we have all the results necessary, save the image to a file in the background. - HandleCompletionLocked (requestId, jpegBuilder, mJpegResultQueue); - HandleCompletionLocked (requestId, rawBuilder, mRawResultQueue); - - if (jpegBuilder != null) { - jpegBuilder.SetResult (result); - sb.Append ("Saving JPEG as: "); - sb.Append (jpegBuilder.GetSaveLocation ()); - } - if (rawBuilder != null) { - rawBuilder.SetResult (result); - if (jpegBuilder != null) - sb.Append (", "); - sb.Append ("Saving RAW as: "); - sb.Append (rawBuilder.GetSaveLocation ()); - } - FinishedCaptureLocked (); - } - - ShowToast (sb.ToString ()); - } - - public override void OnCaptureFailed (CameraCaptureSession session, CaptureRequest request, - CaptureFailure failure) - { - int requestId = (int)request.Tag; - lock (mCameraStateLock) { - mJpegResultQueue.Remove (requestId); - mRawResultQueue.Remove (requestId); - FinishedCaptureLocked (); - } - ShowToast ("Capture failed!"); - } - } - - /// - /// A {@link CameraCaptureSession.CaptureCallback} that handles the still JPEG and RAW capture - /// request. - /// - static readonly CameraCaptureSession.CaptureCallback mCaptureCallback = new CaptureCallback (); - - class MessageHandler : Handler - { - Activity Activity { get; set; } - - public MessageHandler (Looper looper, Activity activity) : base (looper) - { - Activity = activity; - } - - public override void HandleMessage (Message msg) - { - if (Activity != null) { - Toast.MakeText (Activity, (string)msg.Obj, ToastLength.Short).Show (); - } - } - } - - /// - /// A {@link Handler} for showing {@link Toast}s on the UI thread. - /// - static Handler mMessageHandler; - - public static Camera2RawFragment Create () - { - Camera2RawFragment fragment = new Camera2RawFragment (); - fragment.RetainInstance = true; - return fragment; - } - - public override View OnCreateView (LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - return inflater.Inflate (Resource.Layout.fragment_camera2_basic, container, false); - } - - class OrientationListener : OrientationEventListener - { - Activity Activity { get; set; } - - public OrientationListener (Context context, SensorDelay delay, Activity activity) : base (context, delay) - { - Activity = activity; - } - - public override void OnOrientationChanged (int orientation) - { - if (mTextureView != null && mTextureView.IsAvailable) { - ConfigureTransform (mTextureView.Width, mTextureView.Height, Activity); - } - } - } - - public override void OnViewCreated (View view, Bundle savedInstanceState) - { - view.FindViewById (Resource.Id.picture).SetOnClickListener (this); - view.FindViewById (Resource.Id.info).SetOnClickListener (this); - mTextureView = (AutoFitTextureView)view.FindViewById (Resource.Id.texture); - - // Setup a new OrientationEventListener. This is used to handle rotation events like a - // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout - // or otherwise cause the preview TextureView's size to change. - mOrientationListener = new OrientationListener (Activity, SensorDelay.Normal, Activity); - mMessageHandler = new MessageHandler (Looper.MainLooper, Activity); - mPreCaptureCallback = new PreCameraCaptureCallback (Activity); - mSurfaceTextureListener = new SurfaceTextureListener (Activity); - mStateCallback = new StateCallback (Activity); - } - - public override void OnResume () - { - base.OnResume (); - StartBackgroundThread (); - - if (CanOpenCamera ()) { - - // When the screen is turned off and turned back on, the SurfaceTexture is already - // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should - // configure the preview bounds here (otherwise, we wait until the surface is ready in - // the SurfaceTextureListener). - if (mTextureView.IsAvailable) { - ConfigureTransform (mTextureView.Width, mTextureView.Height, Activity); - } else { - mTextureView.SurfaceTextureListener = mSurfaceTextureListener; - } - if (mOrientationListener != null && mOrientationListener.CanDetectOrientation ()) { - mOrientationListener.Enable (); - } - } - } - - public override void OnPause () - { - if (mOrientationListener != null) { - mOrientationListener.Disable (); - } - CloseCamera (); - StopBackgroundThread (); - base.OnPause (); - } - - public void OnClick (View view) - { - switch (view.Id) { - case Resource.Id.picture: - { - TakePicture (); - break; - } - case Resource.Id.info: - { - var activity = Activity; - if (activity != null) { - new AlertDialog.Builder (activity) - .SetMessage (Resource.String.intro_message) - .SetPositiveButton (Android.Resource.String.Ok, default(IDialogInterfaceOnClickListener)) - .Show (); - } - break; - } - } - } - - /// - /// Sets up state related to camera that is needed before opening a {@link CameraDevice}. - /// - /// true, if up camera outputs was set, false otherwise. - bool SetUpCameraOutputs () - { - var activity = Activity; - CameraManager manager = (CameraManager)activity.GetSystemService (Context.CameraService); - if (manager == null) { - ErrorDialog.BuildErrorDialog ("This device doesn't support Camera2 API."). - Show (FragmentManager, "dialog"); - return false; - } - try { - // Find a CameraDevice that supports RAW captures, and configure state. - foreach (string cameraId in manager.GetCameraIdList()) { - CameraCharacteristics characteristics - = manager.GetCameraCharacteristics (cameraId); - - // We only use a camera that supports RAW in this sample. - if (!Contains (characteristics.Get ( - CameraCharacteristics.RequestAvailableCapabilities).ToArray (), - (int)RequestAvailableCapabilities.Raw)) { - continue; - } - - StreamConfigurationMap map = (StreamConfigurationMap)characteristics.Get ( - CameraCharacteristics.ScalerStreamConfigurationMap); - - // For still image captures, we use the largest available size. - Size[] jpegs = map.GetOutputSizes ((int)ImageFormatType.Jpeg); - Size largestJpeg = jpegs.OrderByDescending (element => element.Width * element.Height).First (); - - Size[] raws = map.GetOutputSizes ((int)ImageFormatType.RawSensor); - Size largestRaw = raws.OrderByDescending (element => element.Width * element.Height).First (); - - lock (mCameraStateLock) { - // Set up ImageReaders for JPEG and RAW outputs. Place these in a reference - // counted wrapper to ensure they are only closed when all background tasks - // using them are finished. - if (mJpegImageReader == null || mJpegImageReader.GetAndRetain () == null) { - mJpegImageReader = new RefCountedAutoCloseable ( - ImageReader.NewInstance (largestJpeg.Width, - largestJpeg.Height, ImageFormatType.Jpeg, /*maxImages*/5)); - } - - mJpegImageReader.Get ().SetOnImageAvailableListener ( - mOnJpegImageAvailableListener, mBackgroundHandler); - - if (mRawImageReader == null || mRawImageReader.GetAndRetain () == null) { - mRawImageReader = new RefCountedAutoCloseable ( - ImageReader.NewInstance (largestRaw.Width, - largestRaw.Height, ImageFormatType.RawSensor, /*maxImages*/5)); - } - mRawImageReader.Get ().SetOnImageAvailableListener ( - mOnRawImageAvailableListener, mBackgroundHandler); - - mCharacteristics = characteristics; - mCameraId = cameraId; - } - return true; - } - } catch (CameraAccessException e) { - e.PrintStackTrace (); - } - - // If we found no suitable cameras for capturing RAW, warn the user. - ErrorDialog.BuildErrorDialog ("This device doesn't support capturing RAW photos"). - Show (FragmentManager, "dialog"); - return false; - } - - /// - /// Opens the camera specified by {@link #mCameraId}. - /// - bool CanOpenCamera () - { - if (!SetUpCameraOutputs ()) - return false; - - var activity = Activity; - CameraManager manager = (CameraManager)activity.GetSystemService (Context.CameraService); - try { - // Wait for any previously running session to finish. - if (!mCameraOpenCloseLock.TryAcquire (2500, TimeUnit.Milliseconds)) - throw new RuntimeException ("Time out waiting to lock camera opening."); - - string cameraId; - Handler backgroundHandler; - lock (mCameraStateLock) { - cameraId = mCameraId; - backgroundHandler = mBackgroundHandler; - } - - // Attempt to open the camera. mStateCallback will be called on the background handler's - // thread when this succeeds or fails. - manager.OpenCamera (cameraId, mStateCallback, backgroundHandler); - } catch (CameraAccessException e) { - e.PrintStackTrace (); - } catch (InterruptedException e) { - throw new RuntimeException ("Interrupted while trying to lock camera opening.", e); - } - return true; - } - - /// - /// Closes the current {@link CameraDevice}. - /// - void CloseCamera () - { - try { - mCameraOpenCloseLock.Acquire (); - lock (mCameraStateLock) { - - // Reset state and clean up resources used by the camera. - // Note: After calling this, the ImageReaders will be closed after any background - // tasks saving Images from these readers have been completed. - mPendingUserCaptures = 0; - mState = STATE_CLOSED; - if (null != mCaptureSession) { - mCaptureSession.Close (); - mCaptureSession = null; - } - if (null != mCameraDevice) { - mCameraDevice.Close (); - mCameraDevice = null; - } - if (null != mJpegImageReader) { - mJpegImageReader.Close (); - mJpegImageReader = null; - } - if (null != mRawImageReader) { - mRawImageReader.Close (); - mRawImageReader = null; - } - } - } catch (InterruptedException e) { - throw new RuntimeException ("Interrupted while trying to lock camera closing.", e); - } finally { - mCameraOpenCloseLock.Release (); - } - } - - /// - /// Starts a background thread and its {@link Handler}. - /// - void StartBackgroundThread () - { - mBackgroundThread = new HandlerThread ("CameraBackground"); - mBackgroundThread.Start (); - lock (mCameraStateLock) { - mBackgroundHandler = new Handler (mBackgroundThread.Looper); - } - } - - /// - /// Stops the background thread and its {@link Handler}. - /// - void StopBackgroundThread () - { - mBackgroundThread.QuitSafely (); - try { - mBackgroundThread.Join (); - mBackgroundThread = null; - lock (mCameraStateLock) { - mBackgroundHandler = null; - } - } catch (InterruptedException e) { - e.PrintStackTrace (); - } - } - - class CameraPreviewCaptureCallback : CameraCaptureSession.StateCallback - { - public override void OnConfigured (CameraCaptureSession cameraCaptureSession) - { - lock (mCameraStateLock) { - // The camera is already closed - if (null == mCameraDevice) - return; - - try { - Setup3AControlsLocked (mPreviewRequestBuilder); - // Finally, we start displaying the camera preview. - cameraCaptureSession.SetRepeatingRequest ( - mPreviewRequestBuilder.Build (), - mPreCaptureCallback, mBackgroundHandler); - mState = STATE_PREVIEW; - } catch (CameraAccessException e) { - e.PrintStackTrace (); - return; - } catch (IllegalStateException e) { - e.PrintStackTrace (); - return; - } - // When the session is ready, we start displaying the preview. - mCaptureSession = cameraCaptureSession; - } - } - - public override void OnConfigureFailed (CameraCaptureSession session) - { - ShowToast ("Failed to configure camera."); - } - } - - /// - /// Creates a new {@link CameraCaptureSession} for camera preview. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - static void CreateCameraPreviewSessionLocked () - { - try { - SurfaceTexture texture = mTextureView.SurfaceTexture; - // We configure the size of default buffer to be the size of camera preview we want. - texture.SetDefaultBufferSize (mPreviewSize.Width, mPreviewSize.Height); - - // This is the output Surface we need to start preview. - Surface surface = new Surface (texture); - - // We set up a CaptureRequest.Builder with the output Surface. - mPreviewRequestBuilder - = mCameraDevice.CreateCaptureRequest (CameraTemplate.Preview); - mPreviewRequestBuilder.AddTarget (surface); - - // Here, we create a CameraCaptureSession for camera preview. - mCameraDevice.CreateCaptureSession (new List () {surface, - mJpegImageReader.Get ().Surface, - mRawImageReader.Get ().Surface - }, new CameraPreviewCaptureCallback (), mBackgroundHandler); - } catch (CameraAccessException e) { - e.PrintStackTrace (); - } - } - - /// - /// Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and - /// auto-white-balance controls if available. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - /// the builder to configure. - static void Setup3AControlsLocked (CaptureRequest.Builder builder) - { - // Enable auto-magical 3A run by camera device - builder.Set (CaptureRequest.ControlMode, (int)ControlMode.Auto); - - var minFocusDist = (float)mCharacteristics.Get (CameraCharacteristics.LensInfoMinimumFocusDistance); - - // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run. - mNoAFRun = (minFocusDist == null || minFocusDist == 0); - - if (!mNoAFRun) { - // If there is a "continuous picture" mode available, use it, otherwise default to AUTO. - if (Contains (mCharacteristics.Get ( - CameraCharacteristics.ControlAfAvailableModes).ToArray (), - (int)ControlAFMode.ContinuousPicture)) { - builder.Set (CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture); - } else { - builder.Set (CaptureRequest.ControlAfMode, (int)ControlAFMode.Auto); - } - } - - // If there is an auto-magical flash control mode available, use it, otherwise default to - // the "on" mode, which is guaranteed to always be available. - if (Contains (mCharacteristics.Get ( - CameraCharacteristics.ControlAeAvailableModes).ToArray (), (int)ControlAEMode.OnAutoFlash)) { - builder.Set (CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash); - } else { - builder.Set (CaptureRequest.ControlAeMode, (int)ControlAEMode.On); - } - - // If there is an auto-magical white balance control mode available, use it. - if (Contains (mCharacteristics.Get ( - CameraCharacteristics.ControlAwbAvailableModes).ToArray (), (int)ControlAwbMode.Auto)) { - // Allow AWB to run auto-magically if this device supports this - builder.Set (CaptureRequest.ControlAwbMode, (int)ControlAwbMode.Auto); - } - } - - /// - /// Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`, - /// and start/restart the preview capture session if necessary. - /// - /// This method should be called after the camera state has been initialized in - /// setUpCameraOutputs. - /// - /// The width of `mTextureView` - /// The height of `mTextureView` - static void ConfigureTransform (int viewWidth, int viewHeight, Activity activity) - { - lock (mCameraStateLock) { - if (mTextureView == null || activity == null) { - return; - } - - var map = (StreamConfigurationMap)mCharacteristics.Get (CameraCharacteristics.ScalerStreamConfigurationMap); - - // For still image captures, we always use the largest available size. - Size largestJpeg = (Size)Collections.Max (Arrays.AsList (map.GetOutputSizes ((int)ImageFormatType.Jpeg)), - new CompareSizesByArea ()); - - // Find the rotation of the device relative to the native device orientation. - var deviceRotation = (int)activity.WindowManager.DefaultDisplay.Rotation; - - // Find the rotation of the device relative to the camera sensor's orientation. - int totalRotation = SensorToDeviceRotation (mCharacteristics, deviceRotation); - - // Swap the view dimensions for calculation as needed if they are rotated relative to - // the sensor. - bool swappedDimensions = totalRotation == 90 || totalRotation == 270; - int rotatedViewWidth = viewWidth; - int rotatedViewHeight = viewHeight; - if (swappedDimensions) { - rotatedViewWidth = viewHeight; - rotatedViewHeight = viewWidth; - } - - // Find the best preview size for these view dimensions and configured JPEG size. - Size previewSize = ChooseOptimalSize (map.GetOutputSizes (Class.FromType (typeof(SurfaceTexture))), - rotatedViewWidth, rotatedViewHeight, largestJpeg); - - if (swappedDimensions) { - mTextureView.SetAspectRatio ( - previewSize.Height, previewSize.Width); - } else { - mTextureView.SetAspectRatio ( - previewSize.Width, previewSize.Height); - } - - // Find rotation of device in degrees (reverse device orientation for front-facing - // cameras). - int rotation = ((int)mCharacteristics.Get (CameraCharacteristics.LensFacing) == - (int)LensFacing.Front) ? - (360 + ORIENTATIONS.Get (deviceRotation)) % 360 : - (360 - ORIENTATIONS.Get (deviceRotation)) % 360; - - Matrix matrix = new Matrix (); - RectF viewRect = new RectF (0, 0, viewWidth, viewHeight); - RectF bufferRect = new RectF (0, 0, previewSize.Height, previewSize.Width); - float centerX = viewRect.CenterX (); - float centerY = viewRect.CenterY (); - - // Initially, output stream images from the Camera2 API will be rotated to the native - // device orientation from the sensor's orientation, and the TextureView will default to - // scaling these buffers to fill it's view bounds. If the aspect ratios and relative - // orientations are correct, this is fine. - // - // However, if the device orientation has been rotated relative to its native - // orientation so that the TextureView's dimensions are swapped relative to the - // native device orientation, we must do the following to ensure the output stream - // images are not incorrectly scaled by the TextureView: - // - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions - // in the native device orientation) to the TextureView's dimension. - // - Apply a scale-to-fill from the output buffer's rotated dimensions - // (i.e. its dimensions in the current device orientation) to the TextureView's - // dimensions. - // - Apply the rotation from the native device orientation to the current device - // rotation. - if (deviceRotation == (int)SurfaceOrientation.Rotation90 || deviceRotation == (int)SurfaceOrientation.Rotation270) { - bufferRect.Offset (centerX - bufferRect.CenterX (), centerY - bufferRect.CenterY ()); - matrix.SetRectToRect (viewRect, bufferRect, Matrix.ScaleToFit.Fill); - float scale = System.Math.Max ( - (float)viewHeight / previewSize.Height, - (float)viewWidth / previewSize.Width); - matrix.PostScale (scale, scale, centerX, centerY); - - } - matrix.PostRotate (rotation, centerX, centerY); - - mTextureView.SetTransform (matrix); - - // Start or restart the active capture session if the preview was initialized or - // if its aspect ratio changed significantly. - if (mPreviewSize == null || !CheckAspectsEqual (previewSize, mPreviewSize)) { - mPreviewSize = previewSize; - if (mState != STATE_CLOSED) { - CreateCameraPreviewSessionLocked (); - } - } - } - } - - /// - /// Initiate a still image capture. - /// - /// This function sends a capture request that initiates a pre-capture sequence in our state - /// machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no - /// longer moving, waits for auto-exposure to choose a good exposure value, and waits for - /// auto-white-balance to converge. - /// - void TakePicture () - { - lock (mCameraStateLock) { - mPendingUserCaptures++; - - // If we already triggered a pre-capture sequence, or are in a state where we cannot - // do this, return immediately. - if (mState != STATE_PREVIEW) { - return; - } - - try { - // Trigger an auto-focus run if camera is capable. If the camera is already focused, - // this should do nothing. - if (!mNoAFRun) { - mPreviewRequestBuilder.Set (CaptureRequest.ControlAfTrigger, - (int)ControlAFTrigger.Start); - } - - // If this is not a legacy device, we can also trigger an auto-exposure metering - // run. - if (!IsLegacyLocked ()) { - // Tell the camera to lock focus. - mPreviewRequestBuilder.Set (CaptureRequest.ControlAePrecaptureTrigger, - (int)ControlAEPrecaptureTrigger.Start); - } - - // Update state machine to wait for auto-focus, auto-exposure, and - // auto-white-balance (aka. "3A") to converge. - mState = STATE_WAITING_FOR_3A_CONVERGENCE; - - // Start a timer for the pre-capture sequence. - StartTimerLocked (); - - // Replace the existing repeating request with one with updated 3A triggers. - mCaptureSession.Capture (mPreviewRequestBuilder.Build (), mPreCaptureCallback, - mBackgroundHandler); - } catch (CameraAccessException e) { - e.PrintStackTrace (); - } - } - } - - /// - /// Send a capture request to the camera device that initiates a capture targeting the JPEG and - /// RAW outputs. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - static void CaptureStillPictureLocked (Activity activity) - { - try { - if (null == activity || null == mCameraDevice) - return; - - // This is the CaptureRequest.Builder that we use to take a picture. - CaptureRequest.Builder captureBuilder = - mCameraDevice.CreateCaptureRequest (CameraTemplate.StillCapture); - - captureBuilder.AddTarget (mJpegImageReader.Get ().Surface); - captureBuilder.AddTarget (mRawImageReader.Get ().Surface); - - // Use the same AE and AF modes as the preview. - Setup3AControlsLocked (captureBuilder); - - // Set orientation. - var rotation = activity.WindowManager.DefaultDisplay.Rotation; - captureBuilder.Set (CaptureRequest.JpegOrientation, SensorToDeviceRotation (mCharacteristics, (int)rotation)); - - // Set request tag to easily track results in callbacks. - captureBuilder.SetTag (mRequestCounter.IncrementAndGet ()); - - CaptureRequest request = captureBuilder.Build (); - - // Create an ImageSaverBuilder in which to collect results, and add it to the queue - // of active requests. - ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder (activity) - .SetCharacteristics (mCharacteristics); - ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder (activity) - .SetCharacteristics (mCharacteristics); - - mJpegResultQueue.Put ((int)request.Tag, jpegBuilder); - mRawResultQueue.Put ((int)request.Tag, rawBuilder); - - mCaptureSession.Capture (request, mCaptureCallback, mBackgroundHandler); - - } catch (CameraAccessException e) { - e.PrintStackTrace (); - } - } - - /// - /// Called after a RAW/JPEG capture has completed; resets the AF trigger state for the - /// pre-capture sequence. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - static void FinishedCaptureLocked () - { - try { - // Reset the auto-focus trigger in case AF didn't run quickly enough. - if (!mNoAFRun) { - mPreviewRequestBuilder.Set (CaptureRequest.ControlAfTrigger, - (int)ControlAFTrigger.Cancel); - - mCaptureSession.Capture (mPreviewRequestBuilder.Build (), - mPreCaptureCallback, mBackgroundHandler); - - mPreviewRequestBuilder.Set (CaptureRequest.ControlAfTrigger, - (int)ControlAFTrigger.Idle); - } - } catch (CameraAccessException e) { - e.PrintStackTrace (); - } - } - - /// - /// Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining - /// that {@link ImageReader} until that {@link Image} is no longer in use, and set this - /// {@link Image} as the result for the next request in the queue of pending requests. If - /// all necessary information is available, begin saving the image to a file in a background - /// thread. - /// - /// the currently active requests. - /// a reference counted wrapper containing an {@link ImageReader} from which to acquire an image. - static void DequeueAndSaveImage (TreeMap pendingQueue, - RefCountedAutoCloseable reader) - { - lock (mCameraStateLock) { - IMapEntry entry = pendingQueue.FirstEntry (); - var builder = (ImageSaver.ImageSaverBuilder)entry.Value; - - // Increment reference count to prevent ImageReader from being closed while we - // are saving its Images in a background thread (otherwise their resources may - // be freed while we are writing to a file). - if (reader == null || reader.GetAndRetain () == null) { - Log.Error (TAG, "Paused the activity before we could save the image," + - " ImageReader already closed."); - pendingQueue.Remove (entry.Key); - return; - } - - Image image; - try { - image = reader.Get ().AcquireNextImage (); - } catch (IllegalStateException) { - Log.Error (TAG, "Too many images queued for saving, dropping image for request: " + - entry.Key); - pendingQueue.Remove (entry.Key); - return; - } - - builder.SetRefCountedReader (reader).SetImage (image); - - HandleCompletionLocked ((int)entry.Key, builder, pendingQueue); - } - } - - /// - /// Runnable that saves an {@link Image} into the specified {@link File}, and updates - /// {@link android.provider.MediaStore} to include the resulting file. - /// - /// This can be constructed through an {@link ImageSaverBuilder} as the necessary image and - /// result information becomes available. - /// - class ImageSaver : Java.Lang.Object, IRunnable - { - /// - /// The image to save. - /// - readonly Image mImage; - - /// - /// The file we save the image into. - /// - readonly FileInfo mFile; - - /// - /// The CaptureResult for this image capture. - /// - readonly CaptureResult mCaptureResult; - - /// - /// The CameraCharacteristics for this camera device. - /// - readonly CameraCharacteristics mCharacteristics; - - /// - /// The Context to use when updating MediaStore with the saved images. - /// - readonly Context mContext; - - /// - /// A reference counted wrapper for the ImageReader that owns the given image. - /// - readonly RefCountedAutoCloseable mReader; - - ImageSaver (Image image, FileInfo file, CaptureResult result, - CameraCharacteristics characteristics, Context context, - RefCountedAutoCloseable reader) - { - mImage = image; - mFile = file; - mCaptureResult = result; - mCharacteristics = characteristics; - mContext = context; - mReader = reader; - } - - public void Run () - { - bool success = false; - var format = mImage.Format; - switch (format) { - case ImageFormatType.Jpeg: - { - ByteBuffer buffer = mImage.GetPlanes () [0].Buffer; - byte[] bytes = new byte[buffer.Remaining ()]; - buffer.Get (bytes); - FileStream output = null; - try { - output = mFile.OpenWrite (); - output.Write (bytes, 0, bytes.Length); - success = true; - } catch (IOException e) { - Log.Error (TAG, e.Message); - } finally { - mImage.Close (); - CloseOutput (output); - } - break; - } - case ImageFormatType.RawSensor: - { - DngCreator dngCreator = new DngCreator (mCharacteristics, mCaptureResult); - FileStream output = null; - try { - output = mFile.OpenWrite (); - dngCreator.WriteImage (output, mImage); - success = true; - } catch (IOException e) { - Log.Error (TAG, e.Message); - } finally { - mImage.Close (); - CloseOutput (output); - } - break; - } - default: - { - Log.Error (TAG, "Cannot save image, unexpected image format:" + format); - break; - } - } - - // Decrement reference count to allow ImageReader to be closed to free up resources. - mReader.Close (); - - // If saving the file succeeded, update MediaStore. - if (success) { - MediaScannerConnection.ScanFile (mContext, new string[] { mFile.FullName }, - /*mimeTypes*/null, new MediaScannerClient ()); - } - } - - class MediaScannerClient : Java.Lang.Object, MediaScannerConnection.IOnScanCompletedListener - { - public void OnMediaScannerConnected () - { - //do nothing - } - - public void OnScanCompleted (string path, Android.Net.Uri uri) - { - Log.Info (TAG, "Scanned " + path + ":"); - Log.Info (TAG, "-> uri=" + uri); - } - } - - /// - /// Builder class for constructing {@link ImageSaver}s. - /// - /// This class is thread safe. - /// - public class ImageSaverBuilder : Java.Lang.Object - { - Image mImage; - FileInfo mFile; - CaptureResult mCaptureResult; - CameraCharacteristics mCharacteristics; - Context mContext; - RefCountedAutoCloseable mReader; - - /// - /// Construct a new ImageSaverBuilder using the given {@link Context}. - /// @param context a {@link Context} to for accessing the - /// {@link android.provider.MediaStore}. - /// - public ImageSaverBuilder (Context context) - { - mContext = context; - } - - public ImageSaverBuilder SetRefCountedReader ( - RefCountedAutoCloseable reader) - { - if (reader == null) - throw new NullPointerException (); - - mReader = reader; - return this; - } - - public ImageSaverBuilder SetImage (Image image) - { - if (image == null) - throw new NullPointerException (); - mImage = image; - return this; - } - - public ImageSaverBuilder SetFile (FileInfo file) - { - if (file == null) - throw new NullPointerException (); - mFile = file; - return this; - } - - public ImageSaverBuilder SetResult (CaptureResult result) - { - if (result == null) - throw new NullPointerException (); - mCaptureResult = result; - return this; - } - - public ImageSaverBuilder SetCharacteristics ( - CameraCharacteristics characteristics) - { - if (characteristics == null) - throw new NullPointerException (); - mCharacteristics = characteristics; - return this; - } - - public ImageSaver buildIfComplete () - { - if (!IsComplete) { - return null; - } - return new ImageSaver (mImage, mFile, mCaptureResult, mCharacteristics, mContext, - mReader); - } - - public string GetSaveLocation () - { - return (mFile == null) ? "Unknown" : mFile.ToString (); - } - - bool IsComplete { - get { - return mImage != null && mFile != null && mCaptureResult != null - && mCharacteristics != null; - } - } - } - } - - // Utility classes and methods: - // ********************************************************************************************* - - /// - /// Comparator based on area of the given {@link Size} objects. - /// - class CompareSizesByArea : Java.Lang.Object, IComparator - { - public int Compare (Size lhs, Size rhs) - { - // We cast here to ensure the multiplications won't overflow - return Long.Signum ((long)lhs.Width * lhs.Height - - (long)rhs.Width * rhs.Height); - } - - int IComparator.Compare (Java.Lang.Object lhs, Java.Lang.Object rhs) - { - return 0; - } - - bool IComparator.Equals (Java.Lang.Object @object) - { - return false; - } - } - - /// - /// A dialog fragment for displaying non-recoverable errors; this {@link Activity} will be - /// finished once the dialog has been acknowledged by the user. - /// - public class ErrorDialog : DialogFragment - { - - string mErrorMessage; - - public ErrorDialog () - { - mErrorMessage = "Unknown error occurred!"; - } - - // Build a dialog with a custom message (Fragments require default constructor). - public static ErrorDialog BuildErrorDialog (string errorMessage) - { - ErrorDialog dialog = new ErrorDialog (); - dialog.mErrorMessage = errorMessage; - return dialog; - } - - public override Dialog OnCreateDialog (Bundle savedInstanceState) - { - var activity = Activity; - return new AlertDialog.Builder (activity) - .SetMessage (mErrorMessage) - .SetPositiveButton (Android.Resource.String.Ok, new EventHandler ((s, args) => { - Activity.Finish (); - })).Create (); - } - - - } - - /// - /// A wrapper for an {@link AutoCloseable} object that implements reference counting to allow - /// for resource management. - /// - public class RefCountedAutoCloseable : Java.Lang.Object, IAutoCloseable where T : Java.Lang.Object - { - T mObject; - long mRefCount = 0; - - /// - /// Wrap the given object. - /// - /// object an object to wrap. - public RefCountedAutoCloseable (T obj) - { - if (obj == null) - throw new NullPointerException (); - - mObject = obj; - } - - /// - /// the reference count and return the wrapped object. - /// - /// the wrapped object, or null if the object has been released. - public T GetAndRetain () - { - if (mRefCount < 0) - return default(T); - - mRefCount++; - return mObject; - } - - /// - /// Return the wrapped object. - /// - /// the wrapped object, or null if the object has been released. - public T Get () - { - return mObject; - } - - /// - /// Decrement the reference count and release the wrapped object if there are no other - /// users retaining this object. - /// - public void Close () - { - if (mRefCount >= 0) { - mRefCount--; - if (mRefCount < 0) { - try { - var obj = (mObject as IAutoCloseable); - if (obj == null) - throw new Java.Lang.Exception ("unclosable"); - obj.Close (); - } catch (Java.Lang.Exception e) { - if (e.Message != "unclosable") - throw new RuntimeException (e); - } finally { - mObject = default(T); - } - } - } - } - } - - /// - /// Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose - /// width and height are at least as large as the respective requested values, and whose aspect - /// ratio matches with the specified value. - /// - /// The optimal {@code Size}, or an arbitrary one if none were big enough - /// The list of sizes that the camera supports for the intended output class - /// The minimum desired width - /// The minimum desired height - /// The aspect ratio - static Size ChooseOptimalSize (Size[] choices, int width, int height, Size aspectRatio) - { - // Collect the supported resolutions that are at least as big as the preview Surface - List bigEnough = new List (); - int w = aspectRatio.Width; - int h = aspectRatio.Height; - foreach (Size option in choices) { - if (option.Height == option.Width * h / w && - option.Width >= width && option.Height >= height) { - bigEnough.Add (option); - } - } - - // Pick the smallest of those, assuming we found any - if (bigEnough.Count > 0) { - return (Size)Collections.Min (bigEnough, new CompareSizesByArea ()); - } else { - Log.Error (TAG, "Couldn't find any suitable preview size"); - return choices [0]; - } - } - - /// - /// Generate a string containing a formatted timestamp with the current date and time. - /// - /// a {@link String} representing a time. - static string GenerateTimestamp () - { - SimpleDateFormat sdf = new SimpleDateFormat ("yyyy_MM_dd_HH_mm_ss_SSS", Java.Util.Locale.Us); - return sdf.Format (new Date ()); - } - - /// - /// Cleanup the given {@link OutputStream}. - /// - /// the stream to close. - static void CloseOutput (System.IO.Stream outputStream) - { - if (outputStream != null) { - try { - outputStream.Close (); - } catch (IOException e) { - Log.Error (TAG, e.Message); - } - } - } - - /// - /// Return true if the given array contains the given integer. - /// - /// true, if the array contains the given integer, false otherwise. - /// array to check. - /// integer to get for. - static bool Contains (int[] modes, int mode) - { - if (modes == null) { - return false; - } - foreach (int i in modes) { - if (i == mode) { - return true; - } - } - return false; - } - - /// - /// Return true if the two given {@link Size}s have the same aspect ratio. - /// - /// true, if the sizes have the same aspect ratio, false otherwise. - /// first {@link Size} to compare. - /// second {@link Size} to compare. - static bool CheckAspectsEqual (Size a, Size b) - { - double aAspect = a.Width / (double)a.Height; - double bAspect = b.Width / (double)b.Height; - return System.Math.Abs (aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE; - } - - /// - /// Rotation need to transform from the camera sensor orientation to the device's current - /// orientation. - /// - /// the total rotation from the sensor orientation to the current device orientation. - /// the {@link CameraCharacteristics} to query for the camera sensor orientation. - /// the current device orientation relative to the native device orientation. - static int SensorToDeviceRotation (CameraCharacteristics c, int deviceOrientation) - { - int sensorOrientation = (int)c.Get (CameraCharacteristics.SensorOrientation); - - // Get device orientation in degrees - deviceOrientation = ORIENTATIONS.Get (deviceOrientation); - - // Reverse device orientation for front-facing cameras - if ((int)c.Get (CameraCharacteristics.LensFacing) == (int)LensFacing.Front) { - deviceOrientation = -deviceOrientation; - } - - // Calculate desired JPEG orientation relative to camera orientation to make - // the image upright relative to the device orientation - return (sensorOrientation + deviceOrientation + 360) % 360; - } - - /// - /// Shows a {@link Toast} on the UI thread. - /// - /// The message to show. - static void ShowToast (string text) - { - // We show a Toast by sending request message to mMessageHandler. This makes sure that the - // Toast is shown on the UI thread. - Message message = Message.Obtain (); - message.Obj = text; - mMessageHandler.SendMessage (message); - } - - /// - /// If the given request has been completed, remove it from the queue of active requests and - /// send an {@link ImageSaver} with the results from this request to a background thread to - /// save a file. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - /// the ID of the {@link CaptureRequest} to handle. - /// the {@link ImageSaver.ImageSaverBuilder} for this request. - /// the queue to remove this request from, if completed. - static void HandleCompletionLocked (int requestId, ImageSaver.ImageSaverBuilder builder, TreeMap queue) - { - if (builder == null) - return; - ImageSaver saver = builder.buildIfComplete (); - if (saver != null) { - queue.Remove (requestId); - AsyncTask.ThreadPoolExecutor.Execute (saver); - } - } - - /// - /// Check if we are using a device that only supports the LEGACY hardware level. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - /// true if this is a legacy device; otherwise, false. - static bool IsLegacyLocked () - { - return (int)mCharacteristics.Get (CameraCharacteristics.InfoSupportedHardwareLevel) == (int)InfoSupportedHardwareLevel.Legacy; - } - - /// - /// Start the timer for the pre-capture sequence. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - static void StartTimerLocked () - { - mCaptureTimer = SystemClock.ElapsedRealtime (); - } - - /// - /// Check if the timer for the pre-capture sequence has been hit. - /// - /// Call this only with {@link #mCameraStateLock} held. - /// - /// true, if the timeout occurred, false otherwise. - static bool HitTimeoutLocked () - { - return (SystemClock.ElapsedRealtime () - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS; - } - } + public class Camera2RawFragment : Fragment, View.IOnClickListener + { + // Conversion from screen rotation to JPEG orientation. + static readonly SparseIntArray ORIENTATIONS = new SparseIntArray(); + + static Camera2RawFragment() + { + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation0, 0); + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation90, 90); + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation180, 180); + ORIENTATIONS.Append((int)SurfaceOrientation.Rotation270, 270); + } + + // Request code for camera permissions. + const int REQUEST_CAMERA_PERMISSIONS = 1; + + // Permissions required to take a picture. + static string[] CAMERA_PERMISSIONS = + { + Manifest.Permission.Camera, + Manifest.Permission.ReadExternalStorage, + Manifest.Permission.WriteExternalStorage + }; + + // Timeout for the pre-capture sequence. + const long PRECAPTURE_TIMEOUT_MS = 1000; + + // Tolerance when comparing aspect ratios. + const double ASPECT_RATIO_TOLERANCE = 0.005; + + // Max preview width that is guaranteed by Camera2 API + const int MAX_PREVIEW_WIDTH = 1920; + + // Max preview height that is guaranteed by Camera2 API + const int MAX_PREVIEW_HEIGHT = 1080; + + // Tag for the {@link Log}. + const string TAG = "Camera2RawFragment"; + + // Camera state: Device is closed. + const int STATE_CLOSED = 0; + + // Camera state: Device is opened, but is not capturing. + const int STATE_OPENED = 1; + + // Camera state: Showing camera preview. + const int STATE_PREVIEW = 2; + + // Camera state: Waiting for 3A convergence before capturing a photo. + const int STATE_WAITING_FOR_3A_CONVERGENCE = 3; + + /// + /// An {@link OrientationEventListener} used to determine when device rotation has occurred. + /// This is mainly necessary for when the device is rotated by 180 degrees, in which case + /// onCreate or onConfigurationChanged is not called as the view dimensions remain the same, + /// but the orientation of the has changed, and thus the preview rotation must be updated.. + /// + OrientationEventListener mOrientationListener; + + /// + /// {@link TextureView.SurfaceTextureListener} handles several lifecycle events of a + /// {@link TextureView}. + /// + TextureView.ISurfaceTextureListener mSurfaceTextureListener; + class SurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener + { + Activity Activity { get; set; } + + public SurfaceTextureListener(Activity activity) + { + Activity = activity; + } + + public void OnSurfaceTextureAvailable(Android.Graphics.SurfaceTexture surface, int width, int height) + { + ConfigureTransform(width, height, Activity); + } + + public bool OnSurfaceTextureDestroyed(Android.Graphics.SurfaceTexture surface) + { + lock (mCameraStateLock) + { + mPreviewSize = null; + } + return true; + } + + public void OnSurfaceTextureSizeChanged(Android.Graphics.SurfaceTexture surface, int width, int height) + { + ConfigureTransform(width, height, Activity); + } + + public void OnSurfaceTextureUpdated(Android.Graphics.SurfaceTexture surface) + { + } + } + + /// + /// An {@link AutoFitTextureView} for camera preview. + /// + static AutoFitTextureView mTextureView; + + /// + /// An additional thread for running tasks that shouldn't block the UI. This is used for all + /// callbacks from the {@link CameraDevice} and {@link CameraCaptureSession}s. + /// + HandlerThread mBackgroundThread; + + /// + /// A counter for tracking corresponding {@link CaptureRequest}s and {@link CaptureResult}s + /// across the {@link CameraCaptureSession} capture callbacks. + /// + static readonly AtomicInteger mRequestCounter = new AtomicInteger(); + + /// + /// A {@link Semaphore} to prevent the app from exiting before closing the camera. + /// + static readonly Semaphore mCameraOpenCloseLock = new Semaphore(1); + + /// + /// A lock protecting camera state. + /// + static readonly object mCameraStateLock = new object(); + + // ********************************************************************************************* + // State protected by mCameraStateLock. + // + // The following state is used across both the UI and background threads. Methods with "Locked" + // in the name expect mCameraStateLock to be held while calling. + + /// + /// ID of the current {@link CameraDevice}. + /// + string mCameraId; + + /// + /// A {@link CameraCaptureSession } for camera preview. + /// + static CameraCaptureSession mCaptureSession; + + /// + /// A reference to the open {@link CameraDevice}. + /// + static CameraDevice mCameraDevice; + + /// + /// The {@link Size} of camera preview. + /// + static Size mPreviewSize; + + /// + /// The {@link CameraCharacteristics} for the currently configured camera device. + /// + static CameraCharacteristics mCharacteristics; + + /// + /// A {@link Handler} for running tasks in the background. + /// + static Handler mBackgroundHandler; + + /// + /// A reference counted holder wrapping the {@link ImageReader} that handles JPEG image captures. + /// This is used to allow us to clean up the {@link ImageReader} when all background tasks using + /// its {@link Image}s have completed. + /// + static RefCountedAutoCloseable mJpegImageReader; + + /// + /// A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures. + /// This is used to allow us to clean up the {@link ImageReader} when all background tasks using + /// its {@link Image}s have completed. + /// + static RefCountedAutoCloseable mRawImageReader; + + /// + /// Whether or not the currently configured camera device is fixed-focus. + /// + static bool mNoAFRun = false; + + /// + /// Number of pending user requests to capture a photo. + /// + static int mPendingUserCaptures = 0; + + /// + /// Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress JPEG captures. + /// + static readonly TreeMap mJpegResultQueue = new TreeMap(); + + /// + /// Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress RAW captures. + /// + static readonly TreeMap mRawResultQueue = new TreeMap(); + + /// + /// {@link CaptureRequest.Builder} for the camera preview + /// + static CaptureRequest.Builder mPreviewRequestBuilder; + + /// + /// The state of the camera device. + /// + /// @see #mPreCaptureCallback + /// + static int mState = STATE_CLOSED; + + /// + /// Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is taking + /// too long. + /// + static long mCaptureTimer; + + //********************************************************************************************** + + /// + /// {@link CameraDevice.StateCallback} is called when the currently active {@link CameraDevice} + /// changes its state. + /// + CameraDevice.StateCallback mStateCallback; + class StateCallback : CameraDevice.StateCallback + { + Activity Activity { get; set; } + + public StateCallback(Activity activity) + { + Activity = activity; + } + + public override void OnOpened(CameraDevice camera) + { + // This method is called when the camera is opened. We start camera preview here if + // the TextureView displaying this has been set up. + lock (mCameraStateLock) + { + mState = STATE_OPENED; + mCameraOpenCloseLock.Release(); + mCameraDevice = camera; + + // Start the preview session if the TextureView has been set up already. + if (mPreviewSize != null && mTextureView.IsAvailable) + { + CreateCameraPreviewSessionLocked(); + } + } + } + + public override void OnDisconnected(CameraDevice camera) + { + lock (mCameraStateLock) + { + mState = STATE_CLOSED; + mCameraOpenCloseLock.Release(); + camera.Close(); + mCameraDevice = null; + } + } + + public override void OnError(CameraDevice camera, Android.Hardware.Camera2.CameraError error) + { + Log.Error(TAG, "Received camera device error: " + error); + lock (mCameraStateLock) + { + mState = STATE_CLOSED; + mCameraOpenCloseLock.Release(); + camera.Close(); + mCameraDevice = null; + } + var activity = Activity; + if (null != activity) + { + activity.Finish(); + } + } + } + + /// + /// This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a + /// JPEG image is ready to be saved. + /// + readonly ImageReader.IOnImageAvailableListener mOnJpegImageAvailableListener = new OnJpegImageAvailableListener(); + class OnJpegImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener + { + public void OnImageAvailable(ImageReader reader) + { + DequeueAndSaveImage(mJpegResultQueue, mJpegImageReader); + } + } + + /// + /// This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a + /// RAW image is ready to be saved. + /// + readonly ImageReader.IOnImageAvailableListener mOnRawImageAvailableListener = new OnRawImageAvailableListener(); + class OnRawImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener + { + public void OnImageAvailable(ImageReader reader) + { + DequeueAndSaveImage(mRawResultQueue, mRawImageReader); + } + } + + /// > + /// A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and + /// pre-capture sequence. + /// + static CameraCaptureSession.CaptureCallback mPreCaptureCallback; + class PreCameraCaptureCallback : CameraCaptureSession.CaptureCallback + { + Activity Activity { get; set; } + + public PreCameraCaptureCallback(Activity activity) + { + Activity = activity; + } + + void Process(CaptureResult result) + { + lock (mCameraStateLock) + { + switch (mState) + { + case STATE_PREVIEW: + // We have nothing to do when the camera preview is running normally. + break; + case STATE_WAITING_FOR_3A_CONVERGENCE: + bool readyToCapture = true; + if (!mNoAFRun) + { + var afState = (Integer)result.Get(CaptureResult.ControlAfState); + if (afState == null) + { + break; + } + // If auto-focus has reached locked state, we are ready to capture + readyToCapture = (afState.IntValue() == (int)ControlAFState.FocusedLocked || + afState.IntValue() == (int)ControlAFState.NotFocusedLocked); + } + + // If we are running on an non-legacy device, we should also wait until + // auto-exposure and auto-white-balance have converged as well before + // taking a picture. + if (!IsLegacyLocked()) + { + var aeState = (Integer)result.Get(CaptureResult.ControlAeState); + var awbState = (Integer)result.Get(CaptureResult.ControlAwbState); + if (aeState == null || awbState == null) + { + break; + } + + readyToCapture = readyToCapture && + aeState.IntValue() == (int)ControlAEState.Converged && + awbState.IntValue() == (int)ControlAwbState.Converged; + } + + // If we haven't finished the pre-capture sequence but have hit our maximum + // wait timeout, too bad! Begin capture anyway. + if (!readyToCapture && HitTimeoutLocked()) + { + Log.Warn(TAG, "Timed out waiting for pre-capture sequence to complete."); + readyToCapture = true; + } + + if (readyToCapture && mPendingUserCaptures > 0) + { + // Capture once for each user tap of the "Picture" button. + while (mPendingUserCaptures > 0) + { + CaptureStillPictureLocked(Activity); + mPendingUserCaptures--; + } + // After this, the camera will go back to the normal state of preview. + mState = STATE_PREVIEW; + } + break; + } + } + } + + public override void OnCaptureProgressed(CameraCaptureSession session, CaptureRequest request, + CaptureResult partialResult) + { + Process(partialResult); + } + + public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, + TotalCaptureResult result) + { + Process(result); + } + } + + /// + /// A {@link CameraCaptureSession.CaptureCallback} that handles the still JPEG and RAW capture + /// request. + /// + static readonly CameraCaptureSession.CaptureCallback mCaptureCallback = new CaptureCallback(); + class CaptureCallback : CameraCaptureSession.CaptureCallback + { + public override void OnCaptureStarted(CameraCaptureSession session, CaptureRequest request, + long timestamp, long frameNumber) + { + string currentDateTime = GenerateTimestamp(); + var path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); + var rawFilePath = System.IO.Path.Combine(path, "RAW_" + currentDateTime + ".dng"); + var jpegFilePath = System.IO.Path.Combine(path, "JPEG_" + currentDateTime + ".jpg"); + var rawFile = new FileInfo(rawFilePath); + var jpegFile = new FileInfo(jpegFilePath); + + // Look up the ImageSaverBuilder for this request and update it with the file name + // based on the capture start time. + ImageSaver.ImageSaverBuilder jpegBuilder; + ImageSaver.ImageSaverBuilder rawBuilder; + int requestId = (int)request.Tag; + lock (mCameraStateLock) + { + jpegBuilder = (ImageSaver.ImageSaverBuilder)mJpegResultQueue.Get(requestId); + rawBuilder = (ImageSaver.ImageSaverBuilder)mRawResultQueue.Get(requestId); + } + + if (jpegBuilder != null) + jpegBuilder.SetFile(jpegFile); + + if (rawBuilder != null) + rawBuilder.SetFile(rawFile); + } + + public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, + TotalCaptureResult result) + { + int requestId = (int)request.Tag; + ImageSaver.ImageSaverBuilder jpegBuilder; + ImageSaver.ImageSaverBuilder rawBuilder; + var sb = new System.Text.StringBuilder(); + + // Look up the ImageSaverBuilder for this request and update it with the CaptureResult + lock (mCameraStateLock) + { + jpegBuilder = (ImageSaver.ImageSaverBuilder)mJpegResultQueue.Get(requestId); + rawBuilder = (ImageSaver.ImageSaverBuilder)mRawResultQueue.Get(requestId); + + // If we have all the results necessary, save the image to a file in the background. + HandleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue); + HandleCompletionLocked(requestId, rawBuilder, mRawResultQueue); + + if (jpegBuilder != null) + { + jpegBuilder.SetResult(result); + sb.Append("Saving JPEG as: "); + sb.Append(jpegBuilder.GetSaveLocation()); + } + if (rawBuilder != null) + { + rawBuilder.SetResult(result); + if (jpegBuilder != null) + sb.Append(", "); + sb.Append("Saving RAW as: "); + sb.Append(rawBuilder.GetSaveLocation()); + } + FinishedCaptureLocked(); + } + + ShowToast(sb.ToString()); + } + + public override void OnCaptureFailed(CameraCaptureSession session, CaptureRequest request, + CaptureFailure failure) + { + int requestId = (int)request.Tag; + lock (mCameraStateLock) + { + mJpegResultQueue.Remove(requestId); + mRawResultQueue.Remove(requestId); + FinishedCaptureLocked(); + } + ShowToast("Capture failed!"); + } + } + + class MessageHandler : Handler + { + Activity Activity { get; set; } + + public MessageHandler(Looper looper, Activity activity) : base(looper) + { + Activity = activity; + } + + public override void HandleMessage(Message msg) + { + if (Activity != null) + { + Toast.MakeText(Activity, (string)msg.Obj, ToastLength.Short).Show(); + } + } + } + + /// + /// A {@link Handler} for showing {@link Toast}s on the UI thread. + /// + static Handler mMessageHandler; + + public static Camera2RawFragment Create() + { + Camera2RawFragment fragment = new Camera2RawFragment(); + fragment.RetainInstance = true; + return fragment; + } + + public override View OnCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + return inflater.Inflate(Resource.Layout.fragment_camera2_basic, container, false); + } + + class OrientationListener : OrientationEventListener + { + Activity Activity { get; set; } + + public OrientationListener(Context context, SensorDelay delay, Activity activity) : base(context, delay) + { + Activity = activity; + } + + public override void OnOrientationChanged(int orientation) + { + if (mTextureView != null && mTextureView.IsAvailable) + { + ConfigureTransform(mTextureView.Width, mTextureView.Height, Activity); + } + } + } + + public override void OnViewCreated(View view, Bundle savedInstanceState) + { + view.FindViewById(Resource.Id.picture).SetOnClickListener(this); + view.FindViewById(Resource.Id.info).SetOnClickListener(this); + mTextureView = (AutoFitTextureView)view.FindViewById(Resource.Id.texture); + + // Setup a new OrientationEventListener. This is used to handle rotation events like a + // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout + // or otherwise cause the preview TextureView's size to change. + mOrientationListener = new OrientationListener(Activity, SensorDelay.Normal, Activity); + mMessageHandler = new MessageHandler(Looper.MainLooper, Activity); + mPreCaptureCallback = new PreCameraCaptureCallback(Activity); + mSurfaceTextureListener = new SurfaceTextureListener(Activity); + mStateCallback = new StateCallback(Activity); + } + + public override void OnResume() + { + base.OnResume(); + StartBackgroundThread(); + + if (CanOpenCamera()) + { + + // When the screen is turned off and turned back on, the SurfaceTexture is already + // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should + // configure the preview bounds here (otherwise, we wait until the surface is ready in + // the SurfaceTextureListener). + if (mTextureView.IsAvailable) + { + ConfigureTransform(mTextureView.Width, mTextureView.Height, Activity); + } + else + { + mTextureView.SurfaceTextureListener = mSurfaceTextureListener; + } + if (mOrientationListener != null && mOrientationListener.CanDetectOrientation()) + { + mOrientationListener.Enable(); + } + } + } + + public override void OnPause() + { + if (mOrientationListener != null) + { + mOrientationListener.Disable(); + } + CloseCamera(); + StopBackgroundThread(); + base.OnPause(); + } + + public void OnClick(View view) + { + switch (view.Id) + { + case Resource.Id.picture: + { + TakePicture(); + break; + } + case Resource.Id.info: + { + var activity = Activity; + if (activity != null) + { + new AlertDialog.Builder(activity) + .SetMessage(Resource.String.intro_message) + .SetPositiveButton(Android.Resource.String.Ok, default(IDialogInterfaceOnClickListener)) + .Show(); + } + break; + } + } + } + + /// + /// Sets up state related to camera that is needed before opening a {@link CameraDevice}. + /// + /// true, if up camera outputs was set, false otherwise. + bool SetUpCameraOutputs() + { + var activity = Activity; + CameraManager manager = (CameraManager)activity.GetSystemService(Context.CameraService); + if (manager == null) + { + ErrorDialog.BuildErrorDialog("This device doesn't support Camera2 API."). + Show(FragmentManager, "dialog"); + return false; + } + try + { + // Find a CameraDevice that supports RAW captures, and configure state. + foreach (string cameraId in manager.GetCameraIdList()) + { + CameraCharacteristics characteristics + = manager.GetCameraCharacteristics(cameraId); + + // We only use a camera that supports RAW in this sample. + if (!Contains(characteristics.Get( + CameraCharacteristics.RequestAvailableCapabilities).ToArray(), + (int)RequestAvailableCapabilities.Raw)) + { + continue; + } + + StreamConfigurationMap map = (StreamConfigurationMap)characteristics.Get( + CameraCharacteristics.ScalerStreamConfigurationMap); + + // For still image captures, we use the largest available size. + Size[] jpegs = map.GetOutputSizes((int)ImageFormatType.Jpeg); + Size largestJpeg = jpegs.OrderByDescending(element => element.Width * element.Height).First(); + + Size[] raws = map.GetOutputSizes((int)ImageFormatType.RawSensor); + Size largestRaw = raws.OrderByDescending(element => element.Width * element.Height).First(); + + lock (mCameraStateLock) + { + // Set up ImageReaders for JPEG and RAW outputs. Place these in a reference + // counted wrapper to ensure they are only closed when all background tasks + // using them are finished. + if (mJpegImageReader == null || mJpegImageReader.GetAndRetain() == null) + { + mJpegImageReader = new RefCountedAutoCloseable( + ImageReader.NewInstance(largestJpeg.Width, + largestJpeg.Height, ImageFormatType.Jpeg, /*maxImages*/5)); + } + + mJpegImageReader.Get().SetOnImageAvailableListener( + mOnJpegImageAvailableListener, mBackgroundHandler); + + if (mRawImageReader == null || mRawImageReader.GetAndRetain() == null) + { + mRawImageReader = new RefCountedAutoCloseable( + ImageReader.NewInstance(largestRaw.Width, + largestRaw.Height, ImageFormatType.RawSensor, /*maxImages*/5)); + } + mRawImageReader.Get().SetOnImageAvailableListener( + mOnRawImageAvailableListener, mBackgroundHandler); + + mCharacteristics = characteristics; + mCameraId = cameraId; + } + return true; + } + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + + // If we found no suitable cameras for capturing RAW, warn the user. + ErrorDialog.BuildErrorDialog("This device doesn't support capturing RAW photos"). + Show(FragmentManager, "dialog"); + return false; + } + + /// + /// Opens the camera specified by {@link #mCameraId}. + /// + bool CanOpenCamera() + { + if (!SetUpCameraOutputs()) + return false; + + var activity = Activity; + CameraManager manager = (CameraManager)activity.GetSystemService(Context.CameraService); + try + { + // Wait for any previously running session to finish. + if (!mCameraOpenCloseLock.TryAcquire(2500, TimeUnit.Milliseconds)) + throw new RuntimeException("Time out waiting to lock camera opening."); + + string cameraId; + Handler backgroundHandler; + lock (mCameraStateLock) + { + cameraId = mCameraId; + backgroundHandler = mBackgroundHandler; + } + + // Attempt to open the camera. mStateCallback will be called on the background handler's + // thread when this succeeds or fails. + manager.OpenCamera(cameraId, mStateCallback, backgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + catch (InterruptedException e) + { + throw new RuntimeException("Interrupted while trying to lock camera opening.", e); + } + return true; + } + + /// + /// Closes the current {@link CameraDevice}. + /// + void CloseCamera() + { + try + { + mCameraOpenCloseLock.Acquire(); + lock (mCameraStateLock) + { + + // Reset state and clean up resources used by the camera. + // Note: After calling this, the ImageReaders will be closed after any background + // tasks saving Images from these readers have been completed. + mPendingUserCaptures = 0; + mState = STATE_CLOSED; + if (null != mCaptureSession) + { + mCaptureSession.Close(); + mCaptureSession = null; + } + if (null != mCameraDevice) + { + mCameraDevice.Close(); + mCameraDevice = null; + } + if (null != mJpegImageReader) + { + mJpegImageReader.Close(); + mJpegImageReader = null; + } + if (null != mRawImageReader) + { + mRawImageReader.Close(); + mRawImageReader = null; + } + } + } + catch (InterruptedException e) + { + throw new RuntimeException("Interrupted while trying to lock camera closing.", e); + } + finally + { + mCameraOpenCloseLock.Release(); + } + } + + /// + /// Starts a background thread and its {@link Handler}. + /// + void StartBackgroundThread() + { + mBackgroundThread = new HandlerThread("CameraBackground"); + mBackgroundThread.Start(); + lock (mCameraStateLock) + { + mBackgroundHandler = new Handler(mBackgroundThread.Looper); + } + } + + /// + /// Stops the background thread and its {@link Handler}. + /// + void StopBackgroundThread() + { + mBackgroundThread.QuitSafely(); + try + { + mBackgroundThread.Join(); + mBackgroundThread = null; + lock (mCameraStateLock) + { + mBackgroundHandler = null; + } + } + catch (InterruptedException e) + { + e.PrintStackTrace(); + } + } + + class CameraPreviewCaptureCallback : CameraCaptureSession.StateCallback + { + public override void OnConfigured(CameraCaptureSession cameraCaptureSession) + { + lock (mCameraStateLock) + { + // The camera is already closed + if (null == mCameraDevice) + return; + + try + { + Setup3AControlsLocked(mPreviewRequestBuilder); + // Finally, we start displaying the camera preview. + cameraCaptureSession.SetRepeatingRequest( + mPreviewRequestBuilder.Build(), + mPreCaptureCallback, mBackgroundHandler); + mState = STATE_PREVIEW; + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + return; + } + catch (IllegalStateException e) + { + e.PrintStackTrace(); + return; + } + // When the session is ready, we start displaying the preview. + mCaptureSession = cameraCaptureSession; + } + } + + public override void OnConfigureFailed(CameraCaptureSession session) + { + ShowToast("Failed to configure camera."); + } + } + + /// + /// Creates a new {@link CameraCaptureSession} for camera preview. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + static void CreateCameraPreviewSessionLocked() + { + try + { + SurfaceTexture texture = mTextureView.SurfaceTexture; + // We configure the size of default buffer to be the size of camera preview we want. + texture.SetDefaultBufferSize(mPreviewSize.Width, mPreviewSize.Height); + + // This is the output Surface we need to start preview. + Surface surface = new Surface(texture); + + // We set up a CaptureRequest.Builder with the output Surface. + mPreviewRequestBuilder + = mCameraDevice.CreateCaptureRequest(CameraTemplate.Preview); + mPreviewRequestBuilder.AddTarget(surface); + + // Here, we create a CameraCaptureSession for camera preview. + mCameraDevice.CreateCaptureSession(new List() {surface, + mJpegImageReader.Get ().Surface, + mRawImageReader.Get ().Surface + }, new CameraPreviewCaptureCallback(), mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + /// + /// Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and + /// auto-white-balance controls if available. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + /// the builder to configure. + static void Setup3AControlsLocked(CaptureRequest.Builder builder) + { + // Enable auto-magical 3A run by camera device + builder.Set(CaptureRequest.ControlMode, (int)ControlMode.Auto); + + var minFocusDist = (float)mCharacteristics.Get(CameraCharacteristics.LensInfoMinimumFocusDistance); + + // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run. + mNoAFRun = (minFocusDist == null || minFocusDist == 0); + + if (!mNoAFRun) + { + // If there is a "continuous picture" mode available, use it, otherwise default to AUTO. + if (Contains(mCharacteristics.Get( + CameraCharacteristics.ControlAfAvailableModes).ToArray(), + (int)ControlAFMode.ContinuousPicture)) + { + builder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture); + } + else + { + builder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.Auto); + } + } + + // If there is an auto-magical flash control mode available, use it, otherwise default to + // the "on" mode, which is guaranteed to always be available. + if (Contains(mCharacteristics.Get( + CameraCharacteristics.ControlAeAvailableModes).ToArray(), (int)ControlAEMode.OnAutoFlash)) + { + builder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash); + } + else + { + builder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.On); + } + + // If there is an auto-magical white balance control mode available, use it. + if (Contains(mCharacteristics.Get( + CameraCharacteristics.ControlAwbAvailableModes).ToArray(), (int)ControlAwbMode.Auto)) + { + // Allow AWB to run auto-magically if this device supports this + builder.Set(CaptureRequest.ControlAwbMode, (int)ControlAwbMode.Auto); + } + } + + /// + /// Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`, + /// and start/restart the preview capture session if necessary. + /// + /// This method should be called after the camera state has been initialized in + /// setUpCameraOutputs. + /// + /// The width of `mTextureView` + /// The height of `mTextureView` + static void ConfigureTransform(int viewWidth, int viewHeight, Activity activity) + { + lock (mCameraStateLock) + { + if (mTextureView == null || activity == null) + { + return; + } + + var map = (StreamConfigurationMap)mCharacteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap); + + // For still image captures, we always use the largest available size. + Size largestJpeg = (Size)Collections.Max(Arrays.AsList(map.GetOutputSizes((int)ImageFormatType.Jpeg)), + new CompareSizesByArea()); + + // Find the rotation of the device relative to the native device orientation. + var deviceRotation = (int)activity.WindowManager.DefaultDisplay.Rotation; + + // Find the rotation of the device relative to the camera sensor's orientation. + int totalRotation = SensorToDeviceRotation(mCharacteristics, deviceRotation); + + // Swap the view dimensions for calculation as needed if they are rotated relative to + // the sensor. + bool swappedDimensions = totalRotation == 90 || totalRotation == 270; + int rotatedViewWidth = viewWidth; + int rotatedViewHeight = viewHeight; + if (swappedDimensions) + { + rotatedViewWidth = viewHeight; + rotatedViewHeight = viewWidth; + } + + // Find the best preview size for these view dimensions and configured JPEG size. + Size previewSize = ChooseOptimalSize(map.GetOutputSizes(Class.FromType(typeof(SurfaceTexture))), + rotatedViewWidth, rotatedViewHeight, largestJpeg); + + if (swappedDimensions) + { + mTextureView.SetAspectRatio( + previewSize.Height, previewSize.Width); + } + else + { + mTextureView.SetAspectRatio( + previewSize.Width, previewSize.Height); + } + + // Find rotation of device in degrees (reverse device orientation for front-facing + // cameras). + int rotation = ((int)mCharacteristics.Get(CameraCharacteristics.LensFacing) == + (int)LensFacing.Front) ? + (360 + ORIENTATIONS.Get(deviceRotation)) % 360 : + (360 - ORIENTATIONS.Get(deviceRotation)) % 360; + + Matrix matrix = new Matrix(); + RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); + RectF bufferRect = new RectF(0, 0, previewSize.Height, previewSize.Width); + float centerX = viewRect.CenterX(); + float centerY = viewRect.CenterY(); + + // Initially, output stream images from the Camera2 API will be rotated to the native + // device orientation from the sensor's orientation, and the TextureView will default to + // scaling these buffers to fill it's view bounds. If the aspect ratios and relative + // orientations are correct, this is fine. + // + // However, if the device orientation has been rotated relative to its native + // orientation so that the TextureView's dimensions are swapped relative to the + // native device orientation, we must do the following to ensure the output stream + // images are not incorrectly scaled by the TextureView: + // - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions + // in the native device orientation) to the TextureView's dimension. + // - Apply a scale-to-fill from the output buffer's rotated dimensions + // (i.e. its dimensions in the current device orientation) to the TextureView's + // dimensions. + // - Apply the rotation from the native device orientation to the current device + // rotation. + if (deviceRotation == (int)SurfaceOrientation.Rotation90 || deviceRotation == (int)SurfaceOrientation.Rotation270) + { + bufferRect.Offset(centerX - bufferRect.CenterX(), centerY - bufferRect.CenterY()); + matrix.SetRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.Fill); + float scale = System.Math.Max( + (float)viewHeight / previewSize.Height, + (float)viewWidth / previewSize.Width); + matrix.PostScale(scale, scale, centerX, centerY); + + } + matrix.PostRotate(rotation, centerX, centerY); + + mTextureView.SetTransform(matrix); + + // Start or restart the active capture session if the preview was initialized or + // if its aspect ratio changed significantly. + if (mPreviewSize == null || !CheckAspectsEqual(previewSize, mPreviewSize)) + { + mPreviewSize = previewSize; + if (mState != STATE_CLOSED) + { + CreateCameraPreviewSessionLocked(); + } + } + } + } + + /// + /// Initiate a still image capture. + /// + /// This function sends a capture request that initiates a pre-capture sequence in our state + /// machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no + /// longer moving, waits for auto-exposure to choose a good exposure value, and waits for + /// auto-white-balance to converge. + /// + void TakePicture() + { + lock (mCameraStateLock) + { + mPendingUserCaptures++; + + // If we already triggered a pre-capture sequence, or are in a state where we cannot + // do this, return immediately. + if (mState != STATE_PREVIEW) + { + return; + } + + try + { + // Trigger an auto-focus run if camera is capable. If the camera is already focused, + // this should do nothing. + if (!mNoAFRun) + { + mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, + (int)ControlAFTrigger.Start); + } + + // If this is not a legacy device, we can also trigger an auto-exposure metering + // run. + if (!IsLegacyLocked()) + { + // Tell the camera to lock focus. + mPreviewRequestBuilder.Set(CaptureRequest.ControlAePrecaptureTrigger, + (int)ControlAEPrecaptureTrigger.Start); + } + + // Update state machine to wait for auto-focus, auto-exposure, and + // auto-white-balance (aka. "3A") to converge. + mState = STATE_WAITING_FOR_3A_CONVERGENCE; + + // Start a timer for the pre-capture sequence. + StartTimerLocked(); + + // Replace the existing repeating request with one with updated 3A triggers. + mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mPreCaptureCallback, + mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + } + + /// + /// Send a capture request to the camera device that initiates a capture targeting the JPEG and + /// RAW outputs. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + static void CaptureStillPictureLocked(Activity activity) + { + try + { + if (null == activity || null == mCameraDevice) + return; + + // This is the CaptureRequest.Builder that we use to take a picture. + CaptureRequest.Builder captureBuilder = + mCameraDevice.CreateCaptureRequest(CameraTemplate.StillCapture); + + captureBuilder.AddTarget(mJpegImageReader.Get().Surface); + captureBuilder.AddTarget(mRawImageReader.Get().Surface); + + // Use the same AE and AF modes as the preview. + Setup3AControlsLocked(captureBuilder); + + // Set orientation. + var rotation = activity.WindowManager.DefaultDisplay.Rotation; + captureBuilder.Set(CaptureRequest.JpegOrientation, SensorToDeviceRotation(mCharacteristics, (int)rotation)); + + // Set request tag to easily track results in callbacks. + captureBuilder.SetTag(mRequestCounter.IncrementAndGet()); + + CaptureRequest request = captureBuilder.Build(); + + // Create an ImageSaverBuilder in which to collect results, and add it to the queue + // of active requests. + ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(activity) + .SetCharacteristics(mCharacteristics); + ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder(activity) + .SetCharacteristics(mCharacteristics); + + mJpegResultQueue.Put((int)request.Tag, jpegBuilder); + mRawResultQueue.Put((int)request.Tag, rawBuilder); + + mCaptureSession.Capture(request, mCaptureCallback, mBackgroundHandler); + + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + /// + /// Called after a RAW/JPEG capture has completed; resets the AF trigger state for the + /// pre-capture sequence. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + static void FinishedCaptureLocked() + { + try + { + // Reset the auto-focus trigger in case AF didn't run quickly enough. + if (!mNoAFRun) + { + mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, + (int)ControlAFTrigger.Cancel); + + mCaptureSession.Capture(mPreviewRequestBuilder.Build(), + mPreCaptureCallback, mBackgroundHandler); + + mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, + (int)ControlAFTrigger.Idle); + } + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + + /// + /// Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining + /// that {@link ImageReader} until that {@link Image} is no longer in use, and set this + /// {@link Image} as the result for the next request in the queue of pending requests. If + /// all necessary information is available, begin saving the image to a file in a background + /// thread. + /// + /// the currently active requests. + /// a reference counted wrapper containing an {@link ImageReader} from which to acquire an image. + static void DequeueAndSaveImage(TreeMap pendingQueue, + RefCountedAutoCloseable reader) + { + lock (mCameraStateLock) + { + IMapEntry entry = pendingQueue.FirstEntry(); + var builder = (ImageSaver.ImageSaverBuilder)entry.Value; + + // Increment reference count to prevent ImageReader from being closed while we + // are saving its Images in a background thread (otherwise their resources may + // be freed while we are writing to a file). + if (reader == null || reader.GetAndRetain() == null) + { + Log.Error(TAG, "Paused the activity before we could save the image," + + " ImageReader already closed."); + pendingQueue.Remove(entry.Key); + return; + } + + Image image; + try + { + image = reader.Get().AcquireNextImage(); + } + catch (IllegalStateException) + { + Log.Error(TAG, "Too many images queued for saving, dropping image for request: " + + entry.Key); + pendingQueue.Remove(entry.Key); + return; + } + + builder.SetRefCountedReader(reader).SetImage(image); + + HandleCompletionLocked((int)entry.Key, builder, pendingQueue); + } + } + + /// + /// Runnable that saves an {@link Image} into the specified {@link File}, and updates + /// {@link android.provider.MediaStore} to include the resulting file. + /// + /// This can be constructed through an {@link ImageSaverBuilder} as the necessary image and + /// result information becomes available. + /// + class ImageSaver : Java.Lang.Object, IRunnable + { + /// + /// The image to save. + /// + readonly Image mImage; + + /// + /// The file we save the image into. + /// + readonly FileInfo mFile; + + /// + /// The CaptureResult for this image capture. + /// + readonly CaptureResult mCaptureResult; + + /// + /// The CameraCharacteristics for this camera device. + /// + readonly CameraCharacteristics mCharacteristics; + + /// + /// The Context to use when updating MediaStore with the saved images. + /// + readonly Context mContext; + + /// + /// A reference counted wrapper for the ImageReader that owns the given image. + /// + readonly RefCountedAutoCloseable mReader; + + ImageSaver(Image image, FileInfo file, CaptureResult result, + CameraCharacteristics characteristics, Context context, + RefCountedAutoCloseable reader) + { + mImage = image; + mFile = file; + mCaptureResult = result; + mCharacteristics = characteristics; + mContext = context; + mReader = reader; + } + + public void Run() + { + bool success = false; + var format = mImage.Format; + switch (format) + { + case ImageFormatType.Jpeg: + { + ByteBuffer buffer = mImage.GetPlanes()[0].Buffer; + byte[] bytes = new byte[buffer.Remaining()]; + buffer.Get(bytes); + FileStream output = null; + try + { + output = mFile.OpenWrite(); + output.Write(bytes, 0, bytes.Length); + success = true; + } + catch (IOException e) + { + Log.Error(TAG, e.Message); + } + finally + { + mImage.Close(); + CloseOutput(output); + } + break; + } + case ImageFormatType.RawSensor: + { + DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult); + FileStream output = null; + try + { + output = mFile.OpenWrite(); + dngCreator.WriteImage(output, mImage); + success = true; + } + catch (IOException e) + { + Log.Error(TAG, e.Message); + } + finally + { + mImage.Close(); + CloseOutput(output); + } + break; + } + default: + { + Log.Error(TAG, "Cannot save image, unexpected image format:" + format); + break; + } + } + + // Decrement reference count to allow ImageReader to be closed to free up resources. + mReader.Close(); + + // If saving the file succeeded, update MediaStore. + if (success) + { + MediaScannerConnection.ScanFile(mContext, new string[] { mFile.FullName }, + /*mimeTypes*/null, new MediaScannerClient()); + } + } + + class MediaScannerClient : Java.Lang.Object, MediaScannerConnection.IOnScanCompletedListener + { + public void OnMediaScannerConnected() + { + //do nothing + } + + public void OnScanCompleted(string path, Android.Net.Uri uri) + { + Log.Info(TAG, "Scanned " + path + ":"); + Log.Info(TAG, "-> uri=" + uri); + } + } + + /// + /// Builder class for constructing {@link ImageSaver}s. + /// + /// This class is thread safe. + /// + public class ImageSaverBuilder : Java.Lang.Object + { + Image mImage; + FileInfo mFile; + CaptureResult mCaptureResult; + CameraCharacteristics mCharacteristics; + Context mContext; + RefCountedAutoCloseable mReader; + + /// + /// Construct a new ImageSaverBuilder using the given {@link Context}. + /// @param context a {@link Context} to for accessing the + /// {@link android.provider.MediaStore}. + /// + public ImageSaverBuilder(Context context) + { + mContext = context; + } + + public ImageSaverBuilder SetRefCountedReader( + RefCountedAutoCloseable reader) + { + if (reader == null) + throw new NullPointerException(); + + mReader = reader; + return this; + } + + public ImageSaverBuilder SetImage(Image image) + { + if (image == null) + throw new NullPointerException(); + mImage = image; + return this; + } + + public ImageSaverBuilder SetFile(FileInfo file) + { + if (file == null) + throw new NullPointerException(); + mFile = file; + return this; + } + + public ImageSaverBuilder SetResult(CaptureResult result) + { + if (result == null) + throw new NullPointerException(); + mCaptureResult = result; + return this; + } + + public ImageSaverBuilder SetCharacteristics( + CameraCharacteristics characteristics) + { + if (characteristics == null) + throw new NullPointerException(); + mCharacteristics = characteristics; + return this; + } + + public ImageSaver buildIfComplete() + { + if (!IsComplete) + { + return null; + } + return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext, + mReader); + } + + public string GetSaveLocation() + { + return (mFile == null) ? "Unknown" : mFile.ToString(); + } + + bool IsComplete + { + get + { + return mImage != null && mFile != null && mCaptureResult != null + && mCharacteristics != null; + } + } + } + } + + // Utility classes and methods: + // ********************************************************************************************* + + /// + /// Comparator based on area of the given {@link Size} objects. + /// + class CompareSizesByArea : Java.Lang.Object, IComparator + { + public int Compare(Size lhs, Size rhs) + { + // We cast here to ensure the multiplications won't overflow + return Long.Signum((long)lhs.Width * lhs.Height - + (long)rhs.Width * rhs.Height); + } + + int IComparator.Compare(Java.Lang.Object lhs, Java.Lang.Object rhs) + { + return 0; + } + + bool IComparator.Equals(Java.Lang.Object @object) + { + return false; + } + } + + /// + /// A dialog fragment for displaying non-recoverable errors; this {@link Activity} will be + /// finished once the dialog has been acknowledged by the user. + /// + public class ErrorDialog : DialogFragment + { + + string mErrorMessage; + + public ErrorDialog() + { + mErrorMessage = "Unknown error occurred!"; + } + + // Build a dialog with a custom message (Fragments require default constructor). + public static ErrorDialog BuildErrorDialog(string errorMessage) + { + ErrorDialog dialog = new ErrorDialog(); + dialog.mErrorMessage = errorMessage; + return dialog; + } + + public override Dialog OnCreateDialog(Bundle savedInstanceState) + { + var activity = Activity; + return new AlertDialog.Builder(activity) + .SetMessage(mErrorMessage) + .SetPositiveButton(Android.Resource.String.Ok, new EventHandler((s, args) => + { + Activity.Finish(); + })).Create(); + } + + + } + + /// + /// A wrapper for an {@link AutoCloseable} object that implements reference counting to allow + /// for resource management. + /// + public class RefCountedAutoCloseable : Java.Lang.Object, IAutoCloseable where T : Java.Lang.Object + { + T mObject; + long mRefCount = 0; + + /// + /// Wrap the given object. + /// + /// object an object to wrap. + public RefCountedAutoCloseable(T obj) + { + if (obj == null) + throw new NullPointerException(); + + mObject = obj; + } + + /// + /// the reference count and return the wrapped object. + /// + /// the wrapped object, or null if the object has been released. + public T GetAndRetain() + { + if (mRefCount < 0) + return default(T); + + mRefCount++; + return mObject; + } + + /// + /// Return the wrapped object. + /// + /// the wrapped object, or null if the object has been released. + public T Get() + { + return mObject; + } + + /// + /// Decrement the reference count and release the wrapped object if there are no other + /// users retaining this object. + /// + public void Close() + { + if (mRefCount >= 0) + { + mRefCount--; + if (mRefCount < 0) + { + try + { + var obj = (mObject as IAutoCloseable); + if (obj == null) + throw new Java.Lang.Exception("unclosable"); + obj.Close(); + } + catch (Java.Lang.Exception e) + { + if (e.Message != "unclosable") + throw new RuntimeException(e); + } + finally + { + mObject = default(T); + } + } + } + } + } + + /// + /// Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose + /// width and height are at least as large as the respective requested values, and whose aspect + /// ratio matches with the specified value. + /// + /// The optimal {@code Size}, or an arbitrary one if none were big enough + /// The list of sizes that the camera supports for the intended output class + /// The minimum desired width + /// The minimum desired height + /// The aspect ratio + static Size ChooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) + { + // Collect the supported resolutions that are at least as big as the preview Surface + List bigEnough = new List(); + int w = aspectRatio.Width; + int h = aspectRatio.Height; + foreach (Size option in choices) + { + if (option.Height == option.Width * h / w && + option.Width >= width && option.Height >= height) + { + bigEnough.Add(option); + } + } + + // Pick the smallest of those, assuming we found any + if (bigEnough.Count > 0) + { + return (Size)Collections.Min(bigEnough, new CompareSizesByArea()); + } + else + { + Log.Error(TAG, "Couldn't find any suitable preview size"); + return choices[0]; + } + } + + /// + /// Generate a string containing a formatted timestamp with the current date and time. + /// + /// a {@link String} representing a time. + static string GenerateTimestamp() + { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Java.Util.Locale.Us); + return sdf.Format(new Date()); + } + + /// + /// Cleanup the given {@link OutputStream}. + /// + /// the stream to close. + static void CloseOutput(System.IO.Stream outputStream) + { + if (outputStream != null) + { + try + { + outputStream.Close(); + } + catch (IOException e) + { + Log.Error(TAG, e.Message); + } + } + } + + /// + /// Return true if the given array contains the given integer. + /// + /// true, if the array contains the given integer, false otherwise. + /// array to check. + /// integer to get for. + static bool Contains(int[] modes, int mode) + { + if (modes == null) + { + return false; + } + foreach (int i in modes) + { + if (i == mode) + { + return true; + } + } + return false; + } + + /// + /// Return true if the two given {@link Size}s have the same aspect ratio. + /// + /// true, if the sizes have the same aspect ratio, false otherwise. + /// first {@link Size} to compare. + /// second {@link Size} to compare. + static bool CheckAspectsEqual(Size a, Size b) + { + double aAspect = a.Width / (double)a.Height; + double bAspect = b.Width / (double)b.Height; + return System.Math.Abs(aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE; + } + + /// + /// Rotation need to transform from the camera sensor orientation to the device's current + /// orientation. + /// + /// the total rotation from the sensor orientation to the current device orientation. + /// the {@link CameraCharacteristics} to query for the camera sensor orientation. + /// the current device orientation relative to the native device orientation. + static int SensorToDeviceRotation(CameraCharacteristics c, int deviceOrientation) + { + int sensorOrientation = (int)c.Get(CameraCharacteristics.SensorOrientation); + + // Get device orientation in degrees + deviceOrientation = ORIENTATIONS.Get(deviceOrientation); + + // Reverse device orientation for front-facing cameras + if ((int)c.Get(CameraCharacteristics.LensFacing) == (int)LensFacing.Front) + { + deviceOrientation = -deviceOrientation; + } + + // Calculate desired JPEG orientation relative to camera orientation to make + // the image upright relative to the device orientation + return (sensorOrientation + deviceOrientation + 360) % 360; + } + + /// + /// Shows a {@link Toast} on the UI thread. + /// + /// The message to show. + static void ShowToast(string text) + { + // We show a Toast by sending request message to mMessageHandler. This makes sure that the + // Toast is shown on the UI thread. + Message message = Message.Obtain(); + message.Obj = text; + mMessageHandler.SendMessage(message); + } + + /// + /// If the given request has been completed, remove it from the queue of active requests and + /// send an {@link ImageSaver} with the results from this request to a background thread to + /// save a file. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + /// the ID of the {@link CaptureRequest} to handle. + /// the {@link ImageSaver.ImageSaverBuilder} for this request. + /// the queue to remove this request from, if completed. + static void HandleCompletionLocked(int requestId, ImageSaver.ImageSaverBuilder builder, TreeMap queue) + { + if (builder == null) + return; + ImageSaver saver = builder.buildIfComplete(); + if (saver != null) + { + queue.Remove(requestId); + AsyncTask.ThreadPoolExecutor.Execute(saver); + } + } + + /// + /// Check if we are using a device that only supports the LEGACY hardware level. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + /// true if this is a legacy device; otherwise, false. + static bool IsLegacyLocked() + { + return (int)mCharacteristics.Get(CameraCharacteristics.InfoSupportedHardwareLevel) == (int)InfoSupportedHardwareLevel.Legacy; + } + + /// + /// Start the timer for the pre-capture sequence. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + static void StartTimerLocked() + { + mCaptureTimer = SystemClock.ElapsedRealtime(); + } + + /// + /// Check if the timer for the pre-capture sequence has been hit. + /// + /// Call this only with {@link #mCameraStateLock} held. + /// + /// true, if the timeout occurred, false otherwise. + static bool HitTimeoutLocked() + { + return (SystemClock.ElapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS; + } + } } diff --git a/android5.0/Camera2Raw/Properties/AndroidManifest.xml b/android5.0/Camera2Raw/Properties/AndroidManifest.xml index 1e6eefeef..6c2138115 100644 --- a/android5.0/Camera2Raw/Properties/AndroidManifest.xml +++ b/android5.0/Camera2Raw/Properties/AndroidManifest.xml @@ -4,6 +4,5 @@ - - + \ No newline at end of file From 0d57964865c54144aa1e4034c4d264e39c27dd6f Mon Sep 17 00:00:00 2001 From: Gonzalo Martin Date: Fri, 7 Oct 2016 11:55:17 -0300 Subject: [PATCH 3/4] Quick update about feedback --- .../Camera2Basic/Camera2BasicFragment.cs | 106 +++++++----------- 1 file changed, 40 insertions(+), 66 deletions(-) diff --git a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs index 8d5631d3a..b3ebe7b56 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs +++ b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs @@ -2,13 +2,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using Android; using Android.App; using Android.Content; using Android.Content.PM; -using Android.Content.Res; using Android.OS; using Android.Util; using Android.Views; @@ -66,16 +63,16 @@ public class Camera2BasicFragment : Fragment, View.IOnClickListener, FragmentCom private Camera2BasicSurfaceTextureListener mSurfaceTextureListener; private class Camera2BasicSurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener { - public Camera2BasicFragment owner; + public Camera2BasicFragment Owner { get; set; } public Camera2BasicSurfaceTextureListener(Camera2BasicFragment owner) { - this.owner = owner; + Owner = owner; } public void OnSurfaceTextureAvailable(Android.Graphics.SurfaceTexture surface, int width, int height) { - owner.OpenCamera(width, height); + Owner.OpenCamera(width, height); } public bool OnSurfaceTextureDestroyed(Android.Graphics.SurfaceTexture surface) @@ -85,7 +82,7 @@ public bool OnSurfaceTextureDestroyed(Android.Graphics.SurfaceTexture surface) public void OnSurfaceTextureSizeChanged(Android.Graphics.SurfaceTexture surface, int width, int height) { - owner.ConfigureTransform(width, height); + Owner.ConfigureTransform(width, height); } public void OnSurfaceTextureUpdated(Android.Graphics.SurfaceTexture surface) @@ -135,13 +132,12 @@ public override void OnError(CameraDevice cameraDevice, CameraError error) owner.mCameraOpenCloseLock.Release(); cameraDevice.Close(); owner.mCameraDevice = null; - if (owner != null) + if (owner == null) + return; + Activity activity = owner.Activity; + if (activity != null) { - Activity activity = owner.Activity; - if (activity != null) - { - activity.Finish(); - } + activity.Finish(); } } @@ -166,11 +162,11 @@ public override void OnError(CameraDevice cameraDevice, CameraError error) private ImageAvailableListener mOnImageAvailableListener; private class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener { - public File File; - public Camera2BasicFragment owner; + public File File { get; set; } + public Camera2BasicFragment Owner { get; set; } public void OnImageAvailable(ImageReader reader) { - owner.mBackgroundHandler.Post(new ImageSaver(reader.AcquireNextImage(), File)); + Owner.mBackgroundHandler.Post(new ImageSaver(reader.AcquireNextImage(), File)); } } @@ -197,8 +193,8 @@ public void OnImageAvailable(ImageReader reader) private CameraCaptureListener mCaptureCallback; private class CameraCaptureListener : CameraCaptureSession.CaptureCallback { - public Camera2BasicFragment owner; - public File File; + public Camera2BasicFragment Owner { get; set; } + public File File { get; set; } public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { Process(result); @@ -211,19 +207,14 @@ public override void OnCaptureProgressed(CameraCaptureSession session, CaptureRe private void Process(CaptureResult result) { - switch (owner.mState) + switch (Owner.mState) { - case STATE_PREVIEW: - { - // We have nothing to do when the camera preview is working normally. - break; - } case STATE_WAITING_LOCK: { Integer afState = (Integer)result.Get(CaptureResult.ControlAfState); if (afState == null) { - owner.CaptureStillPicture(); + Owner.CaptureStillPicture(); } else if ((((int)ControlAFState.FocusedLocked) == afState.IntValue()) || @@ -234,12 +225,12 @@ private void Process(CaptureResult result) if (aeState == null || aeState.IntValue() == ((int)ControlAEState.Converged)) { - owner.mState = STATE_PICTURE_TAKEN; - owner.CaptureStillPicture(); + Owner.mState = STATE_PICTURE_TAKEN; + Owner.CaptureStillPicture(); } else { - owner.RunPrecaptureSequence(); + Owner.RunPrecaptureSequence(); } } break; @@ -252,7 +243,7 @@ private void Process(CaptureResult result) aeState.IntValue() == ((int)ControlAEState.Precapture) || aeState.IntValue() == ((int)ControlAEState.FlashRequired)) { - owner.mState = STATE_WAITING_NON_PRECAPTURE; + Owner.mState = STATE_WAITING_NON_PRECAPTURE; } break; } @@ -262,8 +253,8 @@ private void Process(CaptureResult result) Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); if (aeState == null || aeState.IntValue() != ((int)ControlAEState.Precapture)) { - owner.mState = STATE_PICTURE_TAKEN; - owner.CaptureStillPicture(); + Owner.mState = STATE_PICTURE_TAKEN; + Owner.CaptureStillPicture(); } break; } @@ -419,18 +410,13 @@ private void RequestCameraPermission() public void OnRequestPermissionsResult(int requestCode, string[] permissions, int[] grantResults) { - if (requestCode == REQUEST_CAMERA_PERMISSION) - { - if (grantResults.Length != 1 || grantResults[0] != (int)Permission.Granted) - { - ErrorDialog.NewInstance(GetString(Resource.String.request_permission)) - .Show(ChildFragmentManager, FRAGMENT_DIALOG); - } - } - else + if (requestCode != REQUEST_CAMERA_PERMISSION) + return; + + if (grantResults.Length != 1 || grantResults[0] != (int)Permission.Granted) { - // it should call to base.. - //base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + ErrorDialog.NewInstance(GetString(Resource.String.request_permission)) + .Show(ChildFragmentManager, FRAGMENT_DIALOG); } } @@ -939,29 +925,19 @@ public void Run() ByteBuffer buffer = mImage.GetPlanes()[0].Buffer; byte[] bytes = new byte[buffer.Remaining()]; buffer.Get(bytes); - FileOutputStream output = null; - try - { - output = new FileOutputStream(mFile); - output.Write(bytes); - } - catch (IOException e) - { - e.PrintStackTrace(); - } - finally + using (var output = new FileOutputStream(mFile)) { - mImage.Close(); - if (null != output) + try { - try - { - output.Close(); - } - catch (IOException e) - { - e.PrintStackTrace(); - } + output.Write(bytes); + } + catch (IOException e) + { + e.PrintStackTrace(); + } + finally + { + mImage.Close(); } } } @@ -995,11 +971,9 @@ public void OnClick(IDialogInterface dialog, int which) public static ErrorDialog NewInstance(string message) { - ErrorDialog dialog = new ErrorDialog(); var args = new Bundle(); args.PutString(ARG_MESSAGE, message); - dialog.Arguments = args; - return dialog; + return new ErrorDialog { Arguments = args }; } public override Dialog OnCreateDialog(Bundle savedInstanceState) From 4338ccd6ca363755b61dfc8dc4530a098b15e857 Mon Sep 17 00:00:00 2001 From: Gonzalo Martin Date: Wed, 12 Oct 2016 11:47:37 -0300 Subject: [PATCH 4/4] Put inner classes in separate files --- .../Camera2Basic/Camera2Basic.csproj | 9 + .../Camera2Basic/Camera2BasicFragment.cs | 384 ++---------------- .../Camera2Basic/CompareSizesByArea.cs | 17 + .../Camera2Basic/ConfirmationDialog.cs | 44 ++ .../Camera2Basic/Camera2Basic/ErrorDialog.cs | 37 ++ .../Camera2BasicSurfaceTextureListener.cs | 35 ++ .../Listeners/CameraCaptureListener.cs | 78 ++++ .../Listeners/CameraCaptureSessionCallback.cs | 48 +++ ...ameraCaptureStillPictureSessionCallback.cs | 25 ++ .../Listeners/CameraStateListener.cs | 40 ++ .../Listeners/ImageAvailableListener.cs | 56 +++ 11 files changed, 417 insertions(+), 356 deletions(-) create mode 100644 android5.0/Camera2Basic/Camera2Basic/CompareSizesByArea.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/ConfirmationDialog.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/ErrorDialog.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/Listeners/Camera2BasicSurfaceTextureListener.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureListener.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureSessionCallback.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureStillPictureSessionCallback.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/Listeners/CameraStateListener.cs create mode 100644 android5.0/Camera2Basic/Camera2Basic/Listeners/ImageAvailableListener.cs diff --git a/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj b/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj index ada744c72..17f707698 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj +++ b/android5.0/Camera2Basic/Camera2Basic/Camera2Basic.csproj @@ -72,6 +72,15 @@ + + + + + + + + + diff --git a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs index b3ebe7b56..c1eac3884 100644 --- a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs +++ b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs @@ -1,6 +1,4 @@ - - -using System; +using System; using System.Collections.Generic; using Android; using Android.App; @@ -16,15 +14,13 @@ using Android.Media; using Android.Support.V13.App; using Android.Support.V4.Content; +using Camera2Basic.Listeners; using Java.IO; using Java.Lang; -using Java.Nio; using Java.Util; using Java.Util.Concurrent; using Boolean = Java.Lang.Boolean; -using CameraError = Android.Hardware.Camera2.CameraError; using Math = Java.Lang.Math; -using Object = Java.Lang.Object; using Orientation = Android.Content.Res.Orientation; namespace Camera2Basic @@ -32,26 +28,26 @@ namespace Camera2Basic public class Camera2BasicFragment : Fragment, View.IOnClickListener, FragmentCompat.IOnRequestPermissionsResultCallback { private static readonly SparseIntArray ORIENTATIONS = new SparseIntArray(); - private static readonly int REQUEST_CAMERA_PERMISSION = 1; + public static readonly int REQUEST_CAMERA_PERMISSION = 1; private static readonly string FRAGMENT_DIALOG = "dialog"; // Tag for the {@link Log}. private static readonly string TAG = "Camera2BasicFragment"; // Camera state: Showing camera preview. - private const int STATE_PREVIEW = 0; + public const int STATE_PREVIEW = 0; // Camera state: Waiting for the focus to be locked. - private const int STATE_WAITING_LOCK = 1; + public const int STATE_WAITING_LOCK = 1; // Camera state: Waiting for the exposure to be precapture state. - private const int STATE_WAITING_PRECAPTURE = 2; + public const int STATE_WAITING_PRECAPTURE = 2; //Camera state: Waiting for the exposure state to be something other than precapture. - private const int STATE_WAITING_NON_PRECAPTURE = 3; + public const int STATE_WAITING_NON_PRECAPTURE = 3; // Camera state: Picture was taken. - private const int STATE_PICTURE_TAKEN = 4; + public const int STATE_PICTURE_TAKEN = 4; // Max preview width that is guaranteed by Camera2 API private static readonly int MAX_PREVIEW_WIDTH = 1920; @@ -61,36 +57,6 @@ public class Camera2BasicFragment : Fragment, View.IOnClickListener, FragmentCom // TextureView.ISurfaceTextureListener handles several lifecycle events on a TextureView private Camera2BasicSurfaceTextureListener mSurfaceTextureListener; - private class Camera2BasicSurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener - { - public Camera2BasicFragment Owner { get; set; } - - public Camera2BasicSurfaceTextureListener(Camera2BasicFragment owner) - { - Owner = owner; - } - - public void OnSurfaceTextureAvailable(Android.Graphics.SurfaceTexture surface, int width, int height) - { - Owner.OpenCamera(width, height); - } - - public bool OnSurfaceTextureDestroyed(Android.Graphics.SurfaceTexture surface) - { - return true; - } - - public void OnSurfaceTextureSizeChanged(Android.Graphics.SurfaceTexture surface, int width, int height) - { - Owner.ConfigureTransform(width, height); - } - - public void OnSurfaceTextureUpdated(Android.Graphics.SurfaceTexture surface) - { - - } - } - // ID of the current {@link CameraDevice}. private string mCameraId; @@ -99,89 +65,44 @@ public void OnSurfaceTextureUpdated(Android.Graphics.SurfaceTexture surface) private AutoFitTextureView mTextureView; // A {@link CameraCaptureSession } for camera preview. - private CameraCaptureSession mCaptureSession; + public CameraCaptureSession mCaptureSession; // A reference to the opened CameraDevice - private CameraDevice mCameraDevice; + public CameraDevice mCameraDevice; // The size of the camera preview private Size mPreviewSize; // CameraDevice.StateListener is called when a CameraDevice changes its state private CameraStateListener mStateCallback; - private class CameraStateListener : CameraDevice.StateCallback - { - public Camera2BasicFragment owner; - public override void OnOpened(CameraDevice cameraDevice) - { - // This method is called when the camera is opened. We start camera preview here. - owner.mCameraOpenCloseLock.Release(); - owner.mCameraDevice = cameraDevice; - owner.CreateCameraPreviewSession(); - } - - public override void OnDisconnected(CameraDevice cameraDevice) - { - owner.mCameraOpenCloseLock.Release(); - cameraDevice.Close(); - owner.mCameraDevice = null; - } - - public override void OnError(CameraDevice cameraDevice, CameraError error) - { - owner.mCameraOpenCloseLock.Release(); - cameraDevice.Close(); - owner.mCameraDevice = null; - if (owner == null) - return; - Activity activity = owner.Activity; - if (activity != null) - { - activity.Finish(); - } - - } - } // An additional thread for running tasks that shouldn't block the UI. private HandlerThread mBackgroundThread; - // A {@link Handler} for running tasks in the background. - private Handler mBackgroundHandler; - + public Handler mBackgroundHandler; // An {@link ImageReader} that handles still image capture. private ImageReader mImageReader; // This is the output file for our picture. - private File mFile; + public File mFile; // This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a // still image is ready to be saved. private ImageAvailableListener mOnImageAvailableListener; - private class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener - { - public File File { get; set; } - public Camera2BasicFragment Owner { get; set; } - public void OnImageAvailable(ImageReader reader) - { - Owner.mBackgroundHandler.Post(new ImageSaver(reader.AcquireNextImage(), File)); - } - } - //{@link CaptureRequest.Builder} for the camera preview - private CaptureRequest.Builder mPreviewRequestBuilder; + public CaptureRequest.Builder mPreviewRequestBuilder; // {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} - private CaptureRequest mPreviewRequest; + public CaptureRequest mPreviewRequest; // The current state of camera state for taking pictures. - private int mState = STATE_PREVIEW; + public int mState = STATE_PREVIEW; // A {@link Semaphore} to prevent the app from exiting before closing the camera. - private Semaphore mCameraOpenCloseLock = new Semaphore(1); + public Semaphore mCameraOpenCloseLock = new Semaphore(1); // Whether the current camera device supports Flash or not. private bool mFlashSupported; @@ -190,80 +111,10 @@ public void OnImageAvailable(ImageReader reader) private int mSensorOrientation; // A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. - private CameraCaptureListener mCaptureCallback; - private class CameraCaptureListener : CameraCaptureSession.CaptureCallback - { - public Camera2BasicFragment Owner { get; set; } - public File File { get; set; } - public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) - { - Process(result); - } - - public override void OnCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) - { - Process(partialResult); - } - - private void Process(CaptureResult result) - { - switch (Owner.mState) - { - case STATE_WAITING_LOCK: - { - Integer afState = (Integer)result.Get(CaptureResult.ControlAfState); - if (afState == null) - { - Owner.CaptureStillPicture(); - } - - else if ((((int)ControlAFState.FocusedLocked) == afState.IntValue()) || - (((int)ControlAFState.NotFocusedLocked) == afState.IntValue())) - { - // ControlAeState can be null on some devices - Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); - if (aeState == null || - aeState.IntValue() == ((int)ControlAEState.Converged)) - { - Owner.mState = STATE_PICTURE_TAKEN; - Owner.CaptureStillPicture(); - } - else - { - Owner.RunPrecaptureSequence(); - } - } - break; - } - case STATE_WAITING_PRECAPTURE: - { - // ControlAeState can be null on some devices - Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); - if (aeState == null || - aeState.IntValue() == ((int)ControlAEState.Precapture) || - aeState.IntValue() == ((int)ControlAEState.FlashRequired)) - { - Owner.mState = STATE_WAITING_NON_PRECAPTURE; - } - break; - } - case STATE_WAITING_NON_PRECAPTURE: - { - // ControlAeState can be null on some devices - Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); - if (aeState == null || aeState.IntValue() != ((int)ControlAEState.Precapture)) - { - Owner.mState = STATE_PICTURE_TAKEN; - Owner.CaptureStillPicture(); - } - break; - } - } - } - } + public CameraCaptureListener mCaptureCallback; // Shows a {@link Toast} on the UI thread. - private void ShowToast(string text) + public void ShowToast(string text) { if (Activity != null) { @@ -320,11 +171,11 @@ private static Size ChooseOptimalSize(Size[] choices, int textureViewWidth, // largest of those not big enough. if (bigEnough.Count > 0) { - return (Size) Collections.Min(bigEnough, new CompareSizesByArea()); + return (Size)Collections.Min(bigEnough, new CompareSizesByArea()); } else if (notBigEnough.Count > 0) { - return (Size) Collections.Max(notBigEnough, new CompareSizesByArea()); + return (Size)Collections.Max(notBigEnough, new CompareSizesByArea()); } else { @@ -447,7 +298,7 @@ private void SetUpCameraOutputs(int width, int height) } // For still image captures, we use the largest available size. - Size largest = (Size) Collections.Max(Arrays.AsList(map.GetOutputSizes((int)ImageFormatType.Jpeg)), + Size largest = (Size)Collections.Max(Arrays.AsList(map.GetOutputSizes((int)ImageFormatType.Jpeg)), new CompareSizesByArea()); mImageReader = ImageReader.NewInstance(largest.Width, largest.Height, ImageFormatType.Jpeg, /*maxImages*/2); mImageReader.SetOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler); @@ -550,7 +401,7 @@ private void SetUpCameraOutputs(int width, int height) } // Opens the camera specified by {@link Camera2BasicFragment#mCameraId}. - private void OpenCamera(int width, int height) + public void OpenCamera(int width, int height) { if (ContextCompat.CheckSelfPermission(Activity, Manifest.Permission.Camera) != Permission.Granted) { @@ -636,7 +487,7 @@ private void StopBackgroundThread() } // Creates a new {@link CameraCaptureSession} for camera preview. - private void CreateCameraPreviewSession() + public void CreateCameraPreviewSession() { try { @@ -669,50 +520,6 @@ private void CreateCameraPreviewSession() } } - private class CameraCaptureSessionCallback : CameraCaptureSession.StateCallback - { - - private Camera2BasicFragment owner; - - public CameraCaptureSessionCallback(Camera2BasicFragment owner) - { - this.owner = owner; - } - - public override void OnConfigureFailed(CameraCaptureSession session) - { - owner.ShowToast("Failed"); - } - - public override void OnConfigured(CameraCaptureSession session) - { - // The camera is already closed - if (null == owner.mCameraDevice) - { - return; - } - - // When the session is ready, we start displaying the preview. - owner.mCaptureSession = session; - try - { - // Auto focus should be continuous for camera preview. - owner.mPreviewRequestBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture); - // Flash is automatically enabled when necessary. - owner.SetAutoFlash(owner.mPreviewRequestBuilder); - - // Finally, we start displaying the camera preview. - owner.mPreviewRequest = owner.mPreviewRequestBuilder.Build(); - owner.mCaptureSession.SetRepeatingRequest(owner.mPreviewRequest, - owner.mCaptureCallback, owner.mBackgroundHandler); - } - catch (CameraAccessException e) - { - e.PrintStackTrace(); - } - } - } - public static T Cast(Java.Lang.Object obj) where T : class { var propertyInfo = obj.GetType().GetProperty("Instance"); @@ -724,7 +531,7 @@ public static T Cast(Java.Lang.Object obj) where T : class // This method should be called after the camera preview size is determined in // setUpCameraOutputs and also the size of `mTextureView` is fixed. - private void ConfigureTransform(int viewWidth, int viewHeight) + public void ConfigureTransform(int viewWidth, int viewHeight) { Activity activity = Activity; if (null == mTextureView || null == mPreviewSize || null == activity) @@ -779,7 +586,7 @@ private void LockFocus() // Run the precapture sequence for capturing a still image. This method should be called when // we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. - private void RunPrecaptureSequence() + public void RunPrecaptureSequence() { try { @@ -797,7 +604,7 @@ private void RunPrecaptureSequence() // Capture a still picture. This method should be called when we get a response in // {@link #mCaptureCallback} from both {@link #lockFocus()}. - private void CaptureStillPicture() + public void CaptureStillPicture() { try { @@ -827,23 +634,6 @@ private void CaptureStillPicture() } } - private class CameraCaptureStillPictureSessionCallback : CameraCaptureSession.CaptureCallback - { - private Camera2BasicFragment owner; - - public CameraCaptureStillPictureSessionCallback(Camera2BasicFragment owner) - { - this.owner = owner; - } - - public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) - { - owner.ShowToast("Saved: " + owner.mFile); - Log.Debug(TAG, owner.mFile.ToString()); - owner.UnlockFocus(); - } - } - // Retrieves the JPEG orientation from the specified screen rotation. private int GetOrientation(int rotation) { @@ -856,7 +646,7 @@ private int GetOrientation(int rotation) // Unlock the focus. This method should be called when still image capture sequence is // finished. - private void UnlockFocus() + public void UnlockFocus() { try { @@ -897,131 +687,13 @@ public void OnClick(View v) } } - private void SetAutoFlash(CaptureRequest.Builder requestBuilder) + public void SetAutoFlash(CaptureRequest.Builder requestBuilder) { if (mFlashSupported) { requestBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash); } } - - // Saves a JPEG {@link Image} into the specified {@link File}. - private class ImageSaver : Java.Lang.Object, IRunnable - { - // The JPEG image - private Image mImage; - - // The file we save the image into. - private File mFile; - - public ImageSaver(Image image, File file) - { - mImage = image; - mFile = file; - } - - public void Run() - { - ByteBuffer buffer = mImage.GetPlanes()[0].Buffer; - byte[] bytes = new byte[buffer.Remaining()]; - buffer.Get(bytes); - using (var output = new FileOutputStream(mFile)) - { - try - { - output.Write(bytes); - } - catch (IOException e) - { - e.PrintStackTrace(); - } - finally - { - mImage.Close(); - } - } - } - } - - // Compares two {@code Size}s based on their areas. - public class CompareSizesByArea : Java.Lang.Object, IComparator - { - public int Compare(Object lhs, Object rhs) - { - var lhsSize = (Size)lhs; - var rhsSize = (Size)rhs; - // We cast here to ensure the multiplications won't overflow - return Long.Signum((long)lhsSize.Width * lhsSize.Height - (long)rhsSize.Width * rhsSize.Height); - } - } - - // Shows an error message dialog. - public class ErrorDialog : DialogFragment - { - private static readonly string ARG_MESSAGE = "message"; - private static Activity mActivity; - - private class PositiveListener : Java.Lang.Object, IDialogInterfaceOnClickListener - { - public void OnClick(IDialogInterface dialog, int which) - { - mActivity.Finish(); - } - } - - public static ErrorDialog NewInstance(string message) - { - var args = new Bundle(); - args.PutString(ARG_MESSAGE, message); - return new ErrorDialog { Arguments = args }; - } - - public override Dialog OnCreateDialog(Bundle savedInstanceState) - { - mActivity = Activity; - return new AlertDialog.Builder(mActivity) - .SetMessage(Arguments.GetString(ARG_MESSAGE)) - .SetPositiveButton(Android.Resource.String.Ok, new PositiveListener()) - .Create(); - } - } - - // Shows OK/Cancel confirmation dialog about camera permission. - public class ConfirmationDialog : DialogFragment - { - private static Fragment mParent; - private class PositiveListener : Java.Lang.Object, IDialogInterfaceOnClickListener - { - public void OnClick(IDialogInterface dialog, int which) - { - FragmentCompat.RequestPermissions(mParent, - new string[] { Manifest.Permission.Camera }, REQUEST_CAMERA_PERMISSION); - } - } - - private class NegativeListener : Java.Lang.Object, IDialogInterfaceOnClickListener - { - public void OnClick(IDialogInterface dialog, int which) - { - Activity activity = mParent.Activity; - if (activity != null) - { - activity.Finish(); - } - } - } - - public override Dialog OnCreateDialog(Bundle savedInstanceState) - { - mParent = ParentFragment; - return new AlertDialog.Builder(Activity) - .SetMessage(Resource.String.request_permission) - .SetPositiveButton(Android.Resource.String.Ok, new PositiveListener()) - .SetNegativeButton(Android.Resource.String.Cancel, new NegativeListener()) - .Create(); - } - } - } } diff --git a/android5.0/Camera2Basic/Camera2Basic/CompareSizesByArea.cs b/android5.0/Camera2Basic/Camera2Basic/CompareSizesByArea.cs new file mode 100644 index 000000000..5a5937e8f --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/CompareSizesByArea.cs @@ -0,0 +1,17 @@ +using Android.Util; +using Java.Lang; +using Java.Util; + +namespace Camera2Basic +{ + public class CompareSizesByArea : Java.Lang.Object, IComparator + { + public int Compare(Object lhs, Object rhs) + { + var lhsSize = (Size)lhs; + var rhsSize = (Size)rhs; + // We cast here to ensure the multiplications won't overflow + return Long.Signum((long)lhsSize.Width * lhsSize.Height - (long)rhsSize.Width * rhsSize.Height); + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/ConfirmationDialog.cs b/android5.0/Camera2Basic/Camera2Basic/ConfirmationDialog.cs new file mode 100644 index 000000000..fe12ad89e --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/ConfirmationDialog.cs @@ -0,0 +1,44 @@ + +using Android; +using Android.App; +using Android.Content; +using Android.OS; +using Android.Support.V13.App; + +namespace Camera2Basic +{ + public class ConfirmationDialog : DialogFragment + { + private static Fragment mParent; + private class PositiveListener : Java.Lang.Object, IDialogInterfaceOnClickListener + { + public void OnClick(IDialogInterface dialog, int which) + { + FragmentCompat.RequestPermissions(mParent, + new string[] { Manifest.Permission.Camera }, Camera2BasicFragment.REQUEST_CAMERA_PERMISSION); + } + } + + private class NegativeListener : Java.Lang.Object, IDialogInterfaceOnClickListener + { + public void OnClick(IDialogInterface dialog, int which) + { + Activity activity = mParent.Activity; + if (activity != null) + { + activity.Finish(); + } + } + } + + public override Dialog OnCreateDialog(Bundle savedInstanceState) + { + mParent = ParentFragment; + return new AlertDialog.Builder(Activity) + .SetMessage(Resource.String.request_permission) + .SetPositiveButton(Android.Resource.String.Ok, new PositiveListener()) + .SetNegativeButton(Android.Resource.String.Cancel, new NegativeListener()) + .Create(); + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/ErrorDialog.cs b/android5.0/Camera2Basic/Camera2Basic/ErrorDialog.cs new file mode 100644 index 000000000..dd3404f45 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/ErrorDialog.cs @@ -0,0 +1,37 @@ + +using Android.App; +using Android.Content; +using Android.OS; + +namespace Camera2Basic +{ + public class ErrorDialog : DialogFragment + { + private static readonly string ARG_MESSAGE = "message"; + private static Activity mActivity; + + private class PositiveListener : Java.Lang.Object, IDialogInterfaceOnClickListener + { + public void OnClick(IDialogInterface dialog, int which) + { + mActivity.Finish(); + } + } + + public static ErrorDialog NewInstance(string message) + { + var args = new Bundle(); + args.PutString(ARG_MESSAGE, message); + return new ErrorDialog { Arguments = args }; + } + + public override Dialog OnCreateDialog(Bundle savedInstanceState) + { + mActivity = Activity; + return new AlertDialog.Builder(mActivity) + .SetMessage(Arguments.GetString(ARG_MESSAGE)) + .SetPositiveButton(Android.Resource.String.Ok, new PositiveListener()) + .Create(); + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Listeners/Camera2BasicSurfaceTextureListener.cs b/android5.0/Camera2Basic/Camera2Basic/Listeners/Camera2BasicSurfaceTextureListener.cs new file mode 100644 index 000000000..2b37a6af1 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/Listeners/Camera2BasicSurfaceTextureListener.cs @@ -0,0 +1,35 @@ + +using Android.Views; + +namespace Camera2Basic.Listeners +{ + public class Camera2BasicSurfaceTextureListener : Java.Lang.Object, TextureView.ISurfaceTextureListener + { + public Camera2BasicFragment Owner { get; set; } + + public Camera2BasicSurfaceTextureListener(Camera2BasicFragment owner) + { + Owner = owner; + } + + public void OnSurfaceTextureAvailable(Android.Graphics.SurfaceTexture surface, int width, int height) + { + Owner.OpenCamera(width, height); + } + + public bool OnSurfaceTextureDestroyed(Android.Graphics.SurfaceTexture surface) + { + return true; + } + + public void OnSurfaceTextureSizeChanged(Android.Graphics.SurfaceTexture surface, int width, int height) + { + Owner.ConfigureTransform(width, height); + } + + public void OnSurfaceTextureUpdated(Android.Graphics.SurfaceTexture surface) + { + + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureListener.cs b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureListener.cs new file mode 100644 index 000000000..1e5bb1c12 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureListener.cs @@ -0,0 +1,78 @@ + +using Android.Hardware.Camera2; +using Java.IO; +using Java.Lang; + +namespace Camera2Basic.Listeners +{ + public class CameraCaptureListener : CameraCaptureSession.CaptureCallback + { + public Camera2BasicFragment Owner { get; set; } + public File File { get; set; } + public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) + { + Process(result); + } + + public override void OnCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) + { + Process(partialResult); + } + + private void Process(CaptureResult result) + { + switch (Owner.mState) + { + case Camera2BasicFragment.STATE_WAITING_LOCK: + { + Integer afState = (Integer)result.Get(CaptureResult.ControlAfState); + if (afState == null) + { + Owner.CaptureStillPicture(); + } + + else if ((((int)ControlAFState.FocusedLocked) == afState.IntValue()) || + (((int)ControlAFState.NotFocusedLocked) == afState.IntValue())) + { + // ControlAeState can be null on some devices + Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); + if (aeState == null || + aeState.IntValue() == ((int)ControlAEState.Converged)) + { + Owner.mState = Camera2BasicFragment.STATE_PICTURE_TAKEN; + Owner.CaptureStillPicture(); + } + else + { + Owner.RunPrecaptureSequence(); + } + } + break; + } + case Camera2BasicFragment.STATE_WAITING_PRECAPTURE: + { + // ControlAeState can be null on some devices + Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); + if (aeState == null || + aeState.IntValue() == ((int)ControlAEState.Precapture) || + aeState.IntValue() == ((int)ControlAEState.FlashRequired)) + { + Owner.mState = Camera2BasicFragment.STATE_WAITING_NON_PRECAPTURE; + } + break; + } + case Camera2BasicFragment.STATE_WAITING_NON_PRECAPTURE: + { + // ControlAeState can be null on some devices + Integer aeState = (Integer)result.Get(CaptureResult.ControlAeState); + if (aeState == null || aeState.IntValue() != ((int)ControlAEState.Precapture)) + { + Owner.mState = Camera2BasicFragment.STATE_PICTURE_TAKEN; + Owner.CaptureStillPicture(); + } + break; + } + } + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureSessionCallback.cs b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureSessionCallback.cs new file mode 100644 index 000000000..9154cf32f --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureSessionCallback.cs @@ -0,0 +1,48 @@ + +using Android.Hardware.Camera2; + +namespace Camera2Basic.Listeners +{ + public class CameraCaptureSessionCallback : CameraCaptureSession.StateCallback + { + public Camera2BasicFragment Owner { get; set; } + + public CameraCaptureSessionCallback(Camera2BasicFragment owner) + { + Owner = owner; + } + + public override void OnConfigureFailed(CameraCaptureSession session) + { + Owner.ShowToast("Failed"); + } + + public override void OnConfigured(CameraCaptureSession session) + { + // The camera is already closed + if (null == Owner.mCameraDevice) + { + return; + } + + // When the session is ready, we start displaying the preview. + Owner.mCaptureSession = session; + try + { + // Auto focus should be continuous for camera preview. + Owner.mPreviewRequestBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture); + // Flash is automatically enabled when necessary. + Owner.SetAutoFlash(Owner.mPreviewRequestBuilder); + + // Finally, we start displaying the camera preview. + Owner.mPreviewRequest = Owner.mPreviewRequestBuilder.Build(); + Owner.mCaptureSession.SetRepeatingRequest(Owner.mPreviewRequest, + Owner.mCaptureCallback, Owner.mBackgroundHandler); + } + catch (CameraAccessException e) + { + e.PrintStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureStillPictureSessionCallback.cs b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureStillPictureSessionCallback.cs new file mode 100644 index 000000000..8dab96298 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraCaptureStillPictureSessionCallback.cs @@ -0,0 +1,25 @@ + +using Android.Hardware.Camera2; +using Android.Util; + +namespace Camera2Basic.Listeners +{ + public class CameraCaptureStillPictureSessionCallback : CameraCaptureSession.CaptureCallback + { + private static readonly string TAG = "CameraCaptureStillPictureSessionCallback"; + + public Camera2BasicFragment Owner { get; set; } + + public CameraCaptureStillPictureSessionCallback(Camera2BasicFragment owner) + { + Owner = owner; + } + + public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) + { + Owner.ShowToast("Saved: " + Owner.mFile); + Log.Debug(TAG, Owner.mFile.ToString()); + Owner.UnlockFocus(); + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraStateListener.cs b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraStateListener.cs new file mode 100644 index 000000000..81dcc96f5 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/Listeners/CameraStateListener.cs @@ -0,0 +1,40 @@ + +using Android.App; +using Android.Hardware.Camera2; + +namespace Camera2Basic.Listeners +{ + public class CameraStateListener : CameraDevice.StateCallback + { + public Camera2BasicFragment owner; + public override void OnOpened(CameraDevice cameraDevice) + { + // This method is called when the camera is opened. We start camera preview here. + owner.mCameraOpenCloseLock.Release(); + owner.mCameraDevice = cameraDevice; + owner.CreateCameraPreviewSession(); + } + + public override void OnDisconnected(CameraDevice cameraDevice) + { + owner.mCameraOpenCloseLock.Release(); + cameraDevice.Close(); + owner.mCameraDevice = null; + } + + public override void OnError(CameraDevice cameraDevice, CameraError error) + { + owner.mCameraOpenCloseLock.Release(); + cameraDevice.Close(); + owner.mCameraDevice = null; + if (owner == null) + return; + Activity activity = owner.Activity; + if (activity != null) + { + activity.Finish(); + } + + } + } +} \ No newline at end of file diff --git a/android5.0/Camera2Basic/Camera2Basic/Listeners/ImageAvailableListener.cs b/android5.0/Camera2Basic/Camera2Basic/Listeners/ImageAvailableListener.cs new file mode 100644 index 000000000..9ec9a6e87 --- /dev/null +++ b/android5.0/Camera2Basic/Camera2Basic/Listeners/ImageAvailableListener.cs @@ -0,0 +1,56 @@ + +using Android.Media; +using Java.IO; +using Java.Lang; +using Java.Nio; + +namespace Camera2Basic.Listeners +{ + public class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener + { + public File File { get; set; } + public Camera2BasicFragment Owner { get; set; } + public void OnImageAvailable(ImageReader reader) + { + Owner.mBackgroundHandler.Post(new ImageSaver(reader.AcquireNextImage(), File)); + } + + // Saves a JPEG {@link Image} into the specified {@link File}. + private class ImageSaver : Java.Lang.Object, IRunnable + { + // The JPEG image + private Image mImage; + + // The file we save the image into. + private File mFile; + + public ImageSaver(Image image, File file) + { + mImage = image; + mFile = file; + } + + public void Run() + { + ByteBuffer buffer = mImage.GetPlanes()[0].Buffer; + byte[] bytes = new byte[buffer.Remaining()]; + buffer.Get(bytes); + using (var output = new FileOutputStream(mFile)) + { + try + { + output.Write(bytes); + } + catch (IOException e) + { + e.PrintStackTrace(); + } + finally + { + mImage.Close(); + } + } + } + } + } +} \ No newline at end of file