|
@@ -0,0 +1,316 @@
|
|
|
+using Android;
|
|
|
+using Android.Content;
|
|
|
+using Android.Graphics;
|
|
|
+using AndroidX.Camera.Core;
|
|
|
+using AndroidX.Camera.Core.ResolutionSelector;
|
|
|
+using AndroidX.Camera.View;
|
|
|
+using AndroidX.Camera.View.Transform;
|
|
|
+using AndroidX.Core.Content;
|
|
|
+using AndroidX.Lifecycle;
|
|
|
+using Avalonia.Media;
|
|
|
+using Avalonia.Threading;
|
|
|
+using InABox.Avalonia.Platform.Barcodes;
|
|
|
+using Java.Util.Concurrent;
|
|
|
+using Microsoft.Maui.Devices;
|
|
|
+
|
|
|
+using static Android.Views.ViewGroup;
|
|
|
+using Color = Android.Graphics.Color;
|
|
|
+using AvColor = Avalonia.Media.Color;
|
|
|
+using Avalonia;
|
|
|
+
|
|
|
+namespace InABox.Avalonia.Platform.Android.Barcodes;
|
|
|
+
|
|
|
+internal class CameraManager : IDisposable
|
|
|
+{
|
|
|
+ internal BarcodeView BarcodeView { get => _barcodeView; }
|
|
|
+ internal CameraView? CameraView { get => _cameraView; }
|
|
|
+ internal PreviewView PreviewView { get => _previewView; }
|
|
|
+
|
|
|
+ internal CameraState? OpenedCameraState { get; set; }
|
|
|
+
|
|
|
+ private readonly BarcodeAnalyzer _barcodeAnalyzer;
|
|
|
+ private readonly BarcodeView _barcodeView;
|
|
|
+ private readonly Context _context;
|
|
|
+ private readonly IExecutorService _analyzerExecutor;
|
|
|
+ private readonly ImageView _imageView;
|
|
|
+ private readonly LifecycleCameraController _cameraController;
|
|
|
+ private readonly ILifecycleOwner _lifecycleOwner;
|
|
|
+ private readonly PreviewView _previewView;
|
|
|
+ private readonly RelativeLayout _relativeLayout;
|
|
|
+ private readonly CameraStateObserver _cameraStateObserver;
|
|
|
+
|
|
|
+ private readonly CameraView? _cameraView;
|
|
|
+
|
|
|
+ // Caches of CameraView properties to avoid calling on non-main threads.
|
|
|
+ internal bool _pauseScanning;
|
|
|
+ internal bool _forceInverted;
|
|
|
+ internal bool _forceFrameCapture;
|
|
|
+ internal bool _captureNextFrame;
|
|
|
+ internal bool _aimMode;
|
|
|
+ internal bool _viewFinderMode;
|
|
|
+
|
|
|
+ private ICameraInfo? _currentCameraInfo;
|
|
|
+
|
|
|
+ private const int aimRadius = 25;
|
|
|
+
|
|
|
+ static CameraManager()
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ internal CameraManager(CameraView cameraView, Context context)
|
|
|
+ {
|
|
|
+ _context = context;
|
|
|
+ _cameraView = cameraView;
|
|
|
+
|
|
|
+ ILifecycleOwner? owner = null;
|
|
|
+ if (_context is ILifecycleOwner)
|
|
|
+ owner = _context as ILifecycleOwner;
|
|
|
+ else if ((_context as ContextWrapper)?.BaseContext is ILifecycleOwner)
|
|
|
+ owner = (_context as ContextWrapper)?.BaseContext as ILifecycleOwner;
|
|
|
+ // else if (Platform.CurrentActivity is ILifecycleOwner)
|
|
|
+ // owner = Platform.CurrentActivity as ILifecycleOwner;
|
|
|
+
|
|
|
+ var executor = Executors.NewSingleThreadExecutor();
|
|
|
+
|
|
|
+ ArgumentNullException.ThrowIfNull(owner);
|
|
|
+ ArgumentNullException.ThrowIfNull(executor);
|
|
|
+ _lifecycleOwner = owner;
|
|
|
+ _analyzerExecutor = executor;
|
|
|
+
|
|
|
+ _barcodeAnalyzer = new BarcodeAnalyzer(this);
|
|
|
+
|
|
|
+ _cameraStateObserver = new CameraStateObserver(this, _cameraView);
|
|
|
+ _cameraController = new LifecycleCameraController(_context)
|
|
|
+ {
|
|
|
+ TapToFocusEnabled = _cameraView?.TapToFocusEnabled ?? false,
|
|
|
+ ImageAnalysisBackpressureStrategy = ImageAnalysis.StrategyKeepOnlyLatest
|
|
|
+ };
|
|
|
+ _cameraController.SetEnabledUseCases(CameraController.ImageAnalysis);
|
|
|
+ _cameraController.ZoomState.ObserveForever(_cameraStateObserver);
|
|
|
+ _cameraController.InitializationFuture.AddListener(new Java.Lang.Runnable(() =>
|
|
|
+ {
|
|
|
+ _currentCameraInfo?.CameraState.RemoveObserver(_cameraStateObserver);
|
|
|
+ _currentCameraInfo = _cameraController.CameraInfo;
|
|
|
+ _currentCameraInfo?.CameraState.ObserveForever(_cameraStateObserver);
|
|
|
+ }), ContextCompat.GetMainExecutor(_context));
|
|
|
+
|
|
|
+ ArgumentNullException.ThrowIfNull(PreviewView.ImplementationMode.Compatible);
|
|
|
+ ArgumentNullException.ThrowIfNull(PreviewView.ScaleType.FillCenter);
|
|
|
+ ArgumentNullException.ThrowIfNull(Bitmap.Config.Argb8888);
|
|
|
+
|
|
|
+ _previewView = new PreviewView(_context)
|
|
|
+ {
|
|
|
+ LayoutParameters = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
|
|
|
+ };
|
|
|
+ var background = (_cameraView?.Background as SolidColorBrush)?.Color ?? Colors.Transparent;
|
|
|
+ _previewView.SetBackgroundColor(new Color(background.R, background.G, background.B, background.A));
|
|
|
+ _previewView.SetImplementationMode(PreviewView.ImplementationMode.Compatible);
|
|
|
+ _previewView.SetScaleType(PreviewView.ScaleType.FillCenter);
|
|
|
+
|
|
|
+ using var layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
|
|
|
+ layoutParams.AddRule(LayoutRules.CenterInParent);
|
|
|
+ using var circleBitmap = Bitmap.CreateBitmap(2 * aimRadius, 2 * aimRadius, Bitmap.Config.Argb8888);
|
|
|
+ using var canvas = new Canvas(circleBitmap);
|
|
|
+ canvas.DrawCircle(aimRadius, aimRadius, aimRadius, new Paint
|
|
|
+ {
|
|
|
+ AntiAlias = true,
|
|
|
+ Color = Color.Red,
|
|
|
+ Alpha = 150
|
|
|
+ });
|
|
|
+ _imageView = new ImageView(_context)
|
|
|
+ {
|
|
|
+ LayoutParameters = layoutParams
|
|
|
+ };
|
|
|
+ _imageView.SetImageBitmap(circleBitmap);
|
|
|
+
|
|
|
+ _relativeLayout = new RelativeLayout(_context)
|
|
|
+ {
|
|
|
+ LayoutParameters = new RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent)
|
|
|
+ };
|
|
|
+ _relativeLayout.AddView(_previewView);
|
|
|
+
|
|
|
+ _barcodeView = new BarcodeView(_context);
|
|
|
+ _barcodeView.AddView(_relativeLayout);
|
|
|
+
|
|
|
+ DeviceDisplay.Current.MainDisplayInfoChanged += MainDisplayInfoChangedAsync;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void Start()
|
|
|
+ {
|
|
|
+ if (_previewView is not null)
|
|
|
+ _previewView.Controller = null;
|
|
|
+
|
|
|
+ if (OpenedCameraState?.GetType() != CameraState.Type.Closed)
|
|
|
+ _cameraController?.Unbind();
|
|
|
+
|
|
|
+ _cameraController?.ClearImageAnalysisAnalyzer();
|
|
|
+ if (_barcodeAnalyzer is not null && _analyzerExecutor is not null)
|
|
|
+ _cameraController?.SetImageAnalysisAnalyzer(_analyzerExecutor, _barcodeAnalyzer);
|
|
|
+
|
|
|
+ UpdateResolution();
|
|
|
+ UpdateCamera();
|
|
|
+ UpdateSymbologies();
|
|
|
+ UpdateTorch();
|
|
|
+
|
|
|
+ if (_lifecycleOwner is not null)
|
|
|
+ _cameraController?.BindToLifecycle(_lifecycleOwner);
|
|
|
+
|
|
|
+ if (_previewView is not null && _cameraController is not null)
|
|
|
+ _previewView.Controller = _cameraController;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void Stop()
|
|
|
+ {
|
|
|
+ _cameraController?.Unbind();
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateAimMode()
|
|
|
+ {
|
|
|
+ if (_aimMode)
|
|
|
+ _relativeLayout?.AddView(_imageView);
|
|
|
+ else
|
|
|
+ _relativeLayout?.RemoveView(_imageView);
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateBackgroundColor()
|
|
|
+ {
|
|
|
+ var background = (_cameraView?.Background as SolidColorBrush)?.Color ?? Colors.Transparent;
|
|
|
+ _previewView.SetBackgroundColor(new Color(background.R, background.G, background.B, background.A));
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateCamera()
|
|
|
+ {
|
|
|
+ if (_cameraController is not null)
|
|
|
+ {
|
|
|
+ if (_cameraView?.CameraFacing == CameraFacing.Front)
|
|
|
+ _cameraController.CameraSelector = CameraSelector.DefaultFrontCamera;
|
|
|
+ else
|
|
|
+ _cameraController.CameraSelector = CameraSelector.DefaultBackCamera;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateCameraEnabled()
|
|
|
+ {
|
|
|
+ if (_cameraView?.CameraEnabled ?? false)
|
|
|
+ Start();
|
|
|
+ else
|
|
|
+ Stop();
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateResolution()
|
|
|
+ {
|
|
|
+ using var analysisStrategy = new ResolutionStrategy(Methods.TargetResolution(_cameraView?.CaptureQuality), ResolutionStrategy.FallbackRuleClosestHigherThenLower);
|
|
|
+ using var resolutionBuilder = new ResolutionSelector.Builder();
|
|
|
+ resolutionBuilder.SetAllowedResolutionMode(ResolutionSelector.PreferHigherResolutionOverCaptureRate);
|
|
|
+ resolutionBuilder.SetResolutionStrategy(analysisStrategy);
|
|
|
+ resolutionBuilder.SetAspectRatioStrategy(AspectRatioStrategy.Ratio169FallbackAutoStrategy);
|
|
|
+ var selector = resolutionBuilder.Build();
|
|
|
+
|
|
|
+ if (_cameraController is not null)
|
|
|
+ {
|
|
|
+ _cameraController.ImageAnalysisResolutionSelector = selector;
|
|
|
+ _cameraController.PreviewResolutionSelector = selector;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateSymbologies()
|
|
|
+ {
|
|
|
+ _barcodeAnalyzer?.UpdateSymbologies();
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateTapToFocus()
|
|
|
+ {
|
|
|
+ if (_cameraController is not null)
|
|
|
+ _cameraController.TapToFocusEnabled = _cameraView?.TapToFocusEnabled ?? false;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateTorch()
|
|
|
+ {
|
|
|
+ _cameraController?.EnableTorch(_cameraView?.TorchOn ?? false);
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateVibration()
|
|
|
+ {
|
|
|
+ // if ((_cameraView?.VibrationOnDetected ?? false) &&
|
|
|
+ // !Permissions.IsDeclaredInManifest(Manifest.Permission.Vibrate))
|
|
|
+ // _cameraView.VibrationOnDetected = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void UpdateZoomFactor()
|
|
|
+ {
|
|
|
+ if (_cameraView is not null && (_cameraController?.ZoomState?.IsInitialized ?? false))
|
|
|
+ {
|
|
|
+ var factor = _cameraView.RequestZoomFactor;
|
|
|
+ if (factor > 0)
|
|
|
+ {
|
|
|
+ factor = Math.Max(factor, _cameraView.MinZoomFactor);
|
|
|
+ factor = Math.Min(factor, _cameraView.MaxZoomFactor);
|
|
|
+
|
|
|
+ if (factor != _cameraView.CurrentZoomFactor)
|
|
|
+ _cameraController.SetZoomRatio(factor);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal CoordinateTransform? GetCoordinateTransform(IImageProxy proxy)
|
|
|
+ {
|
|
|
+ var imageOutputTransform = new ImageProxyTransformFactory().GetOutputTransform(proxy);
|
|
|
+ var previewOutputTransform = Dispatcher.UIThread.InvokeAsync(() => _previewView?.OutputTransform).GetAwaiter().GetResult();
|
|
|
+
|
|
|
+ if (imageOutputTransform is not null && previewOutputTransform is not null)
|
|
|
+ return new CoordinateTransform(imageOutputTransform, previewOutputTransform);
|
|
|
+ else
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async void MainDisplayInfoChangedAsync(object? sender, DisplayInfoChangedEventArgs e)
|
|
|
+ {
|
|
|
+ if (OpenedCameraState?.GetType() == CameraState.Type.Open)
|
|
|
+ {
|
|
|
+ if (_previewView is not null)
|
|
|
+ _previewView.Controller = null;
|
|
|
+
|
|
|
+ await Task.Delay(100);
|
|
|
+
|
|
|
+ if (_previewView is not null)
|
|
|
+ _previewView.Controller = _cameraController;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ Dispose(true);
|
|
|
+ GC.SuppressFinalize(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected virtual void Dispose(bool disposing)
|
|
|
+ {
|
|
|
+ if (disposing)
|
|
|
+ {
|
|
|
+ Stop();
|
|
|
+
|
|
|
+ DeviceDisplay.Current.MainDisplayInfoChanged -= MainDisplayInfoChangedAsync;
|
|
|
+
|
|
|
+ if (_cameraStateObserver is not null)
|
|
|
+ {
|
|
|
+ _cameraController?.ZoomState.RemoveObserver(_cameraStateObserver);
|
|
|
+ _currentCameraInfo?.CameraState.RemoveObserver(_cameraStateObserver);
|
|
|
+ }
|
|
|
+
|
|
|
+ _cameraController?.ClearImageAnalysisAnalyzer();
|
|
|
+
|
|
|
+ _barcodeView?.RemoveAllViews();
|
|
|
+ _relativeLayout?.RemoveAllViews();
|
|
|
+
|
|
|
+ _barcodeView?.Dispose();
|
|
|
+ _relativeLayout?.Dispose();
|
|
|
+ _imageView?.Dispose();
|
|
|
+ _previewView?.Dispose();
|
|
|
+ _cameraController?.Dispose();
|
|
|
+ _currentCameraInfo?.Dispose();
|
|
|
+ _cameraStateObserver?.Dispose();
|
|
|
+ _barcodeAnalyzer?.Dispose();
|
|
|
+ _analyzerExecutor?.Dispose();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|