mirror of
https://github.com/bitwarden/mobile
synced 2025-01-30 10:15:10 +01:00
[BEEEP] Support for automatic TOTP token copy via external autofill (Android) (#2220)
* Android: Support for automatic TOTP copy via external autofill * update iOS autofill interface * additional tweaks
This commit is contained in:
parent
bafd9ff85d
commit
6973a0b71c
@ -15,7 +15,7 @@
|
|||||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||||
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
||||||
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
||||||
<TargetFrameworkVersion>v12.1</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v13.0</TargetFrameworkVersion>
|
||||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||||
<NuGetPackageImportStamp>
|
<NuGetPackageImportStamp>
|
||||||
</NuGetPackageImportStamp>
|
</NuGetPackageImportStamp>
|
||||||
@ -77,12 +77,12 @@
|
|||||||
<PackageReference Include="Portable.BouncyCastle">
|
<PackageReference Include="Portable.BouncyCastle">
|
||||||
<Version>1.9.0</Version>
|
<Version>1.9.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1" />
|
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1.1" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.13" />
|
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.14" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.16" />
|
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.17" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0" />
|
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0.1" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.14" />
|
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.15" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1.1" />
|
||||||
<PackageReference Include="Xamarin.Essentials">
|
<PackageReference Include="Xamarin.Essentials">
|
||||||
<Version>1.7.3</Version>
|
<Version>1.7.3</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@ -103,8 +103,10 @@
|
|||||||
<Compile Include="Accessibility\Browser.cs" />
|
<Compile Include="Accessibility\Browser.cs" />
|
||||||
<Compile Include="Accessibility\NodeList.cs" />
|
<Compile Include="Accessibility\NodeList.cs" />
|
||||||
<Compile Include="Accessibility\KnownUsernameField.cs" />
|
<Compile Include="Accessibility\KnownUsernameField.cs" />
|
||||||
|
<Compile Include="Autofill\AutofillConstants.cs" />
|
||||||
<Compile Include="Autofill\AutofillHelpers.cs" />
|
<Compile Include="Autofill\AutofillHelpers.cs" />
|
||||||
<Compile Include="Autofill\AutofillService.cs" />
|
<Compile Include="Autofill\AutofillService.cs" />
|
||||||
|
<Compile Include="Autofill\AutofillExternalSelectionActivity.cs" />
|
||||||
<Compile Include="Autofill\Field.cs" />
|
<Compile Include="Autofill\Field.cs" />
|
||||||
<Compile Include="Autofill\FieldCollection.cs" />
|
<Compile Include="Autofill\FieldCollection.cs" />
|
||||||
<Compile Include="Autofill\FilledItem.cs" />
|
<Compile Include="Autofill\FilledItem.cs" />
|
||||||
|
10
src/Android/Autofill/AutofillConstants.cs
Normal file
10
src/Android/Autofill/AutofillConstants.cs
Normal file
@ -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";
|
||||||
|
}
|
||||||
|
}
|
42
src/Android/Autofill/AutofillExternalSelectionActivity.cs
Normal file
42
src/Android/Autofill/AutofillExternalSelectionActivity.cs
Normal file
@ -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<ICipherService>();
|
||||||
|
var cipher = await cipherService.GetAsync(cipherId);
|
||||||
|
var decCipher = await cipher.DecryptAsync();
|
||||||
|
|
||||||
|
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
||||||
|
autofillHandler.Autofill(decCipher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -207,7 +207,7 @@ namespace Bit.Droid.Autofill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
|
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
|
||||||
inlinePresentationSpec);
|
true, inlinePresentationSpec);
|
||||||
if (dataset != null)
|
if (dataset != null)
|
||||||
{
|
{
|
||||||
responseBuilder.AddDataset(dataset);
|
responseBuilder.AddDataset(dataset);
|
||||||
@ -221,7 +221,7 @@ namespace Bit.Droid.Autofill
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
|
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
|
||||||
InlinePresentationSpec inlinePresentationSpec = null)
|
bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null)
|
||||||
{
|
{
|
||||||
var overlayPresentation = BuildOverlayPresentation(
|
var overlayPresentation = BuildOverlayPresentation(
|
||||||
filledItem.Name,
|
filledItem.Name,
|
||||||
@ -242,6 +242,15 @@ namespace Bit.Droid.Autofill
|
|||||||
{
|
{
|
||||||
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
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))
|
if (filledItem.ApplyToFields(fields, datasetBuilder))
|
||||||
{
|
{
|
||||||
return datasetBuilder.Build();
|
return datasetBuilder.Build();
|
||||||
@ -253,25 +262,26 @@ namespace Bit.Droid.Autofill
|
|||||||
IList<InlinePresentationSpec> inlinePresentationSpecs = null)
|
IList<InlinePresentationSpec> inlinePresentationSpecs = null)
|
||||||
{
|
{
|
||||||
var intent = new Intent(context, typeof(MainActivity));
|
var intent = new Intent(context, typeof(MainActivity));
|
||||||
intent.PutExtra("autofillFramework", true);
|
intent.PutExtra(AutofillConstants.AutofillFramework, true);
|
||||||
if (fields.FillableForLogin)
|
if (fields.FillableForLogin)
|
||||||
{
|
{
|
||||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
|
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login);
|
||||||
}
|
}
|
||||||
else if (fields.FillableForCard)
|
else if (fields.FillableForCard)
|
||||||
{
|
{
|
||||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
|
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card);
|
||||||
}
|
}
|
||||||
else if (fields.FillableForIdentity)
|
else if (fields.FillableForIdentity)
|
||||||
{
|
{
|
||||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
|
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
intent.PutExtra("autofillFrameworkUri", uri);
|
intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri);
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
|
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||||
|
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
|
||||||
|
|
||||||
var overlayPresentation = BuildOverlayPresentation(
|
var overlayPresentation = BuildOverlayPresentation(
|
||||||
AppResources.AutofillWithBitwarden,
|
AppResources.AutofillWithBitwarden,
|
||||||
|
@ -23,6 +23,7 @@ namespace Bit.Droid.Autofill
|
|||||||
|
|
||||||
public FilledItem(CipherView cipher)
|
public FilledItem(CipherView cipher)
|
||||||
{
|
{
|
||||||
|
Id = cipher.Id;
|
||||||
Name = cipher.Name;
|
Name = cipher.Name;
|
||||||
Type = cipher.Type;
|
Type = cipher.Type;
|
||||||
Subtitle = cipher.SubTitle;
|
Subtitle = cipher.SubTitle;
|
||||||
@ -55,6 +56,7 @@ namespace Bit.Droid.Autofill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Subtitle { get; set; } = string.Empty;
|
public string Subtitle { get; set; } = string.Empty;
|
||||||
public int Icon { get; set; } = Resource.Drawable.login;
|
public int Icon { get; set; } = Resource.Drawable.login;
|
||||||
|
@ -18,6 +18,7 @@ using Bit.Core;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Bit.Droid.Autofill;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -322,13 +323,13 @@ namespace Bit.Droid
|
|||||||
{
|
{
|
||||||
var options = new AppOptions
|
var options = new AppOptions
|
||||||
{
|
{
|
||||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
|
||||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
||||||
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
|
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
|
||||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
|
FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
|
||||||
CreateSend = GetCreateSendRequest(Intent)
|
CreateSend = GetCreateSendRequest(Intent)
|
||||||
};
|
};
|
||||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);
|
||||||
if (fillType > 0)
|
if (fillType > 0)
|
||||||
{
|
{
|
||||||
options.FillType = (CipherType)fillType;
|
options.FillType = (CipherType)fillType;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.11.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.11.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
@ -40,10 +40,4 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
<!-- Package visibility (for Android 11+) -->
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="*" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,4 +1,5 @@
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
|
||||||
namespace Bit.Droid.Receivers
|
namespace Bit.Droid.Receivers
|
||||||
{
|
{
|
||||||
@ -8,7 +9,17 @@ namespace Bit.Droid.Receivers
|
|||||||
public override void OnReceive(Context context, Intent intent)
|
public override void OnReceive(Context context, Intent intent)
|
||||||
{
|
{
|
||||||
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||||
|
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", " ");
|
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clipboardManager.ClearPrimaryClip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,12 +73,12 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public void Autofill(CipherView cipher)
|
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)
|
if (activity == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
|
if (activity.Intent?.GetBooleanExtra(AutofillConstants.AutofillFramework, false) ?? false)
|
||||||
{
|
{
|
||||||
if (cipher == null)
|
if (cipher == null)
|
||||||
{
|
{
|
||||||
@ -103,7 +103,7 @@ namespace Bit.Droid.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var task = CopyTotpAsync(cipher);
|
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();
|
var replyIntent = new Intent();
|
||||||
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
|
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
|
||||||
activity.SetResult(Result.Ok, replyIntent);
|
activity.SetResult(Result.Ok, replyIntent);
|
||||||
|
@ -6,7 +6,6 @@ using Android.OS;
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Droid.Receivers;
|
using Bit.Droid.Receivers;
|
||||||
using Bit.Droid.Utilities;
|
using Bit.Droid.Utilities;
|
||||||
using Plugin.CurrentActivity;
|
|
||||||
using Xamarin.Essentials;
|
using Xamarin.Essentials;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
@ -21,9 +20,9 @@ namespace Bit.Droid.Services
|
|||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
|
|
||||||
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
|
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
|
||||||
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
|
PendingIntent.GetBroadcast(Application.Context,
|
||||||
0,
|
0,
|
||||||
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
|
new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)),
|
||||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
|
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"))
|
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.
|
// 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)
|
private void CopyToClipboard(string text, bool isSensitive = true)
|
||||||
{
|
{
|
||||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||||
var clipboardManager = activity.GetSystemService(
|
|
||||||
Context.ClipboardService) as Android.Content.ClipboardManager;
|
|
||||||
var clipData = ClipData.NewPlainText("bitwarden", text);
|
var clipData = ClipData.NewPlainText("bitwarden", text);
|
||||||
if (isSensitive)
|
if (isSensitive)
|
||||||
{
|
{
|
||||||
@ -87,7 +84,7 @@ namespace Bit.Droid.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
|
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);
|
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,14 +69,17 @@ namespace Bit.Droid.Services
|
|||||||
|
|
||||||
public bool LaunchApp(string appName)
|
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;
|
var activity = CrossCurrentActivity.Current.Activity;
|
||||||
appName = appName.Replace("androidapp://", string.Empty);
|
appName = appName.Replace("androidapp://", string.Empty);
|
||||||
var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName);
|
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
|
||||||
if (launchIntent != null)
|
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
|
||||||
{
|
return launchIntentSender != null;
|
||||||
activity.StartActivity(launchIntent);
|
|
||||||
}
|
|
||||||
return launchIntent != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ShowLoadingAsync(string text)
|
public async Task ShowLoadingAsync(string text)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user