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