mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-12-22 23:18:48 -05:00
Support for Storage Access Framework
* EntryEditActivity: support content-URIs and extracting display names from them * AndroidContentStorage.cs, SelectStorageLocationActivityBase.cs: support for SAF with persistable URI permissions * FileChooser returns URI as data * CreateDatabaseActivity.cs supports content URIs * FileStorageSelectionActivity: doesn't show "Local file" anymore, now system file picker on KitKat
This commit is contained in:
parent
b848e191fc
commit
80b6a1d287
@ -12,7 +12,6 @@
|
||||
<AndroidResgenFile>Resources\Resource.designer.cs</AndroidResgenFile>
|
||||
<AndroidResgenClass>Resource</AndroidResgenClass>
|
||||
<AssemblyName>KeePassLib2Android</AssemblyName>
|
||||
<AndroidUseLatestPlatformSdk />
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
|
@ -2,14 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Android.Provider;
|
||||
using KeePassLib.Serialization;
|
||||
|
||||
namespace keepass2android.Io
|
||||
@ -97,7 +92,7 @@ namespace keepass2android.Io
|
||||
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId + "://" });
|
||||
activity.IocToIntent(intent, new IOConnectionInfo { Path = protocolId + "://" });
|
||||
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileChooserPrepared, intent);
|
||||
}
|
||||
|
||||
@ -131,16 +126,48 @@ namespace keepass2android.Io
|
||||
|
||||
public string GetDisplayName(IOConnectionInfo ioc)
|
||||
{
|
||||
string displayName = null;
|
||||
if (TryGetDisplayName(ioc, ref displayName))
|
||||
return "content://" + displayName; //make sure we return the protocol in the display name for consistency, also expected e.g. by CreateDatabaseActivity
|
||||
return ioc.Path;
|
||||
}
|
||||
|
||||
private bool TryGetDisplayName(IOConnectionInfo ioc, ref string displayName)
|
||||
{
|
||||
var uri = Android.Net.Uri.Parse(ioc.Path);
|
||||
var cursor = _ctx.ContentResolver.Query(uri, null, null, null, null, null);
|
||||
|
||||
try
|
||||
{
|
||||
if (cursor != null && cursor.MoveToFirst())
|
||||
{
|
||||
displayName = cursor.GetString(cursor.GetColumnIndex(OpenableColumns.DisplayName));
|
||||
if (!string.IsNullOrEmpty(displayName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cursor != null)
|
||||
cursor.Close();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string CreateFilePath(string parent, string newFilename)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!parent.EndsWith("/"))
|
||||
parent += "/";
|
||||
return parent + newFilename;
|
||||
}
|
||||
|
||||
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||
{
|
||||
//TODO: required for OTP Aux file retrieval
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@ -149,15 +176,49 @@ namespace keepass2android.Io
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static bool IsKitKatOrLater
|
||||
{
|
||||
get { return (int)Build.VERSION.SdkInt >= 19; }
|
||||
}
|
||||
|
||||
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||
{
|
||||
//on pre-Kitkat devices, content:// is always temporary:
|
||||
return false;
|
||||
if (!IsKitKatOrLater)
|
||||
return false;
|
||||
|
||||
//try to get a persisted permission for the file
|
||||
return _ctx.ContentResolver.PersistedUriPermissions.Any(p => p.Uri.ToString().Equals(ioc.Path));
|
||||
}
|
||||
|
||||
public bool IsReadOnly(IOConnectionInfo ioc)
|
||||
{
|
||||
//on pre-Kitkat devices, we can't write content:// files
|
||||
if (!IsKitKatOrLater)
|
||||
{
|
||||
Kp2aLog.Log("File is read-only because we're not on KitKat or later.");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//KitKat or later...
|
||||
var uri = Android.Net.Uri.Parse(ioc.Path);
|
||||
var cursor = _ctx.ContentResolver.Query(uri, null, null, null, null, null);
|
||||
|
||||
try
|
||||
{
|
||||
if (cursor != null && cursor.MoveToFirst())
|
||||
{
|
||||
int flags = cursor.GetInt(cursor.GetColumnIndex(DocumentsContract.Document.ColumnFlags));
|
||||
Kp2aLog.Log("File flags: " + flags);
|
||||
return (flags & (long) DocumentContractFlags.SupportsWrite) == 0;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cursor != null)
|
||||
cursor.Close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -189,7 +250,8 @@ namespace keepass2android.Io
|
||||
{
|
||||
using (Stream outputStream = _ctx.ContentResolver.OpenOutputStream(Android.Net.Uri.Parse(_path)))
|
||||
{
|
||||
outputStream.Write(_memoryStream.ToArray(), 0, (int)_memoryStream.Length);
|
||||
byte[] data = _memoryStream.ToArray();
|
||||
outputStream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,8 +12,8 @@
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
|
||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
|
||||
<AndroidUseLatestPlatformSdk />
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using Java.Net;
|
||||
using KeePassLib.Serialization;
|
||||
@ -36,6 +37,7 @@ namespace keepass2android
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
Kp2aLog.Log("base.onAR");
|
||||
base.OnActivityResult(requestCode, resultCode, data);
|
||||
if ((requestCode == RequestCodeFileStorageSelectionForPrimarySelect) || ((requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation)))
|
||||
{
|
||||
@ -52,7 +54,11 @@ namespace keepass2android
|
||||
|
||||
if (protocolId == "androidget")
|
||||
{
|
||||
ShowAndroidBrowseDialog(RequestCodeFileBrowseForOpen, false);
|
||||
ShowAndroidBrowseDialog(browseRequestCode, false, false);
|
||||
}
|
||||
else if (protocolId == "content")
|
||||
{
|
||||
ShowAndroidBrowseDialog(browseRequestCode, browseRequestCode == RequestCodeFileFileBrowseForWritableLocation, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -93,7 +99,10 @@ namespace keepass2android
|
||||
|
||||
if (resultCode == Result.Ok)
|
||||
{
|
||||
Kp2aLog.Log("FileSelection returned "+data.DataString);
|
||||
//TODO: don't try to extract filename if content URI
|
||||
string filename = IntentToFilename(data);
|
||||
Kp2aLog.Log("FileSelection returned filename " + filename);
|
||||
if (filename != null)
|
||||
{
|
||||
if (filename.StartsWith("file://"))
|
||||
@ -113,6 +122,24 @@ namespace keepass2android
|
||||
{
|
||||
if (data.Data.Scheme == "content")
|
||||
{
|
||||
if ((int) Build.VERSION.SdkInt >= 19)
|
||||
{
|
||||
//try to take persistable permissions
|
||||
try
|
||||
{
|
||||
Kp2aLog.Log("TakePersistableUriPermission");
|
||||
var takeFlags = data.Flags
|
||||
& (ActivityFlags.GrantReadUriPermission
|
||||
| ActivityFlags.GrantWriteUriPermission);
|
||||
this.ContentResolver.TakePersistableUriPermission(data.Data, takeFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IocSelected(IOConnectionInfo.FromPath(data.DataString), requestCode);
|
||||
|
||||
}
|
||||
@ -155,7 +182,7 @@ namespace keepass2android
|
||||
/// <param name="protocolId"></param>
|
||||
protected abstract void StartSelectFile(bool isForSave, int browseRequestCode, string protocolId);
|
||||
|
||||
protected abstract void ShowAndroidBrowseDialog(int requestCode, bool isForSave);
|
||||
protected abstract void ShowAndroidBrowseDialog(int requestCode, bool isForSave, bool tryGetPermanentAccess);
|
||||
|
||||
protected abstract bool IsStorageSelectionForSave { get; }
|
||||
|
||||
@ -257,7 +284,6 @@ namespace keepass2android
|
||||
var filestorage = _app.GetFileStorage(ioc, false);
|
||||
if (!filestorage.IsPermanentLocation(ioc))
|
||||
{
|
||||
|
||||
string message = _app.GetResourceString(UiStringKey.FileIsTemporarilyAvailable) + " " + _app.GetResourceString(UiStringKey.CopyFileRequired) + " " + _app.GetResourceString(UiStringKey.ClickOkToSelectLocation);
|
||||
EventHandler<DialogClickEventArgs> onOk = (sender, args) => { MoveToWritableLocation(ioc); };
|
||||
EventHandler<DialogClickEventArgs> onCancel = (sender, args) => { ReturnCancel(); };
|
||||
|
@ -328,7 +328,7 @@ namespace Kp2aUnitTests
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ShowAndroidBrowseDialog(int requestCode, bool isForSave)
|
||||
protected override void ShowAndroidBrowseDialog(int requestCode, bool isForSave, bool tryGetPermanentAccess)
|
||||
{
|
||||
_userAction = new AndroidBrowseDialogAction(requestCode, isForSave, this);
|
||||
}
|
||||
|
@ -322,17 +322,25 @@ namespace keepass2android
|
||||
if (resultCode == KeePass.ExitFileStorageSelectionOk)
|
||||
{
|
||||
string protocolId = data.GetStringExtra("protocolId");
|
||||
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
|
||||
OnActivityResult,
|
||||
defaultPath =>
|
||||
{
|
||||
if (defaultPath.StartsWith("sftp://"))
|
||||
Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
|
||||
else
|
||||
Util.ShowFilenameDialog(this, OnCreateButton, null, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
|
||||
Intents.RequestCodeFileBrowseForOpen);
|
||||
}
|
||||
), true, RequestCodeDbFilename, protocolId);
|
||||
if (protocolId == "content")
|
||||
{
|
||||
Util.ShowBrowseDialog(this, RequestCodeDbFilename, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
|
||||
OnActivityResult,
|
||||
defaultPath =>
|
||||
{
|
||||
if (defaultPath.StartsWith("sftp://"))
|
||||
Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
|
||||
else
|
||||
Util.ShowFilenameDialog(this, OnCreateButton, null, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
|
||||
Intents.RequestCodeFileBrowseForOpen);
|
||||
}
|
||||
), true, RequestCodeDbFilename, protocolId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (resultCode == Result.Ok)
|
||||
@ -349,7 +357,31 @@ namespace keepass2android
|
||||
}
|
||||
if (requestCode == RequestCodeDbFilename)
|
||||
{
|
||||
if (data.Data.Scheme == "content")
|
||||
{
|
||||
if ((int)Build.VERSION.SdkInt >= 19)
|
||||
{
|
||||
//try to take persistable permissions
|
||||
try
|
||||
{
|
||||
Kp2aLog.Log("TakePersistableUriPermission");
|
||||
var takeFlags = data.Flags
|
||||
& (ActivityFlags.GrantReadUriPermission
|
||||
| ActivityFlags.GrantWriteUriPermission);
|
||||
this.ContentResolver.TakePersistableUriPermission(data.Data, takeFlags);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string filename = Util.IntentToFilename(data, this);
|
||||
if (filename == null)
|
||||
filename = data.DataString;
|
||||
|
||||
bool fileExists = data.GetBooleanExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.result_file_exists", true);
|
||||
|
||||
@ -375,9 +407,6 @@ namespace keepass2android
|
||||
new ProgressTask(App.Kp2a, this, task).Run();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,9 +21,11 @@ using System.Linq;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Android.Preferences;
|
||||
using Java.IO;
|
||||
using KeePassLib.Utility;
|
||||
using KeePassLib;
|
||||
using Android.Text;
|
||||
@ -31,6 +33,8 @@ using KeePassLib.Security;
|
||||
using Android.Content.PM;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using File = System.IO.File;
|
||||
using Uri = Android.Net.Uri;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
@ -491,9 +495,50 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
|
||||
void AddBinaryOrAsk(string filename)
|
||||
public String GetFileName(Uri uri)
|
||||
{
|
||||
string strItem = UrlUtil.GetFileName(filename);
|
||||
String result = null;
|
||||
if (uri.Scheme.Equals("content"))
|
||||
{
|
||||
var cursor = ContentResolver.Query(uri, null, null, null, null);
|
||||
try
|
||||
{
|
||||
if (cursor != null && cursor.MoveToFirst())
|
||||
{
|
||||
result = cursor.GetString(cursor.GetColumnIndex(OpenableColumns.DisplayName));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cursor != null)
|
||||
cursor.Close();
|
||||
}
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
result = uri.Path;
|
||||
int cut = result.LastIndexOf('/');
|
||||
if (cut != -1)
|
||||
{
|
||||
result = result.Substring(cut + 1);
|
||||
}
|
||||
|
||||
cut = result.LastIndexOf('?');
|
||||
if (cut != -1)
|
||||
{
|
||||
result = result.Substring(0, cut);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddBinaryOrAsk(Uri filename)
|
||||
{
|
||||
|
||||
string strItem = GetFileName(filename);
|
||||
if (String.IsNullOrEmpty(strItem))
|
||||
strItem = "attachment.bin";
|
||||
|
||||
if(State.Entry.Binaries.Get(strItem) != null)
|
||||
{
|
||||
@ -523,10 +568,9 @@ namespace keepass2android
|
||||
AddBinary(filename, true);
|
||||
}
|
||||
|
||||
void AddBinary(string filename, bool overwrite)
|
||||
void AddBinary(Uri filename, bool overwrite)
|
||||
{
|
||||
string strItem = UrlUtil.GetFileName(filename);
|
||||
|
||||
string strItem = GetFileName(filename);
|
||||
if (!overwrite)
|
||||
{
|
||||
string strFileName = UrlUtil.StripExtension(strItem);
|
||||
@ -547,10 +591,22 @@ namespace keepass2android
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] vBytes = File.ReadAllBytes(filename);
|
||||
ProtectedBinary pb = new ProtectedBinary(false, vBytes);
|
||||
State.Entry.Binaries.Set(strItem, pb);
|
||||
|
||||
byte[] vBytes = null;
|
||||
try
|
||||
{
|
||||
//Android standard way to read the contents (content or file scheme)
|
||||
vBytes = ReadFully(ContentResolver.OpenInputStream(filename));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//if standard way fails, try to read as a file
|
||||
vBytes = File.ReadAllBytes(filename.Path);
|
||||
}
|
||||
|
||||
ProtectedBinary pb = new ProtectedBinary(false, vBytes);
|
||||
State.Entry.Binaries.Set(strItem, pb);
|
||||
}
|
||||
catch(Exception exAttach)
|
||||
{
|
||||
Toast.MakeText(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, ToastLength.Long).Show();
|
||||
@ -558,6 +614,19 @@ namespace keepass2android
|
||||
State.EntryModified = true;
|
||||
PopulateBinaries();
|
||||
}
|
||||
public static byte[] ReadFully(Stream input)
|
||||
{
|
||||
byte[] buffer = new byte[16 * 1024];
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
int read;
|
||||
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
ms.Write(buffer, 0, read);
|
||||
}
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSaveInstanceState(Bundle outState)
|
||||
{
|
||||
@ -637,20 +706,23 @@ namespace keepass2android
|
||||
Reload();
|
||||
break;
|
||||
case Result.Ok:
|
||||
if (requestCode == Intents.RequestCodeFileBrowseForBinary)
|
||||
if (requestCode == Intents.RequestCodeFileBrowseForBinary)
|
||||
{
|
||||
Uri uri = data.Data;
|
||||
if (data.Data == null)
|
||||
{
|
||||
string filename = Util.IntentToFilename(data, this);
|
||||
if (filename != null) {
|
||||
if (filename.StartsWith("file://")) {
|
||||
filename = filename.Substring(7);
|
||||
filename = Java.Net.URLDecoder.Decode(filename);
|
||||
}
|
||||
|
||||
|
||||
AddBinaryOrAsk(filename);
|
||||
string s = Util.GetFilenameFromInternalFileChooser(data, this);
|
||||
if (s == null)
|
||||
{
|
||||
Toast.MakeText(this, "No URI retrieved.", ToastLength.Short).Show();
|
||||
return;
|
||||
}
|
||||
uri = Uri.Parse(s);
|
||||
}
|
||||
Reload();
|
||||
AddBinaryOrAsk(uri);
|
||||
|
||||
}
|
||||
Reload();
|
||||
|
||||
|
||||
break;
|
||||
@ -695,7 +767,7 @@ namespace keepass2android
|
||||
addBinaryButton.Enabled = !State.Entry.Binaries.Any();
|
||||
addBinaryButton.Click += (sender, e) =>
|
||||
{
|
||||
Util.ShowBrowseDialog(this, Intents.RequestCodeFileBrowseForBinary, false);
|
||||
Util.ShowBrowseDialog(this, Intents.RequestCodeFileBrowseForBinary, false, false);
|
||||
|
||||
};
|
||||
binariesGroup.AddView(addBinaryButton,layoutParams);
|
||||
|
@ -39,11 +39,25 @@ namespace keepass2android
|
||||
//show all supported protocols:
|
||||
foreach (IFileStorage fs in App.Kp2a.FileStorages)
|
||||
_protocolIds.AddRange(fs.SupportedProtocols);
|
||||
//put file:// to the top
|
||||
_protocolIds.Remove("file");
|
||||
_protocolIds.Insert(0, "file");
|
||||
//remove "content" (covered by androidget)
|
||||
_protocolIds.Remove("content");
|
||||
|
||||
//special handling for local files:
|
||||
if (!Util.IsKitKatOrLater)
|
||||
{
|
||||
//put file:// to the top
|
||||
_protocolIds.Remove("file");
|
||||
_protocolIds.Insert(0, "file");
|
||||
|
||||
//remove "content" (covered by androidget)
|
||||
//On KitKat, content is handled by AndroidContentStorage taking advantage
|
||||
//of persistable permissions and ACTION_OPEN/CREATE_DOCUMENT
|
||||
_protocolIds.Remove("content");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_protocolIds.Remove("file");
|
||||
}
|
||||
|
||||
|
||||
if (context.Intent.GetBooleanExtra(AllowThirdPartyAppGet, false))
|
||||
_protocolIds.Add("androidget");
|
||||
|
BIN
src/keepass2android/Resources/drawable/ic_storage_content.png
Normal file
BIN
src/keepass2android/Resources/drawable/ic_storage_content.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -426,6 +426,7 @@
|
||||
<string name="filestoragename_gdrive">Google Drive</string>
|
||||
<string name="filestoragename_skydrive">OneDrive</string>
|
||||
<string name="filestoragename_sftp">SFTP (SSH File Transfer)</string>
|
||||
<string name="filestoragename_content">System file picker</string>
|
||||
|
||||
<string name="filestorage_setup_title">File access initialization</string>
|
||||
|
||||
|
@ -113,9 +113,9 @@ namespace keepass2android
|
||||
), isForSave, browseRequestCode, protocolId);
|
||||
}
|
||||
|
||||
protected override void ShowAndroidBrowseDialog(int requestCode, bool isForSave)
|
||||
protected override void ShowAndroidBrowseDialog(int requestCode, bool isForSave, bool tryGetPermanentAccess)
|
||||
{
|
||||
Util.ShowBrowseDialog(this, requestCode, isForSave);
|
||||
Util.ShowBrowseDialog(this, requestCode, isForSave, tryGetPermanentAccess);
|
||||
}
|
||||
|
||||
protected override bool IsStorageSelectionForSave
|
||||
@ -232,6 +232,12 @@ namespace keepass2android
|
||||
// ReturnCancel();
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
Kp2aLog.Log("onAR");
|
||||
base.OnActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ using System.IO;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Database;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Android.Provider;
|
||||
using Android.Views;
|
||||
@ -142,25 +143,52 @@ namespace keepass2android
|
||||
return list.Count > 0;
|
||||
}
|
||||
|
||||
public static void ShowBrowseDialog(Activity act, int requestCodeBrowse, bool forSaving)
|
||||
/// <summary>
|
||||
/// Opens a browse dialog for selecting a file.
|
||||
/// </summary>
|
||||
/// <param name="activity">context activity</param>
|
||||
/// <param name="requestCodeBrowse">requestCode for onActivityResult</param>
|
||||
/// <param name="forSaving">if true, the file location is meant for saving</param>
|
||||
/// <param name="tryGetPermanentAccess">if true, the caller prefers a location that can be used permanently
|
||||
/// This means that ActionOpenDocument should be used instead of ActionGetContent (for not saving), as ActionGetContent
|
||||
/// is more for one-time access, but therefore allows possibly more available sources.</param>
|
||||
public static void ShowBrowseDialog(Activity activity, int requestCodeBrowse, bool forSaving, bool tryGetPermanentAccess)
|
||||
{
|
||||
if ((!forSaving) && (IsIntentAvailable(act, Intent.ActionGetContent, "*/*", new List<string> { Intent.CategoryOpenable})))
|
||||
var loadAction = (tryGetPermanentAccess && IsKitKatOrLater) ?
|
||||
Intent.ActionOpenDocument : Intent.ActionGetContent;
|
||||
if ((!forSaving) && (IsIntentAvailable(activity, loadAction, "*/*", new List<string> { Intent.CategoryOpenable})))
|
||||
{
|
||||
Intent i = new Intent(Intent.ActionGetContent);
|
||||
Intent i = new Intent(loadAction);
|
||||
i.SetType("*/*");
|
||||
i.AddCategory(Intent.CategoryOpenable);
|
||||
|
||||
act.StartActivityForResult(i, requestCodeBrowse);
|
||||
activity.StartActivityForResult(i, requestCodeBrowse);
|
||||
}
|
||||
else
|
||||
{
|
||||
string defaultPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
|
||||
if ((forSaving) && (IsKitKatOrLater))
|
||||
{
|
||||
Intent i = new Intent(Intent.ActionCreateDocument);
|
||||
i.SetType("*/*");
|
||||
i.AddCategory(Intent.CategoryOpenable);
|
||||
|
||||
activity.StartActivityForResult(i, requestCodeBrowse);
|
||||
}
|
||||
else
|
||||
{
|
||||
string defaultPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath;
|
||||
|
||||
ShowInternalLocalFileChooser(act, requestCodeBrowse, forSaving, defaultPath);
|
||||
ShowInternalLocalFileChooser(activity, requestCodeBrowse, forSaving, defaultPath);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsKitKatOrLater
|
||||
{
|
||||
get { return (int)Build.VERSION.SdkInt >= 19; }
|
||||
}
|
||||
|
||||
private static void ShowInternalLocalFileChooser(Activity act, int requestCodeBrowse, bool forSaving, string defaultPath)
|
||||
{
|
||||
|
||||
@ -179,18 +207,17 @@ namespace keepass2android
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to extract the filename from the intent. Returns that filename or null if no success
|
||||
/// (e.g. on content-URIs in Android KitKat+).
|
||||
/// Guarantees that the file exists.
|
||||
/// </summary>
|
||||
public static string IntentToFilename(Intent data, Context ctx)
|
||||
{
|
||||
#if !EXCLUDE_FILECHOOSER
|
||||
string EXTRA_RESULTS = "group.pals.android.lib.ui.filechooser.FileChooserActivity.results";
|
||||
if (data.HasExtra(EXTRA_RESULTS))
|
||||
{
|
||||
IList uris = data.GetParcelableArrayListExtra(EXTRA_RESULTS);
|
||||
Uri uri = (Uri) uris[0];
|
||||
return Group.Pals.Android.Lib.UI.Filechooser.Providers.BaseFileProviderUtils.GetRealUri(ctx, uri).ToString();
|
||||
}
|
||||
string s = GetFilenameFromInternalFileChooser(data, ctx);
|
||||
if (!String.IsNullOrEmpty(s))
|
||||
return s;
|
||||
|
||||
#endif
|
||||
try
|
||||
{
|
||||
Uri uri = data.Data;
|
||||
@ -214,10 +241,30 @@ namespace keepass2android
|
||||
String filename = data.Data.Path;
|
||||
if ((String.IsNullOrEmpty(filename) || (!File.Exists(filename))))
|
||||
filename = data.DataString;
|
||||
return filename;
|
||||
if (File.Exists(filename))
|
||||
return filename;
|
||||
//found no valid file
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static string GetFilenameFromInternalFileChooser(Intent data, Context ctx)
|
||||
{
|
||||
#if !EXCLUDE_FILECHOOSER
|
||||
string EXTRA_RESULTS = "group.pals.android.lib.ui.filechooser.FileChooserActivity.results";
|
||||
if (data.HasExtra(EXTRA_RESULTS))
|
||||
{
|
||||
IList uris = data.GetParcelableArrayListExtra(EXTRA_RESULTS);
|
||||
Uri uri = (Uri) uris[0];
|
||||
{
|
||||
return Group.Pals.Android.Lib.UI.Filechooser.Providers.BaseFileProviderUtils.GetRealUri(ctx, uri).ToString();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static bool HasActionBar(Activity activity)
|
||||
{
|
||||
//Actionbar is available since 11, but the layout has its own "pseudo actionbar" until 13
|
||||
@ -336,11 +383,6 @@ namespace keepass2android
|
||||
onCancel();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button);
|
||||
if (!showBrowseButton)
|
||||
{
|
||||
@ -350,7 +392,7 @@ namespace keepass2android
|
||||
{
|
||||
string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text;
|
||||
|
||||
Util.ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null);
|
||||
Util.ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null, /*TODO should we prefer ActionOpenDocument here?*/ false);
|
||||
|
||||
};
|
||||
|
||||
|
@ -450,6 +450,8 @@ namespace keepass2android
|
||||
{
|
||||
_fileStorages = new List<IFileStorage>
|
||||
{
|
||||
|
||||
new AndroidContentStorage(Application.Context),
|
||||
#if !EXCLUDE_JAVAFILESTORAGE
|
||||
#if !NoNet
|
||||
new DropboxFileStorage(Application.Context, this),
|
||||
@ -459,8 +461,7 @@ namespace keepass2android
|
||||
new SftpFileStorage(this),
|
||||
#endif
|
||||
#endif
|
||||
new BuiltInFileStorage(this),
|
||||
new AndroidContentStorage(Application.Context)
|
||||
new BuiltInFileStorage(this)
|
||||
};
|
||||
}
|
||||
return _fileStorages;
|
||||
|
@ -35,8 +35,17 @@
|
||||
<Command type="BeforeBuild" command="UseManifestDebug.bat" />
|
||||
</CustomCommands>
|
||||
</CustomCommands>
|
||||
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
|
||||
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
|
||||
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
|
||||
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
|
||||
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
|
||||
<AndroidLinkSkip />
|
||||
<BundleAssemblies>False</BundleAssemblies>
|
||||
<AndroidCreatePackagePerAbi>False</AndroidCreatePackagePerAbi>
|
||||
<AndroidStoreUncompressedFileExtensions />
|
||||
<MandroidI18n />
|
||||
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
|
||||
<JavaOptions />
|
||||
<MonoDroidExtraArgs />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>none</DebugType>
|
||||
@ -686,7 +695,6 @@
|
||||
<ProjectReference Include="..\Kp2aBusinessLogic\Kp2aBusinessLogic.csproj">
|
||||
<Project>{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}</Project>
|
||||
<Name>Kp2aBusinessLogic</Name>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj">
|
||||
<Project>{70D3844A-D9FA-4A64-B205-A84C6A822196}</Project>
|
||||
@ -1090,4 +1098,7 @@
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\ic_notify_locked.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\ic_storage_content.png" />
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
Reference in New Issue
Block a user