Share-to-Send for Android (#1343)

* Android implementation

* remove iOS attempt for now
This commit is contained in:
Matt Portune 2021-03-31 10:19:05 -04:00 committed by GitHub
parent ce0b8bc62d
commit d926565358
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 203 additions and 38 deletions

View File

@ -30,6 +30,16 @@ namespace Bit.Droid
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
ConfigChanges.Navigation)]
[IntentFilter(
new[] { Intent.ActionSend },
Categories = new[] { Intent.CategoryDefault },
DataMimeTypes = new[]
{
@"application/*",
@"image/*",
@"video/*",
@"text/*"
})]
[Register("com.x8bit.bitwarden.MainActivity")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
@ -169,7 +179,7 @@ namespace Bit.Droid
_appOptions.GeneratorTile = true;
}
}
if (intent.GetBooleanExtra("myVaultTile", false))
else if (intent.GetBooleanExtra("myVaultTile", false))
{
_messagingService.Send("popAllAndGoToTabMyVault");
if (_appOptions != null)
@ -177,6 +187,14 @@ namespace Bit.Droid
_appOptions.MyVaultTile = true;
}
}
else if (intent.Action == Intent.ActionSend && intent.Type != null)
{
if (_appOptions != null)
{
_appOptions.CreateSend = GetCreateSendRequest(intent);
}
_messagingService.Send("popAllAndGoToTabSend");
}
else
{
ParseYubiKey(intent.DataString);
@ -298,7 +316,8 @@ namespace Bit.Droid
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
CreateSend = GetCreateSendRequest(Intent)
};
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
if (fillType > 0)
@ -320,6 +339,37 @@ namespace Bit.Droid
return options;
}
private Tuple<SendType, string, byte[], string> GetCreateSendRequest(Intent intent)
{
if (intent.Action == Intent.ActionSend && intent.Type != null)
{
var type = intent.Type;
if (type.Contains("text/"))
{
var subject = intent.GetStringExtra(Intent.ExtraSubject);
var text = intent.GetStringExtra(Intent.ExtraText);
return new Tuple<SendType, string, byte[], string>(SendType.Text, subject, null, text);
}
else
{
var data = intent.ClipData?.GetItemAt(0);
var uri = data?.Uri;
var filename = AndroidHelpers.GetFileName(ApplicationContext, uri);
try
{
using (var stream = ContentResolver.OpenInputStream(uri))
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return new Tuple<SendType, string, byte[], string>(SendType.File, filename, memoryStream.ToArray(), null);
}
}
catch (Java.IO.FileNotFoundException) { }
}
}
return null;
}
private void ParseYubiKey(string data)
{
if (data == null)

View File

@ -760,6 +760,17 @@ namespace Bit.Droid.Services
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
return SystemClock.ElapsedRealtime();
}
public void CloseMainApp()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return;
}
activity.Finish();
_messagingService.Send("finishMainActivity");
}
private bool DeleteDir(Java.IO.File dir)
{

View File

@ -44,5 +44,6 @@ namespace Bit.App.Abstractions
void OpenAutofillSettings();
bool UsingDarkTheme();
long GetActiveTime();
void CloseMainApp();
}
}

View File

@ -131,7 +131,8 @@ namespace Bit.App
await SetMainPageAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault")
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend")
{
Device.BeginInvokeOnMainThread(async () =>
{
@ -146,11 +147,15 @@ namespace Bit.App
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else
else if (message.Command == "popAllAndGoToTabGenerator")
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
{
tabsPage.ResetToSendPage();
}
}
});
}
@ -274,6 +279,10 @@ namespace Bit.App
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
}
else if (Options.CreateSend != null)
{
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
}
else
{
Current.MainPage = new TabsPage(Options);

View File

@ -1,4 +1,5 @@
using Bit.Core.Enums;
using System;
using Bit.Core.Enums;
namespace Bit.App.Models
{
@ -19,6 +20,7 @@ namespace Bit.App.Models
public string SaveCardExpYear { get; set; }
public string SaveCardCode { get; set; }
public bool IosExtension { get; set; }
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public void SetAllFrom(AppOptions o)
{
@ -41,6 +43,7 @@ namespace Bit.App.Models
SaveCardExpYear = o.SaveCardExpYear;
SaveCardCode = o.SaveCardCode;
IosExtension = o.IosExtension;
CreateSend = o.CreateSend;
}
}
}

View File

@ -117,7 +117,7 @@
</StackLayout>
<StackLayout
StyleClass="box-row"
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}">
IsVisible="{Binding ShowTypeButtons}">
<Label
Text="{u:I18n Type}"
StyleClass="box-label" />
@ -222,6 +222,7 @@
HorizontalTextAlignment="Center" />
<Button
Text="{u:I18n ChooseFile}"
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-button-row"
Clicked="ChooseFile_Clicked" />
@ -234,7 +235,7 @@
</StackLayout>
<Label
Text="{u:I18n TypeFileInfo}"
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
IsVisible="{Binding ShowTypeButtons}"
StyleClass="box-footer-label"
Margin="0,5,0,0" />
</StackLayout>

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
@ -15,18 +17,21 @@ namespace Bit.App.Pages
{
private readonly IBroadcasterService _broadcasterService;
private AppOptions _appOptions;
private SendAddEditPageViewModel _vm;
public SendAddEditPage(
AppOptions appOptions = null,
string sendId = null,
SendType? type = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as SendAddEditPageViewModel;
_vm.Page = this;
_vm.SendId = sendId;
_vm.Type = type;
_vm.Type = appOptions?.CreateSend?.Item1 ?? type;
SetActivityIndicator();
if (Device.RuntimePlatform == Device.Android)
{
@ -95,6 +100,7 @@ namespace Bit.App.Pages
await Navigation.PopModalAsync();
return;
}
await HandleCreateRequest();
if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
{
RequestFocus(_nameEntry);
@ -271,5 +277,33 @@ namespace Bit.App.Pages
ToolbarItems.Remove(_shareLink);
}
}
private async Task HandleCreateRequest()
{
if (_appOptions?.CreateSend == null)
{
return;
}
_vm.IsAddFromShare = true;
var name = _appOptions.CreateSend.Item2;
_vm.Send.Name = name;
var type = _appOptions.CreateSend.Item1;
if (type == SendType.File)
{
_vm.FileData = _appOptions.CreateSend.Item3;
_vm.FileName = name;
FileType_Clicked(null, null);
}
else
{
var text = _appOptions.CreateSend.Item4;
_vm.Send.Text.Text = text;
TextType_Clicked(null, null);
}
_appOptions.CreateSend = null;
}
}
}

View File

@ -95,6 +95,7 @@ namespace Bit.App.Pages
public string NewPassword { get; set; }
public bool ShareOnSave { get; set; }
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
@ -208,6 +209,7 @@ namespace Bit.App.Pages
get => _sendOptionsPolicyInEffect;
set => SetProperty(ref _sendOptionsPolicyInEffect, value);
}
public bool ShowTypeButtons => !EditMode && !IsAddFromShare;
public bool EditMode => !string.IsNullOrWhiteSpace(SendId);
public bool IsText => Send?.Type == SendType.Text;
public bool IsFile => Send?.Type == SendType.File;
@ -335,7 +337,7 @@ namespace Bit.App.Pages
{
if (!_canAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
return false;
}
if (!EditMode)
@ -374,10 +376,6 @@ namespace Bit.App.Pages
var sendId = await _sendService.SaveWithServerAsync(send, encryptedFileData);
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
await Page.Navigation.PopModalAsync();
if (Device.RuntimePlatform == Device.Android && IsFile)
{
// Workaround for https://github.com/xamarin/Xamarin.Forms/issues/5418
@ -395,7 +393,21 @@ namespace Bit.App.Pages
await AppHelpers.ShareSendUrlAsync(savedSendView);
}
}
else
{
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
}
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
}
else
{
await Page.Navigation.PopModalAsync();
}
return true;
}
catch (ApiException e)
@ -432,11 +444,29 @@ namespace Bit.App.Pages
public async Task TypeChangedAsync(SendType type)
{
if (!SendEnabled)
{
await _platformUtilsService.ShowDialogAsync(AppResources.SendDisabledWarning);
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
}
else
{
await Page.Navigation.PopModalAsync();
}
return;
}
if (Send != null)
{
if (!EditMode && type == SendType.File && !_canAccessPremium)
{
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
{
_deviceActionService.CloseMainApp();
return;
}
type = SendType.Text;
}
Send.Type = type;

View File

@ -19,10 +19,11 @@ namespace Bit.App.Pages
private readonly SendGroupingsPageViewModel _vm;
private readonly string _pageName;
private AppOptions _appOptions;
private PreviousPageInfo _previousPage;
public SendGroupingsPage(bool mainPage, SendType? type = null, string pageTitle = null,
PreviousPageInfo previousPage = null)
AppOptions appOptions = null, PreviousPageInfo previousPage = null)
{
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent();
@ -35,6 +36,7 @@ namespace Bit.App.Pages
_vm.Page = this;
_vm.MainPage = mainPage;
_vm.Type = type;
_appOptions = appOptions;
_previousPage = previousPage;
if (pageTitle != null)
{
@ -109,8 +111,8 @@ namespace Bit.App.Pages
}
}
await ShowPreviousPageAsync();
AdjustToolbar();
await CheckAddRequest();
}, _mainContent);
}
@ -122,6 +124,18 @@ namespace Bit.App.Pages
_vm.DisableRefreshing();
}
private async Task CheckAddRequest()
{
if (_appOptions?.CreateSend != null)
{
if (DoOnce())
{
var page = new SendAddEditPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
{
((ListView)sender).SelectedItem = null;
@ -172,28 +186,11 @@ namespace Bit.App.Pages
{
if (DoOnce())
{
var page = new SendAddEditPage(null, _vm.Type);
var page = new SendAddEditPage(null, null, _vm.Type);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async Task ShowPreviousPageAsync()
{
if (_previousPage == null)
{
return;
}
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
{
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.SendId)));
}
else if (_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
{
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.SendId)));
}
_previousPage = null;
}
private void AdjustToolbar()
{
_addItem.IsEnabled = _vm.SendEnabled;

View File

@ -208,7 +208,7 @@ namespace Bit.App.Pages
public async Task SelectSendAsync(SendView send)
{
var page = new SendAddEditPage(send.Id);
var page = new SendAddEditPage(null, send.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@ -113,7 +113,7 @@ namespace Bit.App.Pages
public async Task SelectSendAsync(SendView send)
{
var page = new SendAddEditPage(send.Id);
var page = new SendAddEditPage(null, send.Id);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@ -20,7 +20,7 @@ namespace Bit.App.Pages
};
Children.Add(_groupingsPage);
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true))
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
{
Title = AppResources.Send,
IconImageSource = "paper_plane.png",
@ -60,6 +60,10 @@ namespace Bit.App.Pages
{
appOptions.MyVaultTile = false;
}
else if (appOptions?.CreateSend != null)
{
ResetToSendPage();
}
}
public void ResetToVaultPage()
@ -71,6 +75,11 @@ namespace Bit.App.Pages
{
CurrentPage = _generatorPage;
}
public void ResetToSendPage()
{
CurrentPage = _sendGroupingsPage;
}
protected async override void OnCurrentPageChanged()
{

View File

@ -3496,5 +3496,11 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("SendOptionsPolicyInEffect", resourceCulture);
}
}
public static string SendFilePremiumRequired {
get {
return ResourceManager.GetString("SendFilePremiumRequired", resourceCulture);
}
}
}
}

View File

@ -1979,4 +1979,8 @@
<value>One or more organization policies are affecting your Send options.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
<data name="SendFilePremiumRequired" xml:space="preserve">
<value>Free accounts are restricted to sharing text only. A premium membership is required to use files with Send.</value>
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
</data>
</root>

View File

@ -154,7 +154,7 @@ namespace Bit.App.Utilities
}
else if (selection == AppResources.Edit)
{
await page.Navigation.PushModalAsync(new NavigationPage(new SendAddEditPage(send.Id)));
await page.Navigation.PushModalAsync(new NavigationPage(new SendAddEditPage(null, send.Id)));
}
else if (selection == AppResources.CopyLink)
{
@ -409,6 +409,11 @@ namespace Bit.App.Utilities
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(appOptions));
return true;
}
if (appOptions.CreateSend != null)
{
Application.Current.MainPage = new NavigationPage(new SendAddEditPage(appOptions));
return true;
}
}
return false;
}

View File

@ -437,6 +437,11 @@ namespace Bit.iOS.Core.Services
return iOSHelpers.GetSystemUpTimeMilliseconds() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
public void CloseMainApp()
{
throw new NotImplementedException();
}
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
{
if (sender is UIImagePickerController picker)