using System; using Android.Content; using Bit.App.Abstractions; using Xamarin.Forms; using Android.Webkit; using Plugin.CurrentActivity; using System.IO; using Android.Support.V4.Content; using Bit.App; using Bit.App.Resources; using Android.Provider; using System.Threading.Tasks; using Android.OS; using System.Collections.Generic; using Android; using Android.Content.PM; using Android.Support.V4.App; using Bit.App.Models.Page; using XLabs.Ioc; using Android.App; using Android.Views.Autofill; using Android.App.Assist; using Bit.Android.Autofill; using System.Linq; using Plugin.Settings.Abstractions; using Acr.UserDialogs; using Android.Views.InputMethods; namespace Bit.Android.Services { public class DeviceActionService : IDeviceActionService { private readonly IAppSettingsService _appSettingsService; private readonly IUserDialogs _userDialogs; private bool _cameraPermissionsDenied; private DateTime? _lastAction; public DeviceActionService( IAppSettingsService appSettingsService, IUserDialogs userDialogs) { _appSettingsService = appSettingsService; _userDialogs = userDialogs; } private Context CurrentContext => CrossCurrentActivity.Current.Activity; public void CopyToClipboard(string text) { var clipboardManager = (ClipboardManager)CurrentContext.GetSystemService(Context.ClipboardService); clipboardManager.Text = text; } public bool OpenFile(byte[] fileData, string id, string fileName) { if(!CanOpenFile(fileName)) { return false; } var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower()); if(extension == null) { return false; } var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension); if(mimeType == null) { return false; } var cachePath = CrossCurrentActivity.Current.Activity.CacheDir; var filePath = Path.Combine(cachePath.Path, fileName); File.WriteAllBytes(filePath, fileData); var file = new Java.IO.File(cachePath, fileName); if(!file.IsFile) { return false; } try { var intent = new Intent(Intent.ActionView); var uri = FileProvider.GetUriForFile(CrossCurrentActivity.Current.Activity.ApplicationContext, "com.x8bit.bitwarden.fileprovider", file); intent.SetDataAndType(uri, mimeType); intent.SetFlags(ActivityFlags.GrantReadUriPermission); CrossCurrentActivity.Current.Activity.StartActivity(intent); return true; } catch { } return false; } public bool CanOpenFile(string fileName) { var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower()); if(extension == null) { return false; } var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension); if(mimeType == null) { return false; } var pm = CrossCurrentActivity.Current.Activity.PackageManager; var intent = new Intent(Intent.ActionView); intent.SetType(mimeType); var activities = pm.QueryIntentActivities(intent, PackageInfoFlags.MatchDefaultOnly); return (activities?.Count ?? 0) > 0; } public void ClearCache() { try { DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir); _appSettingsService.LastCacheClear = DateTime.UtcNow; } catch(Exception) { } } public Task SelectFileAsync() { MessagingCenter.Unsubscribe(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied"); var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage); var additionalIntents = new List(); if(CurrentContext.PackageManager.HasSystemFeature(PackageManager.FeatureCamera)) { var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera); if(!_cameraPermissionsDenied && !hasStorageWritePermission) { AskCameraPermission(Manifest.Permission.WriteExternalStorage); return Task.FromResult(0); } if(!_cameraPermissionsDenied && !hasCameraPermission) { AskCameraPermission(Manifest.Permission.Camera); return Task.FromResult(0); } if(!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission) { try { var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden"); var file = new Java.IO.File(root, "temp_camera_photo.jpg"); if(!file.Exists()) { file.ParentFile.Mkdirs(); file.CreateNewFile(); } var outputFileUri = global::Android.Net.Uri.FromFile(file); additionalIntents.AddRange(GetCameraIntents(outputFileUri)); } catch(Java.IO.IOException) { } } } var docIntent = new Intent(Intent.ActionOpenDocument); docIntent.AddCategory(Intent.CategoryOpenable); docIntent.SetType("*/*"); var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource); if(additionalIntents.Count > 0) { chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray()); } CrossCurrentActivity.Current.Activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode); return Task.FromResult(0); } public void Autofill(VaultListPageModel.Cipher cipher) { var activity = (MainActivity)CurrentContext; if(activity.Intent.GetBooleanExtra("autofillFramework", false)) { if(cipher == null) { activity.SetResult(Result.Canceled); activity.Finish(); return; } var structure = activity.Intent.GetParcelableExtra( AutofillManager.ExtraAssistStructure) as AssistStructure; if(structure == null) { activity.SetResult(Result.Canceled); activity.Finish(); return; } var parser = new Parser(structure); parser.Parse(); if(!parser.FieldCollection.Fields.Any() || string.IsNullOrWhiteSpace(parser.Uri)) { activity.SetResult(Result.Canceled); activity.Finish(); return; } var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher.CipherModel)); var replyIntent = new Intent(); replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset); activity.SetResult(Result.Ok, replyIntent); activity.Finish(); } else { var data = new Intent(); if(cipher == null) { data.PutExtra("canceled", "true"); } else { var isPremium = Resolver.Resolve()?.TokenPremium ?? false; var settings = Resolver.Resolve(); var autoCopyEnabled = !settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false); if(isPremium && autoCopyEnabled && cipher.LoginTotp?.Value != null) { CopyToClipboard(App.Utilities.Crypto.Totp(cipher.LoginTotp.Value)); } data.PutExtra("uri", cipher.LoginUri); data.PutExtra("username", cipher.LoginUsername); data.PutExtra("password", cipher.LoginPassword?.Value ?? null); } if(activity.Parent == null) { activity.SetResult(Result.Ok, data); } else { activity.Parent.SetResult(Result.Ok, data); } activity.Finish(); MessagingCenter.Send(Xamarin.Forms.Application.Current, "FinishMainActivity"); } } public void CloseAutofill() { Autofill(null); } public void Background() { var activity = (MainActivity)CurrentContext; if(activity.Intent.GetBooleanExtra("autofillFramework", false)) { activity.SetResult(Result.Canceled); activity.Finish(); } else { activity.MoveTaskToBack(true); } } public void RateApp() { var activity = (MainActivity)CurrentContext; try { var rateIntent = RateIntentForUrl("market://details", activity); activity.StartActivity(rateIntent); } catch(ActivityNotFoundException) { var rateIntent = RateIntentForUrl("https://play.google.com/store/apps/details", activity); activity.StartActivity(rateIntent); } } public void DismissKeyboard() { var activity = (MainActivity)CurrentContext; try { var imm = (InputMethodManager)activity.GetSystemService(Context.InputMethodService); imm.HideSoftInputFromWindow(activity.CurrentFocus.WindowToken, 0); } catch { } } public void OpenAccessibilitySettings() { var activity = (MainActivity)CurrentContext; var intent = new Intent(Settings.ActionAccessibilitySettings); activity.StartActivity(intent); } public void LaunchApp(string appName) { var activity = (MainActivity)CurrentContext; if(_lastAction.LastActionWasRecent()) { return; } _lastAction = DateTime.UtcNow; appName = appName.Replace("androidapp://", string.Empty); var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName); if(launchIntent == null) { _userDialogs.Alert(string.Format(AppResources.CannotOpenApp, appName)); } else { activity.StartActivity(launchIntent); } } private Intent RateIntentForUrl(string url, Activity activity) { var intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse($"{url}?id={activity.PackageName}")); var flags = ActivityFlags.NoHistory | ActivityFlags.MultipleTask; if((int)Build.VERSION.SdkInt >= 21) { flags |= ActivityFlags.NewDocument; } else { // noinspection deprecation flags |= ActivityFlags.ClearWhenTaskReset; } intent.AddFlags(flags); return intent; } private bool DeleteDir(Java.IO.File dir) { if(dir != null && dir.IsDirectory) { var children = dir.List(); for(int i = 0; i < children.Length; i++) { var success = DeleteDir(new Java.IO.File(dir, children[i])); if(!success) { return false; } } return dir.Delete(); } else if(dir != null && dir.IsFile) { return dir.Delete(); } else { return false; } } private List GetCameraIntents(global::Android.Net.Uri outputUri) { var intents = new List(); var pm = CrossCurrentActivity.Current.Activity.PackageManager; var captureIntent = new Intent(MediaStore.ActionImageCapture); var listCam = pm.QueryIntentActivities(captureIntent, 0); foreach(var res in listCam) { var packageName = res.ActivityInfo.PackageName; var intent = new Intent(captureIntent); intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name)); intent.SetPackage(packageName); intent.PutExtra(MediaStore.ExtraOutput, outputUri); intents.Add(intent); } return intents; } private bool HasPermission(string permission) { return ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, permission) == Permission.Granted; } private void AskCameraPermission(string permission) { MessagingCenter.Subscribe(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied", (sender) => { _cameraPermissionsDenied = true; }); AskPermission(permission); } private void AskPermission(string permission) { ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission }, Constants.SelectFilePermissionRequestCode); } public void OpenAutofillSettings() { var activity = (MainActivity)CurrentContext; var intent = new Intent(Settings.ActionRequestSetAutofillService); intent.SetData(global::Android.Net.Uri.Parse("package:com.x8bit.bitwarden")); activity.StartActivity(intent); } } }