diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs index 8469912524..e913c6b508 100644 --- a/src/Android/MainActivity.cs +++ b/src/Android/MainActivity.cs @@ -201,7 +201,8 @@ namespace Bit.Droid protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { - if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok) + if(resultCode == Result.Ok && + (requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode)) { Android.Net.Uri uri = null; string fileName = null; @@ -222,6 +223,14 @@ namespace Bit.Droid { return; } + + if(requestCode == Constants.SaveFileRequestCode) + { + _messagingService.Send("selectSaveFileResult", + new Tuple(uri.ToString(), fileName)); + return; + } + try { using(var stream = ContentResolver.OpenInputStream(uri)) diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs index e3b5faaf1a..ffdb7370d0 100644 --- a/src/Android/Services/DeviceActionService.cs +++ b/src/Android/Services/DeviceActionService.cs @@ -200,6 +200,61 @@ namespace Bit.Droid.Services catch { } return null; } + + public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri) + { + try + { + var activity = (MainActivity)CrossCurrentActivity.Current.Activity; + + if(contentUri != null) + { + var uri = Android.Net.Uri.Parse(contentUri); + var stream = activity.ContentResolver.OpenOutputStream(uri); + // Using java bufferedOutputStream due to this issue: + // https://github.com/xamarin/xamarin-android/issues/3498 + var javaStream = new Java.IO.BufferedOutputStream(stream); + javaStream.Write(fileData); + javaStream.Flush(); + javaStream.Close(); + return true; + } + + // Prompt for location to save file + var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower()); + if(extension == null) + { + return false; + } + + string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension); + if(mimeType == null) + { + if(extension == "json") + { + // Explicit support for json since older versions of Android don't recognize the extension + mimeType = "text/json"; + } + else + { + return false; + } + } + + var intent = new Intent(Intent.ActionCreateDocument); + intent.SetType(mimeType); + intent.AddCategory(Intent.CategoryOpenable); + intent.PutExtra(Intent.ExtraTitle, fileName); + + activity.StartActivityForResult(intent, Constants.SaveFileRequestCode); + return true; + } + catch(Exception ex) + { + System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace); + } + return false; + } public async Task ClearCacheAsync() { diff --git a/src/App/Abstractions/IDeviceActionService.cs b/src/App/Abstractions/IDeviceActionService.cs index cd706519d8..785402fd61 100644 --- a/src/App/Abstractions/IDeviceActionService.cs +++ b/src/App/Abstractions/IDeviceActionService.cs @@ -13,6 +13,7 @@ namespace Bit.App.Abstractions Task ShowLoadingAsync(string text); Task HideLoadingAsync(); bool OpenFile(byte[] fileData, string id, string fileName); + bool SaveFile(byte[] fileData, string id, string fileName, string contentUri); bool CanOpenFile(string fileName); Task ClearCacheAsync(); Task SelectFileAsync(); diff --git a/src/App/App.csproj b/src/App/App.csproj index d9c007660d..dda5432f6a 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -66,6 +66,9 @@ FoldersPage.xaml + + ExportVaultPage.xaml + OptionsPage.xaml diff --git a/src/App/Pages/Settings/ExportVaultPage.xaml b/src/App/Pages/Settings/ExportVaultPage.xaml new file mode 100644 index 0000000000..f03549926f --- /dev/null +++ b/src/App/Pages/Settings/ExportVaultPage.xaml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +