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..17f707698 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,8 +46,41 @@
+
+ ..\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 +88,7 @@
+
@@ -83,7 +119,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..c1eac3884 100644
--- a/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs
+++ b/android5.0/Camera2Basic/Camera2Basic/Camera2BasicFragment.cs
@@ -1,527 +1,699 @@
-
-
-using System;
+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.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 Camera2Basic.Listeners;
using Java.IO;
-using Java.Nio;
using Java.Lang;
-
-using CameraError = Android.Hardware.Camera2.CameraError;
+using Java.Util;
+using Java.Util.Concurrent;
+using Boolean = Java.Lang.Boolean;
+using Math = Java.Lang.Math;
+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();
+ 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.
+ public const int STATE_PREVIEW = 0;
+
+ // Camera state: Waiting for the focus to be locked.
+ public const int STATE_WAITING_LOCK = 1;
+
+ // Camera state: Waiting for the exposure to be precapture state.
+ public const int STATE_WAITING_PRECAPTURE = 2;
+
+ //Camera state: Waiting for the exposure state to be something other than precapture.
+ public const int STATE_WAITING_NON_PRECAPTURE = 3;
+
+ // Camera state: Picture was taken.
+ public 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;
+
+ // ID of the current {@link CameraDevice}.
+ private string mCameraId;
+
+ // An AutoFitTextureView for camera preview
+ private AutoFitTextureView mTextureView;
+
+ // A {@link CameraCaptureSession } for camera preview.
+ public CameraCaptureSession mCaptureSession;
+
+ // A reference to the opened CameraDevice
+ 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;
+
+ // An additional thread for running tasks that shouldn't block the UI.
+ private HandlerThread mBackgroundThread;
+
+ // A {@link Handler} for running tasks in the background.
+ public Handler mBackgroundHandler;
+
+ // An {@link ImageReader} that handles still image capture.
+ private ImageReader mImageReader;
+
+ // This is the output file for our picture.
+ 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;
+
+ //{@link CaptureRequest.Builder} for the camera preview
+ public CaptureRequest.Builder mPreviewRequestBuilder;
+
+ // {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
+ public CaptureRequest mPreviewRequest;
+
+ // The current state of camera state for taking pictures.
+ public int mState = STATE_PREVIEW;
+
+ // A {@link Semaphore} to prevent the app from exiting before closing the camera.
+ public 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.
+ public CameraCaptureListener mCaptureCallback;
+
+ // Shows a {@link Toast} on the UI thread.
+ public 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)
+ return;
+
+ if (grantResults.Length != 1 || grantResults[0] != (int)Permission.Granted)
+ {
+ ErrorDialog.NewInstance(GetString(Resource.String.request_permission))
+ .Show(ChildFragmentManager, FRAGMENT_DIALOG);
+ }
+ }
+
+
+ // 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}.
+ public 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.
+ public 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();
+ }
+ }
+
+ 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.
+
+ public 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()}.
+ public 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()}.
+ public 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();
+ }
+ }
+
+ // 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.
+ public 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;
+ }
+ }
+
+ public void SetAutoFlash(CaptureRequest.Builder requestBuilder)
+ {
+ if (mFlashSupported)
+ {
+ requestBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash);
+ }
+ }
+ }
}
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
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
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