Browse Source

add workarounds for android native aot

android_naot
Emmanuel Hansen 8 months ago
parent
commit
c304f8ccb2
  1. 4
      src/Android/Avalonia.Android/AndroidDispatcherImpl.cs
  2. 4
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  3. 9
      src/Android/Avalonia.Android/AvaloniaAccessHelper.cs
  4. 2
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  5. 30
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  6. 5
      src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs
  7. 7
      src/Android/Avalonia.Android/Platform/AndroidScreens.cs
  8. 3
      src/Android/Avalonia.Android/Platform/PlatformSupport.cs
  9. 17
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  10. 5
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  11. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  12. 5
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
  13. 9
      src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
  14. 5
      src/Avalonia.Base/Compatibility/OperatingSystem.cs

4
src/Android/Avalonia.Android/AndroidDispatcherImpl.cs

@ -37,7 +37,7 @@ namespace Avalonia.Android
_wakeupSignaler = new Runnable(() => { });
_queue = Looper.MyQueue();
Looper.MyQueue().AddIdleHandler(new IdleHandler(this));
CanQueryPendingInput = OperatingSystem.IsAndroidVersionAtLeast(23);
CanQueryPendingInput = Build.VERSION.SdkInt >= BuildVersionCodes.M;
}
public event Action? Timer;
@ -57,7 +57,7 @@ namespace Avalonia.Android
{
if (s_isUIThread.HasValue)
return s_isUIThread.Value;
var uiThread = OperatingSystem.IsAndroidVersionAtLeast(23)
var uiThread = Build.VERSION.SdkInt >= BuildVersionCodes.M
? _mainLooper.IsCurrentThread
: _mainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());

4
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -7,8 +7,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.7.0.5" />
<PackageReference Include="Xamarin.AndroidX.Window" Version="1.3.0.5" />
<PackageReference Include="Xamarin.AndroidX.Window" Version="1.3.0.7" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.7.0.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />

9
src/Android/Avalonia.Android/AvaloniaAccessHelper.cs

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Android.Content.PM;
using Android.OS;
using AndroidX.Core.View.Accessibility;
using AndroidX.CustomView.Widget;
@ -13,6 +13,7 @@ using Java.Lang;
namespace Avalonia.Android
{
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
internal class AvaloniaAccessHelper : ExploreByTouchHelper
{
private const string AUTOMATION_PROVIDER_NAMESPACE = "Avalonia.Automation.Provider";
@ -96,9 +97,11 @@ namespace Avalonia.Android
}
};
Type peerType = peer.GetType();
IEnumerable<Type> providerTypes = peerType.GetInterfaces()
#pragma warning disable IL2075
IEnumerable<Type> providerTypes = peer.GetType().GetInterfaces()
.Where(x => x.Namespace!.StartsWith(AUTOMATION_PROVIDER_NAMESPACE));
#pragma warning restore IL2075
foreach (Type providerType in providerTypes)
{
if (s_providerTypeInitializers.TryGetValue(providerType.FullName!, out NodeInfoProviderInitializer? ctor))

2
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -131,7 +131,7 @@ public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
base.OnResume();
// Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
if (Build.VERSION.SdkInt >= (BuildVersionCodes)28 && Window is { Attributes: { } attributes })
{
attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
}

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

@ -73,7 +73,7 @@ namespace Avalonia.Android.Platform
_displaysEdgeToEdge = _displayEdgeToEdgePreference;
if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window.Attributes is { } attributes)
if (Build.VERSION.SdkInt >= BuildVersionCodes.P && Window.Attributes is { } attributes)
{
attributes.LayoutInDisplayCutoutMode = _displayEdgeToEdgePreference ? LayoutInDisplayCutoutMode.ShortEdges : LayoutInDisplayCutoutMode.Default;
}
@ -127,7 +127,7 @@ namespace Avalonia.Android.Platform
WindowInsetsCompat.Type.StatusBars() | WindowInsetsCompat.Type.NavigationBars() |
WindowInsetsCompat.Type.DisplayCutout() : 0);
return new Thickness(inset.Left / renderScaling,
return new Thickness(inset!.Left / renderScaling,
inset.Top / renderScaling,
inset.Right / renderScaling,
inset.Bottom / renderScaling);
@ -145,9 +145,9 @@ namespace Avalonia.Android.Platform
if (insets != null)
{
var navbarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars()).Bottom;
var navbarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars())?.Bottom ?? 0;
var height = Math.Max((float)((insets.GetInsets(WindowInsetsCompat.Type.Ime()).Bottom - navbarInset) / _topLevel.RenderScaling), 0);
var height = Math.Max((float)((insets.GetInsets(WindowInsetsCompat.Type.Ime())!.Bottom - navbarInset) / _topLevel.RenderScaling), 0);
return new Rect(0, _topLevel.ClientSize.Height - SafeAreaPadding.Bottom - height, _topLevel.ClientSize.Width, height);
}
@ -156,7 +156,7 @@ namespace Avalonia.Android.Platform
}
}
public WindowInsetsCompat OnApplyWindowInsets(View v, WindowInsetsCompat insets)
public WindowInsetsCompat? OnApplyWindowInsets(View? v, WindowInsetsCompat? insets)
{
insets = ViewCompat.OnApplyWindowInsets(v, insets);
NotifySafeAreaChanged(SafeAreaPadding);
@ -166,15 +166,15 @@ namespace Avalonia.Android.Platform
_previousRect = OccludedRect;
}
State = insets.IsVisible(WindowInsetsCompat.Type.Ime()) ? InputPaneState.Open : InputPaneState.Closed;
State = insets?.IsVisible(WindowInsetsCompat.Type.Ime()) == true ? InputPaneState.Open : InputPaneState.Closed;
// Workaround for weird inset values for android 11
if(Build.VERSION.SdkInt == BuildVersionCodes.R)
{
var imeInset = insets.GetInsets(WindowInsetsCompat.Type.Ime());
var imeInset = insets!.GetInsets(WindowInsetsCompat.Type.Ime());
if(_previousImeInset == default)
_previousImeInset = imeInset;
if(imeInset.Bottom != _previousImeInset.Bottom)
if(imeInset!.Bottom != _previousImeInset!.Bottom)
{
NotifyStateChanged(State, _previousRect, OccludedRect, TimeSpan.Zero, null);
}
@ -285,7 +285,7 @@ namespace Avalonia.Android.Platform
if (_isDisplayEdgeToEdgeForced)
{
// Allow having fully transparent navbars when on api level 35
if (OperatingSystem.IsAndroidVersionAtLeast(35))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)35)
Window.NavigationBarContrastEnforced = _systemBarColor != Colors.Transparent;
return;
}
@ -319,18 +319,18 @@ namespace Avalonia.Android.Platform
SystemBarColor = _systemBarColor;
}
public override WindowInsetsAnimationCompat.BoundsCompat OnStart(WindowInsetsAnimationCompat animation, WindowInsetsAnimationCompat.BoundsCompat bounds)
public override WindowInsetsAnimationCompat.BoundsCompat? OnStart(WindowInsetsAnimationCompat? animation, WindowInsetsAnimationCompat.BoundsCompat? bounds)
{
if ((animation.TypeMask & WindowInsetsCompat.Type.Ime()) != 0)
if ((animation!.TypeMask & WindowInsetsCompat.Type.Ime()) != 0)
{
var insets = ViewCompat.GetRootWindowInsets(Window.DecorView);
if (insets != null)
{
var navbarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars()).Bottom;
var height = Math.Max(0, (float)((bounds.LowerBound.Bottom - navbarInset) / _topLevel.RenderScaling));
var navbarInset = insets.GetInsets(WindowInsetsCompat.Type.NavigationBars())!.Bottom;
var height = Math.Max(0, (float)((bounds!.LowerBound!.Bottom - navbarInset) / _topLevel.RenderScaling));
var upperRect = new Rect(0, _topLevel.ClientSize.Height - SafeAreaPadding.Bottom - height, _topLevel.ClientSize.Width, height);
height = Math.Max(0, (float)((bounds.UpperBound.Bottom - navbarInset) / _topLevel.RenderScaling));
height = Math.Max(0, (float)((bounds.UpperBound!.Bottom - navbarInset) / _topLevel.RenderScaling));
var lowerRect = new Rect(0, _topLevel.ClientSize.Height - SafeAreaPadding.Bottom - height, _topLevel.ClientSize.Width, height);
var duration = TimeSpan.FromMilliseconds(animation.DurationMillis);
@ -344,7 +344,7 @@ namespace Avalonia.Android.Platform
return base.OnStart(animation, bounds);
}
public override WindowInsetsCompat OnProgress(WindowInsetsCompat insets, IList<WindowInsetsAnimationCompat> runningAnimations)
public override WindowInsetsCompat? OnProgress(WindowInsetsCompat? insets, IList<WindowInsetsAnimationCompat>? runningAnimations)
{
return insets;
}

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

@ -1,6 +1,7 @@
using System;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Provider;
using Avalonia.Platform;
using Color = Avalonia.Media.Color;
@ -36,7 +37,7 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
_ => throw new ArgumentOutOfRangeException()
};
if (OperatingSystem.IsAndroidVersionAtLeast(31))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)31)
{
// See https://developer.android.com/reference/android/R.color
var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500
@ -52,7 +53,7 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings
AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B),
};
}
else if (OperatingSystem.IsAndroidVersionAtLeast(23))
else if (Build.VERSION.SdkInt >= (BuildVersionCodes)23)
{
// See https://developer.android.com/reference/android/R.attr
var array = context.Theme?.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent

7
src/Android/Avalonia.Android/Platform/AndroidScreens.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Android.Content;
using Android.Hardware.Display;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
@ -23,7 +24,7 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl
var rotation = display.Rotation;
IsPrimary = display.DisplayId == Display.DefaultDisplay;
if (OperatingSystem.IsAndroidVersionAtLeast(30))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)30)
{
var metricsCalc = WindowMetricsCalculator.Companion.OrCreate;
// a display context is guaranteed to be created for a display on API 30 and above, but we fallback to the app context if OEM messes up
@ -33,7 +34,7 @@ internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandl
Bounds = new(metrics.Bounds.Left, metrics.Bounds.Top, metrics.Bounds.Width(), metrics.Bounds.Height());
var inset = metrics.WindowInsets.GetInsets(WindowInsetsCompat.Type.SystemBars());
WorkingArea = new(Bounds.X + inset.Left,
WorkingArea = new(Bounds.X + inset!.Left,
Bounds.Y + inset.Top,
Bounds.Width - (inset.Left + inset.Right),
Bounds.Height - (inset.Top + inset.Bottom));
@ -103,7 +104,7 @@ internal sealed class AndroidScreens : ScreensBase<Display, AndroidScreen>, IDis
return displays;
}
if (OperatingSystem.IsAndroidVersionAtLeast(30) && _context.Display is { } defaultDisplay)
if (Build.VERSION.SdkInt >= (BuildVersionCodes)30 && _context.Display is { } defaultDisplay)
{
return [defaultDisplay];
}

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

@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace Avalonia.Android.Platform;
@ -19,7 +20,7 @@ internal static class PlatformSupport
throw new InvalidOperationException("Main activity must implement IActivityResultHandler interface.");
}
if (!OperatingSystem.IsAndroidVersionAtLeast(23))
if (Build.VERSION.SdkInt < (BuildVersionCodes)23)
{
return true;
}

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

@ -4,6 +4,7 @@ using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Views;
@ -193,7 +194,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
// Workaround issue #9230 on where screen remains gray after splash screen.
// base.DispatchDraw should punch a hole into the canvas so the surface
// can be seen below, but it does not.
if (OperatingSystem.IsAndroidVersionAtLeast(29))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)29)
{
// Android 10+ does this (BlendMode was new)
var paint = new Paint();
@ -327,7 +328,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
if (level == WindowTransparencyLevel.None)
{
if (OperatingSystem.IsAndroidVersionAtLeast(30))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)30)
{
activity.SetTranslucent(false);
}
@ -336,7 +337,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
else if (level == WindowTransparencyLevel.Transparent)
{
if (OperatingSystem.IsAndroidVersionAtLeast(30))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)30)
{
activity.SetTranslucent(true);
SetBlurBehind(activity, 0);
@ -345,7 +346,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
else if (level == WindowTransparencyLevel.Blur)
{
if (OperatingSystem.IsAndroidVersionAtLeast(31))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)31)
{
activity.SetTranslucent(true);
SetBlurBehind(activity, 120);
@ -358,7 +359,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
// If we get here, we didn't find a supported level. Use the default of None.
if (OperatingSystem.IsAndroidVersionAtLeast(30))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)30)
{
activity.SetTranslucent(false);
}
@ -416,9 +417,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
if (level == WindowTransparencyLevel.None)
return true;
if (level == WindowTransparencyLevel.Transparent)
return OperatingSystem.IsAndroidVersionAtLeast(30);
return Build.VERSION.SdkInt >= (BuildVersionCodes)30;
if (level == WindowTransparencyLevel.Blur)
return OperatingSystem.IsAndroidVersionAtLeast(31);
return Build.VERSION.SdkInt >= (BuildVersionCodes)31;
return false;
}
@ -429,7 +430,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
else
activity.Window?.AddFlags(WindowManagerFlags.BlurBehind);
if (OperatingSystem.IsAndroidVersionAtLeast(31) && activity.Window?.Attributes is { } attr)
if (Build.VERSION.SdkInt >= (BuildVersionCodes)31 && activity.Window?.Attributes is { } attr)
{
attr.BlurBehindRadius = radius;
activity.Window.Attributes = attr;

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

@ -1,5 +1,6 @@
using System;
using System.Runtime.Versioning;
using Android.OS;
using Android.Views;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
@ -43,7 +44,9 @@ namespace Avalonia.Android.Platform.Specific.Helpers
private bool? DispatchKeyEventInternal(KeyEvent e, out bool callBase)
{
var unicodeTextInput = OperatingSystem.IsAndroidVersionAtLeast(29) ? null : UnicodeTextInput(e);
#pragma warning disable CA1422 // Validate platform compatibility
var unicodeTextInput = Build.VERSION.SdkInt >= (BuildVersionCodes)29 ? null : UnicodeTextInput(e);
#pragma warning restore CA1422 // Validate platform compatibility
var inputRoot = _view.InputRoot;
if ((e.Action == KeyEventActions.Multiple && unicodeTextInput == null)

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

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using Android.OS;
using Android.Views;
using Avalonia.Android.Platform.SkiaPlatform;
@ -146,10 +146,12 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{
modifiers |= RawInputModifiers.XButton2MouseButton;
}
if (OperatingSystem.IsAndroidVersionAtLeast(23) && buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary))
#pragma warning disable CA1416 // Validate platform compatibility
if (Build.VERSION.SdkInt >= (BuildVersionCodes)23 && buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary))
{
modifiers |= RawInputModifiers.PenBarrelButton;
}
#pragma warning restore CA1416 // Validate platform compatibility
return modifiers;
}

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

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Webkit;
using Avalonia.Logging;
@ -411,7 +412,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
private bool IsVirtualFile(Context context, AndroidUri uri)
{
if (!OperatingSystem.IsAndroidVersionAtLeast(24))
if (Build.VERSION.SdkInt < (BuildVersionCodes)24)
return false;
if (!DocumentsContract.IsDocumentUri(context, uri))
@ -540,7 +541,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
{
AndroidUri? movedUri = null;
if (OperatingSystem.IsAndroidVersionAtLeast(24))
if (Build.VERSION.SdkInt >= (BuildVersionCodes)24)
{
try
{

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

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Provider;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
@ -25,11 +26,11 @@ internal class AndroidStorageProvider : IStorageProvider
_activity = activity;
}
public bool CanOpen => OperatingSystem.IsAndroidVersionAtLeast(19);
public bool CanOpen => Build.VERSION.SdkInt >= (BuildVersionCodes)19;
public bool CanSave => OperatingSystem.IsAndroidVersionAtLeast(19);
public bool CanSave => Build.VERSION.SdkInt >= (BuildVersionCodes)19;
public bool CanPickFolder => OperatingSystem.IsAndroidVersionAtLeast(21);
public bool CanPickFolder => Build.VERSION.SdkInt >= (BuildVersionCodes)21;
public Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
{
@ -270,7 +271,7 @@ internal class AndroidStorageProvider : IStorageProvider
private static Intent TryAddExtraInitialUri(Intent intent, IStorageFolder? folder)
{
if (OperatingSystem.IsAndroidVersionAtLeast(26)
if (Build.VERSION.SdkInt >= (BuildVersionCodes)26
&& (folder as AndroidStorageItem)?.Uri is { } uri)
{
return intent.PutExtra(DocumentsContract.ExtraInitialUri, uri);

5
src/Avalonia.Base/Compatibility/OperatingSystem.cs

@ -6,12 +6,13 @@ namespace Avalonia.Compatibility
internal sealed class OperatingSystemEx
{
#if NET6_0_OR_GREATER
private const string BionicRuntimeIdentifierPrefix = "linux-bionic-";
public static bool IsWindows() => OperatingSystem.IsWindows();
public static bool IsMacOS() => OperatingSystem.IsMacOS();
public static bool IsMacCatalyst() => OperatingSystem.IsMacCatalyst();
public static bool IsLinux() => OperatingSystem.IsLinux();
public static bool IsLinux() => OperatingSystem.IsLinux() && !RuntimeInformation.RuntimeIdentifier.StartsWith(BionicRuntimeIdentifierPrefix);
public static bool IsFreeBSD() => OperatingSystem.IsFreeBSD();
public static bool IsAndroid() => OperatingSystem.IsAndroid();
public static bool IsAndroid() => OperatingSystem.IsAndroid() || (OperatingSystem.IsLinux() && RuntimeInformation.RuntimeIdentifier.StartsWith(BionicRuntimeIdentifierPrefix));
public static bool IsIOS() => OperatingSystem.IsIOS();
public static bool IsTvOS() => OperatingSystem.IsTvOS();
public static bool IsBrowser() => OperatingSystem.IsBrowser();

Loading…
Cancel
Save