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(); } } }