Browse Source

Enable nullable on Avalonia.Android project (#15331)

* Enable nullable on Avalonia.Android project

* More specific exception
pull/15333/head
Max Katz 3 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;
using System.Diagnostics.CodeAnalysis;
using Android.Content; using Android.Content;
using Android.Runtime; using Android.Runtime;
using Android.Text; using Android.Text;
@ -13,8 +14,9 @@ namespace Avalonia.Android
{ {
public View View { get; } public View View { get; }
public TextInputMethodClient Client { get; } public TextInputMethodClient? Client { get; }
[MemberNotNullWhen(true, nameof(Client))]
public bool IsActive { get; } public bool IsActive { get; }
public InputMethodManager IMM { get; } public InputMethodManager IMM { get; }
@ -38,8 +40,8 @@ namespace Avalonia.Android
{ {
private readonly TView _host; private readonly TView _host;
private readonly InputMethodManager _imm; private readonly InputMethodManager _imm;
private TextInputMethodClient _client; private TextInputMethodClient? _client;
private AvaloniaInputConnection _inputConnection; private AvaloniaInputConnection? _inputConnection;
public AndroidInputMethod(TView host) public AndroidInputMethod(TView host)
{ {
@ -47,7 +49,8 @@ namespace Avalonia.Android
throw new InvalidOperationException("Host should return true from OnCheckIsTextEditor()"); throw new InvalidOperationException("Host should return true from OnCheckIsTextEditor()");
_host = host; _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.Focusable = true;
_host.FocusableInTouchMode = true; _host.FocusableInTouchMode = true;
@ -55,9 +58,11 @@ namespace Avalonia.Android
public View View => _host; public View View => _host;
[MemberNotNullWhen(true, nameof(Client))]
[MemberNotNullWhen(true, nameof(_client))]
public bool IsActive => Client != null; public bool IsActive => Client != null;
public TextInputMethodClient Client => _client; public TextInputMethodClient? Client => _client;
public InputMethodManager IMM => _imm; public InputMethodManager IMM => _imm;
@ -66,7 +71,7 @@ namespace Avalonia.Android
} }
public void SetClient(TextInputMethodClient client) public void SetClient(TextInputMethodClient? client)
{ {
_client = 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; return;
OnSelectionChanged(); OnSelectionChanged();
} }
@ -121,19 +126,19 @@ namespace Avalonia.Android
_imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End); _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; return;
OnSurroundingTextChanged(); OnSurroundingTextChanged();
} }
public void OnBatchEditedEnded() public void OnBatchEditedEnded()
{ {
if (_inputConnection.IsInBatchEdit) if (_inputConnection is null || _inputConnection.IsInBatchEdit)
return; return;
OnSurroundingTextChanged(); OnSurroundingTextChanged();
@ -142,7 +147,7 @@ namespace Avalonia.Android
private void OnSurroundingTextChanged() private void OnSurroundingTextChanged()
{ {
if(_client is null) if(_client is null || _inputConnection is null)
{ {
return; return;
} }
@ -199,7 +204,7 @@ namespace Avalonia.Android
_host.InitEditorInfo((topLevel, outAttrs) => _host.InitEditorInfo((topLevel, outAttrs) =>
{ {
if (_client == null) if (_client == null)
return null; return null!;
_inputConnection = new AvaloniaInputConnection(topLevel, this); _inputConnection = new AvaloniaInputConnection(topLevel, this);

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

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls;
using Avalonia.Android; using Avalonia.Android;
using Avalonia.Android.Platform; using Avalonia.Android.Platform;
using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Input;
@ -12,7 +11,6 @@ using Avalonia.OpenGL.Egl;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.OpenGL;
namespace Avalonia namespace Avalonia
{ {
@ -66,9 +64,9 @@ namespace Avalonia.Android
class AndroidPlatform class AndroidPlatform
{ {
public static readonly AndroidPlatform Instance = new 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() public static void Initialize()
{ {
@ -95,7 +93,7 @@ namespace Avalonia.Android
AvaloniaLocator.CurrentMutable.Bind<Compositor>().ToConstant(Compositor); 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()) 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.PM;
using Android.Content; using Android.Content;
using Avalonia.Platform; using Avalonia.Platform;

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

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

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

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

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

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

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

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

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

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

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

@ -20,7 +20,7 @@ namespace Avalonia.Android
private EmbeddableControlRoot _root; private EmbeddableControlRoot _root;
private readonly ViewImpl _view; private readonly ViewImpl _view;
private IDisposable _timerSubscription; private IDisposable? _timerSubscription;
public AvaloniaView(Context context) : base(context) public AvaloniaView(Context context) : base(context)
{ {
@ -35,9 +35,9 @@ namespace Avalonia.Android
} }
internal TopLevelImpl TopLevelImpl => _view; internal TopLevelImpl TopLevelImpl => _view;
internal TopLevel TopLevel => _root; internal TopLevel? TopLevel => _root;
public object Content public object? Content
{ {
get { return _root.Content; } get { return _root.Content; }
set { _root.Content = value; } set { _root.Content = value; }
@ -47,10 +47,10 @@ namespace Avalonia.Android
{ {
base.Dispose(disposing); base.Dispose(disposing);
_root?.Dispose(); _root?.Dispose();
_root = null; _root = null!;
} }
public override bool DispatchKeyEvent(KeyEvent e) public override bool DispatchKeyEvent(KeyEvent? e)
{ {
return _view.View.DispatchKeyEvent(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); base.OnConfigurationChanged(newConfig);
OnConfigurationChanged(); OnConfigurationChanged();
@ -99,8 +99,12 @@ namespace Avalonia.Android
private void OnConfigurationChanged() private void OnConfigurationChanged()
{ {
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings; if (Context is { } context)
settings?.OnViewConfigurationChanged(Context); {
var settings =
AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
settings?.OnViewConfigurationChanged(context);
}
} }
class ViewImpl : TopLevelImpl class ViewImpl : TopLevelImpl
@ -111,17 +115,11 @@ namespace Avalonia.Android
View.FocusChange += ViewImpl_FocusChange; View.FocusChange += ViewImpl_FocusChange;
} }
private void ViewImpl_FocusChange(object sender, FocusChangeEventArgs e) private void ViewImpl_FocusChange(object? sender, FocusChangeEventArgs e)
{ {
if(!e.HasFocus) if(!e.HasFocus)
LostFocus?.Invoke(); 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 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 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 readonly ISet<AvaloniaView> _views = new HashSet<AvaloniaView>();
private Action<TimeSpan> _tick; private Action<TimeSpan>? _tick;
private int _count; private int _count;
public ChoreographerTimer() public ChoreographerTimer()
@ -29,8 +29,7 @@ namespace Avalonia.Android
_thread = new Thread(Loop); _thread = new Thread(Loop);
_thread.Start(); _thread.Start();
} }
public bool RunsInBackground => true; public bool RunsInBackground => true;
public event Action<TimeSpan> Tick public event Action<TimeSpan> Tick
@ -84,7 +83,7 @@ namespace Avalonia.Android
private void Loop() private void Loop()
{ {
Looper.Prepare(); Looper.Prepare();
_choreographer.SetResult(Choreographer.Instance); _choreographer.SetResult(Choreographer.Instance!);
Looper.Loop(); Looper.Loop();
} }
@ -96,7 +95,7 @@ namespace Avalonia.Android
{ {
if (_count > 0 && _views.Count > 0) 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;
using Android.Content.PM; 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 System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android namespace Avalonia.Android
{ {

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

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

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

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

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

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.App;
using Android.OS; using Android.OS;
using Android.Views; using Android.Views;
using Android.Views.Animations; using Android.Views.Animations;
@ -15,7 +16,7 @@ namespace Avalonia.Android.Platform
{ {
internal sealed class AndroidInsetsManager : WindowInsetsAnimationCompat.Callback, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener, IInputPane internal sealed class AndroidInsetsManager : WindowInsetsAnimationCompat.Callback, IInsetsManager, IOnApplyWindowInsetsListener, ViewTreeObserver.IOnGlobalLayoutListener, IInputPane
{ {
private readonly AvaloniaMainActivity _activity; private readonly Activity _activity;
private readonly TopLevelImpl _topLevel; private readonly TopLevelImpl _topLevel;
private bool _displayEdgeToEdge; private bool _displayEdgeToEdge;
private bool? _systemUiVisibility; private bool? _systemUiVisibility;
@ -28,8 +29,8 @@ namespace Avalonia.Android.Platform
private AndroidWindow Window => _activity.Window ?? throw new InvalidOperationException("Activity.Window must be set."); private AndroidWindow Window => _activity.Window ?? throw new InvalidOperationException("Activity.Window must be set.");
public event EventHandler<SafeAreaChangedArgs> SafeAreaChanged; public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
public event EventHandler<InputPaneStateEventArgs> StateChanged; public event EventHandler<InputPaneStateEventArgs>? StateChanged;
public InputPaneState State 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; _activity = activity;
_topLevel = topLevel; _topLevel = topLevel;
@ -155,7 +156,7 @@ namespace Avalonia.Android.Platform
Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding))); 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))); 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); var duration = TimeSpan.FromMilliseconds(animation.DurationMillis);
bool isOpening = State == InputPaneState.Open; 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 System.Diagnostics.CodeAnalysis;
using Android.Views; using Android.Views;

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

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

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

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

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

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

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

@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content;
using Android.Content.PM; using Android.Content.PM;
namespace Avalonia.Android.Platform; 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; using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
@ -12,8 +13,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_topLevel = topLevel; _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); 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(); readonly object _lock = new object();
private readonly Handler _handler; private readonly Handler _handler;
IntPtr IPlatformHandle.Handle => IntPtr IPlatformHandle.Handle => Holder?.Surface?.Handle is { } handle ?
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, Holder.Surface.Handle); AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, handle) :
default;
public InvalidationAwareSurfaceView(Context context) : base(context) 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.AddCallback(this);
Holder.SetFormat(global::Android.Graphics.Format.Transparent); Holder.SetFormat(global::Android.Graphics.Format.Transparent);
_handler = new Handler(context.MainLooper); _handler = new Handler(context.MainLooper!);
} }
public override void Invalidate() public override void Invalidate()
@ -34,7 +39,7 @@ namespace Avalonia.Android
return; return;
_handler.Post(() => _handler.Post(() =>
{ {
if (Holder.Surface?.IsValid != true) if (Holder?.Surface?.IsValid != true)
return; return;
try try
{ {
@ -77,8 +82,8 @@ namespace Avalonia.Android
protected abstract void Draw(); protected abstract void Draw();
public string HandleDescriptor => "SurfaceView"; 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 AndroidMotionEventsHelper _pointerHelper;
private readonly AndroidInputMethod<ViewImpl> _textInputMethod; private readonly AndroidInputMethod<ViewImpl> _textInputMethod;
private readonly INativeControlHostImpl _nativeControlHost; private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider? _storageProvider;
private readonly AndroidSystemNavigationManagerImpl _systemNavigationManager; private readonly AndroidSystemNavigationManagerImpl _systemNavigationManager;
private readonly AndroidInsetsManager _insetsManager; private readonly AndroidInsetsManager? _insetsManager;
private readonly ClipboardImpl _clipboard; private readonly ClipboardImpl _clipboard;
private readonly AndroidLauncher _launcher; private readonly AndroidLauncher? _launcher;
private ViewImpl _view; private ViewImpl _view;
private WindowTransparencyLevel _transparencyLevel; private WindowTransparencyLevel _transparencyLevel;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) 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); _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view); _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_pointerHelper = new AndroidMotionEventsHelper(this); _pointerHelper = new AndroidMotionEventsHelper(this);
_gl = new EglGlPlatformSurface(this); _gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(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; RenderScaling = _view.Scaling;
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels, if (avaloniaView.Context is Activity mainActivity)
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
if (avaloniaView.Context is AvaloniaMainActivity mainActivity)
{ {
_insetsManager = new AndroidInsetsManager(mainActivity, this); _insetsManager = new AndroidInsetsManager(mainActivity, this);
_storageProvider = new AndroidStorageProvider(mainActivity);
_launcher = new AndroidLauncher(mainActivity);
} }
_nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
_storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
_transparencyLevel = WindowTransparencyLevel.None; _transparencyLevel = WindowTransparencyLevel.None;
_launcher = new AndroidLauncher((Activity)avaloniaView.Context);
_systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService); _systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
Surfaces = new object[] { _gl, _framebuffer, Handle }; Surfaces = new object[] { _gl, _framebuffer, Handle };
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public IInputRoot? InputRoot { get; private set; }
new Point(e.GetX(pointerIndex), e.GetY(pointerIndex)) / RenderScaling;
public IInputRoot InputRoot { get; private set; }
public virtual Size ClientSize => _view.Size.ToSize(RenderScaling); public virtual Size ClientSize => _view.Size.ToSize(RenderScaling);
public Size? FrameSize => null; public Size? FrameSize => null;
public Action? Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice(); public Action<RawInputEventArgs>? Input { get; set; }
public Action Closed { get; set; }
public Action<RawInputEventArgs> Input { get; set; }
public Size MaxClientSize { get; protected 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; public View View => _view;
@ -109,8 +104,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable<object> Surfaces { get; } 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() public virtual void Hide()
{ {
_view.Visibility = ViewStates.Invisible; _view.Visibility = ViewStates.Invisible;
@ -131,7 +127,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return PixelPoint.FromPoint(point, RenderScaling); return PixelPoint.FromPoint(point, RenderScaling);
} }
public void SetCursor(ICursorImpl cursor) public void SetCursor(ICursorImpl? cursor)
{ {
//still not implemented //still not implemented
} }
@ -157,7 +153,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{ {
_systemNavigationManager.Dispose(); _systemNavigationManager.Dispose();
_view.Dispose(); _view.Dispose();
_view = null; _view = null!;
} }
protected virtual void OnResized(Size size) protected virtual void OnResized(Size size)
@ -205,37 +201,34 @@ namespace Avalonia.Android.Platform.SkiaPlatform
else else
{ {
// Android 9 did this // Android 9 did this
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear); canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear!);
} }
base.DispatchDraw(canvas); base.DispatchDraw(canvas);
} }
protected override bool DispatchGenericPointerEvent(MotionEvent e) protected override bool DispatchGenericPointerEvent(MotionEvent? e)
{ {
bool callBase; var result = _tl._pointerHelper.DispatchMotionEvent(e, out var callBase);
bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase); var baseResult = callBase && base.DispatchGenericPointerEvent(e);
bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false;
return result != null ? result.Value : baseResult; return result ?? baseResult;
} }
public override bool DispatchTouchEvent(MotionEvent e) public override bool DispatchTouchEvent(MotionEvent? e)
{ {
bool callBase; var result = _tl._pointerHelper.DispatchMotionEvent(e, out var callBase);
bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase); var baseResult = callBase && base.DispatchTouchEvent(e);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
return result != null ? result.Value : baseResult; return result ?? baseResult;
} }
public override bool DispatchKeyEvent(KeyEvent e) public override bool DispatchKeyEvent(KeyEvent? e)
{ {
bool callBase; var res = _tl._keyboardHelper.DispatchKeyEvent(e, out var callBase);
bool? res = _tl._keyboardHelper.DispatchKeyEvent(e, out callBase); var baseResult = callBase && base.DispatchKeyEvent(e);
bool baseResult = callBase ? base.DispatchKeyEvent(e) : false;
return res != null ? res.Value : baseResult; return res ?? baseResult;
} }
void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height) void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
@ -256,29 +249,24 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return true; return true;
} }
private Func<TopLevelImpl, EditorInfo, IInputConnection> _initEditorInfo; private Func<TopLevelImpl, EditorInfo, IInputConnection>? _initEditorInfo;
public void InitEditorInfo(Func<TopLevelImpl, EditorInfo, IInputConnection> init) public void InitEditorInfo(Func<TopLevelImpl, EditorInfo, IInputConnection> init)
{ {
_initEditorInfo = init; _initEditorInfo = init;
} }
public sealed override IInputConnection OnCreateInputConnection(EditorInfo outAttrs) public sealed override IInputConnection OnCreateInputConnection(EditorInfo? outAttrs)
{ {
if (_initEditorInfo != null) return _initEditorInfo?.Invoke(_tl, outAttrs!)!;
{
return _initEditorInfo(_tl, outAttrs);
}
return null;
} }
} }
public IPopupImpl CreatePopup() => null; public IPopupImpl? CreatePopup() => null;
public Action LostFocus { get; set; } public Action? LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; } public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
public WindowTransparencyLevel TransparencyLevel public WindowTransparencyLevel TransparencyLevel
{ {
@ -374,7 +362,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.White)); activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.White));
} }
public virtual object TryGetFeature(Type featureType) public virtual object? TryGetFeature(Type featureType)
{ {
if (featureType == typeof(IStorageProvider)) if (featureType == typeof(IStorageProvider))
{ {
@ -443,7 +431,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{ {
if(Input != null) 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); Input(args);
} }
@ -464,7 +452,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public bool IgnoreChange { get; set; } 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) if (!IgnoreChange && start != end)
{ {
@ -474,7 +462,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.Replace(start, end, tb); 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) if (!IgnoreChange && start != end)
{ {
@ -486,7 +474,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private void SelectSurroundingTextForDeletion(int start, int end) 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); 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(); BeginBatchEdit();
_editable.IgnoreChange = true; _editable.IgnoreChange = true;
@ -570,8 +563,13 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.EndBatchEdit(); 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(); BeginBatchEdit();
_commitInProgress = true; _commitInProgress = true;
@ -639,7 +637,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return base.PerformEditorAction(actionCode); 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) if (request == null)
return 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; using Avalonia.Input;
namespace Avalonia.Android.Platform.Specific.Helpers; 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;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Android.Views; using Android.Views;
@ -22,9 +20,9 @@ namespace Avalonia.Android.Platform.Specific.Helpers
HandleEvents = true; 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; callBase = true;
return null; return null;
@ -46,8 +44,10 @@ namespace Avalonia.Android.Platform.Specific.Helpers
private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase) private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase)
{ {
var unicodeTextInput = OperatingSystem.IsAndroidVersionAtLeast(29) ? null : UnicodeTextInput(e); 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; callBase = true;
return null; return null;
@ -60,7 +60,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawKeyEvent = new RawKeyEventArgs( var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance!, AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime), Convert.ToUInt64(e.EventTime),
_view.InputRoot, inputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
AndroidKeyboardDevice.ConvertKey(e.KeyCode), AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e), GetModifierKeys(e),
@ -76,7 +76,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawTextEvent = new RawTextInputEventArgs( var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance!, AndroidKeyboardDevice.Instance!,
Convert.ToUInt64(e.EventTime), Convert.ToUInt64(e.EventTime),
_view.InputRoot, inputRoot,
unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString() unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
); );
_view.Input?.Invoke(rawTextEvent); _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;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
#nullable enable
namespace Avalonia.Android.Platform.Specific.Helpers namespace Avalonia.Android.Platform.Specific.Helpers
{ {
internal class AndroidMotionEventsHelper : IDisposable internal class AndroidMotionEventsHelper : IDisposable
@ -30,16 +28,19 @@ namespace Avalonia.Android.Platform.Specific.Helpers
_view = view; _view = view;
} }
public bool? DispatchMotionEvent(MotionEvent e, out bool callBase) public bool? DispatchMotionEvent(MotionEvent? e, out bool callBase)
{ {
callBase = true; callBase = true;
if (_disposed) if (_disposed || e is null)
{ {
return null; return null;
} }
var eventTime = (ulong)e.EventTime; var eventTime = (ulong)e.EventTime;
var inputRoot = _view.InputRoot; var inputRoot = _view.InputRoot;
if (inputRoot is null)
return false; // too early to handle events.
var actionMasked = e.ActionMasked; var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState); var modifiers = GetModifiers(e.MetaState, e.ButtonState);
@ -75,7 +76,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return s_intermediatePointsPooledList; return s_intermediatePointsPooledList;
}) })
}; };
_view.Input(args); _view.Input?.Invoke(args);
} }
} }
else else
@ -90,7 +91,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{ {
var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll)); var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll));
var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None); var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None);
_view.Input(args); _view.Input?.Invoke(args);
} }
else else
{ {
@ -98,7 +99,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
if (eventType >= 0) if (eventType >= 0)
{ {
var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index)); 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -8,7 +6,6 @@ using System.Threading.Tasks;
using Android; using Android;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS;
using Android.Provider; using Android.Provider;
using Android.Webkit; using Android.Webkit;
using AndroidX.DocumentFile.Provider; using AndroidX.DocumentFile.Provider;
@ -136,7 +133,7 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
return true; return true;
} }
return await _activity.CheckPermission(Manifest.Permission.ReadExternalStorage); return await _activity!.CheckPermission(Manifest.Permission.ReadExternalStorage);
} }
public void Dispose() 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,7 +7,6 @@ using Android.App;
using Android.Content; using Android.Content;
using Android.Provider; using Android.Provider;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Java.Lang;
using AndroidUri = Android.Net.Uri; using AndroidUri = Android.Net.Uri;
using Exception = System.Exception; using Exception = System.Exception;
using JavaFile = Java.IO.File; using JavaFile = Java.IO.File;
@ -244,7 +241,7 @@ internal class AndroidStorageProvider : IStorageProvider
return resultList; return resultList;
void OnActivityResult(int requestCode, Result resultCode, Intent data) void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{ {
if (currentRequestCode != requestCode) 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;
using Avalonia.Controls.ApplicationLifetimes; 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 IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException();
public ITrayIconImpl CreateTrayIcon() => null; public ITrayIconImpl? CreateTrayIcon() => null;
} }
internal class PlatformIconLoaderStub : IPlatformIconLoader 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? Deactivated { get; set; }
public Action? Activated { get; set; } public Action? Activated { get; set; }
public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB"); public IPlatformHandle Handle { get; } = new PlatformHandle(IntPtr.Zero, "STUB");
public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize, WindowResizeReason reason) public void Resize(Size clientSize, WindowResizeReason reason)
{ {
if (ClientSize == clientSize) 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 ClientSize { get; set; } = new Size(1000, 1000);
public Size MaxClientSize { get; set; } = Size.Infinity;
public double LayoutScaling { get; set; } = 1; public double LayoutScaling { get; set; } = 1;
internal ILayoutManager LayoutManager { get; set; } internal ILayoutManager LayoutManager { get; set; }

Loading…
Cancel
Save