diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
index 20ca0576d4..1a68c4d732 100644
--- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
+++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj
@@ -16,7 +16,7 @@
Resources\Resource.Designer.cs
Off
False
- v10.0
+ v11.0
Properties\AndroidManifest.xml
diff --git a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
index 02e97f3065..9effda7e79 100644
--- a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
+++ b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/src/Android/Avalonia.Android/ActivityTracker.cs b/src/Android/Avalonia.Android/ActivityTracker.cs
deleted file mode 100644
index 2ad1d9e361..0000000000
--- a/src/Android/Avalonia.Android/ActivityTracker.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using Android.App;
-using Android.OS;
-
-namespace Avalonia.Android
-{
- internal class ActivityTracker : Java.Lang.Object, global::Android.App.Application.IActivityLifecycleCallbacks
- {
- public static Activity Current { get; private set; }
- public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
- {
- Current = activity;
- }
-
- public void OnActivityDestroyed(Activity activity)
- {
- if (Current == activity)
- Current = null;
- }
-
- public void OnActivityPaused(Activity activity)
- {
- if (Current == activity)
- Current = null;
- }
-
- public void OnActivityResumed(Activity activity)
- {
- Current = activity;
- }
-
- public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
- {
- Current = activity;
- }
-
- public void OnActivityStarted(Activity activity)
- {
- Current = activity;
- }
-
- public void OnActivityStopped(Activity activity)
- {
- if (Current == activity)
- Current = null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index e0ceb0c8b7..e0ed9b1fda 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -32,6 +32,7 @@ namespace Avalonia.Android
class AndroidPlatform : IPlatformSettings, IWindowingPlatform
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
+ public static AndroidPlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
public double RenderScalingFactor => _scalingFactor;
@@ -46,23 +47,23 @@ namespace Avalonia.Android
public static void Initialize(Type appType, AndroidPlatformOptions options)
{
+ Options = options;
+
AvaloniaLocator.CurrentMutable
.Bind().ToTransient()
- .Bind().ToTransient()
+ .Bind().ToTransient()
.Bind().ToSingleton()
.Bind().ToConstant(Instance)
.Bind().ToConstant(new AndroidThreadingInterface())
.Bind().ToTransient()
.Bind().ToConstant(Instance)
.Bind().ToSingleton()
- .Bind().ToConstant(new DefaultRenderTimer(60))
+ .Bind().ToConstant(new ChoreographerTimer())
.Bind().ToConstant(new RenderLoop())
.Bind().ToSingleton()
.Bind().ToConstant(new AssetLoader(appType.Assembly));
SkiaPlatform.Initialize();
- ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)
- .RegisterActivityLifecycleCallbacks(new ActivityTracker());
if (options.UseGpu)
{
@@ -83,6 +84,7 @@ namespace Avalonia.Android
public sealed class AndroidPlatformOptions
{
+ public bool UseDeferredRendering { get; set; } = true;
public bool UseGpu { get; set; } = true;
}
}
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index c170e8449c..8c6775733f 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -1,6 +1,6 @@
- monoandroid90
+ monoandroid11.0
true
diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs
index d3696aa41d..52b68f8e2f 100644
--- a/src/Android/Avalonia.Android/AvaloniaActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs
@@ -13,9 +13,8 @@ namespace Avalonia.Android
protected override void OnCreate(Bundle savedInstanceState)
{
- RequestWindowFeature(WindowFeatures.NoTitle);
View = new AvaloniaView(this);
- if(_content != null)
+ if (_content != null)
View.Content = _content;
SetContentView(View);
TakeKeyEvents(true);
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index 72732a1f95..8de3657283 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -1,11 +1,12 @@
using System;
using Android.Content;
+using Android.Runtime;
using Android.Views;
using Android.Widget;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
-using Avalonia.Platform;
+using Avalonia.Rendering;
namespace Avalonia.Android
{
@@ -14,6 +15,8 @@ namespace Avalonia.Android
private readonly EmbeddableControlRoot _root;
private readonly ViewImpl _view;
+ private IDisposable? _timerSubscription;
+
public AvaloniaView(Context context) : base(context)
{
_view = new ViewImpl(context);
@@ -33,6 +36,36 @@ namespace Avalonia.Android
return _view.View.DispatchKeyEvent(e);
}
+ public override void OnVisibilityAggregated(bool isVisible)
+ {
+ base.OnVisibilityAggregated(isVisible);
+ OnVisibilityChanged(isVisible);
+ }
+
+ protected override void OnVisibilityChanged(View changedView, [GeneratedEnum] ViewStates visibility)
+ {
+ base.OnVisibilityChanged(changedView, visibility);
+ OnVisibilityChanged(visibility == ViewStates.Visible);
+ }
+
+ private void OnVisibilityChanged(bool isVisible)
+ {
+ if (isVisible)
+ {
+ if (AvaloniaLocator.Current.GetService() is ChoreographerTimer timer)
+ {
+ _timerSubscription = timer.SubscribeView(this);
+ }
+
+ _root.Renderer.Start();
+ }
+ else
+ {
+ _root.Renderer.Stop();
+ _timerSubscription?.Dispose();
+ }
+ }
+
class ViewImpl : TopLevelImpl
{
public ViewImpl(Context context) : base(context)
diff --git a/src/Android/Avalonia.Android/ChoreographerTimer.cs b/src/Android/Avalonia.Android/ChoreographerTimer.cs
new file mode 100644
index 0000000000..1d898261a3
--- /dev/null
+++ b/src/Android/Avalonia.Android/ChoreographerTimer.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using System.Threading.Tasks;
+
+using Android.OS;
+using Android.Views;
+
+using Avalonia.Rendering;
+
+using Java.Lang;
+
+namespace Avalonia.Android
+{
+ internal sealed class ChoreographerTimer : Java.Lang.Object, IRenderTimer, Choreographer.IFrameCallback
+ {
+ private readonly object _lock = new object();
+
+ private readonly Thread _thread;
+ private readonly TaskCompletionSource _choreographer = new TaskCompletionSource();
+
+ private readonly ISet _views = new HashSet();
+
+ private Action _tick;
+ private int _count;
+
+ public ChoreographerTimer()
+ {
+ _thread = new Thread(Loop);
+ _thread.Start();
+ }
+
+ public event Action Tick
+ {
+ add
+ {
+ lock (_lock)
+ {
+ _tick += value;
+ _count++;
+
+ if (_count == 1)
+ {
+ _choreographer.Task.Result.PostFrameCallback(this);
+ }
+ }
+ }
+ remove
+ {
+ lock (_lock)
+ {
+ _tick -= value;
+ _count--;
+ }
+ }
+ }
+
+ internal IDisposable SubscribeView(AvaloniaView view)
+ {
+ lock (_lock)
+ {
+ _views.Add(view);
+
+ if (_views.Count == 1)
+ {
+ _choreographer.Task.Result.PostFrameCallback(this);
+ }
+ }
+
+ return Disposable.Create(
+ () =>
+ {
+ lock (_lock)
+ {
+ _views.Remove(view);
+ }
+ }
+ );
+ }
+
+ private void Loop()
+ {
+ Looper.Prepare();
+ _choreographer.SetResult(Choreographer.Instance);
+ Looper.Loop();
+ }
+
+ public void DoFrame(long frameTimeNanos)
+ {
+ _tick?.Invoke(TimeSpan.FromTicks(frameTimeNanos / 100));
+
+ lock (_lock)
+ {
+ if (_count > 0 && _views.Count > 0)
+ {
+ Choreographer.Instance.PostFrameCallback(this);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/CursorFactory.cs b/src/Android/Avalonia.Android/CursorFactory.cs
index 9eb28c67f9..6293637d4e 100644
--- a/src/Android/Avalonia.Android/CursorFactory.cs
+++ b/src/Android/Avalonia.Android/CursorFactory.cs
@@ -1,12 +1,21 @@
-using System;
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Android
{
- internal class CursorFactory : IStandardCursorFactory
+ internal class CursorFactory : ICursorFactory
{
- public IPlatformHandle GetCursor(StandardCursorType cursorType)
- => new PlatformHandle(IntPtr.Zero, "ZeroCursor");
+ public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => CursorImpl.ZeroCursor;
+
+ public ICursorImpl GetCursor(StandardCursorType cursorType) => CursorImpl.ZeroCursor;
+
+ private sealed class CursorImpl : ICursorImpl
+ {
+ public static CursorImpl ZeroCursor { get; } = new CursorImpl();
+
+ private CursorImpl() { }
+
+ public void Dispose() { }
+ }
}
}
diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
index 4f4c03fe77..a9710039f8 100644
--- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
+++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
@@ -1,6 +1,4 @@
-using System.Linq;
-
-using Avalonia.OpenGL.Egl;
+using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
@@ -17,7 +15,7 @@ namespace Avalonia.Android.OpenGL
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
- new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle));
+ new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle), _info.Handle);
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{
diff --git a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
index 75bbd15e3e..f9071d9b27 100644
--- a/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
+++ b/src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
@@ -1,23 +1,30 @@
-using Avalonia.OpenGL.Egl;
+using System;
+
+using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
- internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase
+ internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo
{
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglSurface _surface;
+ private readonly IntPtr _handle;
public GlRenderTarget(
EglPlatformOpenGlInterface egl,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
- EglSurface surface)
+ EglSurface surface,
+ IntPtr handle)
: base(egl)
{
_info = info;
_surface = surface;
+ _handle = handle;
}
+ public bool IsCorrupted => _handle != _info.Handle;
+
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index a8c7f7af9b..10bf414f25 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -98,10 +98,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable
@@ -150,4 +150,4 @@
-
\ No newline at end of file
+
diff --git a/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml b/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
index e8e81da9de..57ee503005 100644
--- a/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
+++ b/src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file