diff --git a/src/Mono.Android/Android.App/Activity.cs b/src/Mono.Android/Android.App/Activity.cs index 4fd2a108c7f..4ec9a7f8bea 100644 --- a/src/Mono.Android/Android.App/Activity.cs +++ b/src/Mono.Android/Android.App/Activity.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; using Android.Runtime; namespace Android.App { @@ -39,7 +40,40 @@ public void RunOnUiThread (Action action) { RunOnUiThread (new Java.Lang.Thread.RunnableImplementor (action)); } - } -} + [SupportedOSPlatform ("android19.0")] + [Register ("reportFullyDrawn", "()V", "GetReportFullyDrawnHandler")] + public virtual unsafe void ReportFullyDrawn () + { + const string id = "reportFullyDrawn.()V"; + try { + _members.InstanceMethods.InvokeVirtualVoidMethod (id, this, null); + } finally { + StartupNoGCRegion.End (); + } + } + + static Delegate? cb_reportFullyDrawn_ReportFullyDrawn_V; + + static Delegate GetReportFullyDrawnHandler () + { + return cb_reportFullyDrawn_ReportFullyDrawn_V ??= new _JniMarshal_PP_V (n_ReportFullyDrawn); + } + + static void n_ReportFullyDrawn (IntPtr jnienv, IntPtr native__this) + { + unsafe { + Java.Interop.JniMarshal.SafeInvokeAction (jnienv, native__this, &__n_ReportFullyDrawn); + } + } + static void __n_ReportFullyDrawn (IntPtr jnienv, IntPtr native__this) + { + var activity = Java.Lang.Object.GetObject (jnienv, native__this, JniHandleOwnership.DoNotTransfer); + if (activity == null) { + throw new InvalidOperationException ("Could not obtain the managed Activity instance for reportFullyDrawn."); + } + activity.ReportFullyDrawn (); + } + } +} diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 9e6c6fa599d..e2226dc7d51 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -109,6 +109,7 @@ internal static void InitializeNativeAotRuntime (JniRuntime runtime, JnienvIniti throw new NotSupportedException ("Internal error: NativeAOT cannot be enabled with MonoVM or CoreCLR."); } + StartupNoGCRegion.Start (); androidRuntime = runtime; JniRuntime.SetCurrent (runtime); RegisterTrimmableTypeMapNativeMethodsIfNeeded (); @@ -126,6 +127,8 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) throw new NotSupportedException ("Internal error: exactly one of RuntimeFeature.IsMonoRuntime or RuntimeFeature.IsCoreClrRuntime must be enabled."); } + StartupNoGCRegion.Start (); + IntPtr total_timing_sequence = IntPtr.Zero; IntPtr partial_timing_sequence = IntPtr.Zero; diff --git a/src/Mono.Android/Android.Runtime/StartupNoGCRegion.cs b/src/Mono.Android/Android.Runtime/StartupNoGCRegion.cs new file mode 100644 index 00000000000..cfa0b506e77 --- /dev/null +++ b/src/Mono.Android/Android.Runtime/StartupNoGCRegion.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading; + +namespace Android.Runtime; + +sealed class StartupNoGCRegion +{ + const long Budget = 24 * 1024 * 1024; + // Bound the process-wide region when an app never reports that startup is fully drawn. + static readonly TimeSpan DefaultFallbackTimeout = TimeSpan.FromSeconds (10); + static readonly StartupNoGCRegion instance = new (); + + readonly Lock sync = new (); + Timer? fallbackTimer; + State state; + + enum State + { + NotStarted, + Active, + Ended, + } + + internal static void Start () => instance.StartRegion (); + + internal static void End () => instance.Finish (); + + void StartRegion () + { + if (Microsoft.Android.Runtime.RuntimeFeature.IsMonoRuntime) { + return; + } + + lock (sync) { + if (state != State.NotStarted) { + return; + } + + bool started; + try { + started = GC.TryStartNoGCRegion (Budget, disallowFullBlockingGC: true); + } catch (InvalidOperationException) { + state = State.Ended; + return; + } + + if (!started) { + state = State.Ended; + return; + } + + fallbackTimer = new Timer ( + static value => { + if (value is StartupNoGCRegion noGCRegion) { + noGCRegion.Finish (); + } + }, + this, + DefaultFallbackTimeout, + Timeout.InfiniteTimeSpan + ); + state = State.Active; + } + } + + void Finish () + { + Timer? timer; + lock (sync) { + if (state != State.Active) { + return; + } + + state = State.Ended; + timer = fallbackTimer; + fallbackTimer = null; + } + + timer?.Dispose (); + + try { + GC.EndNoGCRegion (); + } catch (InvalidOperationException) { + // The runtime already left the region because its budget was exhausted + // or a collection was induced. + } + } +} diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index a8b5764cad1..6f67d76a438 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -269,6 +269,7 @@ + diff --git a/src/Mono.Android/metadata b/src/Mono.Android/metadata index f6ba8846fe1..895ed3a6c21 100644 --- a/src/Mono.Android/metadata +++ b/src/Mono.Android/metadata @@ -158,6 +158,7 @@ java.lang.Object Android.AccessibilityServices Android.Views.GravityFlags + KeyPress true true