Key files can be opened from deliberate locations

TODO: fix a problem with .kdb-files and key files

Added very basic and not yet functional AndroidContentStorage.cs
This commit is contained in:
Philipp Crocoll 2014-11-08 21:29:36 +01:00
parent 2e4c3e3490
commit 3239131a84
24 changed files with 3636 additions and 3450 deletions

2
.gitignore vendored
View File

@ -282,3 +282,5 @@ Thumbs.db
/src/java/MasterKee /src/java/MasterKee
/src/PluginSdkBinding/obj/ReleaseNoNet /src/PluginSdkBinding/obj/ReleaseNoNet
/src/MasterKeeWinPlugin/bin/Release
/src/SamplePlugin

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>False</Optimize> <Optimize>False</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>

View File

@ -41,7 +41,7 @@ namespace KeePassLib.Keys
/// </summary> /// </summary>
public sealed class KcpKeyFile : IUserKey public sealed class KcpKeyFile : IUserKey
{ {
private string m_strPath; private IOConnectionInfo m_ioc;
private ProtectedBinary m_pbKeyData; private ProtectedBinary m_pbKeyData;
/// <summary> /// <summary>
@ -49,7 +49,7 @@ namespace KeePassLib.Keys
/// </summary> /// </summary>
public string Path public string Path
{ {
get { return m_strPath; } get { return m_ioc.Path; }
} }
/// <summary> /// <summary>
@ -62,6 +62,11 @@ namespace KeePassLib.Keys
get { return m_pbKeyData; } get { return m_pbKeyData; }
} }
public IOConnectionInfo Ioc
{
get { return m_ioc; }
}
public KcpKeyFile(string strKeyFile) public KcpKeyFile(string strKeyFile)
{ {
Construct(IOConnectionInfo.FromPath(strKeyFile), false); Construct(IOConnectionInfo.FromPath(strKeyFile), false);
@ -82,17 +87,21 @@ namespace KeePassLib.Keys
Construct(iocKeyFile, bThrowIfDbFile); Construct(iocKeyFile, bThrowIfDbFile);
} }
private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile) public KcpKeyFile(byte[] keyFileContents, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile)
{ {
byte[] pbFileData = IOConnection.ReadFile(iocFile); Construct(keyFileContents, iocKeyFile, bThrowIfDbFile);
if(pbFileData == null) throw new Java.IO.FileNotFoundException(); }
if(bThrowIfDbFile && (pbFileData.Length >= 8)) private void Construct(byte[] pbFileData, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile)
{
if (pbFileData == null) throw new Java.IO.FileNotFoundException();
if (bThrowIfDbFile && (pbFileData.Length >= 8))
{ {
uint uSig1 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 0, 4)); uint uSig1 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 0, 4));
uint uSig2 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 4, 4)); uint uSig2 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 4, 4));
if(((uSig1 == KdbxFile.FileSignature1) && if (((uSig1 == KdbxFile.FileSignature1) &&
(uSig2 == KdbxFile.FileSignature2)) || (uSig2 == KdbxFile.FileSignature2)) ||
((uSig1 == KdbxFile.FileSignaturePreRelease1) && ((uSig1 == KdbxFile.FileSignaturePreRelease1) &&
(uSig2 == KdbxFile.FileSignaturePreRelease2)) || (uSig2 == KdbxFile.FileSignaturePreRelease2)) ||
@ -106,16 +115,22 @@ namespace KeePassLib.Keys
} }
byte[] pbKey = LoadXmlKeyFile(pbFileData); byte[] pbKey = LoadXmlKeyFile(pbFileData);
if(pbKey == null) pbKey = LoadKeyFile(pbFileData); if (pbKey == null) pbKey = LoadKeyFile(pbFileData);
if(pbKey == null) throw new InvalidOperationException(); if (pbKey == null) throw new InvalidOperationException();
m_strPath = iocFile.Path; m_ioc = iocKeyFile;
m_pbKeyData = new ProtectedBinary(true, pbKey); m_pbKeyData = new ProtectedBinary(true, pbKey);
MemUtil.ZeroByteArray(pbKey); MemUtil.ZeroByteArray(pbKey);
} }
private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile)
{
byte[] pbFileData = IOConnection.ReadFile(iocFile);
Construct(pbFileData, iocFile, bThrowIfDbFile);
}
// public void Clear() // public void Clear()
// { // {
// m_strPath = string.Empty; // m_strPath = string.Empty;

View File

@ -0,0 +1,183 @@
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 KeePassLib.Serialization;
namespace keepass2android.Io
{
//TODOC,TOTEST, TODO: unimplemented methods?
public class AndroidContentStorage: IFileStorage
{
private readonly Context _ctx;
public AndroidContentStorage(Context ctx)
{
_ctx = ctx;
}
public IEnumerable<string> SupportedProtocols
{
get { yield return "content"; }
}
public void Delete(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
return false;
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
return null;
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
return _ctx.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(ioc.Path));
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new AndroidContentWriteTransaction(ioc.Path, _ctx);
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return "";
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
return false;
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw new NotImplementedException();
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
Intent intent = new Intent();
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId + "://" });
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileChooserPrepared, intent);
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
Intent intent = new Intent();
activity.IocToIntent(intent, ioc);
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
throw new NotImplementedException();
}
public void OnResume(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnStart(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
throw new NotImplementedException();
}
public string GetDisplayName(IOConnectionInfo ioc)
{
return ioc.Path;
}
public string CreateFilePath(string parent, string newFilename)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
throw new NotImplementedException();
}
}
class AndroidContentWriteTransaction : IWriteTransaction
{
private readonly string _path;
private readonly Context _ctx;
private MemoryStream _memoryStream;
public AndroidContentWriteTransaction(string path, Context ctx)
{
_path = path;
_ctx = ctx;
}
public void Dispose()
{
_memoryStream.Dispose();
}
public Stream OpenFile()
{
_memoryStream = new MemoryStream();
return _memoryStream;
}
public void CommitWrite()
{
using (Stream outputStream = _ctx.ContentResolver.OpenOutputStream(Android.Net.Uri.Parse(_path)))
{
outputStream.Write(_memoryStream.ToArray(), 0, (int)_memoryStream.Length);
}
}
}
}

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
@ -64,6 +64,7 @@
<Compile Include="DataExchange\Formats\KeePassKdb2x.cs" /> <Compile Include="DataExchange\Formats\KeePassKdb2x.cs" />
<Compile Include="DataExchange\Formats\KeePassXml2x.cs" /> <Compile Include="DataExchange\Formats\KeePassXml2x.cs" />
<Compile Include="DataExchange\PwExportInfo.cs" /> <Compile Include="DataExchange\PwExportInfo.cs" />
<Compile Include="Io\AndroidContentStorage.cs" />
<Compile Include="Io\BuiltInFileStorage.cs" /> <Compile Include="Io\BuiltInFileStorage.cs" />
<Compile Include="Io\CachingFileStorage.cs" /> <Compile Include="Io\CachingFileStorage.cs" />
<Compile Include="Io\DropboxFileStorage.cs" /> <Compile Include="Io\DropboxFileStorage.cs" />

View File

@ -11,5 +11,5 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target. # Project target.
target=android-20 target=android-19
android.library=true android.library=true

View File

@ -47,10 +47,14 @@ package com.keepassdroid.database;
// Java // Java
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -125,18 +129,18 @@ public class PwDatabaseV3 {
public void setMasterKey(String key, String keyFileName) public void setMasterKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert( key != null && keyFileName != null ); assert( key != null && keyfileStream != null );
masterKey = getMasterKey(key, keyFileName); masterKey = getMasterKey(key, keyfileStream);
} }
protected byte[] getCompositeKey(String key, String keyFileName) protected byte[] getCompositeKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert(key != null && keyFileName != null); assert(key != null && keyfileStream != null);
byte[] fileKey = getFileKey(keyFileName); byte[] fileKey = getFileKey(keyfileStream);
byte[] passwordKey = getPasswordKey(key); byte[] passwordKey = getPasswordKey(key);
@ -152,45 +156,39 @@ public class PwDatabaseV3 {
return md.digest(fileKey); return md.digest(fileKey);
} }
protected byte[] getFileKey(String fileName) protected byte[] getFileKey(InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert(fileName != null); assert(keyfileStream != null);
File keyfile = new File(fileName);
if ( ! keyfile.exists() ) { byte[] buff = new byte[8000];
throw new InvalidKeyFileException();
int bytesRead = 0;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
while ((bytesRead = keyfileStream.read(buff)) != -1) {
bao.write(buff, 0, bytesRead);
} }
byte[] key = loadXmlKeyFile(fileName); byte[] keyFileData = bao.toByteArray();
if ( key != null ) {
return key;
}
FileInputStream fis; ByteArrayInputStream bin = new ByteArrayInputStream(keyFileData);
try {
fis = new FileInputStream(keyfile);
} catch (FileNotFoundException e) {
throw new InvalidKeyFileException();
}
BufferedInputStream bis = new BufferedInputStream(fis, 64);
long fileSize = keyfile.length();
if ( fileSize == 0 ) { if ( keyFileData.length == 32 ) {
throw new KeyFileEmptyException();
} else if ( fileSize == 32 ) {
byte[] outputKey = new byte[32]; byte[] outputKey = new byte[32];
if ( bis.read(outputKey, 0, 32) != 32 ) { if ( bin.read(outputKey, 0, 32) != 32 ) {
throw new IOException("Error reading key."); throw new IOException("Error reading key.");
} }
return outputKey; return outputKey;
} else if ( fileSize == 64 ) { } else if ( keyFileData.length == 64 ) {
byte[] hex = new byte[64]; byte[] hex = new byte[64];
bis.mark(64); bin.mark(64);
if ( bis.read(hex, 0, 64) != 64 ) { if ( bin.read(hex, 0, 64) != 64 ) {
throw new IOException("Error reading key."); throw new IOException("Error reading key.");
} }
@ -198,7 +196,7 @@ public class PwDatabaseV3 {
return hexStringToByteArray(new String(hex)); return hexStringToByteArray(new String(hex));
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// Key is not base 64, treat it as binary data // Key is not base 64, treat it as binary data
bis.reset(); bin.reset();
} }
} }
@ -214,7 +212,7 @@ public class PwDatabaseV3 {
try { try {
while (true) { while (true) {
int bytesRead = bis.read(buffer, 0, 2048); bytesRead = bin.read(buffer, 0, 2048);
if ( bytesRead == -1 ) break; // End of file if ( bytesRead == -1 ) break; // End of file
md.update(buffer, 0, bytesRead); md.update(buffer, 0, bytesRead);
@ -495,16 +493,16 @@ public class PwDatabaseV3 {
return newId; return newId;
} }
public byte[] getMasterKey(String key, String keyFileName) public byte[] getMasterKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert (key != null && keyFileName != null); assert (key != null && keyfileStream != null);
if (key.length() > 0 && keyFileName.length() > 0) { if (key.length() > 0 && keyfileStream != null) {
return getCompositeKey(key, keyFileName); return getCompositeKey(key, keyfileStream);
} else if (key.length() > 0) { } else if (key.length() > 0) {
return getPasswordKey(key); return getPasswordKey(key);
} else if (keyFileName.length() > 0) { } else if (keyfileStream != null) {
return getFileKey(keyFileName); return getFileKey(keyfileStream);
} else { } else {
throw new IllegalArgumentException("Key cannot be empty."); throw new IllegalArgumentException("Key cannot be empty.");
} }
@ -515,11 +513,6 @@ public class PwDatabaseV3 {
return getPasswordKey(key, "ISO-8859-1"); return getPasswordKey(key, "ISO-8859-1");
} }
protected byte[] loadXmlKeyFile(String fileName) {
return null;
}
public long getNumRounds() { public long getNumRounds() {
return numKeyEncRounds; return numKeyEncRounds;

View File

@ -123,13 +123,13 @@ public class ImporterV3 {
* @throws InvalidAlgorithmParameterException if error decrypting main file body. * @throws InvalidAlgorithmParameterException if error decrypting main file body.
* @throws ShortBufferException if error decrypting main file body. * @throws ShortBufferException if error decrypting main file body.
*/ */
public PwDatabaseV3 openDatabase( InputStream inStream, String password, String keyfile ) public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream keyfileStream )
throws IOException, InvalidDBException throws IOException, InvalidDBException
{ {
return openDatabase(inStream, password, keyfile, new UpdateStatus()); return openDatabase(inStream, password, keyfileStream, new UpdateStatus());
} }
public PwDatabaseV3 openDatabase( InputStream inStream, String password, String keyfile, UpdateStatus status ) public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream keyfileStream, UpdateStatus status )
throws IOException, InvalidDBException throws IOException, InvalidDBException
{ {
PwDatabaseV3 newManager; PwDatabaseV3 newManager;
@ -175,7 +175,7 @@ public class ImporterV3 {
} }
newManager = createDB(); newManager = createDB();
newManager.setMasterKey( password, keyfile ); newManager.setMasterKey( password, keyfileStream );
// Select algorithm // Select algorithm
if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) { if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) {

View File

@ -327,9 +327,9 @@ namespace keepass2android
defaultPath => defaultPath =>
{ {
if (defaultPath.StartsWith("sftp://")) if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceiveSftpData); Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
else else
Util.ShowFilenameDialog(this, OnCreateButton, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url), Util.ShowFilenameDialog(this, OnCreateButton, null, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen); Intents.RequestCodeFileBrowseForOpen);
} }
), true, RequestCodeDbFilename, protocolId); ), true, RequestCodeDbFilename, protocolId);

View File

@ -1,4 +1,4 @@
/* /*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify Keepass2Android is free software: you can redistribute it and/or modify
@ -682,7 +682,7 @@ namespace keepass2android
addBinaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuAdd) , null, null, null); addBinaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuAdd) , null, null, null);
addBinaryButton.Click += (sender, e) => addBinaryButton.Click += (sender, e) =>
{ {
Util.ShowBrowseDialog("/mnt/sdcard", this, Intents.RequestCodeFileBrowseForBinary, false); Util.ShowBrowseDialog(this, Intents.RequestCodeFileBrowseForBinary, false);
}; };
binariesGroup.AddView(addBinaryButton,layoutParams); binariesGroup.AddView(addBinaryButton,layoutParams);

View File

@ -61,9 +61,9 @@ namespace keepass2android
defaultPath => defaultPath =>
{ {
if (defaultPath.StartsWith("sftp://")) if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceiveSftpData); Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
else else
Util.ShowFilenameDialog(this, OnCreateButton, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url), Util.ShowFilenameDialog(this, OnCreateButton, null, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen); Intents.RequestCodeFileBrowseForOpen);
} }
), true, RequestCodeDbFilename, protocolId); ), true, RequestCodeDbFilename, protocolId);

View File

@ -14,7 +14,7 @@ namespace keepass2android
[Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden , Theme="@style/NoTitleBar")] [Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden , Theme="@style/NoTitleBar")]
public class FileStorageSelectionActivity : ListActivity public class FileStorageSelectionActivity : ListActivity
{ {
private ActivityDesign _design; private readonly ActivityDesign _design;
private FileStorageAdapter _fileStorageAdapter; private FileStorageAdapter _fileStorageAdapter;
@ -42,6 +42,9 @@ namespace keepass2android
//put file:// to the top //put file:// to the top
_protocolIds.Remove("file"); _protocolIds.Remove("file");
_protocolIds.Insert(0, "file"); _protocolIds.Insert(0, "file");
//remove "content" (covered by androidget)
_protocolIds.Remove("content");
if (context.Intent.GetBooleanExtra(AllowThirdPartyAppGet, false)) if (context.Intent.GetBooleanExtra(AllowThirdPartyAppGet, false))
_protocolIds.Add("androidget"); _protocolIds.Add("androidget");
if (context.Intent.GetBooleanExtra(AllowThirdPartyAppSend, false)) if (context.Intent.GetBooleanExtra(AllowThirdPartyAppSend, false))

View File

@ -24,6 +24,7 @@ using System.Xml.Serialization;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Database; using Android.Database;
using Android.Graphics.Drawables;
using Android.OS; using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
@ -86,7 +87,7 @@ namespace keepass2android
private const int RequestCodePrepareDbFile = 1000; private const int RequestCodePrepareDbFile = 1000;
private const int RequestCodePrepareOtpAuxFile = 1001; private const int RequestCodePrepareOtpAuxFile = 1001;
private const int RequestCodeChallengeYubikey = 1002; private const int RequestCodeChallengeYubikey = 1002;
private const int RequestCodeSelectKeyfile = 1003;
private Task<MemoryStream> _loadDbTask; private Task<MemoryStream> _loadDbTask;
private IOConnectionInfo _ioConnection; private IOConnectionInfo _ioConnection;
@ -137,6 +138,7 @@ namespace keepass2android
private ActivityDesign _design; private ActivityDesign _design;
private bool _performingLoad; private bool _performingLoad;
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer) public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) : base(javaReference, transfer)
{ {
@ -258,26 +260,20 @@ namespace keepass2android
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile)); KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path); FindViewById<TextView>(Resource.Id.label_keyfilename).Text =
App.Kp2a.GetFileStorage(kcpKeyfile.Ioc).GetDisplayName(kcpKeyfile.Ioc);
} }
} }
App.Kp2a.LockDatabase(false); App.Kp2a.LockDatabase(false);
break; break;
case Result.Ok: // Key file browse dialog OK'ed. case Result.Ok:
if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) { if (requestCode == RequestCodeSelectKeyfile)
string filename = Util.IntentToFilename(data, this); {
if (filename != null) { IOConnectionInfo ioc = new IOConnectionInfo();
if (filename.StartsWith("file://")) { SetIoConnectionFromIntent(ioc, data);
filename = filename.Substring(7); _keyFileOrProvider = IOConnectionInfo.SerializeToString(ioc);
} UpdateKeyfileIocView();
filename = URLDecoder.Decode(filename);
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
fn.Text = filename;
}
} }
break; break;
case (Result)FileStorageResults.FileUsagePrepared: case (Result)FileStorageResults.FileUsagePrepared:
@ -354,6 +350,39 @@ namespace keepass2android
} }
private void UpdateKeyfileIocView()
{
//store keyfile in the view so that we can show the selected keyfile again if the user switches to another key provider and back to key file
FindViewById<TextView>(Resource.Id.label_keyfilename).Tag = _keyFileOrProvider;
if (string.IsNullOrEmpty(_keyFileOrProvider))
{
FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Gone;
FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Gone;
FindViewById<TextView>(Resource.Id.label_keyfilename).Text = Resources.GetString(Resource.String.no_keyfile_selected);
return;
}
var ioc = IOConnectionInfo.UnserializeFromString(_keyFileOrProvider);
string displayPath = App.Kp2a.GetFileStorage(ioc).GetDisplayName(ioc);
int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
string protocolId = protocolSeparatorPos < 0 ?
"file" : displayPath.Substring(0, protocolSeparatorPos);
Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId);
FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Visible;
String title = App.Kp2a.GetResourceString("filestoragename_" + protocolId);
FindViewById<TextView>(Resource.Id.filestorage_label).Text = title;
FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Visible;
FindViewById<TextView>(Resource.Id.label_keyfilename).Text = protocolSeparatorPos < 0 ?
displayPath :
displayPath.Substring(protocolSeparatorPos + 3);
}
private void LoadOtpFile() private void LoadOtpFile()
{ {
new LoadingDialog<object, object, object>(this, true, new LoadingDialog<object, object, object>(this, true,
@ -543,14 +572,11 @@ namespace keepass2android
InitializeFilenameView(); InitializeFilenameView();
if (KeyProviderType == KeyProviders.KeyFile) if (KeyProviderType == KeyProviders.KeyFile)
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
(sender, args) =>
{ {
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text; UpdateKeyfileIocView();
UpdateOkButtonState(); }
};
FindViewById<EditText>(Resource.Id.password).TextChanged += FindViewById<EditText>(Resource.Id.password).TextChanged +=
(sender, args) => (sender, args) =>
@ -705,20 +731,14 @@ namespace keepass2android
private void InitializeKeyfileBrowseButton() private void InitializeKeyfileBrowseButton()
{ {
ImageButton browse = (ImageButton) FindViewById(Resource.Id.browse_button); var browseButton = (Button)FindViewById(Resource.Id.btn_change_location);
browse.Click += (sender, evt) => browseButton.Click += (sender, evt) =>
{ {
string filename = null; Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
if (!String.IsNullOrEmpty(_ioConnection.Path)) intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
{ intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
File keyfile = new File(_ioConnection.Path); intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
File parent = keyfile.ParentFile; StartActivityForResult(intent, RequestCodeSelectKeyfile);
if (parent != null)
{
filename = parent.AbsolutePath;
}
}
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
}; };
} }
@ -738,7 +758,7 @@ namespace keepass2android
break; break;
case 1: case 1:
//don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!) //don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!)
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text; _keyFileOrProvider = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString();
break; break;
case 2: case 2:
_keyFileOrProvider = KeyProviderIdOtp; _keyFileOrProvider = KeyProviderIdOtp;
@ -779,7 +799,7 @@ namespace keepass2android
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false); _showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
MakePasswordMaskedOrVisible(); MakePasswordMaskedOrVisible();
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text = savedInstanceState.GetString(KeyFileOrProviderKey); _keyFileOrProvider = savedInstanceState.GetString(KeyFileOrProviderKey);
_password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey); _password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey);
_pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey)); _pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));
@ -850,6 +870,11 @@ namespace keepass2android
FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderType == KeyProviders.KeyFile FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderType == KeyProviders.KeyFile
? ViewStates.Visible ? ViewStates.Visible
: ViewStates.Gone; : ViewStates.Gone;
if (KeyProviderType == KeyProviders.KeyFile)
{
UpdateKeyfileIocView();
}
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
? ViewStates.Visible ? ViewStates.Visible
: ViewStates.Gone; : ViewStates.Gone;
@ -880,12 +905,26 @@ namespace keepass2android
{ {
try try
{ {
compositeKey.AddUserKey(new KcpKeyFile(_keyFileOrProvider)); if (_keyFileOrProvider == "")
throw new System.IO.FileNotFoundException();
var ioc = IOConnectionInfo.UnserializeFromString(_keyFileOrProvider);
using (var stream = App.Kp2a.GetFileStorage(ioc).OpenFileForRead(ioc))
{
byte[] keyfileData = StreamToMemoryStream(stream).ToArray();
compositeKey.AddUserKey(new KcpKeyFile(keyfileData, ioc, true));
}
}
catch (System.IO.FileNotFoundException e)
{
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show();
return;
} }
catch (Exception e) catch (Exception e)
{ {
Kp2aLog.Log(e.ToString()); Kp2aLog.Log(e.ToString());
Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show(); Toast.MakeText(this, e.Message, ToastLength.Long).Show();
return; return;
} }
} }
@ -1032,23 +1071,29 @@ namespace keepass2android
var fileStorage = App.Kp2a.GetFileStorage(_ioConnection); var fileStorage = App.Kp2a.GetFileStorage(_ioConnection);
var stream = fileStorage.OpenFileForRead(_ioConnection); var stream = fileStorage.OpenFileForRead(_ioConnection);
var memoryStream = StreamToMemoryStream(stream);
Kp2aLog.Log("Pre-loading database file completed");
return memoryStream;
}
private static MemoryStream StreamToMemoryStream(Stream stream)
{
var memoryStream = stream as MemoryStream; var memoryStream = stream as MemoryStream;
if (memoryStream == null) if (memoryStream == null)
{ {
// Read the file into memory // Read the stream into memory
int capacity = 4096; // Default initial capacity, if stream can't report it. int capacity = 4096; // Default initial capacity, if stream can't report it.
if (stream.CanSeek) if (stream.CanSeek)
{ {
capacity = (int)stream.Length; capacity = (int) stream.Length;
} }
memoryStream = new MemoryStream(capacity); memoryStream = new MemoryStream(capacity);
stream.CopyTo(memoryStream); stream.CopyTo(memoryStream);
stream.Close(); stream.Close();
memoryStream.Seek(0, System.IO.SeekOrigin.Begin); memoryStream.Seek(0, SeekOrigin.Begin);
} }
Kp2aLog.Log("Pre-loading database file completed");
return memoryStream; return memoryStream;
} }

File diff suppressed because it is too large Load Diff

View File

@ -86,25 +86,61 @@ android:layout_height="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_menu_view" /> android:src="@drawable/ic_menu_view" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/keyfileLine" android:id="@+id/keyfileLine"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false" android:baselineAligned="false"
android:orientation="horizontal"> android:orientation="vertical">
<EditText
android:id="@+id/pass_keyfile" <TextView
android:layout_width="0px" android:id="@+id/keyfile_heading"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight="1"
android:layout_gravity="bottom"
android:hint="@string/entry_keyfile" />
<ImageButton
android:id="@+id/browse_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_folder_small" /> android:text="@string/keyfile_heading" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/filestorage_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_storage_file"
android:padding="5dp"
/>
<TextView
android:id="@+id/filestorage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Local file (TODO!)"
android:textSize="16dp" >
</TextView>
</LinearLayout>
<TextView
android:id="@+id/label_keyfilename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="[path]"
android:layout_marginLeft="18dp"
/>
<Button android:id="@+id/btn_change_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_change_location"
style="@style/TextAppearance_SubElement"
/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/otpView" android:id="@+id/otpView"

View File

@ -32,6 +32,8 @@
<string name="ShowGroupInEntry_title">Show group name in entry view</string> <string name="ShowGroupInEntry_title">Show group name in entry view</string>
<string name="unknown_uri_scheme">Sorry! Keepass2Android cannot handle the returned URI %1$s. Please contact the developer!</string>
<string name="security_prefs">Security</string> <string name="security_prefs">Security</string>
<string name="display_prefs">Display</string> <string name="display_prefs">Display</string>
<string name="password_access_prefs">Password entry access</string> <string name="password_access_prefs">Password entry access</string>
@ -68,6 +70,7 @@
<string name="entry_expires">Expires</string> <string name="entry_expires">Expires</string>
<string name="entry_group_name">Group Name</string> <string name="entry_group_name">Group Name</string>
<string name="entry_keyfile">Key file (optional)</string> <string name="entry_keyfile">Key file (optional)</string>
<string name="keyfile_heading">Key file</string>
<string name="entry_modified">Modified</string> <string name="entry_modified">Modified</string>
<string name="entry_password">Password</string> <string name="entry_password">Password</string>
<string name="entry_save">Save</string> <string name="entry_save">Save</string>
@ -114,6 +117,7 @@
<string name="invalid_algorithm">Invalid algorithm.</string> <string name="invalid_algorithm">Invalid algorithm.</string>
<string name="invalid_db_sig">Database format not recognized.</string> <string name="invalid_db_sig">Database format not recognized.</string>
<string name="keyfile_does_not_exist">Key file does not exist.</string> <string name="keyfile_does_not_exist">Key file does not exist.</string>
<string name="no_keyfile_selected">No key file selected.</string>
<string name="keyfile_is_empty">Key file is empty.</string> <string name="keyfile_is_empty">Key file is empty.</string>
<string name="length">Length</string> <string name="length">Length</string>
<string name="list_size_title">Group list size</string> <string name="list_size_title">Group list size</string>

View File

@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
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 KeePassLib.Serialization;
using keepass2android.Io;
using Environment = Android.OS.Environment;
namespace keepass2android
{
[Activity(Label = "")]
public class SelectStorageLocationActivity : Activity
{
private ActivityDesign _design;
private bool _isRecreated;
private const int RequestCodeFileStorageSelection = 983713;
public SelectStorageLocationActivity()
{
_design = new ActivityDesign(this);
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
_design.ApplyTheme();
Kp2aLog.Log("SelectStorageLocationActivity.OnCreate");
IsForSave = Intent.GetBooleanExtra(FileStorageSetupDefs.ExtraIsForSave, false);
if (IsForSave)
{
throw new Exception("save is not yet implemented. In StartSelectFile, no handler for onCreate is passed.");
}
bool allowThirdPartyGet = Intent.GetBooleanExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, false);
bool allowThirdPartySend = Intent.GetBooleanExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
if (bundle == null)
State = new Bundle();
else
{
State = (Bundle)bundle.Clone();
_isRecreated = true;
}
if (!_isRecreated)
{
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, allowThirdPartyGet);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, allowThirdPartySend);
StartActivityForResult(intent, RequestCodeFileStorageSelection);
}
}
protected Bundle State { get; set; }
protected bool IsForSave { get; set; }
protected override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutAll(State);
}
protected override void OnResume()
{
base.OnResume();
_design.ReapplyTheme();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == RequestCodeFileStorageSelection)
{
if (resultCode == KeePass.ExitFileStorageSelectionOk)
{
string protocolId = data.GetStringExtra("protocolId");
if (protocolId == "androidget")
{
Util.ShowBrowseDialog(this, Intents.RequestCodeFileBrowseForOpen, false);
}
else
{
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
OnActivityResult,
defaultPath =>
{
if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceivedSftpData, ReturnCancel);
else
Util.ShowFilenameDialog(this, OnOpenButton, null, ReturnCancel, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen);
}
), false, 0, protocolId);
}
}
else
{
if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
{
IOConnectionInfo ioc = new IOConnectionInfo();
PasswordActivity.SetIoConnectionFromIntent(ioc, data);
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
ReturnIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx" });
#endif
return;
}
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
{
Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show();
}
ReturnCancel();
}
}
if (requestCode == Intents.RequestCodeFileBrowseForOpen)
{
if (resultCode == Result.Ok)
{
string filename = Util.IntentToFilename(data, this);
if (filename != null)
{
if (filename.StartsWith("file://"))
{
filename = filename.Substring(7);
filename = Java.Net.URLDecoder.Decode(filename);
}
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = filename
};
ReturnIoc(ioc);
}
else
{
if (data.Data.Scheme == "content")
{
ReturnIoc(IOConnectionInfo.FromPath(data.DataString));
}
else
{
Toast.MakeText(this, Resources.GetString(Resource.String.unknown_uri_scheme, new Java.Lang.Object[] {data.DataString}),
ToastLength.Long).Show();
ReturnCancel();
}
}
}
else
{
ReturnCancel();
}
}
}
private void ReturnCancel()
{
SetResult(Result.Canceled);
Finish();
}
private void ReturnIoc(IOConnectionInfo ioc)
{
Intent intent = new Intent();
PasswordActivity.PutIoConnectionToIntent(ioc, intent);
SetResult(Result.Ok, intent);
Finish();
}
private bool OnReceivedSftpData(string filename)
{
IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
ReturnIoc(ioc);
#endif
return true;
}
#if !EXCLUDE_FILECHOOSER
private void StartFileChooser(string defaultPath)
{
Kp2aLog.Log("FSA: defaultPath="+defaultPath);
string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
if (defaultPath.StartsWith("file://"))
{
fileProviderAuthority = PackageName+".android-filechooser.localfile";
}
Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority,
defaultPath);
StartActivityForResult(i, Intents.RequestCodeFileBrowseForOpen);
}
#endif
private bool OnOpenButton(String fileName)
{
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = fileName
};
ReturnIoc(ioc);
return true;
}
}
}

View File

@ -141,7 +141,7 @@ namespace keepass2android
return list.Count > 0; return list.Count > 0;
} }
public static void ShowBrowseDialog(string filename, Activity act, int requestCodeBrowse, bool forSaving) public static void ShowBrowseDialog(Activity act, int requestCodeBrowse, bool forSaving)
{ {
if ((!forSaving) && (IsIntentAvailable(act, Intent.ActionGetContent, "*/*", new List<string> { Intent.CategoryOpenable}))) if ((!forSaving) && (IsIntentAvailable(act, Intent.ActionGetContent, "*/*", new List<string> { Intent.CategoryOpenable})))
{ {
@ -223,7 +223,7 @@ namespace keepass2android
public delegate bool FileSelectedHandler(string filename); public delegate bool FileSelectedHandler(string filename);
public static void ShowSftpDialog(Activity activity, FileSelectedHandler onStartBrowse) public static void ShowSftpDialog(Activity activity, FileSelectedHandler onStartBrowse, Action onCancel)
{ {
#if !EXCLUDE_JAVAFILESTORAGE #if !EXCLUDE_JAVAFILESTORAGE
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@ -244,7 +244,7 @@ namespace keepass2android
password); password);
onStartBrowse(sftpPath); onStartBrowse(sftpPath);
}); });
builder.SetNegativeButton(Android.Resource.String.Cancel, (sender, args) => {}); builder.SetNegativeButton(Android.Resource.String.Cancel, onCancel);
builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title)); builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title));
Dialog dialog = builder.Create(); Dialog dialog = builder.Create();
@ -252,8 +252,22 @@ namespace keepass2android
#endif #endif
} }
public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, bool showBrowseButton, class DismissListener: Java.Lang.Object, IDialogInterfaceOnDismissListener
string defaultFilename, string detailsText, int requestCodeBrowse) {
private readonly Action _onDismiss;
public DismissListener(Action onDismiss)
{
_onDismiss = onDismiss;
}
public void OnDismiss(IDialogInterface dialog)
{
_onDismiss();
}
}
public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, Action onCancel, bool showBrowseButton, string defaultFilename, string detailsText, int requestCodeBrowse)
{ {
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null)); builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null));
@ -262,6 +276,7 @@ namespace keepass2android
Button openButton = (Button) dialog.FindViewById(Resource.Id.open); Button openButton = (Button) dialog.FindViewById(Resource.Id.open);
Button createButton = (Button) dialog.FindViewById(Resource.Id.create); Button createButton = (Button) dialog.FindViewById(Resource.Id.create);
TextView enterFilenameDetails = (TextView) dialog.FindViewById(Resource.Id.label_open_by_filename_details); TextView enterFilenameDetails = (TextView) dialog.FindViewById(Resource.Id.label_open_by_filename_details);
openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone; openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone;
createButton.Visibility = onCreate != null? ViewStates.Visible : ViewStates.Gone; createButton.Visibility = onCreate != null? ViewStates.Visible : ViewStates.Gone;
@ -290,7 +305,15 @@ namespace keepass2android
}; };
Button cancelButton = (Button) dialog.FindViewById(Resource.Id.fnv_cancel); Button cancelButton = (Button) dialog.FindViewById(Resource.Id.fnv_cancel);
cancelButton.Click += (sender, e) => dialog.Dismiss(); cancelButton.Click += delegate
{
dialog.Dismiss();
};
if (onCancel != null)
dialog.SetOnDismissListener(new DismissListener(onCancel));
ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button); ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button);
if (!showBrowseButton) if (!showBrowseButton)
@ -301,7 +324,7 @@ namespace keepass2android
{ {
string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text; string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text;
Util.ShowBrowseDialog(filename, activity, requestCodeBrowse, onCreate != null); Util.ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null);
}; };

View File

@ -443,7 +443,8 @@ namespace keepass2android
new SftpFileStorage(this), new SftpFileStorage(this),
#endif #endif
#endif #endif
new BuiltInFileStorage(this) new BuiltInFileStorage(this),
new AndroidContentStorage(Application.Context)
}; };
} }
return _fileStorages; return _fileStorages;

View File

@ -69,6 +69,7 @@ namespace keepass2android
view.FileSelectButtons _fileSelectButtons; view.FileSelectButtons _fileSelectButtons;
internal AppTask AppTask; internal AppTask AppTask;
private const int RequestCodeSelectIoc = 456;
public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity"; public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity";
@ -129,9 +130,11 @@ namespace keepass2android
EventHandler openFileButtonClick = (sender, e) => EventHandler openFileButtonClick = (sender, e) =>
{ {
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity)); Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
StartActivityForResult(intent, 0); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
StartActivityForResult(intent, RequestCodeSelectIoc);
}; };
openFileButton.Click += openFileButtonClick; openFileButton.Click += openFileButtonClick;
@ -294,19 +297,7 @@ namespace keepass2android
App.Kp2a.GetFileStorage(ioc) App.Kp2a.GetFileStorage(ioc)
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), ioc, 0, false); .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), ioc, 0, false);
} }
private bool OnOpenButton(String fileName)
{
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = fileName
};
LaunchPasswordActivityForIoc(ioc);
return true;
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{ {
@ -326,109 +317,23 @@ namespace keepass2android
FillData(); FillData();
if (resultCode == KeePass.ExitFileStorageSelectionOk)
{
string protocolId = data.GetStringExtra("protocolId"); if (resultCode == (Result)FileStorageResults.FileUsagePrepared)
if (protocolId == "androidget")
{
string defaultFilename = Environment.ExternalStorageDirectory +
GetString(Resource.String.default_file_path);
Util.ShowBrowseDialog(defaultFilename, this, Intents.RequestCodeFileBrowseForOpen, false);
}
else
{
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
OnActivityResult,
defaultPath =>
{
if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceivedSftpData);
else
Util.ShowFilenameDialog(this, OnOpenButton, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen);
}
), false, 0, protocolId);
}
}
if ( (requestCode == Intents.RequestCodeFileBrowseForCreate
|| requestCode == Intents.RequestCodeFileBrowseForOpen)
&& resultCode == Result.Ok) {
string filename = Util.IntentToFilename(data, this);
if (filename != null) {
if (filename.StartsWith("file://")) {
filename = filename.Substring(7);
filename = Java.Net.URLDecoder.Decode(filename);
}
if (requestCode == Intents.RequestCodeFileBrowseForOpen)
{
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = filename
};
LaunchPasswordActivityForIoc(ioc);
}
}
}
if (resultCode == (Result) FileStorageResults.FileUsagePrepared)
{ {
IOConnectionInfo ioc = new IOConnectionInfo(); IOConnectionInfo ioc = new IOConnectionInfo();
PasswordActivity.SetIoConnectionFromIntent(ioc, data); PasswordActivity.SetIoConnectionFromIntent(ioc, data);
LaunchPasswordActivityForIoc(ioc); LaunchPasswordActivityForIoc(ioc);
} }
if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
if ((resultCode == Result.Ok) && (requestCode == RequestCodeSelectIoc))
{ {
IOConnectionInfo ioc = new IOConnectionInfo(); IOConnectionInfo ioc = new IOConnectionInfo();
PasswordActivity.SetIoConnectionFromIntent(ioc, data); PasswordActivity.SetIoConnectionFromIntent(ioc, data);
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx"});
#endif
}
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
{
Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show();
}
}
private bool OnReceivedSftpData(string filename)
{
IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
LaunchPasswordActivityForIoc(ioc); LaunchPasswordActivityForIoc(ioc);
#endif
return true;
} }
#if !EXCLUDE_FILECHOOSER
private void StartFileChooser(string defaultPath)
{
Kp2aLog.Log("FSA: defaultPath="+defaultPath);
string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
if (defaultPath.StartsWith("file://"))
{
fileProviderAuthority = PackageName+".android-filechooser.localfile";
}
Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority,
defaultPath);
StartActivityForResult(i, Intents.RequestCodeFileBrowseForOpen);
} }
#endif
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();

View File

@ -58,6 +58,12 @@ namespace keepass2android.fileselect
} }
protected override void OnRestart()
{
base.OnRestart();
_isRecreated = true;
}
protected override void OnStart() protected override void OnStart()
{ {
base.OnStart(); base.OnStart();

View File

@ -30,7 +30,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
@ -85,9 +85,6 @@
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
<Reference Include="Mono.Android.Support.v4" /> <Reference Include="Mono.Android.Support.v4" />
<Reference Include="GooglePlayServicesFroyoLib">
<HintPath>..\Components\googleplayservicesfroyo-9.0\lib\android\GooglePlayServicesFroyoLib.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="addons\OtpKeyProv\EncodingUtil.cs" /> <Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
@ -138,6 +135,7 @@
<Compile Include="fileselect\FileSelectActivity.cs" /> <Compile Include="fileselect\FileSelectActivity.cs" />
<Compile Include="fileselect\FileDbHelper.cs" /> <Compile Include="fileselect\FileDbHelper.cs" />
<Compile Include="search\SearchProvider.cs" /> <Compile Include="search\SearchProvider.cs" />
<Compile Include="SelectStorageLocationActivity.cs" />
<Compile Include="services\OngoingNotificationsService.cs" /> <Compile Include="services\OngoingNotificationsService.cs" />
<Compile Include="settings\DatabaseSettingsActivity.cs" /> <Compile Include="settings\DatabaseSettingsActivity.cs" />
<Compile Include="intents\Intents.cs" /> <Compile Include="intents\Intents.cs" />
@ -832,12 +830,6 @@
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\layout\text_with_help.xml" /> <AndroidResource Include="Resources\layout\text_with_help.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<XamarinComponentReference Include="googleplayservicesfroyo">
<Version>9.0</Version>
<Visible>False</Visible>
</XamarinComponentReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable\ic_storage_skydrive.png" /> <AndroidResource Include="Resources\drawable\ic_storage_skydrive.png" />
</ItemGroup> </ItemGroup>