diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 4825654e1..73a19d10e 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -15,7 +15,7 @@
Properties\AndroidManifest.xml
Resources
Assets
- v12.1
+ v13.0
Xamarin.Android.Net.AndroidClientHandler
@@ -77,12 +77,12 @@
1.9.0
-
-
-
-
-
-
+
+
+
+
+
+
1.7.3
@@ -103,8 +103,10 @@
+
+
diff --git a/src/Android/Autofill/AutofillConstants.cs b/src/Android/Autofill/AutofillConstants.cs
new file mode 100644
index 000000000..9bb1782f7
--- /dev/null
+++ b/src/Android/Autofill/AutofillConstants.cs
@@ -0,0 +1,10 @@
+namespace Bit.Droid.Autofill
+{
+ public class AutofillConstants
+ {
+ public const string AutofillFramework = "autofillFramework";
+ public const string AutofillFrameworkFillType = "autofillFrameworkFillType";
+ public const string AutofillFrameworkUri = "autofillFrameworkUri";
+ public const string AutofillFrameworkCipherId = "autofillFrameworkCipherId";
+ }
+}
diff --git a/src/Android/Autofill/AutofillExternalSelectionActivity.cs b/src/Android/Autofill/AutofillExternalSelectionActivity.cs
new file mode 100644
index 000000000..c75d150be
--- /dev/null
+++ b/src/Android/Autofill/AutofillExternalSelectionActivity.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Bit.Core.Abstractions;
+using Bit.Core.Utilities;
+using Bit.Droid.Utilities;
+
+namespace Bit.Droid.Autofill
+{
+ [Activity(
+ NoHistory = true,
+ LaunchMode = LaunchMode.SingleTop)]
+ public class AutofillExternalSelectionActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
+ {
+ protected override void OnCreate(Bundle bundle)
+ {
+ Intent?.Validate();
+ base.OnCreate(bundle);
+
+ var cipherId = Intent?.GetStringExtra(AutofillConstants.AutofillFrameworkCipherId);
+ if (string.IsNullOrEmpty(cipherId))
+ {
+ SetResult(Result.Canceled);
+ Finish();
+ return;
+ }
+
+ GetCipherAndPerformAutofillAsync(cipherId).FireAndForget();
+ }
+
+ private async Task GetCipherAndPerformAutofillAsync(string cipherId)
+ {
+ var cipherService = ServiceContainer.Resolve();
+ var cipher = await cipherService.GetAsync(cipherId);
+ var decCipher = await cipher.DecryptAsync();
+
+ var autofillHandler = ServiceContainer.Resolve();
+ autofillHandler.Autofill(decCipher);
+ }
+ }
+}
diff --git a/src/Android/Autofill/AutofillHelpers.cs b/src/Android/Autofill/AutofillHelpers.cs
index f1984dbff..4702af3ac 100644
--- a/src/Android/Autofill/AutofillHelpers.cs
+++ b/src/Android/Autofill/AutofillHelpers.cs
@@ -207,7 +207,7 @@ namespace Bit.Droid.Autofill
}
}
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
- inlinePresentationSpec);
+ true, inlinePresentationSpec);
if (dataset != null)
{
responseBuilder.AddDataset(dataset);
@@ -221,7 +221,7 @@ namespace Bit.Droid.Autofill
}
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
- InlinePresentationSpec inlinePresentationSpec = null)
+ bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null)
{
var overlayPresentation = BuildOverlayPresentation(
filledItem.Name,
@@ -242,6 +242,15 @@ namespace Bit.Droid.Autofill
{
datasetBuilder.SetInlinePresentation(inlinePresentation);
}
+ if (includeAuthIntent)
+ {
+ var intent = new Intent(context, typeof(AutofillExternalSelectionActivity));
+ intent.PutExtra(AutofillConstants.AutofillFramework, true);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkCipherId, filledItem.Id);
+ var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
+ AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
+ datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
+ }
if (filledItem.ApplyToFields(fields, datasetBuilder))
{
return datasetBuilder.Build();
@@ -253,25 +262,26 @@ namespace Bit.Droid.Autofill
IList inlinePresentationSpecs = null)
{
var intent = new Intent(context, typeof(MainActivity));
- intent.PutExtra("autofillFramework", true);
+ intent.PutExtra(AutofillConstants.AutofillFramework, true);
if (fields.FillableForLogin)
{
- intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login);
}
else if (fields.FillableForCard)
{
- intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card);
}
else if (fields.FillableForIdentity)
{
- intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity);
}
else
{
return null;
}
- intent.PutExtra("autofillFrameworkUri", uri);
- var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
+ intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri);
+ var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
+ AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
var overlayPresentation = BuildOverlayPresentation(
AppResources.AutofillWithBitwarden,
diff --git a/src/Android/Autofill/FilledItem.cs b/src/Android/Autofill/FilledItem.cs
index 3964f37bd..46b0436fc 100644
--- a/src/Android/Autofill/FilledItem.cs
+++ b/src/Android/Autofill/FilledItem.cs
@@ -23,6 +23,7 @@ namespace Bit.Droid.Autofill
public FilledItem(CipherView cipher)
{
+ Id = cipher.Id;
Name = cipher.Name;
Type = cipher.Type;
Subtitle = cipher.SubTitle;
@@ -55,6 +56,7 @@ namespace Bit.Droid.Autofill
}
}
+ public string Id { get; set; }
public string Name { get; set; }
public string Subtitle { get; set; } = string.Empty;
public int Icon { get; set; } = Resource.Drawable.login;
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index cffc12ac0..2e25bc1cc 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -18,6 +18,7 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
+using Bit.Droid.Autofill;
using Bit.Droid.Receivers;
using Bit.Droid.Utilities;
using Newtonsoft.Json;
@@ -322,13 +323,13 @@ namespace Bit.Droid
{
var options = new AppOptions
{
- Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
+ Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
- FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
+ FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
CreateSend = GetCreateSendRequest(Intent)
};
- var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
+ var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);
if (fillType > 0)
{
options.FillType = (CipherType)fillType;
diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml
index ea7066845..54ed96ab6 100644
--- a/src/Android/Properties/AndroidManifest.xml
+++ b/src/Android/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
@@ -40,10 +40,4 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Android/Receivers/ClearClipboardAlarmReceiver.cs b/src/Android/Receivers/ClearClipboardAlarmReceiver.cs
index ff1566134..6e57cedb7 100644
--- a/src/Android/Receivers/ClearClipboardAlarmReceiver.cs
+++ b/src/Android/Receivers/ClearClipboardAlarmReceiver.cs
@@ -1,4 +1,5 @@
using Android.Content;
+using Android.OS;
namespace Bit.Droid.Receivers
{
@@ -8,7 +9,17 @@ namespace Bit.Droid.Receivers
public override void OnReceive(Context context, Intent intent)
{
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
- clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
+ if (clipboardManager == null)
+ {
+ return;
+ }
+ // ClearPrimaryClip is supported down to API 28 with mixed results, so we're requiring 33+ instead
+ if ((int)Build.VERSION.SdkInt < 33)
+ {
+ clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
+ return;
+ }
+ clipboardManager.ClearPrimaryClip();
}
}
}
diff --git a/src/Android/Services/AutofillHandler.cs b/src/Android/Services/AutofillHandler.cs
index 9cfa5ec9e..ad1a0ad5d 100644
--- a/src/Android/Services/AutofillHandler.cs
+++ b/src/Android/Services/AutofillHandler.cs
@@ -73,12 +73,12 @@ namespace Bit.Droid.Services
public void Autofill(CipherView cipher)
{
- var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
+ var activity = CrossCurrentActivity.Current.Activity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity;
if (activity == null)
{
return;
}
- if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
+ if (activity.Intent?.GetBooleanExtra(AutofillConstants.AutofillFramework, false) ?? false)
{
if (cipher == null)
{
@@ -103,7 +103,7 @@ namespace Bit.Droid.Services
return;
}
var task = CopyTotpAsync(cipher);
- var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
+ var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher), false);
var replyIntent = new Intent();
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent);
diff --git a/src/Android/Services/ClipboardService.cs b/src/Android/Services/ClipboardService.cs
index 33585a274..fbb7a1ffb 100644
--- a/src/Android/Services/ClipboardService.cs
+++ b/src/Android/Services/ClipboardService.cs
@@ -6,7 +6,6 @@ using Android.OS;
using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Bit.Droid.Utilities;
-using Plugin.CurrentActivity;
using Xamarin.Essentials;
namespace Bit.Droid.Services
@@ -21,9 +20,9 @@ namespace Bit.Droid.Services
_stateService = stateService;
_clearClipboardPendingIntent = new Lazy(() =>
- PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
+ PendingIntent.GetBroadcast(Application.Context,
0,
- new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
+ new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
}
@@ -45,7 +44,7 @@ namespace Bit.Droid.Services
}
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{
- // #1962 Just ignore, the content is copied either way but there is some app interfiering in the process
+ // #1962 Just ignore, the content is copied either way but there is some app interfering in the process
// that the OS catches and just throws this exception.
}
}
@@ -58,9 +57,7 @@ namespace Bit.Droid.Services
private void CopyToClipboard(string text, bool isSensitive = true)
{
- var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
- var clipboardManager = activity.GetSystemService(
- Context.ClipboardService) as Android.Content.ClipboardManager;
+ var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive)
{
@@ -87,7 +84,7 @@ namespace Bit.Droid.Services
return;
}
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
- var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
+ var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
}
}
diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
index c50558280..f189c77ce 100644
--- a/src/Android/Services/DeviceActionService.cs
+++ b/src/Android/Services/DeviceActionService.cs
@@ -69,14 +69,17 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName)
{
+ if ((int)Build.VERSION.SdkInt < 33)
+ {
+ // API 33 required to avoid using wildcard app visibility or dangerous permissions
+ // https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
+ return false;
+ }
var activity = CrossCurrentActivity.Current.Activity;
appName = appName.Replace("androidapp://", string.Empty);
- var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName);
- if (launchIntent != null)
- {
- activity.StartActivity(launchIntent);
- }
- return launchIntent != null;
+ var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
+ launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
+ return launchIntentSender != null;
}
public async Task ShowLoadingAsync(string text)