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();