diff --git a/src/SilentNotes.Android/CloudStorageRedirectActivity.cs b/src/SilentNotes.Android/CloudStorageRedirectActivity.cs
index 7a17763c..e5ee6932 100644
--- a/src/SilentNotes.Android/CloudStorageRedirectActivity.cs
+++ b/src/SilentNotes.Android/CloudStorageRedirectActivity.cs
@@ -9,7 +9,6 @@
using Android.Content.PM;
using Android.OS;
using SilentNotes.Services;
-using SilentNotes.Services.CloudStorageServices;
using SilentNotes.StoryBoards.SynchronizationStory;
namespace SilentNotes.Android
@@ -21,27 +20,31 @@ namespace SilentNotes.Android
///
/// The flags NoHistory and LaunchMode are essential, for the app to appear at the top again.
///
- [Activity(Label = "CloudStorageRedirectActivity", NoHistory = true, LaunchMode = LaunchMode.SingleTask)]
+ [Activity(Label = "CloudStorageRedirectActivity", NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[IntentFilter(
new[] { Intent.ActionView },
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
- DataScheme = "ch.martinstoeckli.silentnotes")]
+ DataScheme = "ch.martinstoeckli.silentnotes",
+ DataPath = "/oauth2redirect")]
public class CloudStorageRedirectActivity : Activity
{
///
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
- System.Uri redirectUri = new Uri(Intent.Data.ToString()); // Convert Android.Net.Uri to System.Uri
- // Call the storage service
- IStoryBoardService storyBoardService = Ioc.GetOrCreate();
- IOauth2CloudStorageService oauthStorageService = storyBoardService.ActiveStory?.LoadFromSession(
- SynchronizationStorySessionKey.OauthCloudStorageService.ToInt());
- oauthStorageService?.HandleOauth2Redirect(redirectUri);
+ string redirectUrl = Intent.Data.ToString();
+ IStoryBoardService storyBoardService = new StoryBoardService();
+ if (storyBoardService.ActiveStory != null)
+ storyBoardService.ActiveStory.StoreToSession(SynchronizationStorySessionKey.OauthRedirectUrl.ToInt(), redirectUrl);
- // Stop the activity, its job is already done.
+ // Stop the redirect activity, its job is already done.
Finish();
+
+ // Clear the activity holding the custom tab and return to already running main activity.
+ Intent intent = new Intent(this, typeof(MainActivity));
+ intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
+ StartActivity(intent);
}
}
}
\ No newline at end of file
diff --git a/src/SilentNotes.Android/MainActivity.cs b/src/SilentNotes.Android/MainActivity.cs
index 1c3ecd3a..a767f212 100644
--- a/src/SilentNotes.Android/MainActivity.cs
+++ b/src/SilentNotes.Android/MainActivity.cs
@@ -16,6 +16,8 @@
using SilentNotes.Controllers;
using SilentNotes.HtmlView;
using SilentNotes.Services;
+using SilentNotes.Services.CloudStorageServices;
+using SilentNotes.StoryBoards.SynchronizationStory;
namespace SilentNotes.Android
{
@@ -71,11 +73,21 @@ protected override void OnStart()
Startup.InitializeApplication(this);
INavigationService navigation = Ioc.GetOrCreate();
+ IStoryBoardService storyBoardService = Ioc.GetOrCreate();
+
if (IsStartedBySendIntent())
{
// Another app sent content to SilentNotes
navigation.Navigate(ControllerNames.Note, ControllerParameters.SendToSilentnotesText, GetSendIntentText());
}
+ else if (IsStartedByOAuthRedirectIndent(storyBoardService))
+ {
+ IOauth2CloudStorageService oauthStorageService = storyBoardService.ActiveStory.LoadFromSession(SynchronizationStorySessionKey.OauthCloudStorageService.ToInt());
+ string redirectUrl = storyBoardService.ActiveStory.LoadFromSession(SynchronizationStorySessionKey.OauthRedirectUrl.ToInt());
+ storyBoardService.ActiveStory.RemoveFromSession(SynchronizationStorySessionKey.OauthRedirectUrl.ToInt());
+
+ oauthStorageService?.HandleOauth2Redirect(new Uri(redirectUrl));
+ }
else
{
// Normal startup
@@ -189,6 +201,12 @@ private string GetSendIntentText()
return Intent.GetStringExtra(Intent.ExtraText);
}
+ private bool IsStartedByOAuthRedirectIndent(IStoryBoardService storyBoardService)
+ {
+ return (storyBoardService.ActiveStory != null) &&
+ storyBoardService.ActiveStory.TryLoadFromSession(SynchronizationStorySessionKey.OauthRedirectUrl.ToInt(), out string _);
+ }
+
private class WebviewValueCallback : Java.Lang.Object, IValueCallback
{
private readonly TaskCompletionSource _taskCompletion;
diff --git a/src/SilentNotes.Android/Properties/AndroidManifest.xml b/src/SilentNotes.Android/Properties/AndroidManifest.xml
index 466d23d1..e0a83b90 100644
--- a/src/SilentNotes.Android/Properties/AndroidManifest.xml
+++ b/src/SilentNotes.Android/Properties/AndroidManifest.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/src/SilentNotes.Android/Services/NativeBrowserService.cs b/src/SilentNotes.Android/Services/NativeBrowserService.cs
index 5c8052fd..a82392a4 100644
--- a/src/SilentNotes.Android/Services/NativeBrowserService.cs
+++ b/src/SilentNotes.Android/Services/NativeBrowserService.cs
@@ -5,6 +5,7 @@
using Android.Content;
using Android.Net;
+using Android.Support.CustomTabs;
using SilentNotes.Services;
namespace SilentNotes.Android.Services
@@ -32,5 +33,14 @@ public void OpenWebsite(string url)
Intent intent = new Intent(Intent.ActionView, webpage);
_applicationContext.StartActivity(intent);
}
+
+ ///
+ public void OpenWebsiteInApp(string url)
+ {
+ // Use the Android custom tabs to display the webpage inside the app.
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ CustomTabsIntent customTabsIntent = builder.Build();
+ customTabsIntent.LaunchUrl(_applicationContext, Uri.Parse(url));
+ }
}
}
\ No newline at end of file
diff --git a/src/SilentNotes.Shared/Services/CloudStorageServices/DropboxCloudStorageService.cs b/src/SilentNotes.Shared/Services/CloudStorageServices/DropboxCloudStorageService.cs
index 584e7cdb..d21785ca 100644
--- a/src/SilentNotes.Shared/Services/CloudStorageServices/DropboxCloudStorageService.cs
+++ b/src/SilentNotes.Shared/Services/CloudStorageServices/DropboxCloudStorageService.cs
@@ -118,7 +118,7 @@ public void ShowOauth2LoginPage()
_oauthState = CryptoUtils.GenerateRandomBase62String(16, _randomSource);
Uri oauthUrl = DropboxOAuth2Helper.GetAuthorizeUri(
OAuthResponseType.Token, GetAppKey(), new Uri(_redirectUrl), _oauthState);
- _nativeBrowserService.OpenWebsite(oauthUrl.AbsoluteUri);
+ _nativeBrowserService.OpenWebsiteInApp(oauthUrl.AbsoluteUri);
}
///
diff --git a/src/SilentNotes.Shared/Services/INativeBrowserService.cs b/src/SilentNotes.Shared/Services/INativeBrowserService.cs
index e276fa63..dda36191 100644
--- a/src/SilentNotes.Shared/Services/INativeBrowserService.cs
+++ b/src/SilentNotes.Shared/Services/INativeBrowserService.cs
@@ -17,5 +17,16 @@ public interface INativeBrowserService
///
/// Url to the website.
void OpenWebsite(string url);
+
+ ///
+ /// Opens a website using the external browser inside the app if possible.
+ ///
+ ///
+ /// OAuth2 authentication requires a login in an external browser, but we want to avoid
+ /// switching between app and browser. With Androids "custom tabs" we can start the browser
+ /// inside the app, thus the app remains active and the browser can be closed afterwards.
+ ///
+ /// Url to the website.
+ void OpenWebsiteInApp(string url);
}
}
diff --git a/src/SilentNotes.Shared/Services/StoryBoardService.cs b/src/SilentNotes.Shared/Services/StoryBoardService.cs
index dd96883b..3679cfe0 100644
--- a/src/SilentNotes.Shared/Services/StoryBoardService.cs
+++ b/src/SilentNotes.Shared/Services/StoryBoardService.cs
@@ -12,7 +12,17 @@ namespace SilentNotes.Services
///
public class StoryBoardService : IStoryBoardService
{
+ ///
+ /// We store the property in this static member, so it remains
+ /// accessible even if the Android activity changes and the Ioc is rebuilt with new services.
+ ///
+ private static IStoryBoard _activeStory;
+
///
- public IStoryBoard ActiveStory { get; set; }
+ public IStoryBoard ActiveStory
+ {
+ get { return _activeStory; }
+ set { _activeStory = value; }
+ }
}
}
diff --git a/src/SilentNotes.Shared/StoryBoards/SynchronizationStory/SynchronizationStoryBoard.cs b/src/SilentNotes.Shared/StoryBoards/SynchronizationStory/SynchronizationStoryBoard.cs
index 0cba1aa9..d42ab77c 100644
--- a/src/SilentNotes.Shared/StoryBoards/SynchronizationStory/SynchronizationStoryBoard.cs
+++ b/src/SilentNotes.Shared/StoryBoards/SynchronizationStory/SynchronizationStoryBoard.cs
@@ -44,6 +44,7 @@ public enum SynchronizationStorySessionKey
UserEnteredTransferCode,
CloudRepository,
OauthCloudStorageService,
+ OauthRedirectUrl,
}
///
diff --git a/src/SilentNotes.UWP/Services/NativeBrowserService.cs b/src/SilentNotes.UWP/Services/NativeBrowserService.cs
index 062df147..affe4049 100644
--- a/src/SilentNotes.UWP/Services/NativeBrowserService.cs
+++ b/src/SilentNotes.UWP/Services/NativeBrowserService.cs
@@ -20,6 +20,12 @@ public void OpenWebsite(string url)
OpenWebsiteAsync(url);
}
+ ///
+ public void OpenWebsiteInApp(string url)
+ {
+ OpenWebsite(url);
+ }
+
private async void OpenWebsiteAsync(string url)
{
await Launcher.LaunchUriAsync(new Uri(url));
diff --git a/src/Tests/SilentNotesTest/Services/CloudStorageServices/DropboxCloudStorageServiceTest.cs b/src/Tests/SilentNotesTest/Services/CloudStorageServices/DropboxCloudStorageServiceTest.cs
index 2129c764..287ebe7c 100644
--- a/src/Tests/SilentNotesTest/Services/CloudStorageServices/DropboxCloudStorageServiceTest.cs
+++ b/src/Tests/SilentNotesTest/Services/CloudStorageServices/DropboxCloudStorageServiceTest.cs
@@ -26,7 +26,7 @@ public void ShowOauth2LoginPageOpensBrowser()
service.ShowOauth2LoginPage();
nativeBrowserService.Verify(
- x => x.OpenWebsite(It.Is(
+ x => x.OpenWebsiteInApp(It.Is(
s => s.Contains("https://www.dropbox.com/oauth2/authorize") && s.Contains("client_id=2drl5n333") && s.Contains("redirect_uri=ch.martinstoeckli.silentnotes") && s.Contains("response_type=token"))),
Times.Once);
}