Browse Source

Enable nullable on Avalonia.Android project (#15331)

* Enable nullable on Avalonia.Android project

* More specific exception
pull/15333/head
Max Katz 2 years ago
committed by GitHub
parent
commit
6258df5b63
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 33
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  2. 8
      src/Android/Avalonia.Android/AndroidPlatform.cs
  3. 4
      src/Android/Avalonia.Android/AndroidRuntimePlatform.cs
  4. 17
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  5. 4
      src/Android/Avalonia.Android/AndroidViewControlHandle.cs
  6. 3
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  7. 1
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  8. 10
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  9. 3
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  10. 28
      src/Android/Avalonia.Android/AvaloniaView.cs
  11. 13
      src/Android/Avalonia.Android/ChoreographerTimer.cs
  12. 11
      src/Android/Avalonia.Android/IActivityResultHandler.cs
  13. 1
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  14. 3
      src/Android/Avalonia.Android/IAvaloniaActivity.cs
  15. 12
      src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs
  16. 14
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  17. 4
      src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
  18. 32
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  19. 4
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  20. 2
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  21. 1
      src/Android/Avalonia.Android/Platform/PlatformSupport.cs
  22. 9
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  23. 17
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  24. 124
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  25. 4
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyInterop.cs
  26. 14
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  27. 15
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  28. 7
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  29. 7
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  30. 3
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  31. 2
      src/Android/Avalonia.Android/Stubs.cs
  32. 1
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  33. 2
      tests/Avalonia.UnitTests/TestRoot.cs

33
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Android.Content;
using Android.Runtime;
using Android.Text;
@ -13,8 +14,9 @@ namespace Avalonia.Android
{
public View View { get; }
public TextInputMethodClient Client { get; }
public TextInputMethodClient? Client { get; }
[MemberNotNullWhen(true, nameof(Client))]
public bool IsActive { get; }
public InputMethodManager IMM { get; }
@ -38,8 +40,8 @@ namespace Avalonia.Android
{
private readonly TView _host;
private readonly InputMethodManager _imm;
private TextInputMethodClient _client;
private AvaloniaInputConnection _inputConnection;
private TextInputMethodClient? _client;
private AvaloniaInputConnection? _inputConnection;
public AndroidInputMethod(TView host)
{
@ -47,7 +49,8 @@ namespace Avalonia.Android
throw new InvalidOperationException("Host should return true from OnCheckIsTextEditor()");
_host = host;
_imm = host.Context.GetSystemService(Context.InputMethodService).JavaCast<InputMethodManager>();
_imm = host.Context?.GetSystemService(Context.InputMethodService).JavaCast<InputMethodManager>()
?? throw new InvalidOperationException("Context.InputMethodService is expected to be not null.");
_host.Focusable = true;
_host.FocusableInTouchMode = true;
@ -55,9 +58,11 @@ namespace Avalonia.Android
public View View => _host;
[MemberNotNullWhen(true, nameof(Client))]
[MemberNotNullWhen(true, nameof(_client))]
public bool IsActive => Client != null;
public TextInputMethodClient Client => _client;
public TextInputMethodClient? Client => _client;
public InputMethodManager IMM => _imm;
@ -66,7 +71,7 @@ namespace Avalonia.Android
}
public void SetClient(TextInputMethodClient client)
public void SetClient(TextInputMethodClient? client)
{
_client = client;
@ -103,9 +108,9 @@ namespace Avalonia.Android
}
}
private void _client_SelectionChanged(object sender, EventArgs e)
private void _client_SelectionChanged(object? sender, EventArgs e)
{
if (_inputConnection.IsInBatchEdit)
if (_inputConnection is null || _inputConnection.IsInBatchEdit)
return;
OnSelectionChanged();
}
@ -121,19 +126,19 @@ namespace Avalonia.Android
_imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);
_inputConnection.SetSelection(selection.Start, selection.End);
_inputConnection?.SetSelection(selection.Start, selection.End);
}
private void _client_SurroundingTextChanged(object sender, EventArgs e)
private void _client_SurroundingTextChanged(object? sender, EventArgs e)
{
if (_inputConnection.IsInBatchEdit)
if (_inputConnection is null || _inputConnection.IsInBatchEdit)
return;
OnSurroundingTextChanged();
}
public void OnBatchEditedEnded()
{
if (_inputConnection.IsInBatchEdit)
if (_inputConnection is null || _inputConnection.IsInBatchEdit)
return;
OnSurroundingTextChanged();
@ -142,7 +147,7 @@ namespace Avalonia.Android
private void OnSurroundingTextChanged()
{
if(_client is null)
if(_client is null || _inputConnection is null)
{
return;
}
@ -199,7 +204,7 @@ namespace Avalonia.Android
_host.InitEditorInfo((topLevel, outAttrs) =>
{
if (_client == null)
return null;
return null!;
_inputConnection = new AvaloniaInputConnection(topLevel, this);

8
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Android;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.Input;
@ -12,7 +11,6 @@ using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.OpenGL;
namespace Avalonia
{
@ -66,9 +64,9 @@ namespace Avalonia.Android
class AndroidPlatform
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; }
public static AndroidPlatformOptions? Options { get; private set; }
internal static Compositor Compositor { get; private set; }
internal static Compositor? Compositor { get; private set; }
public static void Initialize()
{
@ -95,7 +93,7 @@ namespace Avalonia.Android
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor);
}
private static IPlatformGraphics InitializeGraphics(AndroidPlatformOptions opts)
private static IPlatformGraphics? InitializeGraphics(AndroidPlatformOptions opts)
{
if (opts.RenderingMode is null || !opts.RenderingMode.Any())
{

4
src/Android/Avalonia.Android/AndroidRuntimePlatform.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using System;
using Android.Content.PM;
using Android.Content;
using Avalonia.Platform;

17
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -14,11 +14,12 @@ namespace Avalonia.Android
internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface
{
private Handler _handler;
private static Thread s_uiThread;
private static Thread? s_uiThread;
public AndroidThreadingInterface()
{
_handler = new Handler(App.Context.MainLooper);
_handler = new Handler(App.Context.MainLooper
?? throw new InvalidOperationException("Application.Context.MainLooper was not expected to be null."));
}
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
@ -27,7 +28,7 @@ namespace Avalonia.Android
interval = TimeSpan.FromMilliseconds(10);
var stopped = false;
Timer timer = null;
Timer? timer = null;
timer = new Timer(_ =>
{
if (stopped)
@ -42,7 +43,7 @@ namespace Avalonia.Android
finally
{
if (!stopped)
timer.Change(interval, Timeout.InfiniteTimeSpan);
timer!.Change(interval, Timeout.InfiniteTimeSpan);
}
});
},
@ -70,9 +71,9 @@ namespace Avalonia.Android
return s_uiThread == Thread.CurrentThread;
var isOnMainThread = OperatingSystem.IsAndroidVersionAtLeast(23)
? Looper.MainLooper.IsCurrentThread
: Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
if (isOnMainThread)
? Looper.MainLooper?.IsCurrentThread
: Looper.MainLooper?.Thread.Equals(Java.Lang.Thread.CurrentThread());
if (isOnMainThread == true)
{
s_uiThread = Thread.CurrentThread;
return true;
@ -81,6 +82,6 @@ namespace Avalonia.Android
return false;
}
}
public event Action<DispatcherPriority?> Signaled;
public event Action<DispatcherPriority?>? Signaled;
}
}

4
src/Android/Avalonia.Android/AndroidViewControlHandle.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using System;
using Android.Views;

3
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -3,7 +3,6 @@
<TargetFramework>$(AvsCurrentAndroidTargetFramework)</TargetFramework>
<SupportedOSPlatformVersion>$(AvsMinSupportedAndroidVersion)</SupportedOSPlatformVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>portable</DebugType>
<AndroidResgenNamespace>Avalonia.Android.Internal</AndroidResgenNamespace>
</PropertyGroup>
<ItemGroup>
@ -16,6 +15,8 @@
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />
<Import Project="..\..\..\build\NullableEnable.props" />
</Project>

1
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -1,4 +1,3 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Versioning;

10
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@ -1,12 +1,4 @@
#nullable enable
using Android.OS;
using Android.Views;
using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
namespace Avalonia.Android;
namespace Avalonia.Android;
public class AvaloniaMainActivity<TApp> : AvaloniaMainActivity
where TApp : Application, new()

3
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -1,7 +1,4 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using Android.OS;
using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;

28
src/Android/Avalonia.Android/AvaloniaView.cs

@ -20,7 +20,7 @@ namespace Avalonia.Android
private EmbeddableControlRoot _root;
private readonly ViewImpl _view;
private IDisposable _timerSubscription;
private IDisposable? _timerSubscription;
public AvaloniaView(Context context) : base(context)
{
@ -35,9 +35,9 @@ namespace Avalonia.Android
}
internal TopLevelImpl TopLevelImpl => _view;
internal TopLevel TopLevel => _root;
internal TopLevel? TopLevel => _root;
public object Content
public object? Content
{
get { return _root.Content; }
set { _root.Content = value; }
@ -47,10 +47,10 @@ namespace Avalonia.Android
{
base.Dispose(disposing);
_root?.Dispose();
_root = null;
_root = null!;
}
public override bool DispatchKeyEvent(KeyEvent e)
public override bool DispatchKeyEvent(KeyEvent? e)
{
return _view.View.DispatchKeyEvent(e);
}
@ -91,7 +91,7 @@ namespace Avalonia.Android
}
}
protected override void OnConfigurationChanged(Configuration newConfig)
protected override void OnConfigurationChanged(Configuration? newConfig)
{
base.OnConfigurationChanged(newConfig);
OnConfigurationChanged();
@ -99,8 +99,12 @@ namespace Avalonia.Android
private void OnConfigurationChanged()
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(Context);
if (Context is { } context)
{
var settings =
AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(context);
}
}
class ViewImpl : TopLevelImpl
@ -111,17 +115,11 @@ namespace Avalonia.Android
View.FocusChange += ViewImpl_FocusChange;
}
private void ViewImpl_FocusChange(object sender, FocusChangeEventArgs e)
private void ViewImpl_FocusChange(object? sender, FocusChangeEventArgs e)
{
if(!e.HasFocus)
LostFocus?.Invoke();
}
protected override void OnResized(Size size)
{
MaxClientSize = size;
base.OnResized(size);
}
}
}
}

13
src/Android/Avalonia.Android/ChoreographerTimer.cs

@ -14,14 +14,14 @@ namespace Avalonia.Android
{
internal sealed class ChoreographerTimer : Java.Lang.Object, IRenderTimer, Choreographer.IFrameCallback
{
private readonly object _lock = new object();
private readonly object _lock = new();
private readonly Thread _thread;
private readonly TaskCompletionSource<Choreographer> _choreographer = new TaskCompletionSource<Choreographer>();
private readonly TaskCompletionSource<Choreographer> _choreographer = new();
private readonly ISet<AvaloniaView> _views = new HashSet<AvaloniaView>();
private Action<TimeSpan> _tick;
private Action<TimeSpan>? _tick;
private int _count;
public ChoreographerTimer()
@ -29,8 +29,7 @@ namespace Avalonia.Android
_thread = new Thread(Loop);
_thread.Start();
}
public bool RunsInBackground => true;
public event Action<TimeSpan> Tick
@ -84,7 +83,7 @@ namespace Avalonia.Android
private void Loop()
{
Looper.Prepare();
_choreographer.SetResult(Choreographer.Instance);
_choreographer.SetResult(Choreographer.Instance!);
Looper.Loop();
}
@ -96,7 +95,7 @@ namespace Avalonia.Android
{
if (_count > 0 && _views.Count > 0)
{
Choreographer.Instance.PostFrameCallback(this);
Choreographer.Instance!.PostFrameCallback(this);
}
}
}

11
src/Android/Avalonia.Android/IActivityResultHandler.cs

@ -3,12 +3,11 @@ using Android.App;
using Android.Content;
using Android.Content.PM;
namespace Avalonia.Android
namespace Avalonia.Android;
public interface IActivityResultHandler
{
public interface IActivityResultHandler
{
public Action<int, Result, Intent> ActivityResult { get; set; }
public Action<int, Result, Intent?>? ActivityResult { get; set; }
public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; }
}
public Action<int, string[], Permission[]>? RequestPermissionsResult { get; set; }
}

1
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@ -1,5 +1,4 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android
{

3
src/Android/Avalonia.Android/IAvaloniaActivity.cs

@ -1,5 +1,4 @@
#nullable enable
using System;
using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;

12
src/Android/Avalonia.Android/Platform/AndroidActivatableLifetime.cs

@ -6,9 +6,9 @@ namespace Avalonia.Android.Platform;
internal class AndroidActivatableLifetime : IActivatableLifetime
{
private IAvaloniaActivity _activity;
private IAvaloniaActivity? _activity;
public IAvaloniaActivity Activity
public IAvaloniaActivity? Activity
{
get => _activity;
set
@ -29,18 +29,18 @@ internal class AndroidActivatableLifetime : IActivatableLifetime
}
}
public event EventHandler<ActivatedEventArgs> Activated;
public event EventHandler<ActivatedEventArgs> Deactivated;
public event EventHandler<ActivatedEventArgs>? Activated;
public event EventHandler<ActivatedEventArgs>? Deactivated;
public bool TryLeaveBackground() => false;
public bool TryEnterBackground() => (_activity as Activity)?.MoveTaskToBack(true) == true;
private void ActivityOnDeactivated(object sender, ActivatedEventArgs e)
private void ActivityOnDeactivated(object? sender, ActivatedEventArgs e)
{
Deactivated?.Invoke(this, e);
}
private void ActivityOnActivated(object sender, ActivatedEventArgs e)
private void ActivityOnActivated(object? sender, ActivatedEventArgs e)
{
Activated?.Invoke(this, e);
}

14
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Android.App;
using Android.OS;
using Android.Views;
using Android.Views.Animations;
@ -15,7 +16,7 @@ namespace Avalonia.Android.Platform
{
internal sealed class AndroidInsetsManager : WindowInsetsAnimationCompat.Callback, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener, IInputPane
{
private readonly AvaloniaMainActivity _activity;
private readonly Activity _activity;
private readonly TopLevelImpl _topLevel;
private bool _displayEdgeToEdge;
private bool? _systemUiVisibility;
@ -28,8 +29,8 @@ namespace Avalonia.Android.Platform
private AndroidWindow Window => _activity.Window ?? throw new InvalidOperationException("Activity.Window must be set.");
public event EventHandler<SafeAreaChangedArgs> SafeAreaChanged;
public event EventHandler<InputPaneStateEventArgs> StateChanged;
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
public event EventHandler<InputPaneStateEventArgs>? StateChanged;
public InputPaneState State
{
@ -73,7 +74,7 @@ namespace Avalonia.Android.Platform
}
}
internal AndroidInsetsManager(AvaloniaMainActivity activity, TopLevelImpl topLevel) : base(DispatchModeStop)
internal AndroidInsetsManager(Activity activity, TopLevelImpl topLevel) : base(DispatchModeStop)
{
_activity = activity;
_topLevel = topLevel;
@ -155,7 +156,7 @@ namespace Avalonia.Android.Platform
Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding)));
}
private void NotifyStateChanged(InputPaneState newState, Rect? startRect, Rect endRect, TimeSpan animationDuration, IEasing easing)
private void NotifyStateChanged(InputPaneState newState, Rect? startRect, Rect endRect, TimeSpan animationDuration, IEasing? easing)
{
Dispatcher.UIThread.Send(_ => StateChanged?.Invoke(this, new InputPaneStateEventArgs(newState, startRect, endRect, animationDuration, easing)));
}
@ -300,7 +301,8 @@ namespace Avalonia.Android.Platform
var duration = TimeSpan.FromMilliseconds(animation.DurationMillis);
bool isOpening = State == InputPaneState.Open;
NotifyStateChanged(State, isOpening ? upperRect : lowerRect, isOpening ? lowerRect : upperRect, duration, new AnimationEasing(animation.Interpolator));
NotifyStateChanged(State, isOpening ? upperRect : lowerRect, isOpening ? lowerRect : upperRect, duration,
animation.Interpolator is { } interpolator ? new AnimationEasing(interpolator) : null);
}
}

4
src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using System;
using System.Diagnostics.CodeAnalysis;
using Android.Views;

32
src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs

@ -1,12 +1,7 @@
using System;
using Android;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Provider;
using Android.Views.Accessibility;
using AndroidX.Core.Content.Resources;
using Avalonia.Media;
using Avalonia.Platform;
using Color = Avalonia.Media.Color;
@ -60,16 +55,25 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
else if (OperatingSystem.IsAndroidVersionAtLeast(23))
{
// See https://developer.android.com/reference/android/R.attr
var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
var accent = array.GetColor(0, 0);
_latestValues = new PlatformColorValues
var array = context.Theme?.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
if (array is not null)
{
ThemeVariant = systemTheme,
ContrastPreference = IsHighContrast(context),
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B)
};
array.Recycle();
try
{
var accent = array.GetColor(0, 0);
_latestValues = new PlatformColorValues
{
ThemeVariant = systemTheme,
ContrastPreference = IsHighContrast(context),
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B)
};
}
finally
{
array.Recycle();
}
}
}
else
{

4
src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using System;
using Avalonia.Interactivity;
using Avalonia.Platform;

2
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Threading.Tasks;
using Android.Content;

1
src/Android/Avalonia.Android/Platform/PlatformSupport.cs

@ -2,7 +2,6 @@
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
namespace Avalonia.Android.Platform;

9
src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Platform.Surfaces;
using System;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
@ -12,8 +13,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_topLevel = topLevel;
}
public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface, _topLevel.RenderScaling);
public ILockedFramebuffer Lock() => new AndroidFramebuffer(
_topLevel.InternalView.Holder?.Surface ?? throw new InvalidOperationException("TopLevel.InternalView.Holder.Surface was not expected to be null."),
_topLevel.RenderScaling);
public IFramebufferRenderTarget CreateFramebufferRenderTarget() => new FuncFramebufferRenderTarget(Lock);
}
}

17
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@ -16,14 +16,19 @@ namespace Avalonia.Android
readonly object _lock = new object();
private readonly Handler _handler;
IntPtr IPlatformHandle.Handle =>
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, Holder.Surface.Handle);
IntPtr IPlatformHandle.Handle => Holder?.Surface?.Handle is { } handle ?
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, handle) :
default;
public InvalidationAwareSurfaceView(Context context) : base(context)
{
if (Holder is null)
throw new InvalidOperationException(
"SurfaceView.Holder was not expected to be null during InvalidationAwareSurfaceView initialization.");
Holder.AddCallback(this);
Holder.SetFormat(global::Android.Graphics.Format.Transparent);
_handler = new Handler(context.MainLooper);
_handler = new Handler(context.MainLooper!);
}
public override void Invalidate()
@ -34,7 +39,7 @@ namespace Avalonia.Android
return;
_handler.Post(() =>
{
if (Holder.Surface?.IsValid != true)
if (Holder?.Surface?.IsValid != true)
return;
try
{
@ -77,8 +82,8 @@ namespace Avalonia.Android
protected abstract void Draw();
public string HandleDescriptor => "SurfaceView";
public PixelSize Size => new PixelSize(Holder.SurfaceFrame.Width(), Holder.SurfaceFrame.Height());
public PixelSize Size => new(Holder?.SurfaceFrame?.Width() ?? 1, Holder?.SurfaceFrame?.Height() ?? 1);
public double Scaling => Resources.DisplayMetrics.Density;
public double Scaling => Resources?.DisplayMetrics?.Density ?? 1;
}
}

124
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -40,66 +40,61 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly AndroidMotionEventsHelper _pointerHelper;
private readonly AndroidInputMethod<ViewImpl> _textInputMethod;
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
private readonly IStorageProvider? _storageProvider;
private readonly AndroidSystemNavigationManagerImpl _systemNavigationManager;
private readonly AndroidInsetsManager _insetsManager;
private readonly AndroidInsetsManager? _insetsManager;
private readonly ClipboardImpl _clipboard;
private readonly AndroidLauncher _launcher;
private readonly AndroidLauncher? _launcher;
private ViewImpl _view;
private WindowTransparencyLevel _transparencyLevel;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
{
if (avaloniaView.Context is null)
{
throw new ArgumentException("AvaloniaView.Context must not be null");
}
_view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_pointerHelper = new AndroidMotionEventsHelper(this);
_gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this);
_clipboard = new ClipboardImpl(avaloniaView.Context?.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>());
_clipboard = new ClipboardImpl(avaloniaView.Context.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>());
RenderScaling = _view.Scaling;
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
if (avaloniaView.Context is AvaloniaMainActivity mainActivity)
if (avaloniaView.Context is Activity mainActivity)
{
_insetsManager = new AndroidInsetsManager(mainActivity, this);
_storageProvider = new AndroidStorageProvider(mainActivity);
_launcher = new AndroidLauncher(mainActivity);
}
_nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
_storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
_transparencyLevel = WindowTransparencyLevel.None;
_launcher = new AndroidLauncher((Activity)avaloniaView.Context);
_systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
Surfaces = new object[] { _gl, _framebuffer, Handle };
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
new Point(e.GetX(pointerIndex), e.GetY(pointerIndex)) / RenderScaling;
public IInputRoot InputRoot { get; private set; }
public IInputRoot? InputRoot { get; private set; }
public virtual Size ClientSize => _view.Size.ToSize(RenderScaling);
public Size? FrameSize => null;
public Action? Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public Action Closed { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Size MaxClientSize { get; protected set; }
public Action<RawInputEventArgs>? Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Rect>? Paint { get; set; }
public Action<Size, WindowResizeReason> Resized { get; set; }
public Action<Size, WindowResizeReason>? Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action<double>? ScalingChanged { get; set; }
public View View => _view;
@ -109,8 +104,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable<object> Surfaces { get; }
public Compositor Compositor => AndroidPlatform.Compositor;
public Compositor Compositor => AndroidPlatform.Compositor ??
throw new InvalidOperationException("Android backend wasn't initialized. Make sure .UseAndroid() was executed.");
public virtual void Hide()
{
_view.Visibility = ViewStates.Invisible;
@ -131,7 +127,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return PixelPoint.FromPoint(point, RenderScaling);
}
public void SetCursor(ICursorImpl cursor)
public void SetCursor(ICursorImpl? cursor)
{
//still not implemented
}
@ -157,7 +153,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
_systemNavigationManager.Dispose();
_view.Dispose();
_view = null;
_view = null!;
}
protected virtual void OnResized(Size size)
@ -205,37 +201,34 @@ namespace Avalonia.Android.Platform.SkiaPlatform
else
{
// Android 9 did this
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear!);
}
base.DispatchDraw(canvas);
}
protected override bool DispatchGenericPointerEvent(MotionEvent e)
protected override bool DispatchGenericPointerEvent(MotionEvent? e)
{
bool callBase;
bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false;
var result = _tl._pointerHelper.DispatchMotionEvent(e, out var callBase);
var baseResult = callBase && base.DispatchGenericPointerEvent(e);
return result != null ? result.Value : baseResult;
return result ?? baseResult;
}
public override bool DispatchTouchEvent(MotionEvent e)
public override bool DispatchTouchEvent(MotionEvent? e)
{
bool callBase;
bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
var result = _tl._pointerHelper.DispatchMotionEvent(e, out var callBase);
var baseResult = callBase && base.DispatchTouchEvent(e);
return result != null ? result.Value : baseResult;
return result ?? baseResult;
}
public override bool DispatchKeyEvent(KeyEvent e)
public override bool DispatchKeyEvent(KeyEvent? e)
{
bool callBase;
bool? res = _tl._keyboardHelper.DispatchKeyEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchKeyEvent(e) : false;
var res = _tl._keyboardHelper.DispatchKeyEvent(e, out var callBase);
var baseResult = callBase && base.DispatchKeyEvent(e);
return res != null ? res.Value : baseResult;
return res ?? baseResult;
}
void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
@ -256,29 +249,24 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return true;
}
private Func<TopLevelImpl, EditorInfo, IInputConnection> _initEditorInfo;
private Func<TopLevelImpl, EditorInfo, IInputConnection>? _initEditorInfo;
public void InitEditorInfo(Func<TopLevelImpl, EditorInfo, IInputConnection> init)
{
_initEditorInfo = init;
}
public sealed override IInputConnection OnCreateInputConnection(EditorInfo outAttrs)
public sealed override IInputConnection OnCreateInputConnection(EditorInfo? outAttrs)
{
if (_initEditorInfo != null)
{
return _initEditorInfo(_tl, outAttrs);
}
return null;
return _initEditorInfo?.Invoke(_tl, outAttrs!)!;
}
}
public IPopupImpl CreatePopup() => null;
public IPopupImpl? CreatePopup() => null;
public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action? LostFocus { get; set; }
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel TransparencyLevel
{
@ -374,7 +362,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.White));
}
public virtual object TryGetFeature(Type featureType)
public virtual object? TryGetFeature(Type featureType)
{
if (featureType == typeof(IStorageProvider))
{
@ -443,7 +431,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
if(Input != null)
{
var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, InputRoot, text);
var args = new RawTextInputEventArgs(AndroidKeyboardDevice.Instance!, (ulong)DateTime.Now.Ticks, InputRoot!, text);
Input(args);
}
@ -464,7 +452,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public bool IgnoreChange { get; set; }
public override IEditable Replace(int start, int end, ICharSequence tb)
public override IEditable? Replace(int start, int end, ICharSequence? tb)
{
if (!IgnoreChange && start != end)
{
@ -474,7 +462,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.Replace(start, end, tb);
}
public override IEditable Replace(int start, int end, ICharSequence tb, int tbstart, int tbend)
public override IEditable? Replace(int start, int end, ICharSequence? tb, int tbstart, int tbend)
{
if (!IgnoreChange && start != end)
{
@ -486,7 +474,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private void SelectSurroundingTextForDeletion(int start, int end)
{
_inputConnection.InputMethod.Client.Selection = new TextSelection(start, end);
_inputConnection.InputMethod.Client!.Selection = new TextSelection(start, end);
}
}
@ -522,8 +510,13 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.SetComposingRegion(start, end);
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
public override bool SetComposingText(ICharSequence? text, int newCursorPosition)
{
if (InputMethod.Client is null || text is null)
{
return false;
}
BeginBatchEdit();
_editable.IgnoreChange = true;
@ -570,8 +563,13 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.EndBatchEdit();
}
public override bool CommitText(ICharSequence text, int newCursorPosition)
public override bool CommitText(ICharSequence? text, int newCursorPosition)
{
if (InputMethod.Client is null || text is null)
{
return false;
}
BeginBatchEdit();
_commitInProgress = true;
@ -639,7 +637,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.PerformEditorAction(actionCode);
}
public override ExtractedText GetExtractedText(ExtractedTextRequest request, [GeneratedEnum] GetTextFlags flags)
public override ExtractedText? GetExtractedText(ExtractedTextRequest? request, [GeneratedEnum] GetTextFlags flags)
{
if (request == null)
return null;

4
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyInterop.cs

@ -1,6 +1,4 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Generic;
using Avalonia.Input;
namespace Avalonia.Android.Platform.Specific.Helpers;

14
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Runtime.Versioning;
using Android.Views;
@ -22,9 +20,9 @@ namespace Avalonia.Android.Platform.Specific.Helpers
HandleEvents = true;
}
public bool? DispatchKeyEvent(KeyEvent e, out bool callBase)
public bool? DispatchKeyEvent(KeyEvent? e, out bool callBase)
{
if (!HandleEvents)
if (!HandleEvents || e is null)
{
callBase = true;
return null;
@ -46,8 +44,10 @@ namespace Avalonia.Android.Platform.Specific.Helpers
private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase)
{
var unicodeTextInput = OperatingSystem.IsAndroidVersionAtLeast(29) ? null : UnicodeTextInput(e);
var inputRoot = _view.InputRoot;
if (e.Action == KeyEventActions.Multiple && unicodeTextInput == null)
if ((e.Action == KeyEventActions.Multiple && unicodeTextInput == null)
|| inputRoot is null)
{
callBase = true;
return null;
@ -60,7 +60,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
inputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e),
@ -76,7 +76,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
inputRoot,
unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
);
_view.Input?.Invoke(rawTextEvent);

15
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -8,8 +8,6 @@ using Avalonia.Collections.Pooled;
using Avalonia.Input;
using Avalonia.Input.Raw;
#nullable enable
namespace Avalonia.Android.Platform.Specific.Helpers
{
internal class AndroidMotionEventsHelper : IDisposable
@ -30,16 +28,19 @@ namespace Avalonia.Android.Platform.Specific.Helpers
_view = view;
}
public bool? DispatchMotionEvent(MotionEvent e, out bool callBase)
public bool? DispatchMotionEvent(MotionEvent? e, out bool callBase)
{
callBase = true;
if (_disposed)
if (_disposed || e is null)
{
return null;
}
var eventTime = (ulong)e.EventTime;
var inputRoot = _view.InputRoot;
if (inputRoot is null)
return false; // too early to handle events.
var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState);
@ -75,7 +76,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return s_intermediatePointsPooledList;
})
};
_view.Input(args);
_view.Input?.Invoke(args);
}
}
else
@ -90,7 +91,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{
var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll));
var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None);
_view.Input(args);
_view.Input?.Invoke(args);
}
else
{
@ -98,7 +99,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
if (eventType >= 0)
{
var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index));
_view.Input(args);
_view.Input?.Invoke(args);
}
}
}

7
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -8,7 +6,6 @@ using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Webkit;
using AndroidX.DocumentFile.Provider;
@ -136,7 +133,7 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return true;
}
return await _activity.CheckPermission(Manifest.Permission.ReadExternalStorage);
return await _activity!.CheckPermission(Manifest.Permission.ReadExternalStorage);
}
public void Dispose()

7
src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs

@ -1,6 +1,4 @@
#nullable enable
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -9,7 +7,6 @@ using Android.App;
using Android.Content;
using Android.Provider;
using Avalonia.Platform.Storage;
using Java.Lang;
using AndroidUri = Android.Net.Uri;
using Exception = System.Exception;
using JavaFile = Java.IO.File;
@ -244,7 +241,7 @@ internal class AndroidStorageProvider : IStorageProvider
return resultList;
void OnActivityResult(int requestCode, Result resultCode, Intent data)
void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{
if (currentRequestCode != requestCode)
{

3
src/Android/Avalonia.Android/SingleViewLifetime.cs

@ -1,5 +1,4 @@
#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;

2
src/Android/Avalonia.Android/Stubs.cs

@ -10,7 +10,7 @@ namespace Avalonia.Android
public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
public ITrayIconImpl CreateTrayIcon() => null;
public ITrayIconImpl? CreateTrayIcon() => null;
}
internal class PlatformIconLoaderStub : IPlatformIconLoader

1
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -108,7 +108,6 @@ namespace Avalonia.Headless
public Action? Deactivated { get; set; }
public Action? Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize, WindowResizeReason reason)
{
if (ClientSize == clientSize)

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -50,8 +50,6 @@ namespace Avalonia.UnitTests
public Size ClientSize { get; set; } = new Size(1000, 1000);
public Size MaxClientSize { get; set; } = Size.Infinity;
public double LayoutScaling { get; set; } = 1;
internal ILayoutManager LayoutManager { get; set; }

Loading…
Cancel
Save