Compare commits
4 Commits
v1.13
...
feature/we
Author | SHA1 | Date | |
---|---|---|---|
![]() |
057a7e2f7a | ||
![]() |
cfb5098b38 | ||
![]() |
913222d7cb | ||
![]() |
3e6d86c206 |
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -140,6 +140,7 @@ jobs:
|
||||
|
||||
- name: Upload APK to GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: github.ref_type == 'tag'
|
||||
with:
|
||||
draft: true
|
||||
files: |
|
||||
|
@@ -140,6 +140,10 @@ namespace keepass2android
|
||||
|
||||
|
||||
#endif
|
||||
int WebDavChunkedUploadSize
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -15,7 +15,9 @@ namespace keepass2android.Io
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
|
||||
}
|
||||
|
||||
public partial class DropboxAppFolderFileStorage: JavaFileStorage
|
||||
{
|
||||
@@ -29,6 +31,7 @@ namespace keepass2android.Io
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ namespace keepass2android.Io
|
||||
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
public virtual IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this);
|
||||
}
|
||||
|
@@ -6,10 +6,12 @@ using System.Text;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
|
||||
using Keepass2android.Javafilestorage;
|
||||
#endif
|
||||
using KeePassLib.Serialization;
|
||||
@@ -19,9 +21,15 @@ namespace keepass2android.Io
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
public class WebDavFileStorage: JavaFileStorage
|
||||
{
|
||||
public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app)
|
||||
{
|
||||
}
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly WebDavStorage baseWebdavStorage;
|
||||
|
||||
public WebDavFileStorage(IKp2aApp app, int chunkSize) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler, chunkSize), app)
|
||||
{
|
||||
_app = app;
|
||||
baseWebdavStorage = (WebDavStorage)Jfs;
|
||||
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
@@ -75,6 +83,15 @@ namespace keepass2android.Io
|
||||
}
|
||||
return base.IocToPath(ioc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize);
|
||||
return base.OpenWriteTransaction(ioc, useFileTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
}
|
@@ -12,21 +12,20 @@ namespace KeePass.Util
|
||||
|
||||
public static string GetErrorMessage(Exception e)
|
||||
{
|
||||
|
||||
try
|
||||
string errorMessage = e.Message;
|
||||
if (e is Java.Lang.Exception javaException)
|
||||
{
|
||||
string errorMessage = e.Message;
|
||||
if (e is Java.Lang.Exception javaException)
|
||||
try
|
||||
{
|
||||
errorMessage = javaException.LocalizedMessage ?? javaException.Message ?? errorMessage;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package keepass2android.javafilestorage;
|
||||
|
||||
import android.content.Context;
|
||||
import java.math.BigInteger;
|
||||
import android.content.Intent;
|
||||
|
||||
import android.net.Uri;
|
||||
@@ -15,7 +16,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator;
|
||||
import com.burgstaller.okhttp.digest.CachingAuthenticator;
|
||||
import com.burgstaller.okhttp.digest.DigestAuthenticator;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -24,6 +28,7 @@ import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
@@ -44,23 +49,33 @@ import keepass2android.javafilestorage.webdav.DecoratedTrustManager;
|
||||
import keepass2android.javafilestorage.webdav.PropfindXmlParser;
|
||||
import keepass2android.javafilestorage.webdav.WebDavUtil;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
import okio.BufferedSink;
|
||||
|
||||
public class WebDavStorage extends JavaFileStorageBase {
|
||||
|
||||
private final ICertificateErrorHandler mCertificateErrorHandler;
|
||||
private Context appContext;
|
||||
|
||||
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler)
|
||||
int chunkSize;
|
||||
|
||||
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler, int chunkSize)
|
||||
{
|
||||
this.chunkSize = chunkSize;
|
||||
|
||||
mCertificateErrorHandler = certificateErrorHandler;
|
||||
}
|
||||
|
||||
public void setUploadChunkSize(int chunkSize)
|
||||
{
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException {
|
||||
String scheme = url.substring(0, url.indexOf("://"));
|
||||
url = url.substring(scheme.length() + 3);
|
||||
@@ -181,21 +196,119 @@ public class WebDavStorage extends JavaFileStorageBase {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void renameOrMoveWebDavResource(String sourcePath, String destinationPath, boolean overwrite) throws Exception {
|
||||
|
||||
ConnectionInfo sourceCi = splitStringToConnectionInfo(sourcePath);
|
||||
ConnectionInfo destinationCi = splitStringToConnectionInfo(destinationPath);
|
||||
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(new URL(sourceCi.URL))
|
||||
.method("MOVE", null) // "MOVE" is the HTTP method
|
||||
.header("Destination", destinationCi.URL); // New URI for the resource
|
||||
|
||||
// Add Overwrite header
|
||||
if (overwrite) {
|
||||
requestBuilder.header("Overwrite", "T"); // 'T' for true
|
||||
} else {
|
||||
requestBuilder.header("Overwrite", "F"); // 'F' for false
|
||||
}
|
||||
|
||||
Request request = requestBuilder.build();
|
||||
|
||||
Response response = getClient(sourceCi).newCall(request).execute();
|
||||
|
||||
// Check the status code
|
||||
if (response.isSuccessful()) {
|
||||
// WebDAV MOVE can return 201 (Created) if a new resource was created at dest,
|
||||
// or 204 (No Content) if moved to a pre-existing destination (e.g., just renamed).
|
||||
// A 200 OK might also be returned by some servers, though 201/204 are more common.
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + response.code() + " " + response.message());
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateRandomHexString(int length) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
// Generate enough bytes to ensure we can get the desired number of hex characters.
|
||||
// Each byte converts to two hex characters.
|
||||
// For 8 hex characters, we need 4 bytes.
|
||||
int numBytes = (int) Math.ceil(length / 2.0);
|
||||
byte[] randomBytes = new byte[numBytes];
|
||||
secureRandom.nextBytes(randomBytes);
|
||||
|
||||
// Convert the byte array to a hexadecimal string
|
||||
// BigInteger(1, randomBytes) treats the byte array as a positive number.
|
||||
// toString(16) converts it to a hexadecimal string.
|
||||
String hexString = new BigInteger(1, randomBytes).toString(16);
|
||||
|
||||
// Pad with leading zeros if necessary (e.g., if the generated number is small)
|
||||
// and then take the first 'length' characters.
|
||||
// Using String.format to ensure leading zeros if the hexString is shorter.
|
||||
return String.format("%0" + length + "d", new BigInteger(hexString, 16)).substring(0, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadFile(String path, byte[] data, boolean writeTransactional)
|
||||
throws Exception {
|
||||
|
||||
if (writeTransactional)
|
||||
{
|
||||
String randomSuffix = ".tmp." + generateRandomHexString(8);
|
||||
uploadFile(path + randomSuffix, data, false);
|
||||
renameOrMoveWebDavResource(path+randomSuffix, path, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
ConnectionInfo ci = splitStringToConnectionInfo(path);
|
||||
|
||||
|
||||
RequestBody requestBody;
|
||||
if (chunkSize > 0)
|
||||
{
|
||||
// use chunked upload
|
||||
requestBody = new RequestBody() {
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return MediaType.parse("application/binary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
try (InputStream in = new ByteArrayInputStream(data)) {
|
||||
byte[] buffer = new byte[chunkSize];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
sink.write(buffer, 0, read);
|
||||
sink.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return -1; // use chunked upload
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
requestBody = new MultipartBody.Builder()
|
||||
.addPart(RequestBody.create(data, MediaType.parse("application/binary")))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(new URL(ci.URL))
|
||||
.put(RequestBody.create(MediaType.parse("application/binary"), data))
|
||||
.put(requestBody)
|
||||
.build();
|
||||
|
||||
//TODO consider writeTransactional
|
||||
//TODO check for error
|
||||
|
||||
|
||||
Response response = getClient(ci).newCall(request).execute();
|
||||
checkStatus(response);
|
||||
@@ -290,7 +403,10 @@ public class WebDavStorage extends JavaFileStorageBase {
|
||||
e.sizeInBytes = -1;
|
||||
}
|
||||
}
|
||||
e.isDirectory = r.href.endsWith("/");
|
||||
|
||||
e.isDirectory = r.href.endsWith("/") || okprop.IsCollection;
|
||||
|
||||
|
||||
|
||||
e.displayName = okprop.DisplayName;
|
||||
if (e.displayName == null)
|
||||
@@ -519,3 +635,4 @@ public class WebDavStorage extends JavaFileStorageBase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -57,6 +57,8 @@ public class PropfindXmlParser
|
||||
public String DisplayName;
|
||||
public String LastModified;
|
||||
public String ContentLength;
|
||||
|
||||
public boolean IsCollection;
|
||||
}
|
||||
public String status;
|
||||
public Prop prop;
|
||||
@@ -191,6 +193,8 @@ public class PropfindXmlParser
|
||||
continue;
|
||||
}
|
||||
String name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
|
||||
|
||||
android.util.Log.d("PARSE", "4name = " + name);
|
||||
if (name.equals("getcontentlength"))
|
||||
@@ -200,6 +204,9 @@ public class PropfindXmlParser
|
||||
prop.LastModified = readText(parser);
|
||||
} else if (name.equals("displayname")) {
|
||||
prop.DisplayName = readText(parser);
|
||||
} else if (name.equals("resourcetype") && namespace.equals(ns)) {
|
||||
// We found the <d:resourcetype> tag
|
||||
prop.IsCollection = readResourceType(parser);
|
||||
} else {
|
||||
skip(parser);
|
||||
}
|
||||
@@ -208,6 +215,37 @@ public class PropfindXmlParser
|
||||
return prop;
|
||||
}
|
||||
|
||||
private boolean readResourceType(XmlPullParser parser) throws IOException, XmlPullParserException {
|
||||
boolean isCollection = false;
|
||||
parser.require(XmlPullParser.START_TAG, ns, "resourcetype");
|
||||
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
continue;
|
||||
}
|
||||
String name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
|
||||
if (name.equals("collection") && namespace.equals(ns)) {
|
||||
// We found <d:collection/>, so it's a folder
|
||||
isCollection = true;
|
||||
// Since <d:collection/> is usually an empty tag, just consume it.
|
||||
// It might contain text if there's whitespace, so consume text then end tag.
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
parser.nextTag(); // Move to the end tag
|
||||
}
|
||||
parser.require(XmlPullParser.END_TAG, ns, "collection");
|
||||
} else {
|
||||
// Skip any other unexpected tags within <d:resourcetype>
|
||||
skip(parser);
|
||||
}
|
||||
}
|
||||
// After reading all children of <d:resourcetype>, ensure we are at its END_TAG
|
||||
parser.require(XmlPullParser.END_TAG, ns, "resourcetype");
|
||||
return isCollection;
|
||||
}
|
||||
|
||||
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
android.util.Log.d("PARSE", "skipping " + parser.getName());
|
||||
|
||||
|
@@ -548,7 +548,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
|
||||
|
||||
//storageToTest = new GoogleDriveAppDataFileStorage();
|
||||
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
@Override
|
||||
public boolean onValidationError(String error) {
|
||||
return false;
|
||||
@@ -558,12 +558,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
public boolean alwaysFailOnValidationError() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
*/
|
||||
}, 64*1024);
|
||||
|
||||
//storageToTest = new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
|
||||
//storageToTest = new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
|
||||
//storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true);
|
||||
storageToTest = new GoogleDriveFullFileStorage();
|
||||
// storageToTest = new GoogleDriveFullFileStorage();
|
||||
|
||||
|
||||
return storageToTest;
|
||||
|
@@ -31,8 +31,6 @@ namespace keepass2android
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
|
||||
builder.SetTitle(ctx.GetString(Resource.String.ChangeLog_title));
|
||||
List<string> changeLog = new List<string>{
|
||||
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_13}, "1.13"),
|
||||
|
||||
BuildChangelogString(ctx, new List<int>{Resource.Array.ChangeLog_1_12
|
||||
#if !NoNet
|
||||
,Resource.Array.ChangeLog_1_12_net
|
||||
|
@@ -9,7 +9,9 @@ using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.Res;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
@@ -319,7 +321,7 @@ namespace keepass2android
|
||||
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null);
|
||||
if (!defaultPath.EndsWith(_schemeSeparator))
|
||||
{
|
||||
var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler);
|
||||
var webdavStorage = CreateWebdavStorage(activity);
|
||||
var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath);
|
||||
dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url;
|
||||
dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username;
|
||||
@@ -339,7 +341,7 @@ namespace keepass2android
|
||||
string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
|
||||
if (host.Contains(_schemeSeparator) == false)
|
||||
host = scheme + _schemeSeparator + host;
|
||||
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user,
|
||||
string httpPath = CreateWebdavStorage(activity).BuildFullPath(host, user,
|
||||
password);
|
||||
onStartBrowse(httpPath);
|
||||
});
|
||||
@@ -352,8 +354,13 @@ namespace keepass2android
|
||||
dialog.Show();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
|
||||
#if !NoNet
|
||||
private static WebDavStorage CreateWebdavStorage(Activity activity)
|
||||
{
|
||||
return new WebDavStorage(App.Kp2a.CertificateErrorHandler, App.Kp2a.WebDavChunkedUploadSize);
|
||||
}
|
||||
#endif
|
||||
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
|
||||
{
|
||||
#if !NoNet
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
|
||||
@@ -518,7 +525,7 @@ namespace keepass2android
|
||||
string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
|
||||
if (host.Contains(_schemeSeparator) == false)
|
||||
host = scheme + _schemeSeparator + host;
|
||||
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
|
||||
string httpPath = CreateWebdavStorage(activity).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
|
||||
password);
|
||||
onStartBrowse(httpPath);
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="235"
|
||||
android:versionName="1.13-r1"
|
||||
android:versionCode="221"
|
||||
android:versionName="1.13-r0"
|
||||
package="keepass2android.keepass2android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:versionCode="235"
|
||||
android:versionName="1.13-r1"
|
||||
android:versionCode="221"
|
||||
android:versionName="1.13-r0"
|
||||
package="keepass2android.keepass2android_nonet"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:installLocation="auto">
|
||||
|
@@ -1425,20 +1425,18 @@ namespace keepass2android
|
||||
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
|
||||
App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase =
|
||||
(((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure;
|
||||
App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase = PreferenceManager.GetDefaultSharedPreferences(this)
|
||||
.GetBoolean(GetString(Resource.String.QuickUnlockBlockedWhenDeviceNotSecure_key), true);
|
||||
|
||||
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
|
||||
if ((_loadDbFileTask != null) && (App.Kp2a.OfflineMode != _loadDbTaskOffline))
|
||||
{
|
||||
if (App.Kp2a == null)
|
||||
if (App.Kp2a == null)
|
||||
throw new NullPointerException("App.Kp2a");
|
||||
//keep the loading result if we loaded in online-mode (now offline) and the task is completed
|
||||
if (!App.Kp2a.OfflineMode || !_loadDbFileTask.IsCompleted)
|
||||
{
|
||||
//discard the pre-loading task
|
||||
_loadDbFileTask = null;
|
||||
_loadDbFileTask = null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//avoid password being visible while loading:
|
||||
|
@@ -175,7 +175,7 @@ namespace keepass2android
|
||||
App.Kp2a.Lock(false);
|
||||
};
|
||||
|
||||
if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false && App.Kp2a.QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase)
|
||||
if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false)
|
||||
{
|
||||
FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone;
|
||||
FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible;
|
||||
|
@@ -78,11 +78,11 @@ android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="16dp">
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:orientation="horizontal"
|
||||
android:id="@+id/QuickUnlockForm">
|
||||
|
||||
<TextView
|
||||
@@ -94,12 +94,6 @@ android:paddingRight="16dp"
|
||||
android:textSize="14sp"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
<EditText
|
||||
android:inputType="textPassword"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -123,8 +117,8 @@ android:paddingRight="16dp"
|
||||
android:src="@drawable/baseline_fingerprint_24"
|
||||
android:scaleType="fitXY"
|
||||
android:background="?android:selectableItemBackground" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_margin="12dip">
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<EditText
|
||||
android:id="@+id/sftp_host"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:text="144.76.169.229"
|
||||
android:hint="@string/hint_sftp_host" />
|
||||
<TextView
|
||||
android:id="@+id/portsep"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=":" />
|
||||
<EditText
|
||||
android:id="@+id/sftp_port"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:inputType="number"
|
||||
android:text="22"
|
||||
android:hint="@string/hint_sftp_port" />
|
||||
</LinearLayout>
|
||||
<EditText
|
||||
android:id="@+id/sftp_user"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="philipp"
|
||||
android:hint="@string/hint_username" />
|
||||
<EditText
|
||||
android:id="@+id/sftp_password"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:singleLine="true"
|
||||
android:text="l2uientTjVhvyfzNpksa"
|
||||
android:hint="@string/hint_pass" />
|
||||
<TextView
|
||||
android:id="@+id/initial_dir"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="4dip"
|
||||
android:layout_marginTop="4dip"
|
||||
android:text="@string/initial_directory" />
|
||||
<EditText
|
||||
android:id="@+id/sftp_initial_dir"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:text="/home/philipp" />
|
||||
</LinearLayout>
|
@@ -123,7 +123,6 @@
|
||||
<string name="QuickUnlockLength_default">3</string>
|
||||
<string name="QuickUnlockIconHidden_key">QuickUnlockIconHidden_key</string>
|
||||
<string name="QuickUnlockIconHidden16_key">QuickUnlockIconHidden16_key</string>
|
||||
<string name="QuickUnlockBlockedWhenDeviceNotSecure_key">QuickUnlockBlockedWhenDeviceNotSecure_key</string>
|
||||
|
||||
<string name="UsageCount_key">UsageCount</string>
|
||||
<string name="LastInfoVersionCode_key">LastInfoVersion</string>
|
||||
@@ -210,6 +209,7 @@
|
||||
|
||||
<string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string>
|
||||
<bool name="ShowUnlockedNotification_default">true</bool>
|
||||
<integer name="WebDavChunkedUploadSize_default">65536</integer>
|
||||
|
||||
<string name="PreloadDatabaseEnabled_key">PreloadDatabaseEnabled</string>
|
||||
<bool name="PreloadDatabaseEnabled_default">true</bool>
|
||||
|
@@ -330,8 +330,6 @@
|
||||
<string name="QuickUnlockIconHidden_summary">QuickUnlock unfortunately does not work without displaying a notification icon. Select this option to use a transparent icon.</string>
|
||||
<string name="QuickUnlockIconHidden16_title">Hide QuickUnlock icon</string>
|
||||
<string name="QuickUnlockIconHidden16_summary">QuickUnlock requires a notification to work properly. Select this option to display a notification without an icon.</string>
|
||||
<string name="QuickUnlockBlockedWhenDeviceNotSecure_summary">Block entering the QuickUnlock key if the device is not secured by screen lock. This prevents shoulder surfing attacks. Only disable this option if you understand the risk. Note that QuickUnlock can still be used with biometrics.</string>
|
||||
<string name="QuickUnlockBlockedWhenDeviceNotSecure_title">Block QuickUnlock when device is not secure</string>
|
||||
<string name="QuickUnlockLength_title">Length of QuickUnlock key</string>
|
||||
<string name="QuickUnlockLength_summary">Maximum number of characters used as QuickUnlock password.</string>
|
||||
<string name="QuickUnlockHideLength_title">Hide QuickUnlock length</string>
|
||||
@@ -731,6 +729,9 @@
|
||||
<string name="EntryChannel_desc">Notification to simplify access to the currently selected entry.</string>
|
||||
<string name="CloseDbAfterFailedAttempts">Close database after three failed biometric unlock attempts.</string>
|
||||
<string name="WarnFingerprintInvalidated">Warning! Biometric authentication can be invalidated by Android, e.g. after adding a new fingerprint in your device settings. Make sure you always know how to unlock with your master password!</string>
|
||||
<string name="webdav_chunked_upload_size_title">Chunk size for WebDav upload</string>
|
||||
<string name="webdav_chunked_upload_size_summary">Size of chunks when uploading to WebDav servers in bytes. Use 0 to disable chunked upload.</string>
|
||||
|
||||
|
||||
<string-array name="ChangeLog_1_13">
|
||||
<item>Improved password quality estimation by considering most popular passwords.</item>
|
||||
|
@@ -45,6 +45,14 @@
|
||||
android:title="@string/UseFileTransactions_title"
|
||||
android:key="@string/UseFileTransactions_key" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="WebDavChunkedUploadSize_str"
|
||||
android:title="@string/webdav_chunked_upload_size_title"
|
||||
android:summary="@string/webdav_chunked_upload_size_title"
|
||||
android:defaultValue="@integer/WebDavChunkedUploadSize_default"
|
||||
android:inputType="number"
|
||||
/>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:enabled="true"
|
||||
android:persistent="true"
|
||||
@@ -80,5 +88,6 @@
|
||||
android:defaultValue="true"
|
||||
android:title="@string/CheckForDuplicateUuids_title"
|
||||
android:key="@string/CheckForDuplicateUuids_key" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
@@ -26,14 +26,6 @@
|
||||
android:defaultValue="false"
|
||||
android:title="@string/QuickUnlockIconHidden_title"
|
||||
android:key="@string/QuickUnlockIconHidden_key" />
|
||||
<CheckBoxPreference
|
||||
android:enabled="true"
|
||||
android:persistent="true"
|
||||
android:summary="@string/QuickUnlockBlockedWhenDeviceNotSecure_summary"
|
||||
android:defaultValue="true"
|
||||
android:title="@string/QuickUnlockBlockedWhenDeviceNotSecure_title"
|
||||
android:key="@string/QuickUnlockBlockedWhenDeviceNotSecure_key" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:enabled="true"
|
||||
android:persistent="true"
|
||||
|
@@ -352,7 +352,6 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
public bool ScreenLockWasEnabledWhenOpeningDatabase { get; set; }
|
||||
public bool QuickUnlockBlockedWhenDeviceNotSecureWhenOpeningDatabase { get; set; }
|
||||
|
||||
|
||||
public bool QuickUnlockEnabled { get; private set; }
|
||||
@@ -837,8 +836,8 @@ namespace keepass2android
|
||||
new AndroidContentStorage(LocaleManager.LocalizedAppContext),
|
||||
#if !EXCLUDE_JAVAFILESTORAGE
|
||||
#if !NoNet
|
||||
new DropboxFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
DropboxFileStorage.IsConfigured ? new DropboxFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||
DropboxAppFolderFileStorage.IsConfigured ? new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this): null,
|
||||
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveAppDataFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||
new OneDriveFileStorage(this),
|
||||
@@ -847,8 +846,8 @@ namespace keepass2android
|
||||
new OneDrive2AppFolderFileStorage(),
|
||||
new SftpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled()),
|
||||
new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled),
|
||||
new WebDavFileStorage(this),
|
||||
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new WebDavFileStorage(this, WebDavChunkedUploadSize),
|
||||
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new PCloudFileStorageAll(LocaleManager.LocalizedAppContext, this),
|
||||
new MegaFileStorage(App.Context),
|
||||
//new LegacyWebDavStorage(this),
|
||||
@@ -1334,6 +1333,18 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public int WebDavChunkedUploadSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return int.Parse(PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext)
|
||||
.GetString("WebDavChunkedUploadSize_str",
|
||||
LocaleManager.LocalizedAppContext.Resources
|
||||
.GetInteger(Resource.Integer.WebDavChunkedUploadSize_default).ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1459,8 +1470,7 @@ namespace keepass2android
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e.Exception);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -131,15 +131,7 @@ namespace keepass2android.view
|
||||
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible;
|
||||
ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible;
|
||||
|
||||
_db = App.Kp2a.TryFindDatabaseForElement(_entry);
|
||||
if (_db == null)
|
||||
{
|
||||
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Gone;
|
||||
_textView.TextFormatted = new SpannableString("(no data)");
|
||||
_textviewDetails.Visibility = ViewStates.Gone;
|
||||
_textgroupFullPath.Visibility = ViewStates.Gone;
|
||||
return;
|
||||
}
|
||||
_db = App.Kp2a.FindDatabaseForElement(_entry);
|
||||
|
||||
ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon);
|
||||
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
|
||||
@@ -217,6 +209,11 @@ namespace keepass2android.view
|
||||
UpdateTotp();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void ConvertView(PwEntry pw, int pos)
|
||||
|
@@ -15,15 +15,14 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
|
||||
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Android.Graphics;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Text;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using keepass2android;
|
||||
using KeePassLib;
|
||||
using System;
|
||||
using Object = Java.Lang.Object;
|
||||
|
||||
namespace keepass2android.view
|
||||
@@ -65,20 +64,7 @@ namespace keepass2android.view
|
||||
_label = (TextView) gv.FindViewById(Resource.Id.group_label);
|
||||
_label.TextSize = size-8;
|
||||
|
||||
Database db = App.Kp2a.TryFindDatabaseForElement(pw);
|
||||
|
||||
|
||||
if (db == null)
|
||||
{
|
||||
|
||||
gv.FindViewById(Resource.Id.group_icon_bkg).Visibility = ViewStates.Gone;
|
||||
|
||||
gv.FindViewById(Resource.Id.icon).Visibility = ViewStates.Gone;
|
||||
gv.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible;
|
||||
_textview.Text = "(no data)";
|
||||
_label.Text = "";
|
||||
return;
|
||||
}
|
||||
Database db = App.Kp2a.FindDatabaseForElement(pw);
|
||||
|
||||
gv.FindViewById(Resource.Id.group_icon_bkg).Visibility = db.DrawableFactory.IsWhiteIconSet ? ViewStates.Visible : ViewStates.Gone;
|
||||
|
||||
|
Reference in New Issue
Block a user