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 . 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/Online/RealtimeMultiplayer/IMultiplayerClient.cs b/osu.Game/Online/RealtimeMultiplayer/IMultiplayerClient.cs index c162f066d4..9af0047137 100644 --- a/osu.Game/Online/RealtimeMultiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/RealtimeMultiplayer/IMultiplayerClient.cs @@ -32,7 +32,7 @@ namespace osu.Game.Online.RealtimeMultiplayer /// Signal that the host of the room has changed. /// /// The user ID of the new host. - Task HostChanged(long userId); + Task HostChanged(int userId); /// /// Signals that the settings for this room have changed. @@ -45,7 +45,7 @@ namespace osu.Game.Online.RealtimeMultiplayer /// /// The ID of the user performing a state change. /// The new state of the user. - Task UserStateChanged(long userId, MultiplayerUserState state); + Task UserStateChanged(int userId, MultiplayerUserState state); /// /// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point. diff --git a/osu.Game/Online/RealtimeMultiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/RealtimeMultiplayer/IMultiplayerRoomServer.cs index f1b3daf7d3..12dfe481c4 100644 --- a/osu.Game/Online/RealtimeMultiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/RealtimeMultiplayer/IMultiplayerRoomServer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.RealtimeMultiplayer /// The new user which is to become host. /// A user other than the current host is attempting to transfer host. /// If the user is not in a room. - Task TransferHost(long userId); + Task TransferHost(int userId); /// /// As the host, update the settings of the currently joined room. 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(); diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 6a04a95040..0a1de461ea 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -87,7 +87,7 @@ namespace osu.Game.Skinning break; case "HitPosition": - currentConfig.HitPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + currentConfig.HitPosition = (480 - Math.Clamp(float.Parse(pair.Value, CultureInfo.InvariantCulture), 240, 480)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; case "LightPosition":