// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Android.App; using Android.Content; using Android.Content.PM; using Android.Graphics; using Android.OS; using Android.Views; using osu.Framework.Android; using osu.Game.Database; using Debug = System.Diagnostics.Debug; using Uri = Android.Net.Uri; namespace osu.Android { [Activity(ConfigurationChanges = DEFAULT_CONFIG_CHANGES, Exported = true, LaunchMode = DEFAULT_LAUNCH_MODE, MainLauncher = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")] [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed", // newer official mime types (see https://osu.ppy.sh/wiki/en/osu%21_File_Formats). "application/x-osu-beatmap-archive", "application/x-osu-skin-archive", "application/x-osu-replay", })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { private static readonly string[] osu_url_schemes = { "osu", "osump" }; /// /// The default screen orientation. /// /// Adjusted on startup to match expected UX for the current device type (phone/tablet). public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified; private readonly OsuGameAndroid game; private bool gameCreated; protected override Framework.Game CreateGame() { if (gameCreated) throw new InvalidOperationException("Framework tried to create a game twice."); gameCreated = true; return game; } public OsuGameActivity() { game = new OsuGameAndroid(this); } protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); // OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack. // on first launch we still have to fire manually. // reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent) handleIntent(Intent); Debug.Assert(Window != null); Window.AddFlags(WindowManagerFlags.Fullscreen); Window.AddFlags(WindowManagerFlags.KeepScreenOn); Debug.Assert(WindowManager?.DefaultDisplay != null); Debug.Assert(Resources?.DisplayMetrics != null); Point displaySize = new Point(); #pragma warning disable CA1422 // GetSize is deprecated WindowManager.DefaultDisplay.GetSize(displaySize); #pragma warning restore CA1422 float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; bool isTablet = smallestWidthDp >= 600f; RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; // Currently (SDK 6.0.200), BundleAssemblies is not runnable for net6-android. // The assembly files are not available as files either after native AOT. // Manually load them so that they can be loaded by RulesetStore.loadFromAppDomain. // REMEMBER to fully uninstall previous version every time when investigating this! // Don't forget osu.Game.Tests.Android too. Assembly.Load("osu.Game.Rulesets.Osu"); Assembly.Load("osu.Game.Rulesets.Taiko"); Assembly.Load("osu.Game.Rulesets.Catch"); Assembly.Load("osu.Game.Rulesets.Mania"); } protected override void OnNewIntent(Intent? intent) => handleIntent(intent); private void handleIntent(Intent? intent) { if (intent == null) return; switch (intent.Action) { case Intent.ActionDefault: if (intent.Scheme == ContentResolver.SchemeContent) { if (intent.Data != null) handleImportFromUris(intent.Data); } else if (osu_url_schemes.Contains(intent.Scheme)) { if (intent.DataString != null) game.HandleLink(intent.DataString); } break; case Intent.ActionSend: case Intent.ActionSendMultiple: { if (intent.ClipData == null) break; var uris = new List(); for (int i = 0; i < intent.ClipData.ItemCount; i++) { var item = intent.ClipData.GetItemAt(i); if (item?.Uri != null) uris.Add(item.Uri); } handleImportFromUris(uris.ToArray()); break; } } } private void handleImportFromUris(params Uri[] uris) => Task.Factory.StartNew(async () => { var tasks = new List(); await Task.WhenAll(uris.Select(async uri => { var task = await AndroidImportTask.Create(ContentResolver!, uri).ConfigureAwait(false); if (task != null) { lock (tasks) { tasks.Add(task); } } })).ConfigureAwait(false); await game.Import(tasks.ToArray()).ConfigureAwait(false); }, TaskCreationOptions.LongRunning); } }