diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 7e250dce0e..e801c2ca6e 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -1,18 +1,26 @@ // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.IO; +using System.Threading.Tasks; using Android.App; +using Android.Content; using Android.Content.PM; +using Android.Net; using Android.OS; +using Android.Provider; using Android.Views; using osu.Framework.Android; namespace osu.Android { [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] + [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataPathPatterns = new[] { ".*\\.osz", ".*\\.osk" }, DataMimeType = "application/*")] public class OsuGameActivity : AndroidGameActivity { - protected override Framework.Game CreateGame() => new OsuGameAndroid(this); + private OsuGameAndroid game; + + protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this); protected override void OnCreate(Bundle savedInstanceState) { @@ -23,8 +31,58 @@ namespace osu.Android 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); + Window.AddFlags(WindowManagerFlags.Fullscreen); Window.AddFlags(WindowManagerFlags.KeepScreenOn); } + + protected override void OnNewIntent(Intent intent) => handleIntent(intent); + + private void handleIntent(Intent intent) + { + switch (intent.Action) + { + case Intent.ActionDefault: + if (intent.Scheme == ContentResolver.SchemeContent) + handleImportFromUri(intent.Data); + break; + + case Intent.ActionSend: + { + var content = intent.ClipData?.GetItemAt(0); + if (content != null) + handleImportFromUri(content.Uri); + break; + } + } + } + + private void handleImportFromUri(Uri uri) => Task.Factory.StartNew(async () => + { + // there are more performant overloads of this method, but this one is the most backwards-compatible + // (dates back to API 1). + var cursor = ContentResolver?.Query(uri, null, null, null, null); + + if (cursor == null) + return; + + cursor.MoveToFirst(); + + var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); + string filename = cursor.GetString(filenameColumn); + + // SharpCompress requires archive streams to be seekable, which the stream opened by + // OpenInputStream() seems to not necessarily be. + // copy to an arbitrary-access memory stream to be able to proceed with the import. + var copy = new MemoryStream(); + using (var stream = ContentResolver.OpenInputStream(uri)) + await stream.CopyToAsync(copy); + + await game.Import(copy, filename); + }, TaskCreationOptions.LongRunning); } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bb638bcf3a..d67d790ce2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -52,6 +52,7 @@ using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; using osu.Game.Users; +using System.IO; namespace osu.Game { @@ -426,6 +427,16 @@ namespace osu.Game }, validScreens: new[] { typeof(PlaySongSelect) }); } + public override Task Import(Stream stream, string filename) + { + // encapsulate task as we don't want to begin the import process until in a ready state. + var importTask = new Task(async () => await base.Import(stream, filename)); + + waitForReady(() => this, _ => importTask.Start()); + + return importTask; + } + protected virtual Loader CreateLoader() => new Loader(); protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0fc2b8d1d7..150569f1dd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -395,7 +395,7 @@ namespace osu.Game } } - public async Task Import(Stream stream, string filename) + public virtual async Task Import(Stream stream, string filename) { var extension = Path.GetExtension(filename)?.ToLowerInvariant();