Compare commits
43 Commits
v1.12-r4
...
374-improv
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3c9c8610d | ||
![]() |
9c2d50c7b0 | ||
![]() |
0cd9df7415 | ||
![]() |
a4a3112dc6 | ||
![]() |
6351e1f3d0 | ||
![]() |
2959d8cbcc | ||
![]() |
c98680347a | ||
![]() |
35367bb28b | ||
![]() |
8a1890bc10 | ||
![]() |
bf4035fcfe | ||
![]() |
d3dfbaab4b | ||
![]() |
227074efb6 | ||
![]() |
69f79c1b20 | ||
![]() |
000d1254ec | ||
![]() |
324fc1f2ee | ||
![]() |
52121c6a85 | ||
![]() |
c6f494ac33 | ||
![]() |
400e171bc5 | ||
![]() |
41e6e67e87 | ||
![]() |
8277283ebc | ||
![]() |
71806178d0 | ||
![]() |
aa2e4b856d | ||
![]() |
cfb185b53d | ||
![]() |
c3b6612591 | ||
![]() |
fefcf8f30e | ||
![]() |
e95cc84a15 | ||
![]() |
c0ed185612 | ||
![]() |
61c871f782 | ||
![]() |
e5d28f0979 | ||
![]() |
0e581a66c5 | ||
![]() |
ceb31c54b1 | ||
![]() |
42d8be593e | ||
![]() |
313adb6c3e | ||
![]() |
668ba4cdee | ||
![]() |
a36bfa7ff5 | ||
![]() |
26c37bcd2a | ||
![]() |
1980f05a7c | ||
![]() |
dbf10ba9fb | ||
![]() |
4be18d8373 | ||
![]() |
831b290d81 | ||
![]() |
9d4c15f7bc | ||
![]() |
4c4afa792d | ||
![]() |
8e256ac94d |
@@ -86,12 +86,17 @@ namespace KeePassLib.Interfaces
|
|||||||
/// the current work.</returns>
|
/// the current work.</returns>
|
||||||
bool SetText(string strNewText, LogStatusType lsType);
|
bool SetText(string strNewText, LogStatusType lsType);
|
||||||
|
|
||||||
/// <summary>
|
void UpdateMessage(String message);
|
||||||
/// Check if the user cancelled the current work.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Returns <c>true</c> if the caller should continue
|
void UpdateSubMessage(String submessage);
|
||||||
/// the current work.</returns>
|
|
||||||
bool ContinueWork();
|
/// <summary>
|
||||||
|
/// Check if the user cancelled the current work.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns <c>true</c> if the caller should continue
|
||||||
|
/// the current work.</returns>
|
||||||
|
bool ContinueWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class NullStatusLogger : IStatusLogger
|
public sealed class NullStatusLogger : IStatusLogger
|
||||||
@@ -100,6 +105,12 @@ namespace KeePassLib.Interfaces
|
|||||||
public void EndLogging() { }
|
public void EndLogging() { }
|
||||||
public bool SetProgress(uint uPercent) { return true; }
|
public bool SetProgress(uint uPercent) { return true; }
|
||||||
public bool SetText(string strNewText, LogStatusType lsType) { return true; }
|
public bool SetText(string strNewText, LogStatusType lsType) { return true; }
|
||||||
public bool ContinueWork() { return true; }
|
public void UpdateMessage(string message){
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSubMessage(string submessage){
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContinueWork() { return true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,7 @@ using System.IO;
|
|||||||
using Android;
|
using Android;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
using Android.Preferences;
|
using Android.Preferences;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
|
|
||||||
@@ -34,9 +35,17 @@ namespace keepass2android
|
|||||||
|
|
||||||
public static void Log(string message)
|
public static void Log(string message)
|
||||||
{
|
{
|
||||||
if (message != null)
|
if (message != null)
|
||||||
Android.Util.Log.Debug("KP2A", message);
|
{
|
||||||
if (LogToFile)
|
message += Thread.CurrentThread.ManagedThreadId != 0
|
||||||
|
? " (Thread ID: " + Thread.CurrentThread.ManagedThreadId + ")"
|
||||||
|
: "";
|
||||||
|
if (Looper.MainLooper == Looper.MyLooper())
|
||||||
|
message += " (Main Looper)";
|
||||||
|
Android.Util.Log.Debug("KP2A", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LogToFile)
|
||||||
{
|
{
|
||||||
lock (_fileLocker)
|
lock (_fileLocker)
|
||||||
{
|
{
|
||||||
|
@@ -208,7 +208,7 @@ namespace KeePassLib.Serialization
|
|||||||
if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) &&
|
if (!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) &&
|
||||||
!m_bRepairMode)
|
!m_bRepairMode)
|
||||||
{
|
{
|
||||||
Debug.Assert(m_uFileVersion < FileVersion32_4);
|
// Debug.Assert(m_uFileVersion < FileVersion32_4);
|
||||||
|
|
||||||
byte[] pbHash = Convert.FromBase64String(strHash);
|
byte[] pbHash = Convert.FromBase64String(strHash);
|
||||||
if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader))
|
if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader))
|
||||||
|
@@ -488,7 +488,7 @@ namespace KeePassLib.Serialization
|
|||||||
|
|
||||||
ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
|
ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
|
||||||
1, pbData.Length - 1);
|
1, pbData.Length - 1);
|
||||||
Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
|
//Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
|
||||||
m_pbsBinaries.Add(pb);
|
m_pbsBinaries.Add(pb);
|
||||||
|
|
||||||
if (bProt) MemUtil.ZeroByteArray(pbData);
|
if (bProt) MemUtil.ZeroByteArray(pbData);
|
||||||
|
53
src/Kp2aBusinessLogic/BlockingOperationStarter.cs
Normal file
53
src/Kp2aBusinessLogic/BlockingOperationStarter.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Keepass2Android is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Java.Lang;
|
||||||
|
using Java.Security;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class to run a task while a progress dialog is shown
|
||||||
|
/// </summary>
|
||||||
|
public class BlockingOperationStarter
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly OperationWithFinishHandler _task;
|
||||||
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
|
public BlockingOperationStarter(IKp2aApp app, OperationWithFinishHandler task)
|
||||||
|
{
|
||||||
|
_task = task;
|
||||||
|
_app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
_app.CancelBackgroundOperations();
|
||||||
|
OperationRunner.Instance.Run(_app, _task, true);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -41,7 +41,7 @@ namespace keepass2android
|
|||||||
/// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data
|
/// Interface through which Activities and the logic layer can access some app specific functionalities and Application static data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// This also contains methods which are UI specific and should be replacable for testing.
|
/// This also contains methods which are UI specific and should be replacable for testing.
|
||||||
public interface IKp2aApp : ICertificateValidationHandler
|
public interface IKp2aApp : ICertificateValidationHandler, IActiveContextProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock)
|
/// Locks all currently open databases, quicklocking if available (unless false is passed for allowQuickUnlock)
|
||||||
@@ -52,7 +52,9 @@ namespace keepass2android
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the specified data as the currently open database, as unlocked.
|
/// Loads the specified data as the currently open database, as unlocked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent);
|
Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey,
|
||||||
|
IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent,
|
||||||
|
IDatabaseModificationWatcher modificationWatcher);
|
||||||
|
|
||||||
|
|
||||||
HashSet<PwGroup> DirtyGroups { get; }
|
HashSet<PwGroup> DirtyGroups { get; }
|
||||||
@@ -96,7 +98,6 @@ namespace keepass2android
|
|||||||
EventHandler<DialogClickEventArgs> yesHandler,
|
EventHandler<DialogClickEventArgs> yesHandler,
|
||||||
EventHandler<DialogClickEventArgs> noHandler,
|
EventHandler<DialogClickEventArgs> noHandler,
|
||||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
EventHandler<DialogClickEventArgs> cancelHandler,
|
||||||
Context ctx,
|
|
||||||
string messageSuffix = "");
|
string messageSuffix = "");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -107,7 +108,6 @@ namespace keepass2android
|
|||||||
EventHandler<DialogClickEventArgs> yesHandler,
|
EventHandler<DialogClickEventArgs> yesHandler,
|
||||||
EventHandler<DialogClickEventArgs> noHandler,
|
EventHandler<DialogClickEventArgs> noHandler,
|
||||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
EventHandler<DialogClickEventArgs> cancelHandler,
|
||||||
Context ctx,
|
|
||||||
string messageSuffix = "");
|
string messageSuffix = "");
|
||||||
|
|
||||||
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
|
void ShowMessage(Context ctx, int resourceId, MessageSeverity severity);
|
||||||
@@ -136,10 +136,17 @@ namespace keepass2android
|
|||||||
bool CheckForDuplicateUuids { get; }
|
bool CheckForDuplicateUuids { get; }
|
||||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||||
ICertificateErrorHandler CertificateErrorHandler { get; }
|
ICertificateErrorHandler CertificateErrorHandler { get; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
bool SyncInBackgroundPreference { get; set; }
|
||||||
|
void StartBackgroundSyncService();
|
||||||
|
|
||||||
|
ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; }
|
||||||
|
bool CancelBackgroundOperations();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers an action that should be executed when the context instance with the given id has been resumed.
|
||||||
|
/// </summary>
|
||||||
|
void RegisterPendingActionForContextInstance(int contextInstanceId, ActionOnOperationFinished actionToPerformWhenContextIsResumed);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -13,7 +13,7 @@ using Android.Content.PM;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Preferences;
|
using Android.Preferences;
|
||||||
using Java.IO;
|
using Java.IO;
|
||||||
|
using KeePass.Util;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
using File = System.IO.File;
|
using File = System.IO.File;
|
||||||
@@ -121,7 +121,7 @@ namespace keepass2android.Io
|
|||||||
var response = ex.Response as HttpWebResponse;
|
var response = ex.Response as HttpWebResponse;
|
||||||
if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound))
|
if ((response != null) && (response.StatusCode == HttpStatusCode.NotFound))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException(ex.Message, ioc.Path, ex);
|
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(ex), ioc.Path, ex);
|
||||||
}
|
}
|
||||||
if (ex.Status == WebExceptionStatus.TrustFailure)
|
if (ex.Status == WebExceptionStatus.TrustFailure)
|
||||||
{
|
{
|
||||||
|
@@ -186,8 +186,11 @@ namespace keepass2android.Io
|
|||||||
Kp2aLog.Log("couldn't open from remote " + ioc.Path);
|
Kp2aLog.Log("couldn't open from remote " + ioc.Path);
|
||||||
#endif
|
#endif
|
||||||
Kp2aLog.Log(ex.ToString());
|
Kp2aLog.Log(ex.ToString());
|
||||||
|
if (TriggerWarningWhenFallingBackToCache)
|
||||||
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
|
{
|
||||||
|
_cacheSupervisor.CouldntOpenFromRemote(ioc, ex);
|
||||||
|
}
|
||||||
|
|
||||||
return File.OpenRead(cachedFilePath);
|
return File.OpenRead(cachedFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,7 +330,10 @@ namespace keepass2android.Io
|
|||||||
Kp2aLog.Log("couldn't save to remote " + ioc.Path);
|
Kp2aLog.Log("couldn't save to remote " + ioc.Path);
|
||||||
Kp2aLog.Log(e.ToString());
|
Kp2aLog.Log(e.ToString());
|
||||||
//notify the supervisor so it might display a warning or schedule a retry
|
//notify the supervisor so it might display a warning or schedule a retry
|
||||||
_cacheSupervisor.CouldntSaveToRemote(ioc, e);
|
if (TriggerWarningWhenFallingBackToCache)
|
||||||
|
{
|
||||||
|
_cacheSupervisor.CouldntSaveToRemote(ioc, e); }
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,7 +638,9 @@ namespace keepass2android.Io
|
|||||||
set { _cachedStorage.IsOffline = value; }
|
set { _cachedStorage.IsOffline = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
|
public bool TriggerWarningWhenFallingBackToCache { get; set; }
|
||||||
|
|
||||||
|
public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode,
|
||||||
string[] permissions, Permission[] grantResults)
|
string[] permissions, Permission[] grantResults)
|
||||||
{
|
{
|
||||||
_cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults);
|
_cachedStorage.OnRequestPermissionsResult(fileStorageSetupActivity, requestCode, permissions, grantResults);
|
||||||
|
@@ -13,6 +13,7 @@ using Keepass2android.Javafilestorage;
|
|||||||
#endif
|
#endif
|
||||||
using Exception = System.Exception;
|
using Exception = System.Exception;
|
||||||
using FileNotFoundException = Java.IO.FileNotFoundException;
|
using FileNotFoundException = Java.IO.FileNotFoundException;
|
||||||
|
using KeePass.Util;
|
||||||
|
|
||||||
namespace keepass2android.Io
|
namespace keepass2android.Io
|
||||||
{
|
{
|
||||||
@@ -42,7 +43,7 @@ namespace keepass2android.Io
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException e)
|
catch (FileNotFoundException e)
|
||||||
{
|
{
|
||||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||||
}
|
}
|
||||||
catch (Java.Lang.Exception e)
|
catch (Java.Lang.Exception e)
|
||||||
{
|
{
|
||||||
@@ -110,6 +111,11 @@ namespace keepass2android.Io
|
|||||||
}
|
}
|
||||||
|
|
||||||
Java.Lang.Exception exception = e as Java.Lang.Exception;
|
Java.Lang.Exception exception = e as Java.Lang.Exception;
|
||||||
|
|
||||||
|
if ((exception is Java.Lang.InterruptedException) || (exception is Java.IO.InterruptedIOException))
|
||||||
|
{
|
||||||
|
throw new Java.Lang.InterruptedException(exception.Message);
|
||||||
|
}
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
{
|
{
|
||||||
var ex = new Exception(exception.LocalizedMessage ??
|
var ex = new Exception(exception.LocalizedMessage ??
|
||||||
@@ -195,7 +201,7 @@ namespace keepass2android.Io
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException e)
|
catch (FileNotFoundException e)
|
||||||
{
|
{
|
||||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||||
}
|
}
|
||||||
catch (Java.Lang.Exception e)
|
catch (Java.Lang.Exception e)
|
||||||
{
|
{
|
||||||
@@ -214,7 +220,7 @@ namespace keepass2android.Io
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException e)
|
catch (FileNotFoundException e)
|
||||||
{
|
{
|
||||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||||
}
|
}
|
||||||
catch (Java.Lang.Exception e)
|
catch (Java.Lang.Exception e)
|
||||||
{
|
{
|
||||||
@@ -244,7 +250,7 @@ namespace keepass2android.Io
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException e)
|
catch (FileNotFoundException e)
|
||||||
{
|
{
|
||||||
throw new System.IO.FileNotFoundException(e.Message, e);
|
throw new System.IO.FileNotFoundException(ExceptionUtil.GetErrorMessage(e), e);
|
||||||
}
|
}
|
||||||
catch (Java.Lang.Exception e)
|
catch (Java.Lang.Exception e)
|
||||||
{
|
{
|
||||||
|
@@ -8,6 +8,7 @@ using Android.Content;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using FluentFTP;
|
using FluentFTP;
|
||||||
using FluentFTP.Exceptions;
|
using FluentFTP.Exceptions;
|
||||||
|
using KeePass.Util;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
@@ -127,7 +128,7 @@ namespace keepass2android.Io
|
|||||||
var ftpEx = (FtpCommandException) exception;
|
var ftpEx = (FtpCommandException) exception;
|
||||||
|
|
||||||
if (ftpEx.CompletionCode == "550")
|
if (ftpEx.CompletionCode == "550")
|
||||||
throw new FileNotFoundException(exception.Message, exception);
|
throw new FileNotFoundException(ExceptionUtil.GetErrorMessage(exception), exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
return exception;
|
return exception;
|
||||||
|
@@ -11,7 +11,8 @@ namespace keepass2android.Io
|
|||||||
public interface IOfflineSwitchable
|
public interface IOfflineSwitchable
|
||||||
{
|
{
|
||||||
bool IsOffline { get; set; }
|
bool IsOffline { get; set; }
|
||||||
}
|
bool TriggerWarningWhenFallingBackToCache { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing
|
/// Encapsulates another IFileStorage. Allows to switch to offline mode by throwing
|
||||||
@@ -21,8 +22,9 @@ namespace keepass2android.Io
|
|||||||
{
|
{
|
||||||
private readonly IFileStorage _baseStorage;
|
private readonly IFileStorage _baseStorage;
|
||||||
public bool IsOffline { get; set; }
|
public bool IsOffline { get; set; }
|
||||||
|
public bool TriggerWarningWhenFallingBackToCache { get; set; }
|
||||||
|
|
||||||
public OfflineSwitchableFileStorage(IFileStorage baseStorage)
|
public OfflineSwitchableFileStorage(IFileStorage baseStorage)
|
||||||
{
|
{
|
||||||
_baseStorage = baseStorage;
|
_baseStorage = baseStorage;
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Util;
|
using Android.Util;
|
||||||
|
using KeePass.Util;
|
||||||
using keepass2android.Io.ItemLocation;
|
using keepass2android.Io.ItemLocation;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
@@ -522,10 +523,10 @@ namespace keepass2android.Io
|
|||||||
{
|
{
|
||||||
|
|
||||||
if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString()))
|
if (e.IsMatch(GraphErrorCode.ItemNotFound.ToString()))
|
||||||
return new FileNotFoundException(e.Message);
|
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
|
||||||
if (e.Message.Contains("\n\n404 : ")
|
if (e.Message.Contains("\n\n404 : ")
|
||||||
) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround.
|
) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround.
|
||||||
return new FileNotFoundException(e.Message);
|
return new FileNotFoundException(ExceptionUtil.GetErrorMessage(e));
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,20 +16,32 @@ namespace keepass2android.Io
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class OneDriveFileStorage: IFileStorage
|
public class OneDriveFileStorage: IFileStorage
|
||||||
{
|
{
|
||||||
|
public OneDriveFileStorage(IKp2aApp app)
|
||||||
public IEnumerable<string> SupportedProtocols
|
{
|
||||||
|
_app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
|
public IEnumerable<string> SupportedProtocols
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield return "skydrive";
|
yield return "skydrive";
|
||||||
yield return "onedrive";
|
yield return "onedrive";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Exception GetDeprecatedMessage()
|
string GetDeprecatedMessage()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select OneDrive again.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Exception GetDeprecatedException()
|
||||||
{
|
{
|
||||||
return new Exception(
|
return new Exception(
|
||||||
"You have opened your file through a deprecated Microsoft API. Please select Change database, Open Database and then select One Drive again.");
|
GetDeprecatedMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UserShouldBackup
|
public bool UserShouldBackup
|
||||||
@@ -39,133 +51,132 @@ namespace keepass2android.Io
|
|||||||
|
|
||||||
public void Delete(IOConnectionInfo ioc)
|
public void Delete(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
public Stream OpenFileForRead(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFileExtension(IOConnectionInfo ioc)
|
public string GetFileExtension(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RequiresCredentials(IOConnectionInfo ioc)
|
public bool RequiresCredentials(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
public FileDescription GetFileDescription(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
public bool RequiresSetup(IOConnectionInfo ioConnection)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string IocToPath(IOConnectionInfo ioc)
|
public string IocToPath(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
|
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
|
||||||
bool alwaysReturnSuccess)
|
bool alwaysReturnSuccess)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
_app.ShowMessage(activity.Activity, GetDeprecatedMessage(), MessageSeverity.Error);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnResume(IFileStorageSetupActivity activity)
|
public void OnResume(IFileStorageSetupActivity activity)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStart(IFileStorageSetupActivity activity)
|
public void OnStart(IFileStorageSetupActivity activity)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetDisplayName(IOConnectionInfo ioc)
|
public string GetDisplayName(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
return "File using deprecated Microsoft API. Please update.";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CreateFilePath(string parent, string newFilename)
|
public string CreateFilePath(string parent, string newFilename)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
public bool IsPermanentLocation(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
|
||||||
{
|
{
|
||||||
throw GetDeprecatedMessage();
|
throw GetDeprecatedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
239
src/Kp2aBusinessLogic/OperationRunner.cs
Normal file
239
src/Kp2aBusinessLogic/OperationRunner.cs
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Thread = Java.Lang.Thread;
|
||||||
|
|
||||||
|
namespace keepass2android;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows to run tasks in the background. The UI is not blocked by the task. Tasks continue to run in the BackgroundSyncService if the app goes to background while tasks are active.
|
||||||
|
/// </summary>
|
||||||
|
public class OperationRunner
|
||||||
|
{
|
||||||
|
//singleton instance
|
||||||
|
private static OperationRunner _instance = null;
|
||||||
|
|
||||||
|
public static OperationRunner Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
{
|
||||||
|
_instance = new OperationRunner();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize(IKp2aApp app)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct OperationWithMetadata
|
||||||
|
{
|
||||||
|
public OperationWithMetadata()
|
||||||
|
{
|
||||||
|
Operation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationWithFinishHandler Operation { get; set; }
|
||||||
|
public bool RunBlocking { get; set; } = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressUiAsStatusLoggerAdapter StatusLogger => _statusLogger;
|
||||||
|
|
||||||
|
private OperationRunner()
|
||||||
|
{
|
||||||
|
//private constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Queue<OperationWithMetadata> _taskQueue = new Queue<OperationWithMetadata>();
|
||||||
|
private readonly object _taskQueueLock = new object();
|
||||||
|
private Java.Lang.Thread? _thread = null;
|
||||||
|
private OperationWithMetadata? _currentlyRunningTask = null;
|
||||||
|
private ProgressUiAsStatusLoggerAdapter _statusLogger = null;
|
||||||
|
private IProgressDialog _progressDialog;
|
||||||
|
private IKp2aApp _app;
|
||||||
|
|
||||||
|
public void Run(IKp2aApp app, OperationWithFinishHandler operation, bool runBlocking = false)
|
||||||
|
{
|
||||||
|
lock (Instance._taskQueueLock)
|
||||||
|
{
|
||||||
|
_taskQueue.Enqueue(new OperationWithMetadata(){ Operation = operation, RunBlocking = runBlocking});
|
||||||
|
operation.SetStatusLogger(_statusLogger);
|
||||||
|
|
||||||
|
// Start thread to run the task (unless it's already running)
|
||||||
|
if (_thread == null)
|
||||||
|
{
|
||||||
|
_statusLogger.StartLogging("", false);
|
||||||
|
_thread = new Java.Lang.Thread(() =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
|
||||||
|
lock (_taskQueueLock)
|
||||||
|
{
|
||||||
|
if (!_taskQueue.Any())
|
||||||
|
{
|
||||||
|
_thread = null;
|
||||||
|
_statusLogger.EndLogging();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentlyRunningTask = _taskQueue.Dequeue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentlyRunningTask.Value.RunBlocking)
|
||||||
|
{
|
||||||
|
app.UiThreadHandler.Post(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
TrySetupProgressDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalFinishedHandler = _currentlyRunningTask.Value.Operation.operationFinishedHandler;
|
||||||
|
_currentlyRunningTask.Value.Operation.operationFinishedHandler = new ActionOnOperationFinished(app, (
|
||||||
|
(success, message, context) =>
|
||||||
|
{
|
||||||
|
if (_currentlyRunningTask?.RunBlocking == true)
|
||||||
|
{
|
||||||
|
_app.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
_progressDialog?.Dismiss();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_currentlyRunningTask = null;
|
||||||
|
|
||||||
|
}), originalFinishedHandler);
|
||||||
|
_currentlyRunningTask.Value.Operation.Run();
|
||||||
|
|
||||||
|
while (_currentlyRunningTask != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log("Thread interrupted.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
_thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool TrySetupProgressDialog()
|
||||||
|
{
|
||||||
|
string currentMessage = "Initializing...";
|
||||||
|
string currentSubmessage = "";
|
||||||
|
|
||||||
|
if (_statusLogger != null)
|
||||||
|
{
|
||||||
|
currentMessage = _statusLogger.LastMessage;
|
||||||
|
currentSubmessage = _statusLogger.LastSubMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_progressDialog != null)
|
||||||
|
{
|
||||||
|
var pd = _progressDialog;
|
||||||
|
_app.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
pd.Dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show process dialog
|
||||||
|
_progressDialog = _app.CreateProgressDialog(_app.ActiveContext);
|
||||||
|
if (_progressDialog == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var progressUi = new ProgressDialogUi(_app, _app.UiThreadHandler, _progressDialog);
|
||||||
|
_statusLogger.SetNewProgressUi(progressUi);
|
||||||
|
|
||||||
|
_statusLogger.StartLogging("", false);
|
||||||
|
_statusLogger.UpdateMessage(currentMessage);
|
||||||
|
_statusLogger.UpdateSubMessage(currentSubmessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNewActiveContext(IKp2aApp app)
|
||||||
|
{
|
||||||
|
_app = app;
|
||||||
|
Context? context = app.ActiveContext;
|
||||||
|
bool isAppContext = context == null || (context.ApplicationContext == context);
|
||||||
|
lock (_taskQueueLock)
|
||||||
|
{
|
||||||
|
if (isAppContext && _thread != null)
|
||||||
|
{
|
||||||
|
//this will register the service as new active context (see BackgroundSyncService.OnStartCommand())
|
||||||
|
app.StartBackgroundSyncService();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentlyRunningTask?.RunBlocking == true && (context is Activity { IsFinishing: false, IsDestroyed:false}))
|
||||||
|
{
|
||||||
|
app.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
TrySetupProgressDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var progressUi = (context as IProgressUiProvider)?.ProgressUi;
|
||||||
|
if (_statusLogger == null)
|
||||||
|
{
|
||||||
|
_statusLogger = new ProgressUiAsStatusLoggerAdapter(progressUi, app);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_statusLogger.SetNewProgressUi(progressUi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var task in _taskQueue.Concat(_currentlyRunningTask == null ?
|
||||||
|
new List<OperationWithMetadata>() : [_currentlyRunningTask.Value])
|
||||||
|
)
|
||||||
|
{
|
||||||
|
task.Operation.SetStatusLogger(_statusLogger);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelAll()
|
||||||
|
{
|
||||||
|
lock (_taskQueueLock)
|
||||||
|
{
|
||||||
|
if (_thread != null)
|
||||||
|
{
|
||||||
|
_thread.Interrupt();
|
||||||
|
_thread = null;
|
||||||
|
_statusLogger?.EndLogging();
|
||||||
|
}
|
||||||
|
|
||||||
|
_taskQueue.Clear();
|
||||||
|
_currentlyRunningTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -22,116 +22,152 @@ using KeePassLib.Interfaces;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
/// <summary>
|
public interface IKp2aStatusLogger : IStatusLogger
|
||||||
/// StatusLogger implementation which shows the progress in a progress dialog
|
{
|
||||||
/// </summary>
|
void UpdateMessage(UiStringKey stringKey);
|
||||||
public class ProgressDialogStatusLogger: IStatusLogger {
|
string LastMessage { get; }
|
||||||
|
string LastSubMessage { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IProgressUi
|
||||||
|
{
|
||||||
|
void Show();
|
||||||
|
void Hide();
|
||||||
|
void UpdateMessage(String message);
|
||||||
|
void UpdateSubMessage(String submessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IProgressUiProvider
|
||||||
|
{
|
||||||
|
IProgressUi? ProgressUi { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Kp2aNullStatusLogger : IKp2aStatusLogger
|
||||||
|
{
|
||||||
|
public void StartLogging(string strOperation, bool bWriteOperationToLog)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndLogging()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetProgress(uint uPercent)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetText(string strNewText, LogStatusType lsType)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _lastMessage;
|
||||||
|
private string _lastSubMessage;
|
||||||
|
public void UpdateMessage(string message)
|
||||||
|
{
|
||||||
|
_lastMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSubMessage(string submessage)
|
||||||
|
{
|
||||||
|
_lastSubMessage = submessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContinueWork()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMessage(UiStringKey stringKey)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LastMessage { get { return _lastMessage; } }
|
||||||
|
public string LastSubMessage { get { return _lastSubMessage; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// StatusLogger implementation which shows the progress in a progress dialog
|
||||||
|
/// </summary>
|
||||||
|
public class ProgressDialogUi: IProgressUi
|
||||||
|
{
|
||||||
private readonly IProgressDialog _progressDialog;
|
private readonly IProgressDialog _progressDialog;
|
||||||
readonly IKp2aApp _app;
|
|
||||||
private readonly Handler _handler;
|
private readonly Handler _handler;
|
||||||
private string _message = "";
|
private string _message = "";
|
||||||
private string _submessage;
|
private string _submessage;
|
||||||
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
public String SubMessage => _submessage;
|
public String LastSubMessage => _submessage;
|
||||||
public String Message => _message;
|
public String LastMessage => _message;
|
||||||
|
|
||||||
public ProgressDialogStatusLogger() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProgressDialogStatusLogger(IKp2aApp app, Handler handler, IProgressDialog pd) {
|
public ProgressDialogUi(IKp2aApp app, Handler handler, IProgressDialog pd)
|
||||||
_app = app;
|
{
|
||||||
|
_app = app;
|
||||||
_progressDialog = pd;
|
_progressDialog = pd;
|
||||||
_handler = handler;
|
_handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMessage(UiStringKey stringKey) {
|
|
||||||
if (_app != null)
|
|
||||||
UpdateMessage(_app.GetResourceString(stringKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateMessage (String message)
|
public void UpdateSubMessage(String submessage)
|
||||||
{
|
{
|
||||||
Kp2aLog.Log("status message: " + message);
|
Kp2aLog.Log("status submessage: " + submessage);
|
||||||
|
_submessage = submessage;
|
||||||
|
if (_app != null && _progressDialog != null && _handler != null)
|
||||||
|
{
|
||||||
|
_handler.Post(() =>
|
||||||
|
{
|
||||||
|
if (!String.IsNullOrEmpty(submessage))
|
||||||
|
{
|
||||||
|
_progressDialog.SetMessage(_message + " (" + submessage + ")");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_progressDialog.SetMessage(_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
_handler.Post(() =>
|
||||||
|
{
|
||||||
|
_progressDialog?.Show();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
_handler.Post(() =>
|
||||||
|
{
|
||||||
|
_progressDialog?.Dismiss();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMessage(string message)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log("status message: " + message);
|
||||||
_message = message;
|
_message = message;
|
||||||
if ( _app!= null && _progressDialog != null && _handler != null ) {
|
if (_app != null && _progressDialog != null && _handler != null)
|
||||||
_handler.Post(() => {_progressDialog.SetMessage(message); } );
|
{
|
||||||
}
|
_handler.Post(() =>
|
||||||
}
|
{
|
||||||
|
_progressDialog.SetMessage(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateSubMessage(String submessage)
|
}
|
||||||
{
|
|
||||||
Kp2aLog.Log("status submessage: " + submessage);
|
|
||||||
_submessage = submessage;
|
|
||||||
if (_app != null && _progressDialog != null && _handler != null)
|
|
||||||
{
|
|
||||||
_handler.Post(() =>
|
|
||||||
{
|
|
||||||
if (!String.IsNullOrEmpty(submessage))
|
|
||||||
{
|
|
||||||
_progressDialog.SetMessage(_message + " (" + submessage + ")");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_progressDialog.SetMessage(_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IStatusLogger implementation
|
|
||||||
|
|
||||||
public void StartLogging (string strOperation, bool bWriteOperationToLog)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EndLogging ()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetProgress (uint uPercent)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SetText (string strNewText, LogStatusType lsType)
|
|
||||||
{
|
|
||||||
if (strNewText.StartsWith("KP2AKEY_"))
|
|
||||||
{
|
|
||||||
UiStringKey key;
|
|
||||||
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
|
|
||||||
{
|
|
||||||
UpdateMessage(_app.GetResourceString(key), lsType);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UpdateMessage(strNewText, lsType);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateMessage(string message, LogStatusType lsType)
|
|
||||||
{
|
|
||||||
if (lsType == LogStatusType.AdditionalInfo)
|
|
||||||
{
|
|
||||||
UpdateSubMessage(message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ContinueWork ()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Keepass2Android is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Java.Lang;
|
|
||||||
|
|
||||||
namespace keepass2android
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class to run a task while a progress dialog is shown
|
|
||||||
/// </summary>
|
|
||||||
public class ProgressTask
|
|
||||||
{
|
|
||||||
//for handling Activity recreation situations, we need access to the currently active task. It must hold that there is no more than one active task.
|
|
||||||
private static ProgressTask _currentTask = null;
|
|
||||||
|
|
||||||
public static void SetNewActiveActivity(Activity activeActivity)
|
|
||||||
{
|
|
||||||
if (_currentTask != null)
|
|
||||||
{
|
|
||||||
_currentTask.ActiveActivity = activeActivity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static void RemoveActiveActivity(Activity activity)
|
|
||||||
{
|
|
||||||
if ((_currentTask != null) && (_currentTask._activeActivity == activity))
|
|
||||||
_currentTask.ActiveActivity = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Activity ActiveActivity
|
|
||||||
{
|
|
||||||
get { return _activeActivity; }
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
if (_activeActivity != null && _activeActivity != _previouslyActiveActivity)
|
|
||||||
{
|
|
||||||
_previouslyActiveActivity = _activeActivity;
|
|
||||||
|
|
||||||
}
|
|
||||||
_activeActivity = value;
|
|
||||||
if (_task != null)
|
|
||||||
_task.ActiveActivity = _activeActivity;
|
|
||||||
if (_activeActivity != null)
|
|
||||||
{
|
|
||||||
SetupProgressDialog(_app);
|
|
||||||
_progressDialog.Show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Activity PreviouslyActiveActivity
|
|
||||||
{
|
|
||||||
get { return _previouslyActiveActivity; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Handler _handler;
|
|
||||||
private readonly RunnableOnFinish _task;
|
|
||||||
private IProgressDialog _progressDialog;
|
|
||||||
private readonly IKp2aApp _app;
|
|
||||||
private Java.Lang.Thread _thread;
|
|
||||||
private Activity _activeActivity, _previouslyActiveActivity;
|
|
||||||
private ProgressDialogStatusLogger _progressDialogStatusLogger;
|
|
||||||
|
|
||||||
public ProgressTask(IKp2aApp app, Activity activity, RunnableOnFinish task)
|
|
||||||
{
|
|
||||||
_activeActivity = activity;
|
|
||||||
_task = task;
|
|
||||||
_handler = app.UiThreadHandler;
|
|
||||||
_app = app;
|
|
||||||
|
|
||||||
SetupProgressDialog(app);
|
|
||||||
|
|
||||||
// Set code to run when this is finished
|
|
||||||
_task.OnFinishToRun = new AfterTask(activity, task.OnFinishToRun, _handler, this);
|
|
||||||
|
|
||||||
_task.SetStatusLogger(_progressDialogStatusLogger);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetupProgressDialog(IKp2aApp app)
|
|
||||||
{
|
|
||||||
string currentMessage = "Initializing...";
|
|
||||||
string currentSubmessage = "";
|
|
||||||
|
|
||||||
if (_progressDialogStatusLogger != null)
|
|
||||||
{
|
|
||||||
currentMessage = _progressDialogStatusLogger.Message;
|
|
||||||
currentSubmessage = _progressDialogStatusLogger.SubMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_progressDialog != null)
|
|
||||||
{
|
|
||||||
var pd = _progressDialog;
|
|
||||||
app.UiThreadHandler.Post(() =>
|
|
||||||
{
|
|
||||||
pd.Dismiss();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show process dialog
|
|
||||||
_progressDialog = app.CreateProgressDialog(_activeActivity);
|
|
||||||
_progressDialog.SetTitle(_app.GetResourceString(UiStringKey.progress_title));
|
|
||||||
_progressDialogStatusLogger = new ProgressDialogStatusLogger(_app, _handler, _progressDialog);
|
|
||||||
_progressDialogStatusLogger.UpdateMessage(currentMessage);
|
|
||||||
_progressDialogStatusLogger.UpdateSubMessage(currentSubmessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Run(bool allowOverwriteCurrentTask = false)
|
|
||||||
{
|
|
||||||
if ((!allowOverwriteCurrentTask) && (_currentTask != null))
|
|
||||||
throw new System.Exception("Cannot start another ProgressTask while ProgressTask is already running! " + _task.GetType().Name + "/" + _currentTask._task.GetType().Name);
|
|
||||||
_currentTask = this;
|
|
||||||
|
|
||||||
// Show process dialog
|
|
||||||
_progressDialog.Show();
|
|
||||||
|
|
||||||
|
|
||||||
// Start Thread to Run task
|
|
||||||
_thread = new Java.Lang.Thread(_task.Run);
|
|
||||||
_thread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void JoinWorkerThread()
|
|
||||||
{
|
|
||||||
_thread.Join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AfterTask : OnFinish {
|
|
||||||
readonly ProgressTask _progressTask;
|
|
||||||
|
|
||||||
public AfterTask (Activity activity, OnFinish finish, Handler handler, ProgressTask pt): base(activity, finish, handler)
|
|
||||||
{
|
|
||||||
_progressTask = pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Run() {
|
|
||||||
base.Run();
|
|
||||||
|
|
||||||
if (Handler != null) //can be null in tests
|
|
||||||
{
|
|
||||||
// Remove the progress dialog
|
|
||||||
Handler.Post(delegate
|
|
||||||
{
|
|
||||||
_progressTask._progressDialog.Dismiss();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_progressTask._progressDialog.Dismiss();
|
|
||||||
}
|
|
||||||
_currentTask = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
94
src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs
Normal file
94
src/Kp2aBusinessLogic/ProgressUiAsStatusLoggerAdapter.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using KeePassLib.Interfaces;
|
||||||
|
|
||||||
|
namespace keepass2android;
|
||||||
|
|
||||||
|
public class ProgressUiAsStatusLoggerAdapter : IKp2aStatusLogger
|
||||||
|
{
|
||||||
|
private IProgressUi? _progressUi;
|
||||||
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
|
private string _lastMessage = "";
|
||||||
|
private string _lastSubMessage = "";
|
||||||
|
private bool _isVisible = false;
|
||||||
|
|
||||||
|
public ProgressUiAsStatusLoggerAdapter(IProgressUi progressUi, IKp2aApp app)
|
||||||
|
{
|
||||||
|
_progressUi = progressUi;
|
||||||
|
_app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNewProgressUi(IProgressUi progressUi)
|
||||||
|
{
|
||||||
|
_progressUi?.Hide();
|
||||||
|
_progressUi = progressUi;
|
||||||
|
if (_isVisible)
|
||||||
|
{
|
||||||
|
progressUi?.Show();
|
||||||
|
progressUi?.UpdateMessage(_lastMessage);
|
||||||
|
progressUi?.UpdateSubMessage(_lastSubMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progressUi?.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartLogging(string strOperation, bool bWriteOperationToLog)
|
||||||
|
{
|
||||||
|
_progressUi?.Show();
|
||||||
|
_isVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndLogging()
|
||||||
|
{
|
||||||
|
_progressUi?.Hide();
|
||||||
|
_isVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetProgress(uint uPercent)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetText(string strNewText, LogStatusType lsType)
|
||||||
|
{
|
||||||
|
if (strNewText.StartsWith("KP2AKEY_"))
|
||||||
|
{
|
||||||
|
UiStringKey key;
|
||||||
|
if (Enum.TryParse(strNewText.Substring("KP2AKEY_".Length), true, out key))
|
||||||
|
{
|
||||||
|
UpdateMessage(_app.GetResourceString(key));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateMessage(strNewText);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMessage(string message)
|
||||||
|
{
|
||||||
|
_progressUi?.UpdateMessage(message);
|
||||||
|
_lastMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSubMessage(string submessage)
|
||||||
|
{
|
||||||
|
_progressUi?.UpdateSubMessage(submessage);
|
||||||
|
_lastSubMessage = submessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContinueWork()
|
||||||
|
{
|
||||||
|
return !Java.Lang.Thread.Interrupted();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMessage(UiStringKey stringKey)
|
||||||
|
{
|
||||||
|
if (_app != null)
|
||||||
|
UpdateMessage(_app.GetResourceString(stringKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LastMessage { get { return _lastMessage; } }
|
||||||
|
public string LastSubMessage { get { return _lastSubMessage; } }
|
||||||
|
}
|
@@ -4,6 +4,7 @@ using Android.Content;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Java.Net;
|
using Java.Net;
|
||||||
|
using KeePass.Util;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
|
|
||||||
@@ -99,10 +100,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
if (resultCode == Result.Ok)
|
if (resultCode == Result.Ok)
|
||||||
{
|
{
|
||||||
Kp2aLog.Log("FileSelection returned "+data.DataString);
|
|
||||||
//TODO: don't try to extract filename if content URI
|
|
||||||
string filename = IntentToFilename(data);
|
string filename = IntentToFilename(data);
|
||||||
Kp2aLog.Log("FileSelection returned filename " + filename);
|
|
||||||
if (filename != null)
|
if (filename != null)
|
||||||
{
|
{
|
||||||
if (filename.StartsWith("file://"))
|
if (filename.StartsWith("file://"))
|
||||||
@@ -208,7 +206,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message);
|
ShowErrorToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + ExceptionUtil.GetErrorMessage(e));
|
||||||
ReturnCancel();
|
ReturnCancel();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
26
src/Kp2aBusinessLogic/Utils/ExceptionUtil.cs
Normal file
26
src/Kp2aBusinessLogic/Utils/ExceptionUtil.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
|
namespace KeePass.Util
|
||||||
|
{
|
||||||
|
public class ExceptionUtil
|
||||||
|
{
|
||||||
|
|
||||||
|
public static string GetErrorMessage(Exception e)
|
||||||
|
{
|
||||||
|
string errorMessage = e.Message;
|
||||||
|
if (e is Java.Lang.Exception javaException)
|
||||||
|
{
|
||||||
|
errorMessage = javaException.Message ?? errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using KeePass.Util;
|
||||||
using KeePassLib.Cryptography;
|
using KeePassLib.Cryptography;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
@@ -12,16 +13,15 @@ using keepass2android.Io;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public class CheckDatabaseForChanges: RunnableOnFinish
|
public class CheckDatabaseForChanges: OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly Context _context;
|
private readonly Context _context;
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
|
|
||||||
public CheckDatabaseForChanges(Activity context, IKp2aApp app, OnFinish finish)
|
public CheckDatabaseForChanges(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
: base(context, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_context = context;
|
|
||||||
_app = app;
|
_app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Finish(false, e.Message);
|
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -85,7 +85,7 @@ namespace keepass2android
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
|
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
|
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, IKp2aStatusLogger status, IDatabaseFormat databaseFormat)
|
||||||
{
|
{
|
||||||
PwDatabase pwDatabase = new PwDatabase();
|
PwDatabase pwDatabase = new PwDatabase();
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ namespace keepass2android
|
|||||||
get { return GetFingerprintModePrefKey(Ioc); }
|
get { return GetFingerprintModePrefKey(Ioc); }
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
|
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, IKp2aStatusLogger status, IDatabaseFormat databaseFormat)
|
||||||
{
|
{
|
||||||
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
||||||
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
|
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
|
||||||
@@ -194,9 +194,9 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void SaveData() {
|
public void SaveData(IFileStorage fileStorage) {
|
||||||
|
|
||||||
using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
|
using (IWriteTransaction trans = fileStorage.OpenWriteTransaction(Ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
|
||||||
{
|
{
|
||||||
DatabaseFormat.Save(KpDatabase, trans.OpenFile());
|
DatabaseFormat.Save(KpDatabase, trans.OpenFile());
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ using Com.Keepassdroid.Database.Exception;
|
|||||||
#endif
|
#endif
|
||||||
using Com.Keepassdroid.Database.Save;
|
using Com.Keepassdroid.Database.Save;
|
||||||
using Java.Util;
|
using Java.Util;
|
||||||
|
using KeePass.Util;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
using KeePassLib.Cryptography;
|
using KeePassLib.Cryptography;
|
||||||
using KeePassLib.Cryptography.Cipher;
|
using KeePassLib.Cryptography.Cipher;
|
||||||
@@ -82,15 +83,14 @@ namespace keepass2android
|
|||||||
catch (Java.IO.FileNotFoundException e)
|
catch (Java.IO.FileNotFoundException e)
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException(
|
throw new FileNotFoundException(
|
||||||
e.Message, e);
|
ExceptionUtil.GetErrorMessage(e), e);
|
||||||
}
|
}
|
||||||
catch (Java.Lang.Exception e)
|
catch (Java.Lang.Exception e)
|
||||||
{
|
{
|
||||||
if (e.Message == "Invalid key!")
|
if (e.Message == "Invalid key!")
|
||||||
throw new InvalidCompositeKeyException();
|
throw new InvalidCompositeKeyException();
|
||||||
throw new Exception(e.LocalizedMessage ??
|
throw new Exception(ExceptionUtil.GetErrorMessage(e) ??
|
||||||
e.Message ??
|
e.GetType().Name, e);
|
||||||
e.GetType().Name, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HashOfLastStream = hashingStream.Hash;
|
HashOfLastStream = hashingStream.Hash;
|
||||||
@@ -396,8 +396,6 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
PwGroupV3 toGroup = new PwGroupV3();
|
PwGroupV3 toGroup = new PwGroupV3();
|
||||||
toGroup.Name = fromGroup.Name;
|
toGroup.Name = fromGroup.Name;
|
||||||
//todo remove
|
|
||||||
Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name);
|
|
||||||
|
|
||||||
toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
|
toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
|
||||||
toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));
|
toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));
|
||||||
|
@@ -4,120 +4,159 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
|
using KeePass.Util;
|
||||||
|
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
|
||||||
|
using KeePassLib;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public class SynchronizeCachedDatabase: RunnableOnFinish
|
public class SynchronizeCachedDatabase: OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly Activity _context;
|
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private SaveDb _saveDb;
|
private IDatabaseModificationWatcher _modificationWatcher;
|
||||||
|
private readonly Database _database;
|
||||||
|
|
||||||
public SynchronizeCachedDatabase(Activity context, IKp2aApp app, OnFinish finish)
|
|
||||||
: base(context, finish)
|
public SynchronizeCachedDatabase(IKp2aApp app, Database database, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher)
|
||||||
{
|
: base(app, operationFinishedHandler)
|
||||||
_context = context;
|
{
|
||||||
_app = app;
|
_app = app;
|
||||||
}
|
_database = database;
|
||||||
|
_modificationWatcher = modificationWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
public override void Run()
|
public override void Run()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IOConnectionInfo ioc = _app.CurrentDb.Ioc;
|
IOConnectionInfo ioc = _database.Ioc;
|
||||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||||
if (!(fileStorage is CachingFileStorage))
|
if (!(fileStorage is CachingFileStorage))
|
||||||
{
|
{
|
||||||
throw new Exception("Cannot sync a non-cached database!");
|
throw new Exception("Cannot sync a non-cached database!");
|
||||||
}
|
}
|
||||||
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
|
|
||||||
CachingFileStorage cachingFileStorage = (CachingFileStorage) fileStorage;
|
|
||||||
|
|
||||||
//download file from remote location and calculate hash:
|
StatusLogger.UpdateMessage(UiStringKey.SynchronizingCachedDatabase);
|
||||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
|
CachingFileStorage cachingFileStorage = (CachingFileStorage)fileStorage;
|
||||||
string hash;
|
|
||||||
|
//download file from remote location and calculate hash:
|
||||||
MemoryStream remoteData;
|
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
|
||||||
try
|
string hash;
|
||||||
{
|
|
||||||
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
|
MemoryStream remoteData;
|
||||||
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
|
try
|
||||||
}
|
{
|
||||||
catch (FileNotFoundException)
|
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
|
||||||
{
|
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
|
||||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
|
}
|
||||||
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
catch (FileNotFoundException)
|
||||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
{
|
||||||
|
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
|
||||||
|
cachingFileStorage.UpdateRemoteFile(ioc,
|
||||||
|
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||||
|
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||||
Kp2aLog.Log("Checking for file change: file not found");
|
Kp2aLog.Log("Checking for file change: file not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//check if remote file was modified:
|
//check if remote file was modified:
|
||||||
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
|
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
|
||||||
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
|
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
|
||||||
if (baseVersionHash != hash)
|
if (baseVersionHash != hash)
|
||||||
{
|
{
|
||||||
//remote file is modified
|
//remote file is modified
|
||||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||||
{
|
{
|
||||||
//conflict! need to merge
|
//conflict! need to merge
|
||||||
_saveDb = new SaveDb(_context, _app, new ActionOnFinish(ActiveActivity, (success, result, activity) =>
|
var _saveDb = new SaveDb(_app, new ActionOnOperationFinished(_app,
|
||||||
{
|
(success, result, activity) =>
|
||||||
if (!success)
|
{
|
||||||
{
|
if (!success)
|
||||||
Finish(false, result);
|
{
|
||||||
}
|
Finish(false, result);
|
||||||
else
|
}
|
||||||
{
|
else
|
||||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
{
|
||||||
}
|
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||||
_saveDb = null;
|
}
|
||||||
}), _app.CurrentDb, false, remoteData);
|
}), _database, false, remoteData, _modificationWatcher);
|
||||||
_saveDb.Run();
|
_saveDb.SetStatusLogger(StatusLogger);
|
||||||
|
_saveDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
|
||||||
|
_saveDb.SyncInBackground = false;
|
||||||
|
_saveDb.Run();
|
||||||
|
|
||||||
_app.CurrentDb.UpdateGlobals();
|
_database.UpdateGlobals();
|
||||||
|
|
||||||
_app.MarkAllGroupsAsDirty();
|
_app.MarkAllGroupsAsDirty();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//only the remote file was modified -> reload database.
|
//only the remote file was modified -> reload database.
|
||||||
//note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.)
|
var onFinished = new ActionOnOperationFinished(_app, (success, result, activity) =>
|
||||||
_app.TriggerReload(_context, (bool result) => Finish(result));
|
{
|
||||||
}
|
if (!success)
|
||||||
}
|
{
|
||||||
else
|
Finish(false, result);
|
||||||
{
|
}
|
||||||
//remote file is unmodified
|
else
|
||||||
if (cachingFileStorage.HasLocalChanges(ioc))
|
{
|
||||||
{
|
new Handler(Looper.MainLooper).Post(() =>
|
||||||
//but we have local changes -> upload:
|
{
|
||||||
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
|
_database.UpdateGlobals();
|
||||||
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
|
||||||
StatusLogger.UpdateSubMessage("");
|
_app.MarkAllGroupsAsDirty();
|
||||||
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||||
}
|
});
|
||||||
else
|
|
||||||
{
|
}
|
||||||
//files are in sync: just set the result
|
});
|
||||||
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
|
var _loadDb = new LoadDb(_app, ioc, Task.FromResult(remoteData),
|
||||||
}
|
_database.KpDatabase.MasterKey, null, onFinished, true, false, _modificationWatcher);
|
||||||
}
|
_loadDb.SetStatusLogger(StatusLogger);
|
||||||
}
|
_loadDb.DoNotSetStatusLoggerMessage = true; //Keep "sync db" as main message
|
||||||
|
_loadDb.Run();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//remote file is unmodified
|
||||||
|
if (cachingFileStorage.HasLocalChanges(ioc))
|
||||||
|
{
|
||||||
|
//but we have local changes -> upload:
|
||||||
|
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.UploadingFile));
|
||||||
|
cachingFileStorage.UpdateRemoteFile(ioc,
|
||||||
|
_app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
|
||||||
|
StatusLogger.UpdateSubMessage("");
|
||||||
|
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//files are in sync: just set the result
|
||||||
|
Finish(true, _app.GetResourceString(UiStringKey.FilesInSync));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Java.Lang.InterruptedException e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
//no Finish()
|
||||||
|
}
|
||||||
|
catch (Java.IO.InterruptedIOException e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
//no Finish()
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
Finish(false, e.Message);
|
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void JoinWorkerThread()
|
|
||||||
{
|
|
||||||
if (_saveDb != null)
|
|
||||||
_saveDb.JoinWorkerThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll.
|
|
||||||
|
|
||||||
Keepass2Android is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Keepass2Android is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Android.App;
|
|
||||||
using Android.OS;
|
|
||||||
|
|
||||||
namespace keepass2android
|
|
||||||
{
|
|
||||||
public class ActionOnFinish: OnFinish
|
|
||||||
{
|
|
||||||
public delegate void ActionToPerformOnFinsh(bool success, String message, Activity activeActivity);
|
|
||||||
|
|
||||||
readonly ActionToPerformOnFinsh _actionToPerform;
|
|
||||||
|
|
||||||
public ActionOnFinish(Activity activity, ActionToPerformOnFinsh actionToPerform) : base(activity, null, null)
|
|
||||||
{
|
|
||||||
_actionToPerform = actionToPerform;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ActionOnFinish(Activity activity, ActionToPerformOnFinsh actionToPerform, OnFinish finish) : base(activity, finish)
|
|
||||||
{
|
|
||||||
_actionToPerform = actionToPerform;
|
|
||||||
}
|
|
||||||
|
|
||||||
//if set to true, the previously active active will be passed to ActionToPerformOnFinish instead null if no activity is on foreground
|
|
||||||
public bool AllowInactiveActivity { get; set; }
|
|
||||||
|
|
||||||
public override void Run()
|
|
||||||
{
|
|
||||||
if (Message == null)
|
|
||||||
Message = "";
|
|
||||||
if (Handler != null)
|
|
||||||
{
|
|
||||||
Handler.Post(() => {_actionToPerform(Success, Message, ActiveActivity);});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
_actionToPerform(Success, Message, AllowInactiveActivity ? (ActiveActivity ?? PreviouslyActiveActivity) : ActiveActivity);
|
|
||||||
base.Run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll.
|
||||||
|
|
||||||
|
Keepass2Android is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Keepass2Android is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using keepass2android;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
public class ActionOnOperationFinished: OnOperationFinishedHandler
|
||||||
|
{
|
||||||
|
public delegate void ActionToPerformOnFinsh(bool success, String message, Context activeContext);
|
||||||
|
|
||||||
|
readonly ActionToPerformOnFinsh _actionToPerform;
|
||||||
|
|
||||||
|
public ActionOnOperationFinished(IKp2aApp app, ActionToPerformOnFinsh actionToPerform) : base(app, null, null)
|
||||||
|
{
|
||||||
|
_actionToPerform = actionToPerform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActionOnOperationFinished(IKp2aApp app, ActionToPerformOnFinsh actionToPerform, OnOperationFinishedHandler operationFinishedHandler) : base(app, operationFinishedHandler)
|
||||||
|
{
|
||||||
|
_actionToPerform = actionToPerform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Run()
|
||||||
|
{
|
||||||
|
if (Message == null)
|
||||||
|
Message = "";
|
||||||
|
if (Handler != null)
|
||||||
|
{
|
||||||
|
Handler.Post(() =>
|
||||||
|
{
|
||||||
|
_actionToPerform(Success, Message, ActiveContext);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_actionToPerform(Success, Message, ActiveContext);
|
||||||
|
}
|
||||||
|
base.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Action which runs when the contextInstanceId is the active context
|
||||||
|
// otherwise it is registered as pending action for the context instance.
|
||||||
|
public class ActionInContextInstanceOnOperationFinished : ActionOnOperationFinished
|
||||||
|
{
|
||||||
|
private readonly int _contextInstanceId;
|
||||||
|
private IKp2aApp _app;
|
||||||
|
|
||||||
|
public ActionInContextInstanceOnOperationFinished(int contextInstanceId, IKp2aApp app, ActionToPerformOnFinsh actionToPerform) : base(app, actionToPerform)
|
||||||
|
{
|
||||||
|
_contextInstanceId = contextInstanceId;
|
||||||
|
_app = app;
|
||||||
|
}
|
||||||
|
public ActionInContextInstanceOnOperationFinished(int contextInstanceId, IKp2aApp app, ActionToPerformOnFinsh actionToPerform, OnOperationFinishedHandler operationFinishedHandler) : base(app, actionToPerform, operationFinishedHandler)
|
||||||
|
{
|
||||||
|
_contextInstanceId = contextInstanceId;
|
||||||
|
_app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Run()
|
||||||
|
{
|
||||||
|
if ((ActiveContext as IContextInstanceIdProvider)?.ContextInstanceId != _contextInstanceId)
|
||||||
|
{
|
||||||
|
_app.RegisterPendingActionForContextInstance(_contextInstanceId, this);
|
||||||
|
}
|
||||||
|
else _app.UiThreadHandler.Post(() => base.Run());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@@ -21,7 +21,7 @@ using KeePassLib;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public class AddEntry : RunnableOnFinish {
|
public class AddEntry : OperationWithFinishHandler {
|
||||||
protected Database Db
|
protected Database Db
|
||||||
{
|
{
|
||||||
get { return _app.CurrentDb; }
|
get { return _app.CurrentDb; }
|
||||||
@@ -30,22 +30,20 @@ namespace keepass2android
|
|||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly PwEntry _entry;
|
private readonly PwEntry _entry;
|
||||||
private readonly PwGroup _parentGroup;
|
private readonly PwGroup _parentGroup;
|
||||||
private readonly Activity _ctx;
|
|
||||||
private readonly Database _db;
|
private readonly Database _db;
|
||||||
|
|
||||||
public static AddEntry GetInstance(Activity ctx, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish, Database db) {
|
public static AddEntry GetInstance(IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnOperationFinishedHandler operationFinishedHandler, Database db) {
|
||||||
|
|
||||||
return new AddEntry(ctx, db, app, entry, parentGroup, finish);
|
return new AddEntry(db, app, entry, parentGroup, operationFinishedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddEntry(Activity ctx, Database db, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnFinish finish):base(ctx, finish) {
|
public AddEntry(Database db, IKp2aApp app, PwEntry entry, PwGroup parentGroup, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||||
_ctx = ctx;
|
|
||||||
_db = db;
|
_db = db;
|
||||||
_parentGroup = parentGroup;
|
_parentGroup = parentGroup;
|
||||||
_app = app;
|
_app = app;
|
||||||
_entry = entry;
|
_entry = entry;
|
||||||
|
|
||||||
_onFinishToRun = new AfterAdd(ctx, app.CurrentDb, entry, app,OnFinishToRun);
|
_operationFinishedHandler = new AfterAdd(app.CurrentDb, entry, app,operationFinishedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -65,17 +63,17 @@ namespace keepass2android
|
|||||||
_db.Elements.Add(_entry);
|
_db.Elements.Add(_entry);
|
||||||
|
|
||||||
// Commit to disk
|
// Commit to disk
|
||||||
SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun);
|
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.Run();
|
save.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterAdd : OnFinish {
|
private class AfterAdd : OnOperationFinishedHandler {
|
||||||
private readonly Database _db;
|
private readonly Database _db;
|
||||||
private readonly PwEntry _entry;
|
private readonly PwEntry _entry;
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
public AfterAdd(Activity activity, Database db, PwEntry entry, IKp2aApp app, OnFinish finish):base(activity, finish) {
|
public AfterAdd( Database db, PwEntry entry, IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||||
_db = db;
|
_db = db;
|
||||||
_entry = entry;
|
_entry = entry;
|
||||||
_app = app;
|
_app = app;
|
||||||
|
@@ -23,7 +23,7 @@ using KeePassLib;
|
|||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
public class AddGroup : RunnableOnFinish {
|
public class AddGroup : OperationWithFinishHandler {
|
||||||
internal Database Db
|
internal Database Db
|
||||||
{
|
{
|
||||||
get { return _app.CurrentDb; }
|
get { return _app.CurrentDb; }
|
||||||
@@ -38,18 +38,16 @@ namespace keepass2android
|
|||||||
public PwGroup Group;
|
public PwGroup Group;
|
||||||
internal PwGroup Parent;
|
internal PwGroup Parent;
|
||||||
protected bool DontSave;
|
protected bool DontSave;
|
||||||
readonly Activity _ctx;
|
|
||||||
|
|
||||||
|
public static AddGroup GetInstance(IKp2aApp app, string name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnOperationFinishedHandler operationFinishedHandler, bool dontSave) {
|
||||||
public static AddGroup GetInstance(Activity ctx, IKp2aApp app, string name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnFinish finish, bool dontSave) {
|
return new AddGroup(app, name, iconid, groupCustomIconId, parent, operationFinishedHandler, dontSave);
|
||||||
return new AddGroup(ctx, app, name, iconid, groupCustomIconId, parent, finish, dontSave);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private AddGroup(Activity ctx, IKp2aApp app, String name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnFinish finish, bool dontSave)
|
private AddGroup(IKp2aApp app, String name, int iconid, PwUuid groupCustomIconId, PwGroup parent, OnOperationFinishedHandler operationFinishedHandler, bool dontSave)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
|
||||||
_name = name;
|
_name = name;
|
||||||
_iconId = iconid;
|
_iconId = iconid;
|
||||||
_groupCustomIconId = groupCustomIconId;
|
_groupCustomIconId = groupCustomIconId;
|
||||||
@@ -57,7 +55,7 @@ namespace keepass2android
|
|||||||
DontSave = dontSave;
|
DontSave = dontSave;
|
||||||
_app = app;
|
_app = app;
|
||||||
|
|
||||||
_onFinishToRun = new AfterAdd(ctx, this, OnFinishToRun);
|
_operationFinishedHandler = new AfterAdd(_app, this, operationFinishedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -74,15 +72,15 @@ namespace keepass2android
|
|||||||
_app.CurrentDb.Elements.Add(Group);
|
_app.CurrentDb.Elements.Add(Group);
|
||||||
|
|
||||||
// Commit to disk
|
// Commit to disk
|
||||||
SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, DontSave);
|
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, DontSave, null);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.Run();
|
save.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterAdd : OnFinish {
|
private class AfterAdd : OnOperationFinishedHandler {
|
||||||
readonly AddGroup _addGroup;
|
readonly AddGroup _addGroup;
|
||||||
|
|
||||||
public AfterAdd(Activity activity, AddGroup addGroup,OnFinish finish): base(activity, finish) {
|
public AfterAdd(IKp2aApp app, AddGroup addGroup,OnOperationFinishedHandler operationFinishedHandler): base(app, operationFinishedHandler) {
|
||||||
_addGroup = addGroup;
|
_addGroup = addGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@ using KeePassLib.Utility;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public class AddTemplateEntries : RunnableOnFinish {
|
public class AddTemplateEntries : OperationWithFinishHandler {
|
||||||
|
|
||||||
public class TemplateEntry
|
public class TemplateEntry
|
||||||
{
|
{
|
||||||
@@ -130,15 +130,13 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly Activity _ctx;
|
|
||||||
|
public AddTemplateEntries(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
public AddTemplateEntries(Activity ctx, IKp2aApp app, OnFinish finish)
|
: base(app, operationFinishedHandler)
|
||||||
: base(ctx, finish)
|
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
_app = app;
|
||||||
_app = app;
|
|
||||||
|
|
||||||
//_onFinishToRun = new AfterAdd(this, OnFinishToRun);
|
//_operationFinishedHandler = new AfterAdd(this, operationFinishedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>()
|
public static readonly List<TemplateEntry> TemplateEntries = new List<TemplateEntry>()
|
||||||
@@ -313,7 +311,7 @@ namespace keepass2android
|
|||||||
_app.DirtyGroups.Add(templateGroup);
|
_app.DirtyGroups.Add(templateGroup);
|
||||||
|
|
||||||
// Commit to disk
|
// Commit to disk
|
||||||
SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun);
|
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.Run();
|
save.Run();
|
||||||
}
|
}
|
||||||
@@ -337,7 +335,6 @@ namespace keepass2android
|
|||||||
_app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup);
|
_app.DirtyGroups.Add(_app.CurrentDb.KpDatabase.RootGroup);
|
||||||
_app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup;
|
_app.CurrentDb.GroupsById[templateGroup.Uuid] = templateGroup;
|
||||||
_app.CurrentDb.Elements.Add(templateGroup);
|
_app.CurrentDb.Elements.Add(templateGroup);
|
||||||
|
|
||||||
}
|
}
|
||||||
addedEntries = new List<PwEntry>();
|
addedEntries = new List<PwEntry>();
|
||||||
|
|
||||||
@@ -369,11 +366,11 @@ namespace keepass2android
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterAdd : OnFinish {
|
private class AfterAdd : OnOperationFinishedHandler {
|
||||||
private readonly Database _db;
|
private readonly Database _db;
|
||||||
private readonly List<PwEntry> _entries;
|
private readonly List<PwEntry> _entries;
|
||||||
|
|
||||||
public AfterAdd(Activity activity, Database db, List<PwEntry> entries, OnFinish finish):base(activity, finish) {
|
public AfterAdd(IKp2aApp app, Database db, List<PwEntry> entries, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||||
_db = db;
|
_db = db;
|
||||||
_entries = entries;
|
_entries = entries;
|
||||||
|
|
||||||
|
@@ -16,8 +16,8 @@ namespace keepass2android.database.edit
|
|||||||
{
|
{
|
||||||
public class CopyEntry: AddEntry
|
public class CopyEntry: AddEntry
|
||||||
{
|
{
|
||||||
public CopyEntry(Activity ctx, IKp2aApp app, PwEntry entry, OnFinish finish, Database db)
|
public CopyEntry(IKp2aApp app, PwEntry entry, OnOperationFinishedHandler operationFinishedHandler, Database db)
|
||||||
: base(ctx, db, app, CreateCopy(entry, app), entry.ParentGroup, finish)
|
: base(db, app, CreateCopy(entry, app), entry.ParentGroup, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,27 +26,24 @@ using KeePassLib.Keys;
|
|||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
public class CreateDb : RunnableOnFinish {
|
public class CreateDb : OperationWithFinishHandler {
|
||||||
private readonly IOConnectionInfo _ioc;
|
private readonly IOConnectionInfo _ioc;
|
||||||
private readonly bool _dontSave;
|
private readonly bool _dontSave;
|
||||||
private readonly Activity _ctx;
|
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private CompositeKey _key;
|
private CompositeKey _key;
|
||||||
private readonly bool _makeCurrent;
|
private readonly bool _makeCurrent;
|
||||||
|
|
||||||
public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, bool makeCurrent): base(ctx, finish) {
|
public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, bool makeCurrent): base(app, operationFinishedHandler) {
|
||||||
_ctx = ctx;
|
_ioc = ioc;
|
||||||
_ioc = ioc;
|
|
||||||
_dontSave = dontSave;
|
_dontSave = dontSave;
|
||||||
_makeCurrent = makeCurrent;
|
_makeCurrent = makeCurrent;
|
||||||
_app = app;
|
_app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave, CompositeKey key, bool makeCurrent)
|
public CreateDb(IKp2aApp app, Activity ctx, IOConnectionInfo ioc, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, CompositeKey key, bool makeCurrent)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
_ioc = ioc;
|
||||||
_ioc = ioc;
|
|
||||||
_dontSave = dontSave;
|
_dontSave = dontSave;
|
||||||
_app = app;
|
_app = app;
|
||||||
_key = key;
|
_key = key;
|
||||||
@@ -77,19 +74,19 @@ namespace keepass2android
|
|||||||
db.SearchHelper = new SearchDbHelper(_app);
|
db.SearchHelper = new SearchDbHelper(_app);
|
||||||
|
|
||||||
// Add a couple default groups
|
// Add a couple default groups
|
||||||
AddGroup internet = AddGroup.GetInstance(_ctx, _app, "Internet", 1, null, db.KpDatabase.RootGroup, null, true);
|
AddGroup internet = AddGroup.GetInstance(_app, "Internet", 1, null, db.KpDatabase.RootGroup, null, true);
|
||||||
internet.Run();
|
internet.Run();
|
||||||
AddGroup email = AddGroup.GetInstance(_ctx, _app, "eMail", 19, null, db.KpDatabase.RootGroup, null, true);
|
AddGroup email = AddGroup.GetInstance(_app, "eMail", 19, null, db.KpDatabase.RootGroup, null, true);
|
||||||
email.Run();
|
email.Run();
|
||||||
|
|
||||||
List<PwEntry> addedEntries;
|
List<PwEntry> addedEntries;
|
||||||
AddTemplateEntries addTemplates = new AddTemplateEntries(_ctx, _app, null);
|
AddTemplateEntries addTemplates = new AddTemplateEntries(_app, null);
|
||||||
addTemplates.AddTemplates(out addedEntries);
|
addTemplates.AddTemplates(out addedEntries);
|
||||||
|
|
||||||
// Commit changes
|
// Commit changes
|
||||||
SaveDb save = new SaveDb(_ctx, _app, db, OnFinishToRun, _dontSave);
|
SaveDb save = new SaveDb(_app, db, operationFinishedHandler, _dontSave, null);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
_onFinishToRun = null;
|
_operationFinishedHandler = null;
|
||||||
save.Run();
|
save.Run();
|
||||||
|
|
||||||
db.UpdateGlobals();
|
db.UpdateGlobals();
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
using Java.Lang;
|
||||||
|
|
||||||
|
namespace keepass2android;
|
||||||
|
|
||||||
|
public interface IDatabaseModificationWatcher
|
||||||
|
{
|
||||||
|
void BeforeModifyDatabases();
|
||||||
|
void AfterModifyDatabases();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NullDatabaseModificationWatcher : IDatabaseModificationWatcher
|
||||||
|
{
|
||||||
|
public void BeforeModifyDatabases() { }
|
||||||
|
public void AfterModifyDatabases() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BackgroundDatabaseModificationLocker(IKp2aApp app) : IDatabaseModificationWatcher
|
||||||
|
{
|
||||||
|
public void BeforeModifyDatabases()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (app.DatabasesBackgroundModificationLock.TryEnterWriteLock(TimeSpan.FromSeconds(0.1)))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Java.Lang.Thread.Interrupted())
|
||||||
|
{
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AfterModifyDatabases()
|
||||||
|
{
|
||||||
|
app.DatabasesBackgroundModificationLock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
@@ -29,8 +29,8 @@ namespace keepass2android
|
|||||||
private readonly PwEntry _entry;
|
private readonly PwEntry _entry;
|
||||||
private UiStringKey _statusMessage;
|
private UiStringKey _statusMessage;
|
||||||
|
|
||||||
public DeleteEntry(Activity activiy, IKp2aApp app, PwEntry entry, OnFinish finish):base(activiy, finish, app) {
|
public DeleteEntry(IKp2aApp app, PwEntry entry, OnOperationFinishedHandler operationFinishedHandler):base(operationFinishedHandler, app) {
|
||||||
Ctx = activiy;
|
|
||||||
Db = app.FindDatabaseForElement(entry);
|
Db = app.FindDatabaseForElement(entry);
|
||||||
_entry = entry;
|
_entry = entry;
|
||||||
|
|
||||||
|
@@ -29,25 +29,25 @@ namespace keepass2android
|
|||||||
private PwGroup _group;
|
private PwGroup _group;
|
||||||
protected bool DontSave;
|
protected bool DontSave;
|
||||||
|
|
||||||
public DeleteGroup(Activity activity, IKp2aApp app, PwGroup group, OnFinish finish)
|
public DeleteGroup(Activity activity, IKp2aApp app, PwGroup group, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
: base(activity, finish, app)
|
: base(operationFinishedHandler, app)
|
||||||
{
|
{
|
||||||
SetMembers(activity, app, group, false);
|
SetMembers(app, group, false);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
public DeleteGroup(Context ctx, Database db, PwGroup group, Activity act, OnFinish finish, bool dontSave)
|
public DeleteGroup(Context ctx, Database db, PwGroup group, Activity act, OnOperationFinishedHandler operationFinishedHandler, bool dontSave)
|
||||||
: base(finish)
|
: base(operationFinishedHandler)
|
||||||
{
|
{
|
||||||
SetMembers(ctx, db, group, act, dontSave);
|
SetMembers(ctx, db, group, act, dontSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish, bool dontSave):base(finish) {
|
public DeleteGroup(Context ctx, Database db, PwGroup group, OnOperationFinishedHandler operationFinishedHandler, bool dontSave):base(operationFinishedHandler) {
|
||||||
SetMembers(ctx, db, group, null, dontSave);
|
SetMembers(ctx, db, group, null, dontSave);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
private void SetMembers(Activity activity, IKp2aApp app, PwGroup group, bool dontSave)
|
private void SetMembers(IKp2aApp app, PwGroup group, bool dontSave)
|
||||||
{
|
{
|
||||||
base.SetMembers(activity, app.FindDatabaseForElement(group));
|
base.SetMembers(app.FindDatabaseForElement(group));
|
||||||
|
|
||||||
_group = group;
|
_group = group;
|
||||||
DontSave = dontSave;
|
DontSave = dontSave;
|
||||||
|
@@ -12,11 +12,11 @@ namespace keepass2android
|
|||||||
private readonly List<IStructureItem> _elementsToDelete;
|
private readonly List<IStructureItem> _elementsToDelete;
|
||||||
private readonly bool _canRecycle;
|
private readonly bool _canRecycle;
|
||||||
|
|
||||||
public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnFinish finish, IKp2aApp app)
|
public DeleteMultipleItemsFromOneDatabase(Activity activity, Database db, List<IStructureItem> elementsToDelete, OnOperationFinishedHandler operationFinishedHandler, IKp2aApp app)
|
||||||
: base(activity, finish, app)
|
: base(operationFinishedHandler, app)
|
||||||
{
|
{
|
||||||
_elementsToDelete = elementsToDelete;
|
_elementsToDelete = elementsToDelete;
|
||||||
SetMembers(activity, db);
|
SetMembers(db);
|
||||||
|
|
||||||
//determine once. The property is queried for each delete operation, but might return false
|
//determine once. The property is queried for each delete operation, but might return false
|
||||||
//after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore)
|
//after one entry/group is deleted (and thus in recycle bin and thus can't be recycled anymore)
|
||||||
|
@@ -6,10 +6,10 @@ using KeePassLib;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public abstract class DeleteRunnable : RunnableOnFinish
|
public abstract class DeleteRunnable : OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
protected DeleteRunnable(Activity activity, OnFinish finish, IKp2aApp app)
|
protected DeleteRunnable(OnOperationFinishedHandler operationFinishedHandler, IKp2aApp app)
|
||||||
: base(activity, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
App = app;
|
App = app;
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,10 @@ namespace keepass2android
|
|||||||
|
|
||||||
protected Database Db;
|
protected Database Db;
|
||||||
|
|
||||||
protected Activity Ctx;
|
|
||||||
|
|
||||||
protected void SetMembers(Activity activity, Database db)
|
protected void SetMembers( Database db)
|
||||||
{
|
{
|
||||||
Ctx = activity;
|
|
||||||
Db = db;
|
Db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,18 +130,18 @@ namespace keepass2android
|
|||||||
(dlgSender, dlgEvt) =>
|
(dlgSender, dlgEvt) =>
|
||||||
{
|
{
|
||||||
DeletePermanently = true;
|
DeletePermanently = true;
|
||||||
ProgressTask pt = new ProgressTask(App, Ctx, this);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App, this);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
},
|
},
|
||||||
(dlgSender, dlgEvt) =>
|
(dlgSender, dlgEvt) =>
|
||||||
{
|
{
|
||||||
DeletePermanently = false;
|
DeletePermanently = false;
|
||||||
ProgressTask pt = new ProgressTask(App, Ctx, this);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App, this);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
},
|
},
|
||||||
(dlgSender, dlgEvt) => { },
|
(dlgSender, dlgEvt) => { },
|
||||||
Ctx, messageSuffix);
|
messageSuffix);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -153,12 +152,12 @@ namespace keepass2android
|
|||||||
QuestionNoRecycleResourceId,
|
QuestionNoRecycleResourceId,
|
||||||
(dlgSender, dlgEvt) =>
|
(dlgSender, dlgEvt) =>
|
||||||
{
|
{
|
||||||
ProgressTask pt = new ProgressTask(App, Ctx, this);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App, this);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
(dlgSender, dlgEvt) => { },
|
(dlgSender, dlgEvt) => { },
|
||||||
Ctx, messageSuffix);
|
messageSuffix);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -215,7 +214,7 @@ namespace keepass2android
|
|||||||
Android.Util.Log.Debug("KP2A", "Calling PerformDelete..");
|
Android.Util.Log.Debug("KP2A", "Calling PerformDelete..");
|
||||||
PerformDelete(touchedGroups, permanentlyDeletedGroups);
|
PerformDelete(touchedGroups, permanentlyDeletedGroups);
|
||||||
|
|
||||||
_onFinishToRun = new ActionOnFinish(ActiveActivity,(success, message, activity) =>
|
_operationFinishedHandler = new ActionOnOperationFinished(App,(success, message, context) =>
|
||||||
{
|
{
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
@@ -236,10 +235,10 @@ namespace keepass2android
|
|||||||
// Let's not bother recovering from a failure to save. It is too much work.
|
// Let's not bother recovering from a failure to save. It is too much work.
|
||||||
App.Lock(false, false);
|
App.Lock(false, false);
|
||||||
}
|
}
|
||||||
}, OnFinishToRun);
|
}, operationFinishedHandler);
|
||||||
|
|
||||||
// Commit database
|
// Commit database
|
||||||
SaveDb save = new SaveDb(Ctx, App, Db, OnFinishToRun, false);
|
SaveDb save = new SaveDb( App, Db, operationFinishedHandler, false, null);
|
||||||
save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus;
|
save.ShowDatabaseIocInStatus = ShowDatabaseIocInStatus;
|
||||||
|
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
|
@@ -23,7 +23,7 @@ using KeePassLib;
|
|||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
public class EditGroup : RunnableOnFinish {
|
public class EditGroup : OperationWithFinishHandler {
|
||||||
internal Database Db
|
internal Database Db
|
||||||
{
|
{
|
||||||
get { return _app.FindDatabaseForElement(Group); }
|
get { return _app.FindDatabaseForElement(Group); }
|
||||||
@@ -36,19 +36,17 @@ namespace keepass2android
|
|||||||
private readonly PwIcon _iconId;
|
private readonly PwIcon _iconId;
|
||||||
private readonly PwUuid _customIconId;
|
private readonly PwUuid _customIconId;
|
||||||
internal PwGroup Group;
|
internal PwGroup Group;
|
||||||
readonly Activity _ctx;
|
|
||||||
|
|
||||||
public EditGroup(Activity ctx, IKp2aApp app, String name, PwIcon iconid, PwUuid customIconId, PwGroup group, OnFinish finish)
|
public EditGroup(IKp2aApp app, String name, PwIcon iconid, PwUuid customIconId, PwGroup group, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
|
||||||
_name = name;
|
_name = name;
|
||||||
_iconId = iconid;
|
_iconId = iconid;
|
||||||
Group = group;
|
Group = group;
|
||||||
_customIconId = customIconId;
|
_customIconId = customIconId;
|
||||||
_app = app;
|
_app = app;
|
||||||
|
|
||||||
_onFinishToRun = new AfterEdit(ctx, this, OnFinishToRun);
|
_operationFinishedHandler = new AfterEdit(app, this, operationFinishedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -60,16 +58,16 @@ namespace keepass2android
|
|||||||
Group.Touch(true);
|
Group.Touch(true);
|
||||||
|
|
||||||
// Commit to disk
|
// Commit to disk
|
||||||
SaveDb save = new SaveDb(_ctx, _app, Db, OnFinishToRun);
|
SaveDb save = new SaveDb(_app, Db, operationFinishedHandler);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.Run();
|
save.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterEdit : OnFinish {
|
private class AfterEdit : OnOperationFinishedHandler {
|
||||||
readonly EditGroup _editGroup;
|
readonly EditGroup _editGroup;
|
||||||
|
|
||||||
public AfterEdit(Activity ctx, EditGroup editGroup, OnFinish finish)
|
public AfterEdit(IKp2aApp app, EditGroup editGroup, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_editGroup = editGroup;
|
_editGroup = editGroup;
|
||||||
}
|
}
|
||||||
|
@@ -21,10 +21,10 @@ using Android.App;
|
|||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
public abstract class FileOnFinish : OnFinish {
|
public abstract class FileOnFinish : OnOperationFinishedHandler {
|
||||||
private String _filename = "";
|
private String _filename = "";
|
||||||
|
|
||||||
protected FileOnFinish(Activity activity, FileOnFinish finish):base(activity, finish) {
|
protected FileOnFinish(IKp2aApp app, FileOnFinish operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Filename
|
public string Filename
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
namespace keepass2android;
|
||||||
|
|
||||||
|
// A context instance can be the instance of an Activity. Even if the activity is recreated (due to a configuration change, for example), the instance id must remain the same
|
||||||
|
// but it must be different for other activities/services or if the activity is finished and then starts again.
|
||||||
|
// We want to be able to perform actions on a context instance, even though that instance might not live at the time when we want to perform the action.
|
||||||
|
// In that case, we want to be able to register the action such that it is performed when the activity is recreated.
|
||||||
|
public interface IContextInstanceIdProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
int ContextInstanceId { get; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -21,59 +21,88 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.OS;
|
||||||
|
using KeePass.Util;
|
||||||
using keepass2android.database.edit;
|
using keepass2android.database.edit;
|
||||||
|
using keepass2android.Io;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
using KeePassLib.Keys;
|
using KeePassLib.Keys;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public class LoadDb : RunnableOnFinish {
|
public class LoadDb : OperationWithFinishHandler {
|
||||||
private readonly IOConnectionInfo _ioc;
|
private readonly IOConnectionInfo _ioc;
|
||||||
private readonly Task<MemoryStream> _databaseData;
|
private readonly Task<MemoryStream> _databaseData;
|
||||||
private readonly CompositeKey _compositeKey;
|
private readonly CompositeKey _compositeKey;
|
||||||
private readonly string _keyfileOrProvider;
|
private readonly string? _keyfileOrProvider;
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly bool _rememberKeyfile;
|
private readonly bool _rememberKeyfile;
|
||||||
IDatabaseFormat _format;
|
IDatabaseFormat _format;
|
||||||
|
|
||||||
public LoadDb(Activity activity, IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish, bool updateLastUsageTimestamp, bool makeCurrent): base(activity, finish)
|
public bool DoNotSetStatusLoggerMessage = false;
|
||||||
|
|
||||||
|
|
||||||
|
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey,
|
||||||
|
string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler,
|
||||||
|
bool updateLastUsageTimestamp, bool makeCurrent, IDatabaseModificationWatcher modificationWatcher = null): base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_app = app;
|
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||||
|
_app = app;
|
||||||
_ioc = ioc;
|
_ioc = ioc;
|
||||||
_databaseData = databaseData;
|
_databaseData = databaseData;
|
||||||
_compositeKey = compositeKey;
|
_compositeKey = compositeKey;
|
||||||
_keyfileOrProvider = keyfileOrProvider;
|
_keyfileOrProvider = keyfileOrProvider;
|
||||||
_updateLastUsageTimestamp = updateLastUsageTimestamp;
|
_updateLastUsageTimestamp = updateLastUsageTimestamp;
|
||||||
_makeCurrent = makeCurrent;
|
_makeCurrent = makeCurrent;
|
||||||
|
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
|
||||||
|
}
|
||||||
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool success = false;
|
protected bool success = false;
|
||||||
private bool _updateLastUsageTimestamp;
|
private bool _updateLastUsageTimestamp;
|
||||||
private readonly bool _makeCurrent;
|
private readonly bool _makeCurrent;
|
||||||
|
private readonly IDatabaseModificationWatcher _modificationWatcher;
|
||||||
|
|
||||||
public override void Run()
|
public override void Run()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//make sure the file data is stored in the recent files list even if loading fails
|
//make sure the file data is stored in the recent files list even if loading fails
|
||||||
SaveFileData(_ioc, _keyfileOrProvider);
|
SaveFileData(_ioc, _keyfileOrProvider);
|
||||||
|
|
||||||
|
|
||||||
|
var fileStorage = _app.GetFileStorage(_ioc);
|
||||||
|
|
||||||
|
RequiresSubsequentSync = false;
|
||||||
|
|
||||||
|
|
||||||
StatusLogger.UpdateMessage(UiStringKey.loading_database);
|
if (!DoNotSetStatusLoggerMessage)
|
||||||
|
{
|
||||||
|
StatusLogger.UpdateMessage(UiStringKey.loading_database);
|
||||||
|
}
|
||||||
|
|
||||||
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
|
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
|
||||||
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
|
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
|
||||||
MemoryStream databaseStream;
|
MemoryStream databaseStream;
|
||||||
if (preloadedMemoryStream != null)
|
if (preloadedMemoryStream != null)
|
||||||
databaseStream = preloadedMemoryStream;
|
{
|
||||||
else
|
//note: if the stream has been loaded already, we don't need to trigger another sync later on
|
||||||
|
databaseStream = preloadedMemoryStream;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
|
if (_app.SyncInBackgroundPreference && fileStorage is CachingFileStorage cachingFileStorage &&
|
||||||
|
cachingFileStorage.IsCached(_ioc))
|
||||||
|
{
|
||||||
|
cachingFileStorage.IsOffline = true;
|
||||||
|
//no warning. We'll trigger a sync later.
|
||||||
|
cachingFileStorage.TriggerWarningWhenFallingBackToCache = false;
|
||||||
|
RequiresSubsequentSync = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
using (Stream s = fileStorage.OpenFileForRead(_ioc))
|
||||||
{
|
{
|
||||||
databaseStream = new MemoryStream();
|
databaseStream = new MemoryStream();
|
||||||
s.CopyTo(databaseStream);
|
s.CopyTo(databaseStream);
|
||||||
@@ -81,8 +110,13 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!StatusLogger.ContinueWork())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
|
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
|
||||||
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(_app.GetFileStorage(_ioc).GetFileExtension(_ioc)));
|
_format = new KdbxDatabaseFormat(KdbxDatabaseFormat.GetFormatToUse(fileStorage.GetFileExtension(_ioc)));
|
||||||
TryLoad(databaseStream);
|
TryLoad(databaseStream);
|
||||||
|
|
||||||
|
|
||||||
@@ -103,10 +137,10 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (AggregateException e)
|
catch (AggregateException e)
|
||||||
{
|
{
|
||||||
string message = e.Message;
|
string message = ExceptionUtil.GetErrorMessage(e);
|
||||||
foreach (var innerException in e.InnerExceptions)
|
foreach (var innerException in e.InnerExceptions)
|
||||||
{
|
{
|
||||||
message = innerException.Message;
|
message = ExceptionUtil.GetErrorMessage(innerException);
|
||||||
// Override the message shown with the last (hopefully most recent) inner exception
|
// Override the message shown with the last (hopefully most recent) inner exception
|
||||||
Kp2aLog.LogUnexpectedError(innerException);
|
Kp2aLog.LogUnexpectedError(innerException);
|
||||||
}
|
}
|
||||||
@@ -116,21 +150,29 @@ namespace keepass2android
|
|||||||
catch (DuplicateUuidsException e)
|
catch (DuplicateUuidsException e)
|
||||||
{
|
{
|
||||||
Kp2aLog.Log(e.ToString());
|
Kp2aLog.Log(e.ToString());
|
||||||
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + ExceptionUtil.GetErrorMessage(e) + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), false, Exception);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Java.Lang.InterruptedException)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log("Load interrupted");
|
||||||
|
//close without Finish()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (!(e is InvalidCompositeKeyException))
|
if (!(e is InvalidCompositeKeyException))
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (e.Message ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
|
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + (ExceptionUtil.GetErrorMessage(e) ?? (e is FileNotFoundException ? _app.GetResourceString(UiStringKey.FileNotFound) : "")), false, Exception);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public bool RequiresSubsequentSync { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
/// Holds the exception which was thrown during execution (if any)
|
/// Holds the exception which was thrown during execution (if any)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Exception Exception { get; set; }
|
public Exception Exception { get; set; }
|
||||||
@@ -145,15 +187,21 @@ namespace keepass2android
|
|||||||
workingCopy.Seek(0, SeekOrigin.Begin);
|
workingCopy.Seek(0, SeekOrigin.Begin);
|
||||||
//reset stream if we need to reuse it later:
|
//reset stream if we need to reuse it later:
|
||||||
databaseStream.Seek(0, SeekOrigin.Begin);
|
databaseStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
if (!StatusLogger.ContinueWork())
|
||||||
|
{
|
||||||
|
throw new Java.Lang.InterruptedException();
|
||||||
|
}
|
||||||
|
|
||||||
//now let's go:
|
//now let's go:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Database newDb = _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent);
|
Database newDb =
|
||||||
Kp2aLog.Log("LoadDB OK");
|
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format, _makeCurrent, _modificationWatcher);
|
||||||
|
Kp2aLog.Log("LoadDB OK");
|
||||||
|
|
||||||
Finish(true, _format.SuccessMessage);
|
Finish(true, _format.SuccessMessage);
|
||||||
return newDb;
|
return newDb;
|
||||||
}
|
}
|
||||||
catch (OldFormatException)
|
catch (OldFormatException)
|
||||||
{
|
{
|
||||||
_format = new KdbDatabaseFormat(_app);
|
_format = new KdbDatabaseFormat(_app);
|
||||||
|
@@ -10,18 +10,16 @@ using KeePassLib.Interfaces;
|
|||||||
|
|
||||||
namespace keepass2android.database.edit
|
namespace keepass2android.database.edit
|
||||||
{
|
{
|
||||||
public class MoveElements: RunnableOnFinish
|
public class MoveElements: OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly List<IStructureItem> _elementsToMove;
|
private readonly List<IStructureItem> _elementsToMove;
|
||||||
private readonly PwGroup _targetGroup;
|
private readonly PwGroup _targetGroup;
|
||||||
private readonly Activity _ctx;
|
private readonly IKp2aApp _app;
|
||||||
private readonly IKp2aApp _app;
|
|
||||||
|
|
||||||
public MoveElements(List<IStructureItem> elementsToMove, PwGroup targetGroup, Activity ctx, IKp2aApp app, OnFinish finish) : base(ctx, finish)
|
public MoveElements(List<IStructureItem> elementsToMove, PwGroup targetGroup,IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler) : base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_elementsToMove = elementsToMove;
|
_elementsToMove = elementsToMove;
|
||||||
_targetGroup = targetGroup;
|
_targetGroup = targetGroup;
|
||||||
_ctx = ctx;
|
|
||||||
_app = app;
|
_app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,24 +121,24 @@ namespace keepass2android.database.edit
|
|||||||
|
|
||||||
int indexToSave = 0;
|
int indexToSave = 0;
|
||||||
bool allSavesSuccess = true;
|
bool allSavesSuccess = true;
|
||||||
void ContinueSave(bool success, string message, Activity activeActivity)
|
void ContinueSave(bool success, string message, Context activeActivity)
|
||||||
{
|
{
|
||||||
allSavesSuccess &= success;
|
allSavesSuccess &= success;
|
||||||
indexToSave++;
|
indexToSave++;
|
||||||
if (indexToSave == allDatabasesToSave.Count)
|
if (indexToSave == allDatabasesToSave.Count)
|
||||||
{
|
{
|
||||||
OnFinishToRun.SetResult(allSavesSuccess);
|
operationFinishedHandler.SetResult(allSavesSuccess);
|
||||||
OnFinishToRun.Run();
|
operationFinishedHandler.Run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SaveDb saveDb = new SaveDb(_ctx, _app, allDatabasesToSave[indexToSave], new ActionOnFinish(activeActivity, ContinueSave), false);
|
SaveDb saveDb = new SaveDb( _app, allDatabasesToSave[indexToSave], new ActionOnOperationFinished(_app, ContinueSave), false, null);
|
||||||
saveDb.SetStatusLogger(StatusLogger);
|
saveDb.SetStatusLogger(StatusLogger);
|
||||||
saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
|
saveDb.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
|
||||||
saveDb.Run();
|
saveDb.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SaveDb save = new SaveDb(_ctx, _app, allDatabasesToSave[0], new ActionOnFinish(ActiveActivity, ContinueSave), false);
|
SaveDb save = new SaveDb(_app, allDatabasesToSave[0], new ActionOnOperationFinished(_app, ContinueSave), false, null);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
|
save.ShowDatabaseIocInStatus = allDatabasesToSave.Count > 1;
|
||||||
save.Run();
|
save.Run();
|
||||||
|
@@ -22,10 +22,16 @@ using Android.Content;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Google.Android.Material.Dialog;
|
using Google.Android.Material.Dialog;
|
||||||
|
using KeePassLib.Interfaces;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public abstract class OnFinish
|
public interface IActiveContextProvider
|
||||||
|
{
|
||||||
|
Context ActiveContext { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class OnOperationFinishedHandler
|
||||||
{
|
{
|
||||||
protected bool Success;
|
protected bool Success;
|
||||||
protected String Message;
|
protected String Message;
|
||||||
@@ -37,63 +43,41 @@ namespace keepass2android
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OnFinish BaseOnFinish;
|
protected Context ActiveContext
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _activeContextProvider?.ActiveContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OnOperationFinishedHandler NextOnOperationFinishedHandler;
|
||||||
protected Handler Handler;
|
protected Handler Handler;
|
||||||
private ProgressDialogStatusLogger _statusLogger = new ProgressDialogStatusLogger(); //default: no logging but not null -> can be used whenever desired
|
private IKp2aStatusLogger _statusLogger = new Kp2aNullStatusLogger(); //default: no logging but not null -> can be used whenever desired
|
||||||
private Activity _activeActivity, _previouslyActiveActivity;
|
private readonly IActiveContextProvider _activeContextProvider;
|
||||||
|
|
||||||
|
public IKp2aStatusLogger StatusLogger
|
||||||
public ProgressDialogStatusLogger StatusLogger
|
|
||||||
{
|
{
|
||||||
get { return _statusLogger; }
|
get { return _statusLogger; }
|
||||||
set { _statusLogger = value; }
|
set { _statusLogger = value; }
|
||||||
}
|
} protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, Handler handler)
|
||||||
|
|
||||||
public Activity ActiveActivity
|
|
||||||
{
|
|
||||||
get { return _activeActivity; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_activeActivity != null && _activeActivity != _previouslyActiveActivity)
|
|
||||||
{
|
|
||||||
_previouslyActiveActivity = _activeActivity;
|
|
||||||
|
|
||||||
}
|
|
||||||
_activeActivity = value;
|
|
||||||
if (BaseOnFinish != null)
|
|
||||||
{
|
|
||||||
BaseOnFinish.ActiveActivity = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Activity PreviouslyActiveActivity
|
|
||||||
{
|
{
|
||||||
get { return _previouslyActiveActivity; }
|
_activeContextProvider = activeContextProvider;
|
||||||
|
NextOnOperationFinishedHandler = null;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected OnFinish(Activity activeActivity, Handler handler)
|
|
||||||
{
|
|
||||||
ActiveActivity = activeActivity;
|
|
||||||
BaseOnFinish = null;
|
|
||||||
Handler = handler;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected OnFinish(Activity activeActivity, OnFinish finish, Handler handler)
|
|
||||||
{
|
|
||||||
ActiveActivity = activeActivity;
|
|
||||||
BaseOnFinish = finish;
|
|
||||||
Handler = handler;
|
Handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected OnFinish(Activity activeActivity, OnFinish finish)
|
protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler, Handler handler)
|
||||||
{
|
{
|
||||||
ActiveActivity = activeActivity;
|
_activeContextProvider = activeContextProvider;
|
||||||
BaseOnFinish = finish;
|
NextOnOperationFinishedHandler = operationFinishedHandler;
|
||||||
|
Handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OnOperationFinishedHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
|
{
|
||||||
|
_activeContextProvider = activeContextProvider;
|
||||||
|
NextOnOperationFinishedHandler = operationFinishedHandler;
|
||||||
Handler = null;
|
Handler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,14 +94,19 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Run() {
|
public virtual void Run() {
|
||||||
if (BaseOnFinish == null) return;
|
if (NextOnOperationFinishedHandler == null) return;
|
||||||
// Pass on result on call finish
|
// Pass on result on call finish
|
||||||
BaseOnFinish.SetResult(Success, Message, ImportantMessage, Exception);
|
NextOnOperationFinishedHandler.SetResult(Success, Message, ImportantMessage, Exception);
|
||||||
|
|
||||||
if ( Handler != null ) {
|
var handler = Handler ?? NextOnOperationFinishedHandler.Handler ?? null;
|
||||||
Handler.Post(BaseOnFinish.Run);
|
|
||||||
|
if (handler != null ) {
|
||||||
|
handler.Post(() =>
|
||||||
|
{
|
||||||
|
NextOnOperationFinishedHandler.Run();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
BaseOnFinish.Run();
|
NextOnOperationFinishedHandler.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +117,7 @@ namespace keepass2android
|
|||||||
public static void DisplayMessage(Context ctx, string message, bool makeDialog)
|
public static void DisplayMessage(Context ctx, string message, bool makeDialog)
|
||||||
{
|
{
|
||||||
if ( !String.IsNullOrEmpty(message) ) {
|
if ( !String.IsNullOrEmpty(message) ) {
|
||||||
Kp2aLog.Log("OnFinish message: " + message);
|
Kp2aLog.Log("OnOperationFinishedHandler message: " + message);
|
||||||
if (makeDialog && ctx != null)
|
if (makeDialog && ctx != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -152,3 +141,4 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Keepass2Android is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using KeePassLib.Interfaces;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
|
||||||
|
public abstract class OperationWithFinishHandler {
|
||||||
|
|
||||||
|
protected OnOperationFinishedHandler _operationFinishedHandler;
|
||||||
|
public IKp2aStatusLogger StatusLogger = new Kp2aNullStatusLogger(); //default: empty but not null
|
||||||
|
private IActiveContextProvider _activeContextProvider;
|
||||||
|
|
||||||
|
protected OperationWithFinishHandler(IActiveContextProvider activeContextProvider, OnOperationFinishedHandler operationFinishedHandler)
|
||||||
|
{
|
||||||
|
_activeContextProvider = activeContextProvider;
|
||||||
|
_operationFinishedHandler = operationFinishedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnOperationFinishedHandler operationFinishedHandler
|
||||||
|
{
|
||||||
|
get { return _operationFinishedHandler; }
|
||||||
|
set { _operationFinishedHandler = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void Finish(bool result, String message, bool importantMessage = false, Exception exception = null) {
|
||||||
|
if ( operationFinishedHandler != null ) {
|
||||||
|
operationFinishedHandler.SetResult(result, message, importantMessage, exception);
|
||||||
|
operationFinishedHandler.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Finish(bool result) {
|
||||||
|
if ( operationFinishedHandler != null ) {
|
||||||
|
operationFinishedHandler.SetResult(result);
|
||||||
|
operationFinishedHandler.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStatusLogger(IKp2aStatusLogger statusLogger) {
|
||||||
|
if (operationFinishedHandler != null)
|
||||||
|
{
|
||||||
|
operationFinishedHandler.StatusLogger = statusLogger;
|
||||||
|
}
|
||||||
|
StatusLogger = statusLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Keepass2Android is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
using System;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
|
|
||||||
namespace keepass2android
|
|
||||||
{
|
|
||||||
|
|
||||||
public abstract class RunnableOnFinish {
|
|
||||||
|
|
||||||
protected OnFinish _onFinishToRun;
|
|
||||||
public ProgressDialogStatusLogger StatusLogger = new ProgressDialogStatusLogger(); //default: empty but not null
|
|
||||||
private Activity _activeActivity;
|
|
||||||
|
|
||||||
protected RunnableOnFinish(Activity activeActivity, OnFinish finish)
|
|
||||||
{
|
|
||||||
_activeActivity = activeActivity;
|
|
||||||
_onFinishToRun = finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnFinish OnFinishToRun
|
|
||||||
{
|
|
||||||
get { return _onFinishToRun; }
|
|
||||||
set { _onFinishToRun = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public Activity ActiveActivity
|
|
||||||
{
|
|
||||||
get { return _activeActivity; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_activeActivity = value;
|
|
||||||
if (_onFinishToRun != null)
|
|
||||||
_onFinishToRun.ActiveActivity = _activeActivity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Finish(bool result, String message, bool importantMessage = false, Exception exception = null) {
|
|
||||||
if ( OnFinishToRun != null ) {
|
|
||||||
OnFinishToRun.SetResult(result, message, importantMessage, exception);
|
|
||||||
OnFinishToRun.Run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Finish(bool result) {
|
|
||||||
if ( OnFinishToRun != null ) {
|
|
||||||
OnFinishToRun.SetResult(result);
|
|
||||||
OnFinishToRun.Run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetStatusLogger(ProgressDialogStatusLogger status) {
|
|
||||||
if (OnFinishToRun != null)
|
|
||||||
{
|
|
||||||
OnFinishToRun.StatusLogger = status;
|
|
||||||
}
|
|
||||||
StatusLogger = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void Run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -29,57 +29,70 @@ using KeePassLib.Utility;
|
|||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
using Debug = System.Diagnostics.Debug;
|
using Debug = System.Diagnostics.Debug;
|
||||||
using Exception = System.Exception;
|
using Exception = System.Exception;
|
||||||
|
using KeePass.Util;
|
||||||
|
using Thread = System.Threading.Thread;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the database. If the file has changed, ask the user if he wants to overwrite or sync.
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
public class SaveDb : RunnableOnFinish {
|
public class SaveDb : OperationWithFinishHandler {
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly Database _db;
|
private readonly Database _db;
|
||||||
private readonly bool _dontSave;
|
private readonly bool _dontSave;
|
||||||
|
private readonly IDatabaseModificationWatcher _modificationWatcher;
|
||||||
|
private bool requiresSubsequentSync = false; //if true, we need to sync the file after saving.
|
||||||
|
|
||||||
|
public bool DoNotSetStatusLoggerMessage = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync
|
/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Stream _streamForOrigFile;
|
private readonly Stream _streamForOrigFile;
|
||||||
private readonly Context _ctx;
|
|
||||||
private Java.Lang.Thread _workerThread;
|
private Java.Lang.Thread _workerThread;
|
||||||
|
|
||||||
public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish, bool dontSave)
|
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, bool dontSave, IDatabaseModificationWatcher modificationWatcher)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
_ctx = ctx;
|
|
||||||
_app = app;
|
_app = app;
|
||||||
_dontSave = dontSave;
|
_dontSave = dontSave;
|
||||||
|
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor for sync
|
/// Constructor for sync
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ctx"></param>
|
|
||||||
/// <param name="app"></param>
|
/// <param name="app"></param>
|
||||||
/// <param name="finish"></param>
|
/// <param name="operationFinishedHandler"></param>
|
||||||
/// <param name="dontSave"></param>
|
/// <param name="dontSave"></param>
|
||||||
/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param>
|
/// <param name="streamForOrigFile">Stream for reading the data from the (changed) original location</param>
|
||||||
public SaveDb(Activity ctx, IKp2aApp app, OnFinish finish, Database db, bool dontSave, Stream streamForOrigFile)
|
public SaveDb(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, Database db, bool dontSave, Stream streamForOrigFile, IDatabaseModificationWatcher modificationWatcher = null)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_db = db;
|
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||||
_ctx = ctx;
|
_db = db;
|
||||||
_app = app;
|
_app = app;
|
||||||
_dontSave = dontSave;
|
_dontSave = dontSave;
|
||||||
_streamForOrigFile = streamForOrigFile;
|
_streamForOrigFile = streamForOrigFile;
|
||||||
}
|
SyncInBackground = _app.SyncInBackgroundPreference;
|
||||||
|
|
||||||
public SaveDb(Activity ctx, IKp2aApp app, Database db, OnFinish finish)
|
}
|
||||||
: base(ctx, finish)
|
|
||||||
|
public SaveDb(IKp2aApp app, Database db, OnOperationFinishedHandler operationFinishedHandler, IDatabaseModificationWatcher modificationWatcher = null)
|
||||||
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
|
||||||
_app = app;
|
_modificationWatcher = modificationWatcher ?? new NullDatabaseModificationWatcher();
|
||||||
|
_app = app;
|
||||||
_db = db;
|
_db = db;
|
||||||
_dontSave = false;
|
_dontSave = false;
|
||||||
}
|
SyncInBackground = _app.SyncInBackgroundPreference;
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowDatabaseIocInStatus { get; set; }
|
public bool ShowDatabaseIocInStatus { get; set; }
|
||||||
|
|
||||||
@@ -102,29 +115,42 @@ namespace keepass2android
|
|||||||
if (ShowDatabaseIocInStatus)
|
if (ShowDatabaseIocInStatus)
|
||||||
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
|
message += " (" + _app.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
|
||||||
|
|
||||||
StatusLogger.UpdateMessage(message);
|
if (!DoNotSetStatusLoggerMessage)
|
||||||
|
{
|
||||||
IOConnectionInfo ioc = _db.Ioc;
|
StatusLogger.UpdateMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
IOConnectionInfo ioc = _db.Ioc;
|
||||||
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
IFileStorage fileStorage = _app.GetFileStorage(ioc);
|
||||||
|
|
||||||
if (_streamForOrigFile == null)
|
if (SyncInBackground && fileStorage is IOfflineSwitchable offlineSwitchable)
|
||||||
|
{
|
||||||
|
offlineSwitchable.IsOffline = true;
|
||||||
|
//no warning. We'll trigger a sync later.
|
||||||
|
offlineSwitchable.TriggerWarningWhenFallingBackToCache = false;
|
||||||
|
requiresSubsequentSync = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (_streamForOrigFile == null)
|
||||||
{
|
{
|
||||||
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|
if ((!_app.GetBooleanPreference(PreferenceKey.CheckForFileChangesOnSave))
|
||||||
|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving
|
|| (_db.KpDatabase.HashOfFileOnDisk == null)) //first time saving
|
||||||
{
|
{
|
||||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||||
Finish(true);
|
FinishWithSuccess();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool hasStreamForOrigFile = (_streamForOrigFile != null);
|
bool hasStreamForOrigFile = (_streamForOrigFile != null);
|
||||||
bool hasChangeFast = hasStreamForOrigFile ||
|
bool hasChangeFast = hasStreamForOrigFile ||
|
||||||
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
|
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
|
||||||
bool hasHashChanged = hasChangeFast ||
|
bool hasHashChanged = !requiresSubsequentSync && (
|
||||||
|
hasChangeFast ||
|
||||||
(FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
|
(FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
|
||||||
FileHashChange.Changed); //if that fails, hash the file and compare:
|
FileHashChange.Changed)); //if that fails, hash the file and compare:
|
||||||
|
|
||||||
if (hasHashChanged)
|
if (hasHashChanged)
|
||||||
{
|
{
|
||||||
@@ -157,15 +183,14 @@ namespace keepass2android
|
|||||||
RunInWorkerThread(() =>
|
RunInWorkerThread(() =>
|
||||||
{
|
{
|
||||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||||
Finish(true);
|
FinishWithSuccess();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
//cancel
|
//cancel
|
||||||
(sender, args) =>
|
(sender, args) =>
|
||||||
{
|
{
|
||||||
RunInWorkerThread(() => Finish(false));
|
RunInWorkerThread(() => Finish(false));
|
||||||
},
|
}
|
||||||
_ctx
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +198,7 @@ namespace keepass2android
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||||
Finish(true);
|
FinishWithSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -187,28 +212,73 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
Finish(false, e.Message);
|
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Finish(true);
|
FinishWithSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SyncInBackground { get; set; }
|
||||||
|
|
||||||
|
private void FinishWithSuccess()
|
||||||
|
{
|
||||||
|
if (requiresSubsequentSync)
|
||||||
|
{
|
||||||
|
var syncTask = new SynchronizeCachedDatabase(_app, _db, new ActionOnOperationFinished(_app,
|
||||||
|
(success, message, context) =>
|
||||||
|
{
|
||||||
|
if (!System.String.IsNullOrEmpty(message))
|
||||||
|
_app.ShowMessage(context, message, success ? MessageSeverity.Info : MessageSeverity.Error);
|
||||||
|
|
||||||
|
}), new BackgroundDatabaseModificationLocker(_app)
|
||||||
|
);
|
||||||
|
OperationRunner.Instance.Run(_app, syncTask);
|
||||||
|
}
|
||||||
|
Finish(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc)
|
private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
//note: when synced, the file might be downloaded once again from the server. Caching the data
|
//note: when synced, the file might be downloaded once again from the server. Caching the data
|
||||||
//in the hashing function would solve this but increases complexity. I currently assume the files are
|
//in the hashing function would solve this but increases complexity. I currently assume the files are
|
||||||
//small.
|
//small.
|
||||||
MergeIn(fileStorage, ioc);
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_modificationWatcher.BeforeModifyDatabases();
|
||||||
|
}
|
||||||
|
catch (Java.Lang.InterruptedException)
|
||||||
|
{
|
||||||
|
// leave without Finish()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MergeIn(fileStorage, ioc);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_modificationWatcher.AfterModifyDatabases();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
PerformSaveWithoutCheck(fileStorage, ioc);
|
PerformSaveWithoutCheck(fileStorage, ioc);
|
||||||
_db.UpdateGlobals();
|
new Handler(Looper.MainLooper).Post(() =>
|
||||||
Finish(true);
|
{
|
||||||
|
_db.UpdateGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
FinishWithSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void RunInWorkerThread(Action runHandler)
|
private void RunInWorkerThread(Action runHandler)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -222,8 +292,8 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
Kp2aLog.Log("Error in worker thread of SaveDb: " + e);
|
Kp2aLog.Log("Error in worker thread of SaveDb: " + ExceptionUtil.GetErrorMessage(e));
|
||||||
Finish(false, e.Message);
|
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -233,7 +303,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
Kp2aLog.Log("Error starting worker thread of SaveDb: "+e);
|
Kp2aLog.Log("Error starting worker thread of SaveDb: "+e);
|
||||||
Finish(false, e.Message);
|
Finish(false, ExceptionUtil.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -281,7 +351,7 @@ namespace keepass2android
|
|||||||
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
|
private void PerformSaveWithoutCheck(IFileStorage fileStorage, IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
StatusLogger.UpdateSubMessage("");
|
StatusLogger.UpdateSubMessage("");
|
||||||
_db.SaveData();
|
_db.SaveData(fileStorage);
|
||||||
_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
|
_db.LastFileVersion = fileStorage.GetCurrentFileVersionFast(ioc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,26 +22,24 @@ using KeePassLib.Keys;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public class SetPassword : RunnableOnFinish {
|
public class SetPassword : OperationWithFinishHandler {
|
||||||
|
|
||||||
private readonly String _password;
|
private readonly String _password;
|
||||||
private readonly String _keyfile;
|
private readonly String _keyfile;
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly bool _dontSave;
|
private readonly bool _dontSave;
|
||||||
private readonly Activity _ctx;
|
|
||||||
|
|
||||||
public SetPassword(Activity ctx, IKp2aApp app, String password, String keyfile, OnFinish finish): base(ctx, finish) {
|
public SetPassword(IKp2aApp app, String password, String keyfile, OnOperationFinishedHandler operationFinishedHandler): base(app, operationFinishedHandler) {
|
||||||
_ctx = ctx;
|
|
||||||
_app = app;
|
_app = app;
|
||||||
_password = password;
|
_password = password;
|
||||||
_keyfile = keyfile;
|
_keyfile = keyfile;
|
||||||
_dontSave = false;
|
_dontSave = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SetPassword(Activity ctx, IKp2aApp app, String password, String keyfile, OnFinish finish, bool dontSave)
|
public SetPassword(IKp2aApp app, String password, String keyfile, OnOperationFinishedHandler operationFinishedHandler, bool dontSave)
|
||||||
: base(ctx, finish)
|
: base(app, operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
|
||||||
_app = app;
|
_app = app;
|
||||||
_password = password;
|
_password = password;
|
||||||
_keyfile = keyfile;
|
_keyfile = keyfile;
|
||||||
@@ -73,18 +71,18 @@ namespace keepass2android
|
|||||||
pm.MasterKey = newKey;
|
pm.MasterKey = newKey;
|
||||||
|
|
||||||
// Save Database
|
// Save Database
|
||||||
_onFinishToRun = new AfterSave(ActiveActivity, previousKey, previousMasterKeyChanged, pm, OnFinishToRun);
|
_operationFinishedHandler = new AfterSave(_app, previousKey, previousMasterKeyChanged, pm, operationFinishedHandler);
|
||||||
SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun, _dontSave);
|
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler, _dontSave, null);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.Run();
|
save.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterSave : OnFinish {
|
private class AfterSave : OnOperationFinishedHandler {
|
||||||
private readonly CompositeKey _backup;
|
private readonly CompositeKey _backup;
|
||||||
private readonly DateTime _previousKeyChanged;
|
private readonly DateTime _previousKeyChanged;
|
||||||
private readonly PwDatabase _db;
|
private readonly PwDatabase _db;
|
||||||
|
|
||||||
public AfterSave(Activity activity, CompositeKey backup, DateTime previousKeyChanged, PwDatabase db, OnFinish finish): base(activity, finish) {
|
public AfterSave(IActiveContextProvider activeContextProvider, CompositeKey backup, DateTime previousKeyChanged, PwDatabase db, OnOperationFinishedHandler operationFinishedHandler): base(activeContextProvider, operationFinishedHandler) {
|
||||||
_previousKeyChanged = previousKeyChanged;
|
_previousKeyChanged = previousKeyChanged;
|
||||||
_backup = backup;
|
_backup = backup;
|
||||||
_db = db;
|
_db = db;
|
||||||
|
@@ -22,31 +22,29 @@ using KeePassLib;
|
|||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
public class UpdateEntry : RunnableOnFinish {
|
public class UpdateEntry : OperationWithFinishHandler {
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly Activity _ctx;
|
|
||||||
|
|
||||||
public UpdateEntry(Activity ctx, IKp2aApp app, PwEntry oldE, PwEntry newE, OnFinish finish):base(ctx, finish) {
|
|
||||||
_ctx = ctx;
|
|
||||||
_app = app;
|
|
||||||
|
|
||||||
_onFinishToRun = new AfterUpdate(ctx, oldE, newE, app, finish);
|
public UpdateEntry(IKp2aApp app, PwEntry oldE, PwEntry newE, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||||
|
_app = app;
|
||||||
|
|
||||||
|
_operationFinishedHandler = new AfterUpdate( oldE, newE, app, operationFinishedHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void Run() {
|
public override void Run() {
|
||||||
// Commit to disk
|
// Commit to disk
|
||||||
SaveDb save = new SaveDb(_ctx, _app, _app.CurrentDb, OnFinishToRun);
|
SaveDb save = new SaveDb(_app, _app.CurrentDb, operationFinishedHandler);
|
||||||
save.SetStatusLogger(StatusLogger);
|
save.SetStatusLogger(StatusLogger);
|
||||||
save.Run();
|
save.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterUpdate : OnFinish {
|
private class AfterUpdate : OnOperationFinishedHandler {
|
||||||
private readonly PwEntry _backup;
|
private readonly PwEntry _backup;
|
||||||
private readonly PwEntry _updatedEntry;
|
private readonly PwEntry _updatedEntry;
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
|
|
||||||
public AfterUpdate(Activity activity, PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnFinish finish):base(activity, finish) {
|
public AfterUpdate(PwEntry backup, PwEntry updatedEntry, IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler):base(app, operationFinishedHandler) {
|
||||||
_backup = backup;
|
_backup = backup;
|
||||||
_updatedEntry = updatedEntry;
|
_updatedEntry = updatedEntry;
|
||||||
_app = app;
|
_app = app;
|
||||||
|
@@ -228,9 +228,9 @@ namespace keepass2android
|
|||||||
newEntry.SetUuid(new PwUuid(true), true); // Create new UUID
|
newEntry.SetUuid(new PwUuid(true), true); // Create new UUID
|
||||||
string strTitle = newEntry.Strings.ReadSafe(PwDefs.TitleField);
|
string strTitle = newEntry.Strings.ReadSafe(PwDefs.TitleField);
|
||||||
newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, strTitle + " (" + Android.OS.Build.Model + ")"));
|
newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, strTitle + " (" + Android.OS.Build.Model + ")"));
|
||||||
var addTask = new AddEntry(this, App.Kp2a.CurrentDb, App.Kp2a, newEntry,item.Entry.ParentGroup,new ActionOnFinish(this, (success, message, activity) => ((ConfigureChildDatabasesActivity)activity).Update()));
|
var addTask = new AddEntry( App.Kp2a.CurrentDb, App.Kp2a, newEntry,item.Entry.ParentGroup,new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => (context as ConfigureChildDatabasesActivity)?.Update()));
|
||||||
|
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, addTask);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -260,9 +260,9 @@ namespace keepass2android
|
|||||||
|
|
||||||
private void Save(AutoExecItem item)
|
private void Save(AutoExecItem item)
|
||||||
{
|
{
|
||||||
var addTask = new SaveDb(this, App.Kp2a, App.Kp2a.FindDatabaseForElement(item.Entry), new ActionOnFinish(this, (success, message, activity) => ((ConfigureChildDatabasesActivity)activity).Update()));
|
var addTask = new SaveDb(App.Kp2a, App.Kp2a.FindDatabaseForElement(item.Entry), new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => (context as ConfigureChildDatabasesActivity)?.Update()));
|
||||||
|
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, addTask);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
if (autoOpenGroup == null)
|
if (autoOpenGroup == null)
|
||||||
{
|
{
|
||||||
AddGroup addGroupTask = AddGroup.GetInstance(this, App.Kp2a, "AutoOpen", 1, null, rootGroup, null, true);
|
AddGroup addGroupTask = AddGroup.GetInstance(App.Kp2a, "AutoOpen", 1, null, rootGroup, null, true);
|
||||||
addGroupTask.Run();
|
addGroupTask.Run();
|
||||||
autoOpenGroup = addGroupTask.Group;
|
autoOpenGroup = addGroupTask.Group;
|
||||||
}
|
}
|
||||||
@@ -367,9 +367,9 @@ namespace keepass2android
|
|||||||
{KeeAutoExecExt.ThisDeviceId, true}
|
{KeeAutoExecExt.ThisDeviceId, true}
|
||||||
})));
|
})));
|
||||||
|
|
||||||
var addTask = new AddEntry(this, db, App.Kp2a, newEntry, autoOpenGroup, new ActionOnFinish(this, (success, message, activity) => (activity as ConfigureChildDatabasesActivity)?.Update()));
|
var addTask = new AddEntry( db, App.Kp2a, newEntry, autoOpenGroup, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) => (context as ConfigureChildDatabasesActivity)?.Update()));
|
||||||
|
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, addTask);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -213,10 +213,9 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the new database
|
// Create the new database
|
||||||
CreateDb create = new CreateDb(App.Kp2a, this, _ioc, new LaunchGroupActivity(_ioc, this), false, newKey, makeCurrent);
|
CreateDb create = new CreateDb(App.Kp2a, this, _ioc, new LaunchGroupActivity(_ioc, App.Kp2a, this), false, newKey, makeCurrent);
|
||||||
ProgressTask createTask = new ProgressTask(
|
BlockingOperationStarter createTask = new BlockingOperationStarter(
|
||||||
App.Kp2a,
|
App.Kp2a, create);
|
||||||
this, create);
|
|
||||||
createTask.Run();
|
createTask.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +316,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
if (resultCode == KeePass.ResultOkPasswordGenerator)
|
if (resultCode == KeePass.ResultOkPasswordGenerator)
|
||||||
{
|
{
|
||||||
String generatedPassword = data.GetStringExtra("keepass2android.password.generated_password");
|
String generatedPassword = data.GetStringExtra(GeneratePasswordActivity.GeneratedPasswordKey);
|
||||||
FindViewById<TextView>(Resource.Id.entry_password).Text = generatedPassword;
|
FindViewById<TextView>(Resource.Id.entry_password).Text = generatedPassword;
|
||||||
FindViewById<TextView>(Resource.Id.entry_confpassword).Text = generatedPassword;
|
FindViewById<TextView>(Resource.Id.entry_confpassword).Text = generatedPassword;
|
||||||
}
|
}
|
||||||
@@ -403,14 +402,14 @@ namespace keepass2android
|
|||||||
|
|
||||||
private class LaunchGroupActivity : FileOnFinish
|
private class LaunchGroupActivity : FileOnFinish
|
||||||
{
|
{
|
||||||
readonly CreateDatabaseActivity _activity;
|
|
||||||
private readonly IOConnectionInfo _ioc;
|
private readonly IOConnectionInfo _ioc;
|
||||||
|
private readonly CreateDatabaseActivity _activity;
|
||||||
|
|
||||||
public LaunchGroupActivity(IOConnectionInfo ioc, CreateDatabaseActivity activity)
|
public LaunchGroupActivity(IOConnectionInfo ioc, IKp2aApp app, CreateDatabaseActivity activity)
|
||||||
: base(activity, null)
|
: base(app, null)
|
||||||
{
|
{
|
||||||
_activity = activity;
|
_activity = activity;
|
||||||
_ioc = ioc;
|
_ioc = ioc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Run()
|
public override void Run()
|
||||||
@@ -420,7 +419,7 @@ namespace keepass2android
|
|||||||
// Update the ongoing notification
|
// Update the ongoing notification
|
||||||
App.Kp2a.UpdateOngoingNotification();
|
App.Kp2a.UpdateOngoingNotification();
|
||||||
|
|
||||||
if (PreferenceManager.GetDefaultSharedPreferences(_activity).GetBoolean(_activity.GetString(Resource.String.RememberRecentFiles_key), _activity.Resources.GetBoolean(Resource.Boolean.RememberRecentFiles_default)))
|
if (PreferenceManager.GetDefaultSharedPreferences(App.Context).GetBoolean(App.Context.GetString(Resource.String.RememberRecentFiles_key), App.Context.Resources.GetBoolean(Resource.Boolean.RememberRecentFiles_default)))
|
||||||
{
|
{
|
||||||
// Add to recent files
|
// Add to recent files
|
||||||
FileDbHelper dbHelper = App.Kp2a.FileDbHelper;
|
FileDbHelper dbHelper = App.Kp2a.FileDbHelper;
|
||||||
|
@@ -4,12 +4,12 @@ using KeePassLib.Serialization;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
class CreateNewFilename : RunnableOnFinish
|
class CreateNewFilename : OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly string _filename;
|
private readonly string _filename;
|
||||||
|
|
||||||
public CreateNewFilename(Activity activity, OnFinish finish, string filename)
|
public CreateNewFilename(IKp2aApp app, OnOperationFinishedHandler operationFinishedHandler, string filename)
|
||||||
: base(activity,finish)
|
: base(app,operationFinishedHandler)
|
||||||
{
|
{
|
||||||
_filename = filename;
|
_filename = filename;
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Finish(false, e.Message);
|
Finish(false, Util.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -56,6 +56,7 @@ using Android.Util;
|
|||||||
using AndroidX.Core.Content;
|
using AndroidX.Core.Content;
|
||||||
using Google.Android.Material.Dialog;
|
using Google.Android.Material.Dialog;
|
||||||
using keepass2android;
|
using keepass2android;
|
||||||
|
using keepass2android.views;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
@@ -63,7 +64,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
private readonly string _binaryToSave;
|
private readonly string _binaryToSave;
|
||||||
|
|
||||||
public ExportBinaryProcessManager(int requestCode, Activity activity, string key) : base(requestCode, activity)
|
public ExportBinaryProcessManager(int requestCode, LifecycleAwareActivity activity, string key) : base(requestCode, activity)
|
||||||
{
|
{
|
||||||
_binaryToSave = key;
|
_binaryToSave = key;
|
||||||
}
|
}
|
||||||
@@ -75,13 +76,13 @@ namespace keepass2android
|
|||||||
|
|
||||||
protected override void SaveFile(IOConnectionInfo ioc)
|
protected override void SaveFile(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
var task = new EntryActivity.WriteBinaryTask(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
|
var task = new EntryActivity.WriteBinaryTask(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
}
|
}
|
||||||
), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc);
|
), ((EntryActivity)_activity).Entry.Binaries.Get(_binaryToSave), ioc);
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, _activity, task);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, task);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ namespace keepass2android
|
|||||||
public override void OnSaveInstanceState(Bundle outState)
|
public override void OnSaveInstanceState(Bundle outState)
|
||||||
{
|
{
|
||||||
outState.PutString("BinaryToSave", _binaryToSave);
|
outState.PutString("BinaryToSave", _binaryToSave);
|
||||||
|
base.OnSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
[Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
|
[Activity (Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden,
|
||||||
Theme = "@style/Kp2aTheme_ActionBar")]
|
Theme = "@style/Kp2aTheme_ActionBar")]
|
||||||
public class EntryActivity : LockCloseActivity
|
public class EntryActivity : LockCloseActivity, IProgressUiProvider
|
||||||
{
|
{
|
||||||
public const String KeyEntry = "entry";
|
public const String KeyEntry = "entry";
|
||||||
public const String KeyRefreshPos = "refresh_pos";
|
public const String KeyRefreshPos = "refresh_pos";
|
||||||
@@ -110,6 +112,45 @@ namespace keepass2android
|
|||||||
|
|
||||||
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
|
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
|
||||||
|
|
||||||
|
public class UpdateEntryActivityBroadcastReceiver : BroadcastReceiver
|
||||||
|
{
|
||||||
|
private readonly EntryActivity _activity;
|
||||||
|
|
||||||
|
public UpdateEntryActivityBroadcastReceiver(EntryActivity activity)
|
||||||
|
{
|
||||||
|
_activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnReceive(Context? context, Intent? intent)
|
||||||
|
{
|
||||||
|
if (intent?.Action == Intents.DataUpdated)
|
||||||
|
{
|
||||||
|
_activity.OnDataUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDataUpdated()
|
||||||
|
{
|
||||||
|
if (Entry == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryUId = Entry.Uuid;
|
||||||
|
if (!App.Kp2a.CurrentDb.EntriesById.ContainsKey(entryUId))
|
||||||
|
{
|
||||||
|
Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var newEntry = App.Kp2a.CurrentDb.EntriesById[entryUId];
|
||||||
|
if (!newEntry.EqualsEntry(Entry, PwCompareOptions.None, MemProtCmpMode.Full))
|
||||||
|
{
|
||||||
|
Recreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1)
|
public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask, ActivityFlags? flags = null, int historyIndex=-1)
|
||||||
{
|
{
|
||||||
Intent i = new Intent(act, typeof(EntryActivity));
|
Intent i = new Intent(act, typeof(EntryActivity));
|
||||||
@@ -481,8 +522,8 @@ namespace keepass2android
|
|||||||
Entry.Expires = true;
|
Entry.Expires = true;
|
||||||
Entry.Touch(true);
|
Entry.Touch(true);
|
||||||
RequiresRefresh();
|
RequiresRefresh();
|
||||||
UpdateEntry update = new UpdateEntry(this, App.Kp2a, backupEntry, Entry, null);
|
UpdateEntry update = new UpdateEntry(App.Kp2a, backupEntry, Entry, null);
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this, update);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, update);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
}
|
}
|
||||||
FillData();
|
FillData();
|
||||||
@@ -501,7 +542,13 @@ namespace keepass2android
|
|||||||
|
|
||||||
//the rest of the things to do depends on the current app task:
|
//the rest of the things to do depends on the current app task:
|
||||||
AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread);
|
AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread);
|
||||||
}
|
|
||||||
|
_dataUpdatedIntentReceiver = new UpdateEntryActivityBroadcastReceiver(this);
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.AddAction(Intents.DataUpdated);
|
||||||
|
ContextCompat.RegisterReceiver(this, _dataUpdatedIntentReceiver, filter, (int)ReceiverFlags.Exported);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void RemoveFromHistory()
|
private void RemoveFromHistory()
|
||||||
{
|
{
|
||||||
@@ -525,13 +572,17 @@ namespace keepass2android
|
|||||||
App.Kp2a.DirtyGroups.Add(parent);
|
App.Kp2a.DirtyGroups.Add(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
var saveTask = new SaveDb(this, App.Kp2a, App.Kp2a.FindDatabaseForElement(Entry), new ActionOnFinish(this, (success, message, activity) =>
|
var saveTask = new SaveDb( App.Kp2a, App.Kp2a.FindDatabaseForElement(Entry), new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
activity.SetResult(KeePass.ExitRefresh);
|
if (context is Activity activity)
|
||||||
activity.Finish();
|
{
|
||||||
|
activity.SetResult(KeePass.ExitRefresh);
|
||||||
|
activity.Finish();
|
||||||
|
}
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this, saveTask);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, saveTask);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -796,7 +847,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
App.Kp2a.ShowMessage(this,
|
App.Kp2a.ShowMessage(this,
|
||||||
GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] {filename})
|
GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] {filename})
|
||||||
+ exWrite.Message, MessageSeverity.Error);
|
+ Util.GetErrorMessage(exWrite), MessageSeverity.Error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -1078,7 +1129,9 @@ namespace keepass2android
|
|||||||
UnregisterReceiver(_pluginActionReceiver);
|
UnregisterReceiver(_pluginActionReceiver);
|
||||||
if (_pluginFieldReceiver != null)
|
if (_pluginFieldReceiver != null)
|
||||||
UnregisterReceiver(_pluginFieldReceiver);
|
UnregisterReceiver(_pluginFieldReceiver);
|
||||||
base.OnDestroy();
|
if (_dataUpdatedIntentReceiver != null)
|
||||||
|
UnregisterReceiver(_dataUpdatedIntentReceiver);
|
||||||
|
base.OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyPluginsOnClose()
|
private void NotifyPluginsOnClose()
|
||||||
@@ -1260,13 +1313,13 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class WriteBinaryTask : RunnableOnFinish
|
public class WriteBinaryTask : OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly ProtectedBinary _data;
|
private readonly ProtectedBinary _data;
|
||||||
private IOConnectionInfo _targetIoc;
|
private IOConnectionInfo _targetIoc;
|
||||||
|
|
||||||
public WriteBinaryTask(Activity activity, IKp2aApp app, OnFinish onFinish, ProtectedBinary data, IOConnectionInfo targetIoc) : base(activity, onFinish)
|
public WriteBinaryTask(IKp2aApp app, OnOperationFinishedHandler onOperationFinishedHandler, ProtectedBinary data, IOConnectionInfo targetIoc) : base(app, onOperationFinishedHandler)
|
||||||
{
|
{
|
||||||
_app = app;
|
_app = app;
|
||||||
_data = data;
|
_data = data;
|
||||||
@@ -1305,7 +1358,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Finish(false, ex.Message);
|
Finish(false, Util.GetErrorMessage(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1354,6 +1407,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isPaused = false;
|
bool isPaused = false;
|
||||||
|
private UpdateEntryActivityBroadcastReceiver _dataUpdatedIntentReceiver;
|
||||||
|
|
||||||
protected override void OnPause()
|
protected override void OnPause()
|
||||||
{
|
{
|
||||||
@@ -1440,8 +1494,8 @@ namespace keepass2android
|
|||||||
Finish();
|
Finish();
|
||||||
return true;
|
return true;
|
||||||
case Resource.Id.menu_delete:
|
case Resource.Id.menu_delete:
|
||||||
DeleteEntry task = new DeleteEntry(this, App.Kp2a, Entry,
|
DeleteEntry task = new DeleteEntry(App.Kp2a, Entry,
|
||||||
new ActionOnFinish(this, (success, message, activity) => { if (success) { RequiresRefresh(); Finish();}}));
|
new ActionOnOperationFinished(App.Kp2a, (success, message, context) => { if (success) { RequiresRefresh(); Finish();}}));
|
||||||
task.Start();
|
task.Start();
|
||||||
break;
|
break;
|
||||||
case Resource.Id.menu_toggle_pass:
|
case Resource.Id.menu_toggle_pass:
|
||||||
@@ -1504,16 +1558,16 @@ namespace keepass2android
|
|||||||
|
|
||||||
//save the entry:
|
//save the entry:
|
||||||
|
|
||||||
ActionOnFinish closeOrShowError = new ActionOnFinish(this, (success, message, activity) =>
|
ActionOnOperationFinished closeOrShowError = new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
OnFinish.DisplayMessage(this, message, true);
|
OnOperationFinishedHandler.DisplayMessage(this, message, true);
|
||||||
finishAction((EntryActivity)activity);
|
finishAction(context as EntryActivity);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
RunnableOnFinish runnable = new UpdateEntry(this, App.Kp2a, initialEntry, newEntry, closeOrShowError);
|
OperationWithFinishHandler runnable = new UpdateEntry(App.Kp2a, initialEntry, newEntry, closeOrShowError);
|
||||||
|
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this, runnable);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, runnable);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1603,5 +1657,7 @@ namespace keepass2android
|
|||||||
imageViewerIntent.PutExtra("EntryKey", key);
|
imageViewerIntent.PutExtra("EntryKey", key);
|
||||||
StartActivity(imageViewerIntent);
|
StartActivity(imageViewerIntent);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public IProgressUi? ProgressUi => FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
|
||||||
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the state of the EntrryEditActivity. This is required to be able to keep a partially modified entry in memory
|
/// Holds the state of the EntryEditActivity. This is required to be able to keep a partially modified entry in memory
|
||||||
/// through the App variable. Serializing this state (especially the Entry/EntryInDatabase) can be a performance problem
|
/// through the App variable. Serializing this state (especially the Entry/EntryInDatabase) can be a performance problem
|
||||||
/// when there are big attachements.
|
/// when there are big attachements.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -19,23 +19,23 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
private readonly FileFormatProvider _ffp;
|
private readonly FileFormatProvider _ffp;
|
||||||
|
|
||||||
public ExportDbProcessManager(int requestCode, Activity activity, FileFormatProvider ffp) : base(requestCode, activity)
|
public ExportDbProcessManager(int requestCode, LifecycleAwareActivity activity, FileFormatProvider ffp) : base(requestCode, activity)
|
||||||
{
|
{
|
||||||
_ffp = ffp;
|
_ffp = ffp;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SaveFile(IOConnectionInfo ioc)
|
protected override void SaveFile(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
var exportDb = new ExportDatabaseActivity.ExportDb(_activity, App.Kp2a, new ActionOnFinish(_activity, (success, message, activity) =>
|
var exportDb = new ExportDatabaseActivity.ExportDb(App.Kp2a, new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
else
|
else
|
||||||
App.Kp2a.ShowMessage(activity, _activity.GetString(Resource.String.export_database_successful), MessageSeverity.Info);
|
App.Kp2a.ShowMessage(context, _activity.GetString(Resource.String.export_database_successful), MessageSeverity.Info);
|
||||||
activity.Finish();
|
(context as Activity)?.Finish();
|
||||||
}
|
}
|
||||||
), _ffp, ioc);
|
), _ffp, ioc);
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, _activity, exportDb);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, exportDb);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -93,13 +93,13 @@ namespace keepass2android
|
|||||||
get { return 0; }
|
get { return 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ExportDb : RunnableOnFinish
|
public class ExportDb : OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private readonly FileFormatProvider _fileFormat;
|
private readonly FileFormatProvider _fileFormat;
|
||||||
private IOConnectionInfo _targetIoc;
|
private IOConnectionInfo _targetIoc;
|
||||||
|
|
||||||
public ExportDb(Activity activity, IKp2aApp app, OnFinish onFinish, FileFormatProvider fileFormat, IOConnectionInfo targetIoc) : base(activity, onFinish)
|
public ExportDb(IKp2aApp app, OnOperationFinishedHandler onOperationFinishedHandler, FileFormatProvider fileFormat, IOConnectionInfo targetIoc) : base(app, onOperationFinishedHandler)
|
||||||
{
|
{
|
||||||
_app = app;
|
_app = app;
|
||||||
this._fileFormat = fileFormat;
|
this._fileFormat = fileFormat;
|
||||||
@@ -140,7 +140,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Finish(false, ex.Message);
|
Finish(false, Util.GetErrorMessage(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -12,9 +12,9 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
|
|
||||||
protected readonly int _requestCode;
|
protected readonly int _requestCode;
|
||||||
protected readonly Activity _activity;
|
protected readonly LifecycleAwareActivity _activity;
|
||||||
|
|
||||||
public FileSaveProcessManager(int requestCode, Activity activity)
|
public FileSaveProcessManager(int requestCode, LifecycleAwareActivity activity)
|
||||||
{
|
{
|
||||||
_requestCode = requestCode;
|
_requestCode = requestCode;
|
||||||
_activity = activity;
|
_activity = activity;
|
||||||
@@ -103,7 +103,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = new CreateNewFilename(_activity, new ActionOnFinish(_activity, (success, messageOrFilename, activity) =>
|
var task = new CreateNewFilename(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, messageOrFilename, activity) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
@@ -115,7 +115,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
}), filename);
|
}), filename);
|
||||||
|
|
||||||
new ProgressTask(App.Kp2a, _activity, task).Run();
|
new BlockingOperationStarter(App.Kp2a, task).Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -128,7 +128,7 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
toastMsg = ctx.GetString(Resource.String.private_key_save_failed,
|
toastMsg = ctx.GetString(Resource.String.private_key_save_failed,
|
||||||
new Java.Lang.Object[] { e.Message });
|
new Java.Lang.Object[] { Util.GetErrorMessage(e)});
|
||||||
severity = MessageSeverity.Error;
|
severity = MessageSeverity.Error;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -667,10 +667,10 @@ namespace keepass2android
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IocSelected(Activity activity, IOConnectionInfo ioc)
|
private void IocSelected(Context context, IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
if (OnOpen != null)
|
if (OnOpen != null)
|
||||||
OnOpen(activity, ioc);
|
OnOpen(context, ioc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StartFileChooser(string defaultPath)
|
public bool StartFileChooser(string defaultPath)
|
||||||
@@ -781,7 +781,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var task = new CreateNewFilename(activity, new ActionOnFinish(activity, (success, messageOrFilename, newActivity) =>
|
var task = new CreateNewFilename(App.Kp2a, new ActionOnOperationFinished(App.Kp2a, (success, messageOrFilename, newActivity) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
@@ -793,7 +793,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
}), filename);
|
}), filename);
|
||||||
|
|
||||||
new ProgressTask(App.Kp2a, activity, task).Run();
|
new BlockingOperationStarter(App.Kp2a, task).Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,9 @@ namespace keepass2android
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
{
|
{
|
||||||
private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
|
public const string GeneratedPasswordKey = "keepass2android.password.generated_password";
|
||||||
|
|
||||||
|
private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6,
|
||||||
Resource.Id.btn_length8,
|
Resource.Id.btn_length8,
|
||||||
Resource.Id.btn_length12,
|
Resource.Id.btn_length12,
|
||||||
Resource.Id.btn_length16,
|
Resource.Id.btn_length16,
|
||||||
@@ -259,7 +261,7 @@ namespace keepass2android
|
|||||||
EditText password = (EditText) FindViewById(Resource.Id.password_edit);
|
EditText password = (EditText) FindViewById(Resource.Id.password_edit);
|
||||||
|
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.PutExtra("keepass2android.password.generated_password", password.Text);
|
intent.PutExtra(GeneratedPasswordKey, password.Text);
|
||||||
|
|
||||||
SetResult(KeePass.ResultOkPasswordGenerator, intent);
|
SetResult(KeePass.ResultOkPasswordGenerator, intent);
|
||||||
|
|
||||||
@@ -543,7 +545,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
App.Kp2a.ShowMessage(this, e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return password;
|
return password;
|
||||||
|
@@ -223,9 +223,9 @@ namespace keepass2android
|
|||||||
(o, args) =>
|
(o, args) =>
|
||||||
{
|
{
|
||||||
//yes
|
//yes
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, this,
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a,
|
||||||
new AddTemplateEntries(this, App.Kp2a, new ActionOnFinish(this,
|
new AddTemplateEntries(App.Kp2a, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a,
|
||||||
(success, message, activity) => ((GroupActivity)activity)?.StartAddEntry())));
|
(success, message, context) => (context as GroupActivity)?.StartAddEntry())));
|
||||||
pt.Run();
|
pt.Run();
|
||||||
},
|
},
|
||||||
(o, args) =>
|
(o, args) =>
|
||||||
@@ -235,7 +235,7 @@ namespace keepass2android
|
|||||||
edit.Commit();
|
edit.Commit();
|
||||||
//no
|
//no
|
||||||
StartAddEntry();
|
StartAddEntry();
|
||||||
},null, this);
|
},null);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -43,11 +43,13 @@ using keepass2android;
|
|||||||
using KeeTrayTOTP.Libraries;
|
using KeeTrayTOTP.Libraries;
|
||||||
using AndroidX.AppCompat.Widget;
|
using AndroidX.AppCompat.Widget;
|
||||||
using Google.Android.Material.Dialog;
|
using Google.Android.Material.Dialog;
|
||||||
|
using keepass2android.views;
|
||||||
using SearchView = AndroidX.AppCompat.Widget.SearchView;
|
using SearchView = AndroidX.AppCompat.Widget.SearchView;
|
||||||
|
using AndroidX.Core.Content;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
public abstract class GroupBaseActivity : LockCloseActivity
|
public abstract class GroupBaseActivity : LockCloseActivity, IProgressUiProvider
|
||||||
{
|
{
|
||||||
public const String KeyEntry = "entry";
|
public const String KeyEntry = "entry";
|
||||||
public const String KeyMode = "mode";
|
public const String KeyMode = "mode";
|
||||||
@@ -201,19 +203,18 @@ namespace keepass2android
|
|||||||
new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId)));
|
new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId)));
|
||||||
String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid);
|
String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid);
|
||||||
GroupBaseActivity act = this;
|
GroupBaseActivity act = this;
|
||||||
Handler handler = new Handler();
|
OperationWithFinishHandler task;
|
||||||
RunnableOnFinish task;
|
|
||||||
if (strGroupUuid == null)
|
if (strGroupUuid == null)
|
||||||
{
|
{
|
||||||
task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, new RefreshTask(handler, this), false);
|
task = AddGroup.GetInstance(App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, CreateRefreshAction(), false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid));
|
PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid));
|
||||||
task = new EditGroup(this, App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.FindGroup(groupUuid),
|
task = new EditGroup(App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.FindGroup(groupUuid),
|
||||||
new RefreshTask(handler, this));
|
CreateRefreshAction());
|
||||||
}
|
}
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, act, task);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, task);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +275,7 @@ namespace keepass2android
|
|||||||
private IMenuItem searchItem;
|
private IMenuItem searchItem;
|
||||||
private IMenuItem searchItemDummy;
|
private IMenuItem searchItemDummy;
|
||||||
private bool isPaused;
|
private bool isPaused;
|
||||||
|
private UpdateGroupBaseActivityBroadcastReceiver _dataUpdatedIntentReceiver;
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
@@ -745,9 +747,10 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_dataUpdatedIntentReceiver = new UpdateGroupBaseActivityBroadcastReceiver(this);
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.AddAction(Intents.DataUpdated);
|
||||||
|
ContextCompat.RegisterReceiver(this, _dataUpdatedIntentReceiver, filter, (int)ReceiverFlags.Exported);
|
||||||
|
|
||||||
SetResult(KeePass.ExitNormal);
|
SetResult(KeePass.ExitNormal);
|
||||||
|
|
||||||
@@ -924,14 +927,14 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
var moveElement = new MoveElements(elementsToMove.ToList(), Group, this, App.Kp2a, new ActionOnFinish(this,
|
var moveElement = new MoveElements(elementsToMove.ToList(), Group, App.Kp2a, new ActionInContextInstanceOnOperationFinished(ContextInstanceId, App.Kp2a,
|
||||||
(success, message, activity) =>
|
(success, message, context) =>
|
||||||
{
|
{
|
||||||
((GroupBaseActivity)activity)?.StopMovingElements();
|
(context as GroupBaseActivity)?.StopMovingElements();
|
||||||
if (!String.IsNullOrEmpty(message))
|
if (!String.IsNullOrEmpty(message))
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
}));
|
}));
|
||||||
var progressTask = new ProgressTask(App.Kp2a, this, moveElement);
|
var progressTask = new BlockingOperationStarter(App.Kp2a, moveElement);
|
||||||
progressTask.Run();
|
progressTask.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1033,6 +1036,13 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
UnregisterReceiver(_dataUpdatedIntentReceiver);
|
||||||
|
base.OnDestroy();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public override bool OnCreateOptionsMenu(IMenu menu)
|
public override bool OnCreateOptionsMenu(IMenu menu)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -1212,7 +1222,7 @@ namespace keepass2android
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Resource.Id.menu_sync:
|
case Resource.Id.menu_sync:
|
||||||
new SyncUtil(this).SynchronizeDatabase(() => { });
|
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Resource.Id.menu_work_offline:
|
case Resource.Id.menu_work_offline:
|
||||||
@@ -1223,7 +1233,7 @@ namespace keepass2android
|
|||||||
case Resource.Id.menu_work_online:
|
case Resource.Id.menu_work_online:
|
||||||
App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false;
|
App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false;
|
||||||
UpdateOfflineModeMenu();
|
UpdateOfflineModeMenu();
|
||||||
new SyncUtil(this).SynchronizeDatabase(() => { });
|
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
|
||||||
return true;
|
return true;
|
||||||
case Resource.Id.menu_open_other_db:
|
case Resource.Id.menu_open_other_db:
|
||||||
AppTask.SetActivityResult(this, KeePass.ExitLoadAnotherDb);
|
AppTask.SetActivityResult(this, KeePass.ExitLoadAnotherDb);
|
||||||
@@ -1293,51 +1303,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RefreshTask : OnFinish
|
|
||||||
{
|
|
||||||
public RefreshTask(Handler handler, GroupBaseActivity act)
|
|
||||||
: base(act, handler)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Run()
|
|
||||||
{
|
|
||||||
if (Success)
|
|
||||||
{
|
|
||||||
((GroupBaseActivity)ActiveActivity)?.RefreshIfDirty();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisplayMessage(ActiveActivity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class AfterDeleteGroup : OnFinish
|
|
||||||
{
|
|
||||||
public AfterDeleteGroup(Handler handler, GroupBaseActivity act)
|
|
||||||
: base(act, handler)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override void Run()
|
|
||||||
{
|
|
||||||
if (Success)
|
|
||||||
{
|
|
||||||
((GroupBaseActivity)ActiveActivity)?.RefreshIfDirty();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Handler.Post(() =>
|
|
||||||
{
|
|
||||||
App.Kp2a.ShowMessage(ActiveActivity ?? LocaleManager.LocalizedAppContext, "Unrecoverable error: " + Message, MessageSeverity.Error);
|
|
||||||
});
|
|
||||||
|
|
||||||
App.Kp2a.Lock(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsBeingMoved(PwUuid uuid)
|
public bool IsBeingMoved(PwUuid uuid)
|
||||||
{
|
{
|
||||||
@@ -1408,6 +1374,79 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup);
|
GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IProgressUi ProgressUi
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return FindViewById<BackgroundOperationContainer>(Resource.Id.background_ops_container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnDataUpdated()
|
||||||
|
{
|
||||||
|
if (Group == null || FragmentManager.IsDestroyed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupId = Group.Uuid;
|
||||||
|
if (!App.Kp2a.CurrentDb.GroupsById.ContainsKey(groupId))
|
||||||
|
{
|
||||||
|
Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Group = App.Kp2a.CurrentDb.GroupsById[groupId];
|
||||||
|
var fragment = FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment);
|
||||||
|
if (fragment == null)
|
||||||
|
{
|
||||||
|
throw new Exception("did not find fragment");
|
||||||
|
}
|
||||||
|
fragment.ListAdapter = new PwGroupListAdapter(this, Group);
|
||||||
|
SetGroupIcon();
|
||||||
|
SetGroupTitle();
|
||||||
|
ListAdapter?.NotifyDataSetChanged();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnOperationFinishedHandler CreateRefreshAction()
|
||||||
|
{
|
||||||
|
return new ActionInContextInstanceOnOperationFinished(
|
||||||
|
ContextInstanceId, App.Kp2a,
|
||||||
|
(success, message, context) =>
|
||||||
|
{
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
RunOnUiThread(() =>
|
||||||
|
{
|
||||||
|
(context as GroupBaseActivity)?.RefreshIfDirty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateGroupBaseActivityBroadcastReceiver : BroadcastReceiver
|
||||||
|
{
|
||||||
|
private readonly GroupBaseActivity _groupBaseActivity;
|
||||||
|
|
||||||
|
public UpdateGroupBaseActivityBroadcastReceiver(GroupBaseActivity groupBaseActivity)
|
||||||
|
{
|
||||||
|
_groupBaseActivity = groupBaseActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnReceive(Context? context, Intent? intent)
|
||||||
|
{
|
||||||
|
if (intent?.Action == Intents.DataUpdated)
|
||||||
|
{
|
||||||
|
_groupBaseActivity.OnDataUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener
|
public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener
|
||||||
@@ -1470,12 +1509,12 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Handler handler = new Handler();
|
|
||||||
switch (item.ItemId)
|
switch (item.ItemId)
|
||||||
{
|
{
|
||||||
|
|
||||||
case Resource.Id.menu_delete:
|
case Resource.Id.menu_delete:
|
||||||
DeleteMultipleItems((GroupBaseActivity)Activity, checkedItems, new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a);
|
DeleteMultipleItems((GroupBaseActivity)Activity, checkedItems, ((GroupBaseActivity)Activity).CreateRefreshAction(), App.Kp2a);
|
||||||
break;
|
break;
|
||||||
case Resource.Id.menu_move:
|
case Resource.Id.menu_move:
|
||||||
var navMove = new NavigateToFolderAndLaunchMoveElementTask(App.Kp2a.CurrentDb, checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult);
|
var navMove = new NavigateToFolderAndLaunchMoveElementTask(App.Kp2a.CurrentDb, checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult);
|
||||||
@@ -1483,10 +1522,10 @@ namespace keepass2android
|
|||||||
break;
|
break;
|
||||||
case Resource.Id.menu_copy:
|
case Resource.Id.menu_copy:
|
||||||
|
|
||||||
var copyTask = new CopyEntry((GroupBaseActivity)Activity, App.Kp2a, (PwEntry)checkedItems.First(),
|
var copyTask = new CopyEntry(App.Kp2a, (PwEntry)checkedItems.First(),
|
||||||
new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a.CurrentDb);
|
((GroupBaseActivity)Activity).CreateRefreshAction(), App.Kp2a.CurrentDb);
|
||||||
|
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, copyTask);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, copyTask);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -1631,7 +1670,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void DeleteMultipleItems(GroupBaseActivity activity, List<IStructureItem> checkedItems, OnFinish onFinish, Kp2aApp app)
|
public void DeleteMultipleItems(GroupBaseActivity activity, List<IStructureItem> checkedItems, OnOperationFinishedHandler onOperationFinishedHandler, Kp2aApp app)
|
||||||
{
|
{
|
||||||
if (checkedItems.Any() == false)
|
if (checkedItems.Any() == false)
|
||||||
return;
|
return;
|
||||||
@@ -1662,30 +1701,30 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
int dbIndex = 0;
|
int dbIndex = 0;
|
||||||
Action<bool, string, Activity> action = null;
|
Action<bool, string, Context> action = null;
|
||||||
action = (success, message, activeActivity) =>
|
action = (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
dbIndex++;
|
dbIndex++;
|
||||||
if (dbIndex == itemsForDatabases.Count)
|
if (dbIndex == itemsForDatabases.Count)
|
||||||
{
|
{
|
||||||
onFinish.SetResult(true);
|
onOperationFinishedHandler.SetResult(true);
|
||||||
onFinish.Run();
|
onOperationFinishedHandler.Run();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
|
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
|
||||||
itemsForDatabases[dbIndex].Value, new ActionOnFinish(activeActivity, (b, s, activity1) => action(b, s, activity1)), app)
|
itemsForDatabases[dbIndex].Value, new ActionOnOperationFinished(App.Kp2a, (b, s, activity1) => action(b, s, activity1)), app)
|
||||||
.Start();
|
.Start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
onFinish.SetResult(false, message, true, null);
|
onOperationFinishedHandler.SetResult(false, message, true, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
|
new DeleteMultipleItemsFromOneDatabase(activity, itemsForDatabases[dbIndex].Key,
|
||||||
itemsForDatabases[dbIndex].Value, new ActionOnFinish(activity, (b, s, activity1) => action(b, s, activity1)), app)
|
itemsForDatabases[dbIndex].Value, new ActionOnOperationFinished(App.Kp2a, (b, s, activity1) => action(b, s, activity1)), app)
|
||||||
.Start();
|
.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -148,17 +148,13 @@ namespace KeeChallenge
|
|||||||
{
|
{
|
||||||
//read the secret from the stream
|
//read the secret from the stream
|
||||||
int totalBytesRead = 0;
|
int totalBytesRead = 0;
|
||||||
byte[] buffer = new byte[secret.Length];
|
|
||||||
var secretOutputStream = new MemoryStream(secret);
|
|
||||||
|
|
||||||
int bytesRead = csDecrypt.Read(buffer, totalBytesRead, secret.Length - totalBytesRead);
|
int bytesRead = csDecrypt.Read(secret, totalBytesRead, secret.Length - totalBytesRead);
|
||||||
while (bytesRead > 0 && totalBytesRead < secret.Length)
|
while (bytesRead > 0 && totalBytesRead < secret.Length)
|
||||||
{
|
{
|
||||||
secretOutputStream.Write(buffer, 0, bytesRead);
|
|
||||||
totalBytesRead += bytesRead;
|
totalBytesRead += bytesRead;
|
||||||
bytesRead = csDecrypt.Read(buffer, totalBytesRead, secret.Length - totalBytesRead);
|
bytesRead = csDecrypt.Read(secret, totalBytesRead, secret.Length - totalBytesRead);
|
||||||
}
|
}
|
||||||
secretOutputStream.Close();
|
|
||||||
|
|
||||||
csDecrypt.Close();
|
csDecrypt.Close();
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ using Android.Runtime;
|
|||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
|
||||||
public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity
|
public abstract class LifecycleAwareActivity : AndroidX.AppCompat.App.AppCompatActivity, IContextInstanceIdProvider
|
||||||
{
|
{
|
||||||
protected override void AttachBaseContext(Context baseContext)
|
protected override void AttachBaseContext(Context baseContext)
|
||||||
{
|
{
|
||||||
@@ -84,12 +84,11 @@ namespace keepass2android
|
|||||||
return baseRes;
|
return baseRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action? OnResumeListener { get; set; }
|
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
OnResumeListener?.Invoke();
|
App.Kp2a.PerformPendingActions(_instanceId);
|
||||||
|
|
||||||
Kp2aLog.Log(ClassName + ".OnResume " + ID);
|
Kp2aLog.Log(ClassName + ".OnResume " + ID);
|
||||||
if (App.Kp2a.CurrentDb == null)
|
if (App.Kp2a.CurrentDb == null)
|
||||||
@@ -104,26 +103,38 @@ namespace keepass2android
|
|||||||
|
|
||||||
protected override void OnStart()
|
protected override void OnStart()
|
||||||
{
|
{
|
||||||
ProgressTask.SetNewActiveActivity(this);
|
App.Kp2a.ActiveContext = this;
|
||||||
base.OnStart();
|
base.OnStart();
|
||||||
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID);
|
Kp2aLog.Log(ClassName + ".OnStart" + " " + ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const string ID_KEY = "kp2a_context_instance_id";
|
||||||
|
const int InvalidId = -1;
|
||||||
|
|
||||||
|
private int _instanceId;
|
||||||
|
|
||||||
protected override void OnCreate(Bundle bundle)
|
protected override void OnCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
|
|
||||||
base.OnCreate(bundle);
|
base.OnCreate(bundle);
|
||||||
|
|
||||||
|
_instanceId = bundle?.GetInt(ID_KEY, InvalidId) ?? InvalidId;
|
||||||
|
if (_instanceId == InvalidId)
|
||||||
|
{
|
||||||
|
_instanceId = _nextContextInstanceId++;
|
||||||
|
}
|
||||||
|
|
||||||
OnCreateListener?.Invoke(bundle);
|
OnCreateListener?.Invoke(bundle);
|
||||||
|
|
||||||
Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID);
|
Kp2aLog.Log(ClassName + ".OnCreate" + " " + ID + " (instance=" + _instanceId +")");
|
||||||
Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID);
|
Kp2aLog.Log(ClassName + ":apptask=" + Intent.GetStringExtra("KP2A_APP_TASK_TYPE") + " " + ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
{
|
{
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
Kp2aLog.Log(ClassName + ".OnDestroy" + IsFinishing.ToString() + " " + ID);
|
Kp2aLog.Log(ClassName + ".OnDestroy " + IsFinishing.ToString() + " " + ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPause()
|
protected override void OnPause()
|
||||||
@@ -136,14 +147,18 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
base.OnStop();
|
base.OnStop();
|
||||||
Kp2aLog.Log(ClassName + ".OnStop" + " " + ID);
|
Kp2aLog.Log(ClassName + ".OnStop" + " " + ID);
|
||||||
ProgressTask.RemoveActiveActivity(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnSaveInstanceState(Bundle outState)
|
protected override void OnSaveInstanceState(Bundle outState)
|
||||||
{
|
{
|
||||||
base.OnSaveInstanceState(outState);
|
base.OnSaveInstanceState(outState);
|
||||||
|
outState.PutInt(ID_KEY, _instanceId);
|
||||||
OnSaveInstanceStateListener?.Invoke(outState);
|
OnSaveInstanceStateListener?.Invoke(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _nextContextInstanceId = 0;
|
||||||
|
|
||||||
|
public int ContextInstanceId => _instanceId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ namespace keepass2android
|
|||||||
protected const string NoLockCheck = "NO_LOCK_CHECK";
|
protected const string NoLockCheck = "NO_LOCK_CHECK";
|
||||||
|
|
||||||
protected IOConnectionInfo _ioc;
|
protected IOConnectionInfo _ioc;
|
||||||
private BroadcastReceiver _intentReceiver;
|
private BroadcastReceiver _lockCloseIntentReceiver;
|
||||||
private ActivityDesign _design;
|
private ActivityDesign _design;
|
||||||
|
|
||||||
public LockCloseActivity()
|
public LockCloseActivity()
|
||||||
@@ -66,11 +66,11 @@ namespace keepass2android
|
|||||||
if (Intent.GetBooleanExtra(NoLockCheck, false))
|
if (Intent.GetBooleanExtra(NoLockCheck, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_intentReceiver = new LockCloseActivityBroadcastReceiver(this);
|
_lockCloseIntentReceiver = new LockCloseActivityBroadcastReceiver(this);
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.AddAction(Intents.DatabaseLocked);
|
filter.AddAction(Intents.DatabaseLocked);
|
||||||
filter.AddAction(Intent.ActionScreenOff);
|
filter.AddAction(Intent.ActionScreenOff);
|
||||||
ContextCompat.RegisterReceiver(this, _intentReceiver, filter, (int)ReceiverFlags.Exported);
|
ContextCompat.RegisterReceiver(this, _lockCloseIntentReceiver, filter, (int)ReceiverFlags.Exported);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
@@ -79,7 +79,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UnregisterReceiver(_intentReceiver);
|
UnregisterReceiver(_lockCloseIntentReceiver);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@@ -266,6 +266,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
|
|||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" />
|
<uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
|
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" />
|
<uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" />
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:versionCode="205"
|
android:versionCode="206"
|
||||||
android:versionName="1.12-r4"
|
android:versionName="1.12-r5"
|
||||||
package="keepass2android.keepass2android"
|
package="keepass2android.keepass2android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
@@ -278,6 +278,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
|
|||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" />
|
<uses-permission android:name="android.permission.USE_CREDENTIALS" android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
|
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" />
|
<uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalFileBrowsing" />
|
||||||
|
@@ -257,6 +257,9 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
|
|||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
|
||||||
|
@@ -222,6 +222,7 @@ namespace keepass2android
|
|||||||
//StackBaseActivity will launch the next activity
|
//StackBaseActivity will launch the next activity
|
||||||
Intent data = new Intent();
|
Intent data = new Intent();
|
||||||
data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection));
|
data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioConnection));
|
||||||
|
data.PutExtra("requiresSubsequentSync", _lastLoadOperation?.RequiresSubsequentSync == true);
|
||||||
|
|
||||||
SetResult(Result.Ok, data);
|
SetResult(Result.Ok, data);
|
||||||
|
|
||||||
@@ -310,7 +311,7 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.Log(e.ToString());
|
Kp2aLog.Log(e.ToString());
|
||||||
App.Kp2a.ShowMessage(this, "Error: " + e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(this, "Error: " + Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,8 +997,12 @@ namespace keepass2android
|
|||||||
|
|
||||||
btn.PostDelayed(() =>
|
btn.PostDelayed(() =>
|
||||||
{
|
{
|
||||||
//fire
|
//fire if everything else is ready
|
||||||
OnOk(true);
|
if (FindViewById(Resource.Id.pass_ok).Enabled)
|
||||||
|
{
|
||||||
|
OnOk(true);
|
||||||
|
}
|
||||||
|
|
||||||
FindViewById<EditText>(Resource.Id.password_edit).Enabled = true;
|
FindViewById<EditText>(Resource.Id.password_edit).Enabled = true;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
@@ -1436,15 +1441,22 @@ namespace keepass2android
|
|||||||
MakePasswordMaskedOrVisible();
|
MakePasswordMaskedOrVisible();
|
||||||
|
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
|
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
|
||||||
LoadDb task = (KeyProviderTypes.Contains(KeyProviders.Otp))
|
LoadDb loadOperation = (KeyProviderTypes.Contains(KeyProviders.Otp))
|
||||||
? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
|
? new SaveOtpAuxFileAndLoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(),
|
||||||
onFinish, this, true, _makeCurrent)
|
onOperationFinishedHandler, this, true, _makeCurrent)
|
||||||
: new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onFinish,true, _makeCurrent);
|
: new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKey, GetKeyProviderString(), onOperationFinishedHandler,true, _makeCurrent);
|
||||||
_loadDbFileTask = null; // prevent accidental re-use
|
_loadDbFileTask = null; // prevent accidental re-use
|
||||||
|
|
||||||
new ProgressTask(App.Kp2a, this, task).Run();
|
_lastLoadOperation = loadOperation;
|
||||||
}
|
|
||||||
|
|
||||||
|
//Don't use BlockingOperationStarter as that would cancel running operations.
|
||||||
|
//This is bad when used with AutoOpen: we might get here when one database has loaded and is now synced in the background.
|
||||||
|
//We don't want to cancel this.
|
||||||
|
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
|
||||||
|
|
||||||
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(new Exception("cannot load database: "+e + ", c: " + (compositeKey != null) + (_ioConnection != null) + (_keyFile != null), e));
|
Kp2aLog.LogUnexpectedError(new Exception("cannot load database: "+e + ", c: " + (compositeKey != null) + (_ioConnection != null) + (_keyFile != null), e));
|
||||||
@@ -1481,7 +1493,7 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
errorMessage = e.Message;
|
errorMessage = Util.GetErrorMessage(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1566,15 +1578,21 @@ namespace keepass2android
|
|||||||
|
|
||||||
base.OnPause();
|
base.OnPause();
|
||||||
}
|
}
|
||||||
protected override void OnStart()
|
|
||||||
|
private bool hasRequestedKeyboardActivation = false;
|
||||||
|
private LoadDb _lastLoadOperation;
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
{
|
{
|
||||||
base.OnStart();
|
base.OnStart();
|
||||||
_starting = true;
|
_starting = true;
|
||||||
|
|
||||||
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
||||||
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false))
|
.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)
|
||||||
{
|
&& !hasRequestedKeyboardActivation)
|
||||||
CopyToClipboardService.ActivateKeyboard(this);
|
{
|
||||||
|
hasRequestedKeyboardActivation = true;
|
||||||
|
CopyToClipboardService.ActivateKeyboard(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
DonateReminder.ShowDonateReminderIfAppropriate(this);
|
DonateReminder.ShowDonateReminderIfAppropriate(this);
|
||||||
@@ -1745,17 +1763,10 @@ namespace keepass2android
|
|||||||
cbOfflineMode.Checked =
|
cbOfflineMode.Checked =
|
||||||
App.Kp2a
|
App.Kp2a
|
||||||
.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
|
.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
|
||||||
LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container);
|
|
||||||
var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
|
CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background)!;
|
||||||
if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
|
cbSyncInBackground.Checked = App.Kp2a.SyncInBackgroundPreference;
|
||||||
{
|
UpdateInternalCacheCheckboxesVisibility();
|
||||||
offlineModeContainer.Visibility = ViewStates.Visible;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
offlineModeContainer.Visibility = ViewStates.Gone;
|
|
||||||
App.Kp2a.OfflineMode = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1883,13 +1894,14 @@ namespace keepass2android
|
|||||||
// to retry with typing the full password, but that's intended to avoid showing the password to a
|
// to retry with typing the full password, but that's intended to avoid showing the password to a
|
||||||
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
|
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
|
OnOperationFinishedHandler onOperationFinishedHandler = new AfterLoad(handler, this, _ioConnection);
|
||||||
_performingLoad = true;
|
_performingLoad = true;
|
||||||
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
|
LoadDb loadOperation = new LoadDb(App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
|
||||||
onFinish, false, _makeCurrent);
|
onOperationFinishedHandler, false, _makeCurrent);
|
||||||
_loadDbFileTask = null; // prevent accidental re-use
|
_loadDbFileTask = null; // prevent accidental re-use
|
||||||
new ProgressTask(App.Kp2a, this, task).Run();
|
_lastLoadOperation = loadOperation;
|
||||||
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
|
OperationRunner.Instance.Run(App.Kp2a, loadOperation, true);
|
||||||
|
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -2031,10 +2043,38 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
App.Kp2a.OfflineModePreference = App.Kp2a.OfflineMode = args.IsChecked;
|
App.Kp2a.OfflineModePreference = App.Kp2a.OfflineMode = args.IsChecked;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
CheckBox cbSyncInBackground = (CheckBox)FindViewById(Resource.Id.sync_in_background);
|
||||||
|
cbSyncInBackground.CheckedChange += (sender, args) =>
|
||||||
private String LoadKeyProviderStringForIoc(String filename) {
|
{
|
||||||
|
App.Kp2a.SyncInBackgroundPreference = args.IsChecked;
|
||||||
|
UpdateInternalCacheCheckboxesVisibility();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateInternalCacheCheckboxesVisibility()
|
||||||
|
{
|
||||||
|
|
||||||
|
LinearLayout syncInBackgroundContainer = FindViewById<LinearLayout>(Resource.Id.sync_in_background_container)!;
|
||||||
|
|
||||||
|
LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container)!;
|
||||||
|
var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
|
||||||
|
if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
|
||||||
|
{
|
||||||
|
syncInBackgroundContainer.Visibility = ViewStates.Visible;
|
||||||
|
offlineModeContainer.Visibility =
|
||||||
|
App.Kp2a.SyncInBackgroundPreference ? ViewStates.Gone : ViewStates.Visible;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
syncInBackgroundContainer.Visibility = offlineModeContainer.Visibility = ViewStates.Gone;
|
||||||
|
App.Kp2a.OfflineMode = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String LoadKeyProviderStringForIoc(String filename) {
|
||||||
if ( _rememberKeyfile ) {
|
if ( _rememberKeyfile ) {
|
||||||
string keyfile = App.Kp2a.FileDbHelper.GetKeyFileForFile(filename);
|
string keyfile = App.Kp2a.FileDbHelper.GetKeyFileForFile(filename);
|
||||||
if (String.IsNullOrEmpty(keyfile))
|
if (String.IsNullOrEmpty(keyfile))
|
||||||
@@ -2102,11 +2142,11 @@ namespace keepass2android
|
|||||||
Finish();
|
Finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AfterLoad : OnFinish {
|
private class AfterLoad : OnOperationFinishedHandler {
|
||||||
readonly PasswordActivity _act;
|
readonly PasswordActivity _act;
|
||||||
private readonly IOConnectionInfo _ioConnection;
|
private readonly IOConnectionInfo _ioConnection;
|
||||||
|
|
||||||
public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(act, handler)
|
public AfterLoad(Handler handler, PasswordActivity act, IOConnectionInfo ioConnection):base(App.Kp2a, handler)
|
||||||
{
|
{
|
||||||
_act = act;
|
_act = act;
|
||||||
_ioConnection = ioConnection;
|
_ioConnection = ioConnection;
|
||||||
@@ -2213,7 +2253,7 @@ namespace keepass2android
|
|||||||
if (!Success)
|
if (!Success)
|
||||||
_act.InitFingerprintUnlock();
|
_act.InitFingerprintUnlock();
|
||||||
|
|
||||||
|
_act._lastLoadOperation = null;
|
||||||
_act._performingLoad = false;
|
_act._performingLoad = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2247,7 +2287,7 @@ namespace keepass2android
|
|||||||
private readonly PasswordActivity _act;
|
private readonly PasswordActivity _act;
|
||||||
|
|
||||||
|
|
||||||
public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnFinish finish, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(act, app, ioc, databaseData, compositeKey, keyfileOrProvider, finish,updateLastUsageTimestamp,makeCurrent)
|
public SaveOtpAuxFileAndLoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, string keyfileOrProvider, OnOperationFinishedHandler operationFinishedHandler, PasswordActivity act, bool updateLastUsageTimestamp, bool makeCurrent) : base(app, ioc, databaseData, compositeKey, keyfileOrProvider, operationFinishedHandler,updateLastUsageTimestamp,makeCurrent)
|
||||||
{
|
{
|
||||||
_act = act;
|
_act = act;
|
||||||
}
|
}
|
||||||
@@ -2269,7 +2309,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
|
||||||
ShowError( _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + e.Message);
|
ShowError( _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile) + " " + Util.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -339,10 +339,10 @@ namespace keepass2android
|
|||||||
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
||||||
.GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false))
|
.GetBoolean(GetString(Resource.String.SyncAfterQuickUnlock_key), false))
|
||||||
{
|
{
|
||||||
new SyncUtil(this).SynchronizeDatabase(Finish);
|
new SyncUtil(this).StartSynchronizeDatabase(App.Kp2a.CurrentDb.Ioc);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
Finish();
|
Finish();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/background_ops_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/md_theme_surfaceVariant"
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/background_ops_message"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginBottom="3dp"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/background_ops_submessage"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginBottom="3dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/cancel_background"
|
||||||
|
style="?attr/materialIconButtonStyle"
|
||||||
|
app:icon="@drawable/baseline_close_24"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="6dip"
|
||||||
|
android:layout_margin="6dip"
|
||||||
|
android:layout_weight="0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
@@ -43,7 +43,6 @@
|
|||||||
style="@style/EntryEditSingleLine_EditText"
|
style="@style/EntryEditSingleLine_EditText"
|
||||||
android:layout_marginRight="0dip" />
|
android:layout_marginRight="0dip" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
>
|
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/protection"
|
android:id="@+id/protection"
|
||||||
|
@@ -75,6 +75,11 @@ android:layout_height="wrap_content">
|
|||||||
android:layout_marginRight="0dip"
|
android:layout_marginRight="0dip"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
/>
|
/>
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/protection"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@@ -4,18 +4,35 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
|
<LinearLayout android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<keepass2android.views.BackgroundOperationContainer
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/background_ops_container"
|
||||||
|
android:layout_below="@id/top"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:id="@+id/entry_scroll"
|
android:id="@+id/entry_scroll"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:fillViewport="true"
|
|
||||||
android:background="?android:attr/colorBackground"
|
android:background="?android:attr/colorBackground"
|
||||||
android:scrollbarStyle="insideOverlay">
|
android:scrollbarStyle="insideOverlay">
|
||||||
|
|
||||||
<keepass2android.view.EntryContentsView
|
<keepass2android.view.EntryContentsView
|
||||||
android:id="@+id/entry_contents"
|
android:id="@+id/entry_contents"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="fill_parent" />
|
android:layout_width="fill_parent" />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/entry_edit"
|
android:id="@+id/entry_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@@ -354,13 +354,22 @@
|
|||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_above="@id/bottom_bar"
|
android:layout_above="@id/bottom_bar"
|
||||||
android:background="#b8b8b8" />
|
android:background="#b8b8b8" />
|
||||||
|
|
||||||
|
<keepass2android.views.BackgroundOperationContainer
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/background_ops_container"
|
||||||
|
android:layout_below="@id/top"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/main_content"
|
android:id="@+id/main_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_above="@id/divider2"
|
android:layout_above="@id/divider2"
|
||||||
android:layout_below="@id/top"
|
android:layout_below="@id/background_ops_container"
|
||||||
android:fitsSystemWindows="true">
|
android:fitsSystemWindows="true">
|
||||||
<fragment
|
<fragment
|
||||||
android:name="keepass2android.GroupListFragment"
|
android:name="keepass2android.GroupListFragment"
|
||||||
|
@@ -318,6 +318,30 @@
|
|||||||
android:text="@string/help_quickunlock"
|
android:text="@string/help_quickunlock"
|
||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/sync_in_background_container"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/sync_in_background"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/SyncOfflineCacheInBackground_title" />
|
||||||
|
<keepass2android.views.Kp2aShortHelpView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/TextAppearance_Help_Dense"
|
||||||
|
|
||||||
|
app:help_text="@string/SyncOfflineCacheInBackground_summary"
|
||||||
|
app:title_text="@string/SyncOfflineCacheInBackground_title"
|
||||||
|
android:text="@string/SyncOfflineCacheInBackground_summary"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/work_offline_container"
|
android:id="@+id/work_offline_container"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
@@ -328,7 +352,7 @@
|
|||||||
android:id="@+id/work_offline"
|
android:id="@+id/work_offline"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/UseOfflineMode" />
|
android:text="@string/UseOfflineMode" />
|
||||||
<keepass2android.views.Kp2aShortHelpView
|
<keepass2android.views.Kp2aShortHelpView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -341,6 +365,7 @@
|
|||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/kill_app"
|
android:id="@+id/kill_app"
|
||||||
android:text="@string/kill_app_label"
|
android:text="@string/kill_app_label"
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/main_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@@ -14,6 +19,7 @@
|
|||||||
android:text="@string/switch_ime_text" />
|
android:text="@string/switch_ime_text" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|
||||||
android:id="@+id/btn_reopen"
|
android:id="@+id/btn_reopen"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -25,4 +31,51 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/cancel" />
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
|
<LinearLayout android:orientation="vertical"
|
||||||
|
android:paddingTop="64dp"
|
||||||
|
android:id="@+id/settings_notes_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<TextView
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:id="@+id/note_kp2a_switch_rooted"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/autoswitch_enabled_but_not_setup"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:id="@+id/note_AutoFillTotp_prefs_ActivateKeyboard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/switch_keyboard_for_totp_enabled"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:id="@+id/note_UseKp2aKeyboardInKp2a"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/switch_keyboard_inside_kp2a_enabled"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/note_OpenKp2aKeyboardAutomatically"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/switch_keyboard_on_search_enabled"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="?attr/materialButtonOutlinedStyle"
|
||||||
|
android:id="@+id/btn_open_settings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/IconVisibilityInfo_Android8_btnSettings" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="about_feedback">Σχόλια</string>
|
<string name="about_feedback">Σχόλια</string>
|
||||||
<string name="AboutText">Το KP2A είναι ένας διαχειριστής συνθηματικών, που παρέχει την δυνατότητα ανάγνωσης/εγγραφής σε βάσεις δεδομένων του KeePass 2.x στο Android.</string>
|
<string name="AboutText">Το KP2A είναι ένας διαχειριστής συνθηματικών, που παρέχει δυνατότητα ανάγνωσης / εγγραφής σε βάσεις δεδομένων του KeePass 2.x στο Android.</string>
|
||||||
<string name="CreditsText">Το περιβάλλον εργασίας χρήστη βασίζεται σε μια έκδοση του KeepassDroid που αναπτύχθηκε από τον Brian Pellin. Ο κώδικας για τις λειτουργίες της βάσης δεδομένων βασίζεται στο KeePass του Dominik Reichl. Το ρομπότ Android αναπαράγεται ή τροποποιείται από εργασία που δημιουργήθηκε και διαμοιράστηκε από την Google και χρησιμοποιείται σύμφωνα με τους όρους που περιγράφονται στο το Creative Commons 3.0 Attribution License.</string>
|
<string name="CreditsText">Το περιβάλλον εργασίας χρήστη βασίζεται σε μια έκδοση του KeepassDroid που αναπτύχθηκε από τον Brian Pellin. Ο κώδικας για τις λειτουργίες της βάσης δεδομένων βασίζεται στο KeePass του Dominik Reichl. Το ρομπότ Android αναπαράγεται ή τροποποιείται από εργασία που δημιουργήθηκε και διαμοιράστηκε από την Google και χρησιμοποιείται σύμφωνα με τους όρους που περιγράφονται στο το Creative Commons 3.0 Attribution License.</string>
|
||||||
<string name="CreditsTextSFTP">Το SFTP υποστηρίζεται με χρήση της βιβλιοθήκης JSch με άδεια BSD που δημιουργήθηκε από τη JCraft, Inc.</string>
|
<string name="CreditsTextSFTP">Το SFTP υποστηρίζεται με χρήση της βιβλιοθήκης JSch με άδεια BSD που δημιουργήθηκε από τη JCraft, Inc.</string>
|
||||||
<string name="CreditsIcons">Το εικονίδιο Σφυρί δημιουργήθηκε από τον John Caserta, το εικονίδιο Πιγκουίνος δημιουργήθηκε από τον Adriano Emerick, το εικονίδιο Φτερό δημιουργήθηκε από τον Jon Testa και το εικονίδιο της Apple δημιουργήθηκε από την Ava Rowell. Όλα τα προηγούμενα εικονίδια είναι από το Noun Project. Το εικονίδιο της Εικόνας είναι από το https://icons8.com/icon/5570/Picture.</string>
|
<string name="CreditsIcons">Το εικονίδιο Σφυρί δημιουργήθηκε από τον John Caserta, το εικονίδιο Πιγκουίνος δημιουργήθηκε από τον Adriano Emerick, το εικονίδιο Φτερό δημιουργήθηκε από τον Jon Testa και το εικονίδιο της Apple δημιουργήθηκε από την Ava Rowell. Όλα τα προηγούμενα εικονίδια είναι από το Noun Project. Το εικονίδιο της Εικόνας είναι από το https://icons8.com/icon/5570/Picture.</string>
|
||||||
@@ -401,6 +401,14 @@
|
|||||||
<string name="ShowSeparateNotifications_summary">Εμφάνιση χωριστών ειδοποιήσεων για την αντιγραφή του ονόματος χρήστη και του συνθηματικού στο πρόχειρο και ενεργοποίηση του πληκτρολογίου.</string>
|
<string name="ShowSeparateNotifications_summary">Εμφάνιση χωριστών ειδοποιήσεων για την αντιγραφή του ονόματος χρήστη και του συνθηματικού στο πρόχειρο και ενεργοποίηση του πληκτρολογίου.</string>
|
||||||
<string name="AccServiceAutoFill_prefs">Αυτόματη συμπλήρωση υπηρεσίας προσβασιμότητας</string>
|
<string name="AccServiceAutoFill_prefs">Αυτόματη συμπλήρωση υπηρεσίας προσβασιμότητας</string>
|
||||||
<string name="AutoFill_prefs">Λειτουργία αυτόματης συμπλήρωσης</string>
|
<string name="AutoFill_prefs">Λειτουργία αυτόματης συμπλήρωσης</string>
|
||||||
|
<string name="AutoFillTotp_prefs_ShowNotification_summary">Κατά την αυτόματη συμπλήρωση μιας καταχώρισης με TOTP, εμφανίστε την ειδοποίηση με ένα κουμπί Αντιγραφή TOTP</string>
|
||||||
|
<string name="AutoFillTotp_prefs_ShowNotification_title">Εμφάνιση ειδοποίησης καταχώρισης</string>
|
||||||
|
<string name="AutoFillTotp_prefs_title">Αυτόματη συμπλήρωση για καταχωρίσεις TOTP</string>
|
||||||
|
<string name="AutoFillTotp_prefs_CopyTotpToClipboard_title">Αντιγραφή TOTP στο πρόχειρο</string>
|
||||||
|
<string name="AutoFillTotp_prefs_CopyTotpToClipboard_summary">Κατά την αυτόματη συμπλήρωση μιας καταχώρισης με TOTP, αντιγράφει το TOTP στο πρόχειρο</string>
|
||||||
|
<string name="AutoFillTotp_prefs_ActivateKeyboard_summary">Κατά την αυτόματη συμπλήρωση μιας καταχώρισης με TOTP, ενεργοποιήστε το ενσωματωμένο πληκτρολόγιο. Το πληκτρολόγιο έχει ένα κουμπί TOTP.</string>
|
||||||
|
<string name="AutoFillTotp_prefs_ActivateKeyboard_title">Ενεργοποίηση ενσωματωμένου πληκτρολογίου</string>
|
||||||
|
<string name="TotpCopiedToClipboard">Αντιγράφηκε το TOTP στο πρόχειρο</string>
|
||||||
<string name="ShowKp2aKeyboardNotification_title">Ειδοποίηση πληκτρολογίου KP2A</string>
|
<string name="ShowKp2aKeyboardNotification_title">Ειδοποίηση πληκτρολογίου KP2A</string>
|
||||||
<string name="ShowKp2aKeyboardNotification_summary">Κάντε προσβάσιμη την πλήρη καταχώριση μέσω του πληκτρολογίου KP2A (συνιστάται).</string>
|
<string name="ShowKp2aKeyboardNotification_summary">Κάντε προσβάσιμη την πλήρη καταχώριση μέσω του πληκτρολογίου KP2A (συνιστάται).</string>
|
||||||
<string name="OpenKp2aKeyboardAutomatically_title">Εναλλαγή πληκτρολογίου</string>
|
<string name="OpenKp2aKeyboardAutomatically_title">Εναλλαγή πληκτρολογίου</string>
|
||||||
@@ -538,6 +546,7 @@
|
|||||||
<string name="filestoragename_dropboxKP2A">Dropbox (φάκελος KP2A)</string>
|
<string name="filestoragename_dropboxKP2A">Dropbox (φάκελος KP2A)</string>
|
||||||
<string name="filestoragehelp_dropboxKP2A">Αν δεν θέλεις να δώσεις στο Keepass2Android πλήρη πρόσβαση στο Dropbox, μπορείς να διαλέξεις αυτή την επιλογή. Θα ζητήσει πρόσβαση μόνο στο φάκελο Apps/Keepass2Android. Ταιριάζει ειδικά στη δημιουργία νέας βάσης δεδομένων. Αν ήδη έχεις μια βάση δεδομένων, διάλεξε αυτή την επιλογή για να δημιουργήσεις το φάκελο, μετά τοποθέτησε το αρχείο σου μέσα στο φάκελο (από το PC) και μετά διάλεξε αυτή την επιλογή πάλι για να ανοίξεις το αρχείο. </string>
|
<string name="filestoragehelp_dropboxKP2A">Αν δεν θέλεις να δώσεις στο Keepass2Android πλήρη πρόσβαση στο Dropbox, μπορείς να διαλέξεις αυτή την επιλογή. Θα ζητήσει πρόσβαση μόνο στο φάκελο Apps/Keepass2Android. Ταιριάζει ειδικά στη δημιουργία νέας βάσης δεδομένων. Αν ήδη έχεις μια βάση δεδομένων, διάλεξε αυτή την επιλογή για να δημιουργήσεις το φάκελο, μετά τοποθέτησε το αρχείο σου μέσα στο φάκελο (από το PC) και μετά διάλεξε αυτή την επιλογή πάλι για να ανοίξεις το αρχείο. </string>
|
||||||
<string name="filestoragename_gdrive">Google Drive</string>
|
<string name="filestoragename_gdrive">Google Drive</string>
|
||||||
|
<string name="filestoragehelp_gdrive">Παρακαλώ σημειώστε: Η Google περιορίζει την πρόσβαση στο Google Drive από εφαρμογές για όλο και περισσότερους χρήστες. Εάν η ενσωματωμένη υλοποίηση του Google Drive δεν λειτουργεί, χρησιμοποιήστε τον επιλογέα αρχείων συστήματος και επιλέξτε το Google Drive εκεί!</string>
|
||||||
<string name="filestoragename_gdriveKP2A">Google Drive (αρχεία KP2A)</string>
|
<string name="filestoragename_gdriveKP2A">Google Drive (αρχεία KP2A)</string>
|
||||||
<string name="filestoragehelp_gdriveKP2A">Αν δεν θέλετε να δώσετε πρόσβαση KP2A πρόσβαση σε όλο το Google Drive σας, μπορείτε να επιλέξετε αυτή την επιλογή. Σημειώστε ότι πρέπει να δημιουργήσετε ένα αρχείο βάσης δεδομένων πρώτα - τα υπάρχοντα αρχεία δεν είναι ορατά στην εφαρμογή. Επιλέξτε αυτή την επιλογή από την οθόνη Δημιουργία βάσης δεδομένων ή, αν έχετε ήδη ανοίξει μια βάση δεδομένων, με την εξαγωγή της βάσης δεδομένων επιλέγοντας αυτή την επιλογή.</string>
|
<string name="filestoragehelp_gdriveKP2A">Αν δεν θέλετε να δώσετε πρόσβαση KP2A πρόσβαση σε όλο το Google Drive σας, μπορείτε να επιλέξετε αυτή την επιλογή. Σημειώστε ότι πρέπει να δημιουργήσετε ένα αρχείο βάσης δεδομένων πρώτα - τα υπάρχοντα αρχεία δεν είναι ορατά στην εφαρμογή. Επιλέξτε αυτή την επιλογή από την οθόνη Δημιουργία βάσης δεδομένων ή, αν έχετε ήδη ανοίξει μια βάση δεδομένων, με την εξαγωγή της βάσης δεδομένων επιλέγοντας αυτή την επιλογή.</string>
|
||||||
<string name="filestoragename_pcloud">PCloud (φάκελος KP2A)</string>
|
<string name="filestoragename_pcloud">PCloud (φάκελος KP2A)</string>
|
||||||
@@ -588,6 +597,7 @@
|
|||||||
<string name="CouldntLoadChalAuxFile_Hint">Χρησιμοποιήστε το πρόσθετο KeeChallenge σε KeePass 2.x (PC) για να ρυθμίσετε τη βάση δεδομένων για χρήση με Πρόκληση-Απόκριση!</string>
|
<string name="CouldntLoadChalAuxFile_Hint">Χρησιμοποιήστε το πρόσθετο KeeChallenge σε KeePass 2.x (PC) για να ρυθμίσετε τη βάση δεδομένων για χρήση με Πρόκληση-Απόκριση!</string>
|
||||||
<string name="ErrorUpdatingChalAuxFile">Σφάλμα ενημέρωσης βοηθητικού αρχείου OTP!</string>
|
<string name="ErrorUpdatingChalAuxFile">Σφάλμα ενημέρωσης βοηθητικού αρχείου OTP!</string>
|
||||||
<string name="TrayTotp_SeedField_title">Όνομα πεδίου για seed του TOTP</string>
|
<string name="TrayTotp_SeedField_title">Όνομα πεδίου για seed του TOTP</string>
|
||||||
|
<string name="TOTP">TOTP</string>
|
||||||
<string name="TrayTotp_SeedField_summary">Εάν χρησιμοποιείτε το πρόσθετο \"TrayTotp\" του Keepass 2 με μη προεπιλεγμένες ρυθμίσεις, πληκτρολογήστε το όνομα του πεδίου για το πεδίο seed σύμφωνα με τις ρυθμίσεις στον υπολογιστή.</string>
|
<string name="TrayTotp_SeedField_summary">Εάν χρησιμοποιείτε το πρόσθετο \"TrayTotp\" του Keepass 2 με μη προεπιλεγμένες ρυθμίσεις, πληκτρολογήστε το όνομα του πεδίου για το πεδίο seed σύμφωνα με τις ρυθμίσεις στον υπολογιστή.</string>
|
||||||
<string name="TrayTotp_SettingsField_title">Όνομα πεδίου στις ρυθμίσεις TOTP</string>
|
<string name="TrayTotp_SettingsField_title">Όνομα πεδίου στις ρυθμίσεις TOTP</string>
|
||||||
<string name="TrayTotp_SettingsField_summary">Εισάγετε όνομα πεδίου από ρυθμίσεις TrayTotp.</string>
|
<string name="TrayTotp_SettingsField_summary">Εισάγετε όνομα πεδίου από ρυθμίσεις TrayTotp.</string>
|
||||||
@@ -705,6 +715,33 @@
|
|||||||
<string name="EntryChannel_desc">Ειδοποίηση για απλοποιημένη πρόσβαση στην τρέχουσα καταχώριση.</string>
|
<string name="EntryChannel_desc">Ειδοποίηση για απλοποιημένη πρόσβαση στην τρέχουσα καταχώριση.</string>
|
||||||
<string name="CloseDbAfterFailedAttempts">Κλείσιμο της βάσης δεδομένων μετά από 3 ανεπιτυχείς προσπάθειες βιομετρικού ξεκλειδώματος.</string>
|
<string name="CloseDbAfterFailedAttempts">Κλείσιμο της βάσης δεδομένων μετά από 3 ανεπιτυχείς προσπάθειες βιομετρικού ξεκλειδώματος.</string>
|
||||||
<string name="WarnFingerprintInvalidated">Προσοχή! Ο βιομετρικός έλεγχος ταυτότητας μπορεί να ακυρωθεί από το Android, π.χ. μετά την προσθήκη ενός νέου δακτυλικού αποτυπώματος στις ρυθμίσεις της συσκευής σας. Βεβαιωθείτε ότι ξέρετε πάντα πώς να ξεκλειδώσετε με τον κύριο κωδικό πρόσβασης!</string>
|
<string name="WarnFingerprintInvalidated">Προσοχή! Ο βιομετρικός έλεγχος ταυτότητας μπορεί να ακυρωθεί από το Android, π.χ. μετά την προσθήκη ενός νέου δακτυλικού αποτυπώματος στις ρυθμίσεις της συσκευής σας. Βεβαιωθείτε ότι ξέρετε πάντα πώς να ξεκλειδώσετε με τον κύριο κωδικό πρόσβασης!</string>
|
||||||
|
<string-array name="ChangeLog_1_12">
|
||||||
|
<item>Αναβαθμίστηκε από Xamarin Android σε .ΝΕΤ 8</item>
|
||||||
|
<item>Αναβαθμίστηκε στοχεύοντας το SDK 34</item>
|
||||||
|
<item>Αναβαθμίστηκε σε διεπαφή χρήστη Material 3</item>
|
||||||
|
<item>Βελτιώστε την αυτόματη συμπλήρωση για να εργαστείτε με Compose Apps</item>
|
||||||
|
<item>Διόρθωση ονόματος host στην αυτόματη συμπλήρωση και αναζήτηση</item>
|
||||||
|
<item>Διόρθωση προβλήματος με τη γεννήτρια κωδικού πρόσβασης</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="ChangeLog_1_12_net">
|
||||||
|
<item>Αναβαθμίστηκε το OneDrive SDK στην έκδοση 5.68</item>
|
||||||
|
<item>Αναβαθμίστηκε το Dropbox SDK στην έκδοση 7.0.0</item>
|
||||||
|
<item>Αναβαθμισμένο Gradle, NewtonsoftJson, FluentFTP, MegaApiClient και okhttp</item>
|
||||||
|
<item>Επιδιόρθωση σφαλμάτων στην επιλογή αρχείου WebDav</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="ChangeLog_1_11">
|
||||||
|
<item>Προστέθηκαν τα αιωρούμενα κουμπιά ενεργειών για αναζήτηση και επισκόπηση TOTP (αν υπάρχουν καταχωρήσεις TOTP).</item>
|
||||||
|
<item>Βελτιωμένη εμφάνιση των πεδίων TOTP με την προσθήκη ενός δείκτη χρονικού ορίου και πιο εμφανή εμφάνιση.</item>
|
||||||
|
<item>Το TOTP εμφανίζεται από την προβολή ομάδας.</item>
|
||||||
|
<item>Αντιγραφή τιμής κειμένου στο πρόχειρο με παρατεταμένο πάτημα στην προβολή καταχωρίσεων.</item>
|
||||||
|
<item>Κάντε το TOTP πιο εύκολα προσβάσιμο στο ενσωματωμένο πληκτρολόγιο.</item>
|
||||||
|
<item>Εμφάνιση ειδοποίησης καταχώρησης κατά την αυτόματη συμπλήρωση μιας καταχώρισης TOTP. Επιτρέπει την αντιγραφή του TOTP στο πρόχειρο. Δείτε τις προτιμήσεις για τη ρύθμιση της συμπεριφοράς.</item>
|
||||||
|
<item>Ενημερώθηκε η υλοποίηση του TOTP για την επίλυση προβλημάτων συμβατότητας με το KeePass2 και το TrayTOTP</item>
|
||||||
|
<item>Μικρές βελτιώσεις</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="ChangeLog_1_11_net">
|
||||||
|
<item>Ενημέρωση pCloud SDK για να παρέχει πρόσβαση σε κοινόχρηστους φακέλους</item>
|
||||||
|
</string-array>
|
||||||
<string-array name="ChangeLog_1_10">
|
<string-array name="ChangeLog_1_10">
|
||||||
<item>Προσθήκη υποστήριξης για δικαιώματα ειδοποίησης στο Android 13+</item>
|
<item>Προσθήκη υποστήριξης για δικαιώματα ειδοποίησης στο Android 13+</item>
|
||||||
<item>Βελτίωση της υλοποίησης FTP και SFTP</item>
|
<item>Βελτίωση της υλοποίησης FTP και SFTP</item>
|
||||||
@@ -826,8 +863,8 @@
|
|||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="sftp_auth_modes">
|
<string-array name="sftp_auth_modes">
|
||||||
<item>Συνθηματικό</item>
|
<item>Συνθηματικό</item>
|
||||||
<item>KP2A Private/Public key</item>
|
<item>Ιδιωτικό / Δημόσιο κλειδί KP2A</item>
|
||||||
<item>Custom Private key</item>
|
<item>Προσαρμοσμένο ιδιωτικό κλειδί</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="AcceptAllServerCertificates_options">
|
<string-array name="AcceptAllServerCertificates_options">
|
||||||
<item>Παράβλεψη αποτυχιών επικύρωσης πιστοποιητικού</item>
|
<item>Παράβλεψη αποτυχιών επικύρωσης πιστοποιητικού</item>
|
||||||
@@ -845,6 +882,9 @@
|
|||||||
<string name="autofill_enable_for">Ενεργοποίηση AutoFill για %1$s</string>
|
<string name="autofill_enable_for">Ενεργοποίηση AutoFill για %1$s</string>
|
||||||
<string name="invalid_link_association">Δεν σχετίζεται το web domain %1$s με την εφαρμογή %2$s</string>
|
<string name="invalid_link_association">Δεν σχετίζεται το web domain %1$s με την εφαρμογή %2$s</string>
|
||||||
<string name="enable_fingerprint_hint">Το Keepass2Android ανίχνευσε βιομετρικό εξοπλισμό. Θέλετε να ενεργοποιήσετε βιομετρικό ξεκλείδωμα για αυτή τη βάση δεδομένων;</string>
|
<string name="enable_fingerprint_hint">Το Keepass2Android ανίχνευσε βιομετρικό εξοπλισμό. Θέλετε να ενεργοποιήσετε βιομετρικό ξεκλείδωμα για αυτή τη βάση δεδομένων;</string>
|
||||||
|
<string name="post_notifications_dialog_title">Να επιτρέπονται οι ειδοποιήσεις</string>
|
||||||
|
<string name="post_notifications_dialog_message">Το Keepass2Android μπορεί να εμφανίσει ειδοποιήσεις με κουμπιά για να αντιγράψετε τιμές, όπως κωδικούς πρόσβασης και TOTP στο πρόχειρο, ή για να εμφανιστεί το ενσωματωμένο πληκτρολόγιο. Αυτό είναι χρήσιμο για να μεταφέρετε τιμές σε άλλες εφαρμογές, χωρίς να μεταβείτε σε Keepass2Android επανειλημμένα. Θέλετε να ενεργοποιήσετε αυτές τις ειδοποιήσεις;</string>
|
||||||
|
<string name="post_notifications_dialog_allow">Να επιτρέπονται οι ειδοποιήσεις</string>
|
||||||
<string name="post_notifications_dialog_disable">Απενεργοποιήστε αυτό το χαρακτηριστικό</string>
|
<string name="post_notifications_dialog_disable">Απενεργοποιήστε αυτό το χαρακτηριστικό</string>
|
||||||
<string name="post_notifications_dialog_notnow">Όχι τώρα</string>
|
<string name="post_notifications_dialog_notnow">Όχι τώρα</string>
|
||||||
<string name="understand">Καταλαβαίνω</string>
|
<string name="understand">Καταλαβαίνω</string>
|
||||||
@@ -865,5 +905,8 @@
|
|||||||
<string name="AutofillWarning_Intro">Πρόκειται να εισάγετε διαπιστευτήρια για τον τομέα \"%1$s\" στην εφαρμογή \"%2$s\".</string>
|
<string name="AutofillWarning_Intro">Πρόκειται να εισάγετε διαπιστευτήρια για τον τομέα \"%1$s\" στην εφαρμογή \"%2$s\".</string>
|
||||||
<string name="AutofillWarning_FillDomainInUntrustedApp">Εάν εμπιστεύεστε ότι το \"%2$s\" ανήκει στο \"%1$s\" ή εμπιστεύεστε ότι η εφαρμογή \"%2$s\" δεν καταχράται τα διαπιστευτήρια (πχ. επειδή είναι μια αξιόπιστη εφαρμογή περιήγησης), είναι εντάξει να συνεχίσετε. Αν όχι, ακυρώστε.</string>
|
<string name="AutofillWarning_FillDomainInUntrustedApp">Εάν εμπιστεύεστε ότι το \"%2$s\" ανήκει στο \"%1$s\" ή εμπιστεύεστε ότι η εφαρμογή \"%2$s\" δεν καταχράται τα διαπιστευτήρια (πχ. επειδή είναι μια αξιόπιστη εφαρμογή περιήγησης), είναι εντάξει να συνεχίσετε. Αν όχι, ακυρώστε.</string>
|
||||||
<string name="AutofillWarning_trustAsBrowser">Αποδοχή πάντα στο \"%1$s\"</string>
|
<string name="AutofillWarning_trustAsBrowser">Αποδοχή πάντα στο \"%1$s\"</string>
|
||||||
|
<string name="kp2a_switch_on_sendgodone">Εναλλαγή μετά την ολοκλήρωση</string>
|
||||||
|
<string name="kp2a_switch_on_sendgodone_summary">Εναλλαγή πίσω όταν πατήσετε αποστολή / λήψη / ολοκλήρωση</string>
|
||||||
|
<string name="qr_scanning_error_no_google_play_services">Η σάρωση QR κώδικα απαιτεί Google Play Services. Παρακαλώ εγκαταστήστε ή ενημερώστε τις Google Play Services στη συσκευή σας.</string>
|
||||||
<string name="english_ime_settings">Ρυθμίσεις πληκτρολογίου Android</string>
|
<string name="english_ime_settings">Ρυθμίσεις πληκτρολογίου Android</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -501,6 +501,7 @@
|
|||||||
<string name="hint_sftp_host">host (ex: 192.168.0.1)</string>
|
<string name="hint_sftp_host">host (ex: 192.168.0.1)</string>
|
||||||
<string name="hint_sftp_port">port</string>
|
<string name="hint_sftp_port">port</string>
|
||||||
<string name="initial_directory">Folder inițial (opțional):</string>
|
<string name="initial_directory">Folder inițial (opțional):</string>
|
||||||
|
<string name="connect_timeout">Secunde expirare conexiune (opțional)</string>
|
||||||
<string name="enter_sftp_login_title">Introduceţi datele de conectare SFTP:</string>
|
<string name="enter_sftp_login_title">Introduceţi datele de conectare SFTP:</string>
|
||||||
<string name="sftp_auth_mode">Mod autentificare</string>
|
<string name="sftp_auth_mode">Mod autentificare</string>
|
||||||
<string name="send_public_key">Trimite cheia publică...</string>
|
<string name="send_public_key">Trimite cheia publică...</string>
|
||||||
@@ -531,6 +532,7 @@
|
|||||||
<string name="filestoragename_dropboxKP2A">Dropbox (folder KP2A)</string>
|
<string name="filestoragename_dropboxKP2A">Dropbox (folder KP2A)</string>
|
||||||
<string name="filestoragehelp_dropboxKP2A">Dacă nu doriți să dați KP2A acces la Dropbox-ul complet, puteți selecta această opțiune. Va solicita acces doar la directorul Apps/Keepass2Android. Acest lucru este potrivit în special atunci când se creează o bază de date nouă. Dacă aveţi deja o bază de date, selectaţi această opţiune pentru a crea directorul, apoi plasați fișierul în director (de la PC) și apoi selectați din nou această opțiune pentru deschiderea fișierului.</string>
|
<string name="filestoragehelp_dropboxKP2A">Dacă nu doriți să dați KP2A acces la Dropbox-ul complet, puteți selecta această opțiune. Va solicita acces doar la directorul Apps/Keepass2Android. Acest lucru este potrivit în special atunci când se creează o bază de date nouă. Dacă aveţi deja o bază de date, selectaţi această opţiune pentru a crea directorul, apoi plasați fișierul în director (de la PC) și apoi selectați din nou această opțiune pentru deschiderea fișierului.</string>
|
||||||
<string name="filestoragename_gdrive">Google Drive</string>
|
<string name="filestoragename_gdrive">Google Drive</string>
|
||||||
|
<string name="filestoragename_gdriveKP2A">Google Drive (fișiere KP2A)</string>
|
||||||
<string name="filestoragename_pcloudall">PCloud (Acces complet)</string>
|
<string name="filestoragename_pcloudall">PCloud (Acces complet)</string>
|
||||||
<string name="filestoragename_onedrive">OneDrive</string>
|
<string name="filestoragename_onedrive">OneDrive</string>
|
||||||
<string name="filestoragename_onedrive2">OneDrive</string>
|
<string name="filestoragename_onedrive2">OneDrive</string>
|
||||||
@@ -606,7 +608,9 @@
|
|||||||
<string name="CopyFileRequired">Pentru a-l folosi, trebuie copiat într-o altă locație.</string>
|
<string name="CopyFileRequired">Pentru a-l folosi, trebuie copiat într-o altă locație.</string>
|
||||||
<string name="CopyFileRequiredForEditing">Pentru a-l edita, trebuie copiat fișierul într-o altă locație.</string>
|
<string name="CopyFileRequiredForEditing">Pentru a-l edita, trebuie copiat fișierul într-o altă locație.</string>
|
||||||
<string name="FileReadOnlyTitle">Baza de date permite doar citirea</string>
|
<string name="FileReadOnlyTitle">Baza de date permite doar citirea</string>
|
||||||
|
<string name="FileReadOnlyMessagePre">Keepass2Android a deschis baza de date curentă în modul doar pentru citire.</string>
|
||||||
<string name="ReadOnlyReason_PreKitKat">Se pare ca ai deschis fişierul dintr-o aplicaţie externă. În acest fel nu se acceptă modificări. Dacă doriţi să faceţi modificări în baza de date, închideţi baza de date şi selectaţi Schimbare bază de date. Apoi deschide fisierul folosind una dintre opţiunile disponibile.</string>
|
<string name="ReadOnlyReason_PreKitKat">Se pare ca ai deschis fişierul dintr-o aplicaţie externă. În acest fel nu se acceptă modificări. Dacă doriţi să faceţi modificări în baza de date, închideţi baza de date şi selectaţi Schimbare bază de date. Apoi deschide fisierul folosind una dintre opţiunile disponibile.</string>
|
||||||
|
<string name="ReadOnlyReason_ReadOnlyFlag">Steagul doar-în-citire este setat. Elimină-l dacă dorești să faci modificări în baza de date.</string>
|
||||||
<string name="ReadOnlyReason_ReadOnlyKitKat">Modificarea nu este posibilă din cauza restricţiilor introduse în Android KitKat. Dacă doriţi să faceţi modificări în baza de date, închideți baza de date şi selectaţi Schimbare bază de date. Apoi deschide fisierul folosind selectorul de fisiere al sistemului.</string>
|
<string name="ReadOnlyReason_ReadOnlyKitKat">Modificarea nu este posibilă din cauza restricţiilor introduse în Android KitKat. Dacă doriţi să faceţi modificări în baza de date, închideți baza de date şi selectaţi Schimbare bază de date. Apoi deschide fisierul folosind selectorul de fisiere al sistemului.</string>
|
||||||
<string name="AddCustomIcon">Adaugă pictogramă din file...</string>
|
<string name="AddCustomIcon">Adaugă pictogramă din file...</string>
|
||||||
<string name="CopyingFile">Se copiază fișierul...</string>
|
<string name="CopyingFile">Se copiază fișierul...</string>
|
||||||
@@ -630,6 +634,7 @@
|
|||||||
<string name="TemplateTitle_Membership">Apartenenta</string>
|
<string name="TemplateTitle_Membership">Apartenenta</string>
|
||||||
<string name="ChangeLog_title">Jurnal modificări</string>
|
<string name="ChangeLog_title">Jurnal modificări</string>
|
||||||
<string name="AskAddTemplatesTitle">Adauga template-uri?</string>
|
<string name="AskAddTemplatesTitle">Adauga template-uri?</string>
|
||||||
|
<string name="AskAddTemplatesMessage">Keepass2Android conține șabloane de intrare pentru conturi de e-mail, parole wireless-LAN, note sigure și multe altele. Dorești să le adaugi în baza de date? Dacă alegi Nu, le poți adăuga mai târziu în setările bazei de date.</string>
|
||||||
<string name="AddTemplates_pref">Adauga template-uri in baza de date</string>
|
<string name="AddTemplates_pref">Adauga template-uri in baza de date</string>
|
||||||
<string name="Continue">Continuare</string>
|
<string name="Continue">Continuare</string>
|
||||||
<string name="NoFilenameWarning">URI introdus nu arata ca un nume de file. Sunteţi sigur că acesta este un file valid?</string>
|
<string name="NoFilenameWarning">URI introdus nu arata ca un nume de file. Sunteţi sigur că acesta este un file valid?</string>
|
||||||
@@ -646,6 +651,9 @@
|
|||||||
<string name="child_db_Enabled_title">Deschide automat</string>
|
<string name="child_db_Enabled_title">Deschide automat</string>
|
||||||
<string name="database_file_heading">Fișier bază de date</string>
|
<string name="database_file_heading">Fișier bază de date</string>
|
||||||
<string name="if_device_text">Activează pentru %1$s</string>
|
<string name="if_device_text">Activează pentru %1$s</string>
|
||||||
|
<string name="restore_history">Restaurează această versiune</string>
|
||||||
|
<string name="remove_history">Elimină această versiune</string>
|
||||||
|
<string name="DbUnlockedChannel_name">Bază de date deblocată</string>
|
||||||
<string name="DbUnlockedChannel_desc">Notificare despre deblocarea bazei de date</string>
|
<string name="DbUnlockedChannel_desc">Notificare despre deblocarea bazei de date</string>
|
||||||
<string name="DbQuicklockedChannel_name">QuickUnlock</string>
|
<string name="DbQuicklockedChannel_name">QuickUnlock</string>
|
||||||
<string name="DbQuicklockedChannel_desc">Notificare despre blocarea bazei de date cu QuickUnlock</string>
|
<string name="DbQuicklockedChannel_desc">Notificare despre blocarea bazei de date cu QuickUnlock</string>
|
||||||
@@ -653,6 +661,14 @@
|
|||||||
<string name="EntryChannel_desc">Notificare pentru simplificarea accesului la intrarea selectată în prezent.</string>
|
<string name="EntryChannel_desc">Notificare pentru simplificarea accesului la intrarea selectată în prezent.</string>
|
||||||
<string name="CloseDbAfterFailedAttempts">Închide baza de date după trei încercări de deblocare biometrică eșuate.</string>
|
<string name="CloseDbAfterFailedAttempts">Închide baza de date după trei încercări de deblocare biometrică eșuate.</string>
|
||||||
<string name="WarnFingerprintInvalidated">Avertizare! Autentificarea biometrică poate fi invalidată de Android, de ex. după adăugarea unei amprente noi în setările dispozitivului. Asigură-te că știi întotdeauna cum să deblochezi cu parola principală!</string>
|
<string name="WarnFingerprintInvalidated">Avertizare! Autentificarea biometrică poate fi invalidată de Android, de ex. după adăugarea unei amprente noi în setările dispozitivului. Asigură-te că știi întotdeauna cum să deblochezi cu parola principală!</string>
|
||||||
|
<string-array name="ChangeLog_1_12">
|
||||||
|
<item>Upgraded from Xamarin Android to .net 8</item>
|
||||||
|
<item>Upgraded to Target SDK 34</item>
|
||||||
|
<item>Upgraded to Material 3 user interface</item>
|
||||||
|
<item>Improve autofill to work with Compose apps</item>
|
||||||
|
<item>Fix hostname matching in autofill and search</item>
|
||||||
|
<item>S-a reparat problema cu generatorul de parole</item>
|
||||||
|
</string-array>
|
||||||
<string-array name="ChangeLog_1_11">
|
<string-array name="ChangeLog_1_11">
|
||||||
<item>Added floating action buttons for search and TOTP overview (if TOTP entries are present).</item>
|
<item>Added floating action buttons for search and TOTP overview (if TOTP entries are present).</item>
|
||||||
<item>Improved display of TOTP fields by adding a timeout indicator and showing it more prominently.</item>
|
<item>Improved display of TOTP fields by adding a timeout indicator and showing it more prominently.</item>
|
||||||
|
@@ -137,6 +137,7 @@
|
|||||||
<string name="NoDonationReminder_key">NoDonationReminder</string>
|
<string name="NoDonationReminder_key">NoDonationReminder</string>
|
||||||
|
|
||||||
<string name="UseOfflineCache_key">UseOfflineCache</string>
|
<string name="UseOfflineCache_key">UseOfflineCache</string>
|
||||||
|
<string name="SyncOfflineCacheInBackground_key">SyncOfflineCacheInBackground_key</string>
|
||||||
<string name="CreateBackups_key">CreateBackups_key</string>
|
<string name="CreateBackups_key">CreateBackups_key</string>
|
||||||
<string name="AcceptAllServerCertificates_key">AcceptAllServerCertificates</string>
|
<string name="AcceptAllServerCertificates_key">AcceptAllServerCertificates</string>
|
||||||
<string name="CheckForFileChangesOnSave_key">CheckForFileChangesOnSave</string>
|
<string name="CheckForFileChangesOnSave_key">CheckForFileChangesOnSave</string>
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
<item>System</item>
|
<item>System</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string name="AcceptAllServerCertificates_default">WARN</string>
|
<string name="AcceptAllServerCertificates_default">ERROR</string>
|
||||||
<string-array name="AcceptAllServerCertificates_values">
|
<string-array name="AcceptAllServerCertificates_values">
|
||||||
<item>IGNORE</item>
|
<item>IGNORE</item>
|
||||||
<item>WARN</item>
|
<item>WARN</item>
|
||||||
|
@@ -382,6 +382,8 @@
|
|||||||
<string name="NoDonationReminder_summary">I won\'t give you a dime or I have already donated. Don\'t ask for a donation, not even at the author\'s birthday.</string>
|
<string name="NoDonationReminder_summary">I won\'t give you a dime or I have already donated. Don\'t ask for a donation, not even at the author\'s birthday.</string>
|
||||||
<string name="UseOfflineCache_title">Database caching</string>
|
<string name="UseOfflineCache_title">Database caching</string>
|
||||||
<string name="UseOfflineCache_summary">Keep a copy of the database files in the app\'s cache directory. This allows to use databases even while the database file is not accessible.</string>
|
<string name="UseOfflineCache_summary">Keep a copy of the database files in the app\'s cache directory. This allows to use databases even while the database file is not accessible.</string>
|
||||||
|
<string name="SyncOfflineCacheInBackground_summary">When saving or loading, use the internal cache. Then synchronize with remote storage in a background process.</string>
|
||||||
|
<string name="SyncOfflineCacheInBackground_title">Synchronize in background</string>
|
||||||
<string name="CreateBackups_title">Local backups</string>
|
<string name="CreateBackups_title">Local backups</string>
|
||||||
<string name="CreateBackups_summary">Create a local backup copy after successfully loading a database.</string>
|
<string name="CreateBackups_summary">Create a local backup copy after successfully loading a database.</string>
|
||||||
<string name="UpdatingBackup">Updating local backup...</string>
|
<string name="UpdatingBackup">Updating local backup...</string>
|
||||||
@@ -724,6 +726,9 @@
|
|||||||
<string name="DbQuicklockedChannel_desc">Notification about the database being locked with QuickUnlock</string>
|
<string name="DbQuicklockedChannel_desc">Notification about the database being locked with QuickUnlock</string>
|
||||||
<string name="EntryChannel_name">Entry notifications</string>
|
<string name="EntryChannel_name">Entry notifications</string>
|
||||||
<string name="EntryChannel_desc">Notification to simplify access to the currently selected entry.</string>
|
<string name="EntryChannel_desc">Notification to simplify access to the currently selected entry.</string>
|
||||||
|
<string name="BackgroundSyncChannel_name">Synchronization operation</string>
|
||||||
|
<string name="BackgroundSyncChannel_desc">Notification to indicate that a synchronization is performed in the background.</string>
|
||||||
|
|
||||||
<string name="CloseDbAfterFailedAttempts">Close database after three failed biometric unlock attempts.</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="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>
|
||||||
|
|
||||||
@@ -1249,5 +1254,10 @@
|
|||||||
<string name="kp2a_switch_on_sendgodone_summary">Switch back when pressing send/go/done</string>
|
<string name="kp2a_switch_on_sendgodone_summary">Switch back when pressing send/go/done</string>
|
||||||
<string name="qr_scanning_error_no_google_play_services">QR code scanning requires Google Play Services. Please install or update Google Play Services on your device.</string>
|
<string name="qr_scanning_error_no_google_play_services">QR code scanning requires Google Play Services. Please install or update Google Play Services on your device.</string>
|
||||||
<string name="english_ime_settings">Android keyboard settings</string>
|
<string name="english_ime_settings">Android keyboard settings</string>
|
||||||
|
<string name="autoswitch_enabled_but_not_setup">Note: You have enabled App - Settings - Password access - Keyboard switching - Auto-switch keyboard, but it doesn\'t seem to be configured correctly.</string>
|
||||||
</resources>
|
<string name="switch_keyboard_for_totp_enabled">Note: You have enabled App - Password access - Autofill-Service - Autofill for TOTP entries. This can cause this window to show when you open an entry with a TOTP.</string>
|
||||||
|
<string name="switch_keyboard_inside_kp2a_enabled">Note: You have enabled App - Security - Use built-in keyboard inside Keepass2Android. This can cause this window to show when you open the app or edit an entry.</string>
|
||||||
|
<string name="switch_keyboard_on_search_enabled">Note: You have enabled App - Security - Password access - Keyboard switching - Switch keyboard. This can cause this window to show when you search for an entry from the browser.</string>
|
||||||
|
<string name="user_interaction_required">User interaction required. Please open the app.</string>
|
||||||
|
<string name="failed_to_access_database">Database is currently in use and cannot be accessed.</string>
|
||||||
|
</resources>
|
@@ -12,7 +12,17 @@
|
|||||||
android:title="@string/UseOfflineCache_title"
|
android:title="@string/UseOfflineCache_title"
|
||||||
android:key="@string/UseOfflineCache_key" />
|
android:key="@string/UseOfflineCache_key" />
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:enabled="true"
|
||||||
|
android:persistent="true"
|
||||||
|
android:summary="@string/SyncOfflineCacheInBackground_summary"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:title="@string/SyncOfflineCacheInBackground_title"
|
||||||
|
android:key="@string/SyncOfflineCacheInBackground_key" />
|
||||||
|
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:persistent="true"
|
android:persistent="true"
|
||||||
android:summary="@string/CreateBackups_summary"
|
android:summary="@string/CreateBackups_summary"
|
||||||
|
@@ -558,6 +558,7 @@ namespace keepass2android
|
|||||||
private OpenDatabaseAdapter _adapter;
|
private OpenDatabaseAdapter _adapter;
|
||||||
private MyBroadcastReceiver _intentReceiver;
|
private MyBroadcastReceiver _intentReceiver;
|
||||||
private bool _isForeground;
|
private bool _isForeground;
|
||||||
|
private readonly List<IOConnectionInfo> _pendingBackgroundSyncs = new List<IOConnectionInfo>();
|
||||||
|
|
||||||
public override void OnBackPressed()
|
public override void OnBackPressed()
|
||||||
{
|
{
|
||||||
@@ -598,11 +599,36 @@ namespace keepass2android
|
|||||||
|
|
||||||
string iocString = data?.GetStringExtra("ioc");
|
string iocString = data?.GetStringExtra("ioc");
|
||||||
IOConnectionInfo ioc = IOConnectionInfo.UnserializeFromString(iocString);
|
IOConnectionInfo ioc = IOConnectionInfo.UnserializeFromString(iocString);
|
||||||
|
|
||||||
|
//we first store the required sync operation and delay its execution until we loaded all AutoOpen entries (from local file)
|
||||||
|
//if required
|
||||||
|
bool requiresSubsequentSync = data?.GetBooleanExtra("requiresSubsequentSync", false) ?? false;
|
||||||
|
if (requiresSubsequentSync)
|
||||||
|
{
|
||||||
|
_pendingBackgroundSyncs.Add(ioc);
|
||||||
|
}
|
||||||
|
|
||||||
if (App.Kp2a.TrySelectCurrentDb(ioc))
|
if (App.Kp2a.TrySelectCurrentDb(ioc))
|
||||||
{
|
{
|
||||||
if (OpenAutoExecEntries(App.Kp2a.CurrentDb)) return;
|
if (OpenAutoExecEntries(App.Kp2a.CurrentDb)) return;
|
||||||
LaunchingOther = true;
|
LaunchingOther = true;
|
||||||
AppTask.CanActivateSearchViewOnStart = true;
|
AppTask.CanActivateSearchViewOnStart = true;
|
||||||
|
|
||||||
|
foreach (var pendingSyncIoc in _pendingBackgroundSyncs)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new SyncUtil(this).StartSynchronizeDatabase(pendingSyncIoc);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
App.Kp2a.ShowMessage(this, "Failed to synchronize database", MessageSeverity.Error);
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingBackgroundSyncs.Clear();
|
||||||
|
|
||||||
AppTask.LaunchFirstGroupActivity(this);
|
AppTask.LaunchFirstGroupActivity(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -72,8 +72,8 @@ namespace keepass2android
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetPassword sp = new SetPassword(_activity, App.Kp2a, pass, keyfile, new AfterSave(_activity, this, null, new Handler()));
|
SetPassword sp = new SetPassword(App.Kp2a, pass, keyfile, new AfterSave(_activity, this, null, new Handler()));
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, _activity, sp);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, sp);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,21 +88,21 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AfterSave : OnFinish {
|
class AfterSave : OnOperationFinishedHandler {
|
||||||
private readonly FileOnFinish _finish;
|
private readonly FileOnFinish _operationFinishedHandler;
|
||||||
|
|
||||||
readonly SetPasswordDialog _dlg;
|
readonly SetPasswordDialog _dlg;
|
||||||
|
|
||||||
public AfterSave(Activity activity, SetPasswordDialog dlg, FileOnFinish finish, Handler handler): base(activity, finish, handler) {
|
public AfterSave(Activity activity, SetPasswordDialog dlg, FileOnFinish operationFinishedHandler, Handler handler): base(App.Kp2a, operationFinishedHandler, handler) {
|
||||||
_finish = finish;
|
_operationFinishedHandler = operationFinishedHandler;
|
||||||
_dlg = dlg;
|
_dlg = dlg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void Run() {
|
public override void Run() {
|
||||||
if ( Success ) {
|
if ( Success ) {
|
||||||
if ( _finish != null ) {
|
if ( _operationFinishedHandler != null ) {
|
||||||
_finish.Filename = _dlg.Keyfile;
|
_operationFinishedHandler.Filename = _dlg.Keyfile;
|
||||||
}
|
}
|
||||||
FingerprintUnlockMode um;
|
FingerprintUnlockMode um;
|
||||||
Enum.TryParse(PreferenceManager.GetDefaultSharedPreferences(_dlg.Context).GetString(App.Kp2a.CurrentDb.CurrentFingerprintModePrefKey, ""), out um);
|
Enum.TryParse(PreferenceManager.GetDefaultSharedPreferences(_dlg.Context).GetString(App.Kp2a.CurrentDb.CurrentFingerprintModePrefKey, ""), out um);
|
||||||
|
@@ -78,7 +78,15 @@ namespace keepass2android
|
|||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
base.OnCreate(savedInstanceState);
|
//we don't want any background thread to update/reload the database while we're in this activity.
|
||||||
|
if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||||
|
{
|
||||||
|
App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error);
|
||||||
|
Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnCreate(savedInstanceState);
|
||||||
|
|
||||||
//if user presses back to leave this activity:
|
//if user presses back to leave this activity:
|
||||||
SetResult(Result.Canceled);
|
SetResult(Result.Canceled);
|
||||||
@@ -121,7 +129,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
} catch (Exception e)
|
} catch (Exception e)
|
||||||
{
|
{
|
||||||
App.Kp2a.ShowMessage(this, e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
SetResult(Result.Canceled);
|
SetResult(Result.Canceled);
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
@@ -288,5 +296,12 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
get { return null; }
|
get { return null; }
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
using Android.Preferences;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Util;
|
using Android.Util;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
@@ -15,8 +16,8 @@ using keepass2android;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
[Activity(Label = AppNames.AppName, TaskAffinity = "", NoHistory = true)]
|
[Activity(Label = AppNames.AppName, Theme = "@style/Kp2aTheme_BlueActionBar")]
|
||||||
public class SwitchImeActivity : Activity
|
public class SwitchImeActivity : AndroidX.AppCompat.App.AppCompatActivity
|
||||||
{
|
{
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
@@ -24,7 +25,26 @@ namespace keepass2android
|
|||||||
SetContentView(Resource.Layout.switch_ime_activity_layout);
|
SetContentView(Resource.Layout.switch_ime_activity_layout);
|
||||||
FindViewById<Button>(Resource.Id.btn_reopen).Click += (sender, args) => TrySwitchKeyboard();
|
FindViewById<Button>(Resource.Id.btn_reopen).Click += (sender, args) => TrySwitchKeyboard();
|
||||||
FindViewById<Button>(Resource.Id.btn_cancel).Click += (sender, args) => Finish();
|
FindViewById<Button>(Resource.Id.btn_cancel).Click += (sender, args) => Finish();
|
||||||
|
FindViewById<Button>(Resource.Id.btn_open_settings).Click += (sender, args) => AppSettingsActivity.Launch(this);
|
||||||
|
|
||||||
|
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
||||||
|
|
||||||
|
bool useKp2aKeyboardInKp2a = prefs.GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false);
|
||||||
|
bool kp2a_switch_rooted = prefs.GetBoolean("kp2a_switch_rooted", false);
|
||||||
|
bool AutoFillTotp_prefs_ActivateKeyboard = prefs.GetBoolean("AutoFillTotp_prefs_ActivateKeyboard_key", false);
|
||||||
|
bool OpenKp2aKeyboardAutomatically = prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default));
|
||||||
|
|
||||||
|
FindViewById(Resource.Id.note_UseKp2aKeyboardInKp2a).Visibility = useKp2aKeyboardInKp2a ? ViewStates.Visible : ViewStates.Gone;
|
||||||
|
FindViewById(Resource.Id.note_kp2a_switch_rooted).Visibility = kp2a_switch_rooted ? ViewStates.Visible : ViewStates.Gone;
|
||||||
|
FindViewById(Resource.Id.note_AutoFillTotp_prefs_ActivateKeyboard).Visibility = AutoFillTotp_prefs_ActivateKeyboard ? ViewStates.Visible : ViewStates.Gone;
|
||||||
|
FindViewById(Resource.Id.note_OpenKp2aKeyboardAutomatically).Visibility = OpenKp2aKeyboardAutomatically ? ViewStates.Visible : ViewStates.Gone;
|
||||||
|
|
||||||
|
bool hasNote = useKp2aKeyboardInKp2a || kp2a_switch_rooted || AutoFillTotp_prefs_ActivateKeyboard || OpenKp2aKeyboardAutomatically;
|
||||||
|
((LinearLayout)FindViewById(Resource.Id.settings_notes_container)).Visibility = hasNote ? ViewStates.Visible : ViewStates.Gone;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private string Kp2aInputMethodName
|
private string Kp2aInputMethodName
|
||||||
{
|
{
|
||||||
get { return PackageName + "/keepass2android.softkeyboard.KP2AKeyboard"; }
|
get { return PackageName + "/keepass2android.softkeyboard.KP2AKeyboard"; }
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.OS;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
@@ -8,19 +9,19 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
public class SyncUtil
|
public class SyncUtil
|
||||||
{
|
{
|
||||||
private Activity _activity;
|
private LifecycleAwareActivity _activity;
|
||||||
|
|
||||||
public SyncUtil(Activity activity)
|
public SyncUtil(LifecycleAwareActivity activity)
|
||||||
{
|
{
|
||||||
_activity = activity;
|
_activity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SyncOtpAuxFile : RunnableOnFinish
|
public class SyncOtpAuxFile : OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly IOConnectionInfo _ioc;
|
private readonly IOConnectionInfo _ioc;
|
||||||
|
|
||||||
public SyncOtpAuxFile(Activity activity, IOConnectionInfo ioc)
|
public SyncOtpAuxFile(Activity activity, IOConnectionInfo ioc)
|
||||||
: base(activity, null)
|
: base(App.Kp2a, null)
|
||||||
{
|
{
|
||||||
_ioc = ioc;
|
_ioc = ioc;
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,7 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
||||||
Finish(false, e.Message);
|
Finish(false, Util.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -49,50 +50,45 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void SynchronizeDatabase(Action runAfterSuccess)
|
public void StartSynchronizeDatabase(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.CurrentDb.Ioc);
|
var filestorage = App.Kp2a.GetFileStorage(ioc);
|
||||||
RunnableOnFinish task;
|
var databaseForIoc = App.Kp2a.GetDatabase(ioc);
|
||||||
OnFinish onFinish = new ActionOnFinish(_activity, (success, message, activity) =>
|
|
||||||
|
OperationWithFinishHandler task;
|
||||||
|
OnOperationFinishedHandler onOperationFinishedHandler = new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrEmpty(message))
|
App.Kp2a.UiThreadHandler.Post(() =>
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
|
||||||
|
|
||||||
// Tell the adapter to refresh it's list
|
|
||||||
BaseAdapter adapter = (activity as GroupBaseActivity)?.ListAdapter;
|
|
||||||
adapter?.NotifyDataSetChanged();
|
|
||||||
|
|
||||||
if (App.Kp2a.CurrentDb?.OtpAuxFileIoc != null)
|
|
||||||
{
|
{
|
||||||
var task2 = new SyncOtpAuxFile(_activity, App.Kp2a.CurrentDb.OtpAuxFileIoc);
|
if (!String.IsNullOrEmpty(message))
|
||||||
task2.OnFinishToRun = new ActionOnFinish(_activity, (b, s, activeActivity) =>
|
App.Kp2a.ShowMessage(context, message, success ? MessageSeverity.Info : MessageSeverity.Error);
|
||||||
{
|
|
||||||
runAfterSuccess();
|
// Tell the adapter to refresh it's list
|
||||||
});
|
BaseAdapter adapter = (context as GroupBaseActivity)?.ListAdapter;
|
||||||
new ProgressTask(App.Kp2a, activity, task2).Run(true);
|
|
||||||
}
|
adapter?.NotifyDataSetChanged();
|
||||||
else
|
});
|
||||||
|
|
||||||
|
if (databaseForIoc?.OtpAuxFileIoc != null)
|
||||||
{
|
{
|
||||||
runAfterSuccess();
|
var task2 = new SyncOtpAuxFile(_activity, databaseForIoc.OtpAuxFileIoc);
|
||||||
|
|
||||||
|
OperationRunner.Instance.Run(App.Kp2a, task2);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (filestorage is CachingFileStorage)
|
if (filestorage is CachingFileStorage)
|
||||||
{
|
{
|
||||||
|
|
||||||
task = new SynchronizeCachedDatabase(_activity, App.Kp2a, onFinish);
|
task = new SynchronizeCachedDatabase(App.Kp2a, databaseForIoc, onOperationFinishedHandler, new BackgroundDatabaseModificationLocker(App.Kp2a));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
task = new CheckDatabaseForChanges( App.Kp2a, onOperationFinishedHandler);
|
||||||
task = new CheckDatabaseForChanges(_activity, App.Kp2a, onFinish);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OperationRunner.Instance.Run(App.Kp2a, task);
|
||||||
|
|
||||||
|
|
||||||
var progressTask = new ProgressTask(App.Kp2a, _activity, task);
|
|
||||||
progressTask.Run();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,10 @@ namespace keepass2android.Utils
|
|||||||
{
|
{
|
||||||
public void ShowMessage(Message message)
|
public void ShowMessage(Message message)
|
||||||
{
|
{
|
||||||
Toast.MakeText(App.Context, message.Text, ToastLength.Long).Show();
|
new Handler(Looper.MainLooper).Post(() =>
|
||||||
|
{
|
||||||
|
Toast.MakeText(App.Context, message.Text, ToastLength.Long).Show();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Message> PendingMessages => new();
|
public List<Message> PendingMessages => new();
|
||||||
|
@@ -37,10 +37,12 @@ using Android.Util;
|
|||||||
using Android.Views.InputMethods;
|
using Android.Views.InputMethods;
|
||||||
using AndroidX.Core.View.InputMethod;
|
using AndroidX.Core.View.InputMethod;
|
||||||
using Google.Android.Material.Dialog;
|
using Google.Android.Material.Dialog;
|
||||||
|
using KeePass.Util;
|
||||||
using keepass2android;
|
using keepass2android;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
using KeePassLib.Security;
|
using KeePassLib.Security;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
|
using Org.Apache.Http.Util;
|
||||||
using Uri = Android.Net.Uri;
|
using Uri = Android.Net.Uri;
|
||||||
|
|
||||||
|
|
||||||
@@ -198,6 +200,12 @@ namespace keepass2android
|
|||||||
ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(prefix + KeyServercredmode, (int)IOCredSaveMode.NoSave);
|
ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(prefix + KeyServercredmode, (int)IOCredSaveMode.NoSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static string GetErrorMessage(Exception e)
|
||||||
|
{
|
||||||
|
return ExceptionUtil.GetErrorMessage(e);
|
||||||
|
}
|
||||||
|
|
||||||
public static Bitmap DrawableToBitmap(Drawable drawable)
|
public static Bitmap DrawableToBitmap(Drawable drawable)
|
||||||
{
|
{
|
||||||
Bitmap bitmap = null;
|
Bitmap bitmap = null;
|
||||||
@@ -865,8 +873,6 @@ namespace keepass2android
|
|||||||
|
|
||||||
entry.Strings.Set(prefix + c, new ProtectedString(false, url));
|
entry.Strings.Set(prefix + c, new ProtectedString(false, url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
@@ -46,6 +47,7 @@ using keepass2android;
|
|||||||
using keepass2android.Utils;
|
using keepass2android.Utils;
|
||||||
using KeePassLib.Interfaces;
|
using KeePassLib.Interfaces;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
|
using AlertDialog = AndroidX.AppCompat.App.AlertDialog;
|
||||||
using Message = keepass2android.Utils.Message;
|
using Message = keepass2android.Utils.Message;
|
||||||
#if !NoNet
|
#if !NoNet
|
||||||
#if !EXCLUDE_JAVAFILESTORAGE
|
#if !EXCLUDE_JAVAFILESTORAGE
|
||||||
@@ -56,6 +58,9 @@ using GoogleDriveAppDataFileStorage = keepass2android.Io.GoogleDriveAppDataFileS
|
|||||||
using PCloudFileStorage = keepass2android.Io.PCloudFileStorage;
|
using PCloudFileStorage = keepass2android.Io.PCloudFileStorage;
|
||||||
using static keepass2android.Util;
|
using static keepass2android.Util;
|
||||||
using static Android.Provider.Telephony.MmsSms;
|
using static Android.Provider.Telephony.MmsSms;
|
||||||
|
using AndroidX.Lifecycle;
|
||||||
|
using Java.Interop;
|
||||||
|
using keepass2android.services;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -111,15 +116,12 @@ namespace keepass2android
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Main implementation of the IKp2aApp interface for usage in the real app.
|
/// Main implementation of the IKp2aApp interface for usage in the real app.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Kp2aApp: IKp2aApp, ICacheSupervisor
|
public class Kp2aApp: IKp2aApp, ICacheSupervisor
|
||||||
{
|
{
|
||||||
|
public void Lock(bool allowQuickUnlock = true, bool lockWasTriggeredByTimeout = false)
|
||||||
|
|
||||||
public void Lock(bool allowQuickUnlock = true, bool lockWasTriggeredByTimeout = false)
|
|
||||||
{
|
{
|
||||||
if (OpenDatabases.Any())
|
if (OpenDatabases.Any())
|
||||||
{
|
{
|
||||||
@@ -147,10 +149,13 @@ namespace keepass2android
|
|||||||
|
|
||||||
// Couldn't quick-lock, so unload database(s) instead
|
// Couldn't quick-lock, so unload database(s) instead
|
||||||
_openAttempts.Clear();
|
_openAttempts.Clear();
|
||||||
_openDatabases.Clear();
|
_openDatabases.Clear();
|
||||||
_currentDatabase = null;
|
|
||||||
LastOpenedEntry = null;
|
_currentDatabase = null;
|
||||||
QuickLocked = false;
|
LastOpenedEntry = null;
|
||||||
|
QuickLocked = false;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -186,42 +191,73 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent)
|
public Database LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream,
|
||||||
|
CompositeKey compositeKey, IKp2aStatusLogger statusLogger, IDatabaseFormat databaseFormat, bool makeCurrent,
|
||||||
|
IDatabaseModificationWatcher modificationWatcher)
|
||||||
{
|
{
|
||||||
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
|
||||||
var createBackup = prefs.GetBoolean(LocaleManager.LocalizedAppContext.GetString(Resource.String.CreateBackups_key), true)
|
|
||||||
|
|
||||||
|
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
||||||
|
var createBackup =
|
||||||
|
prefs.GetBoolean(LocaleManager.LocalizedAppContext.GetString(Resource.String.CreateBackups_key),
|
||||||
|
true)
|
||||||
&& !(new LocalFileStorage(this).IsLocalBackup(ioConnectionInfo));
|
&& !(new LocalFileStorage(this).IsLocalBackup(ioConnectionInfo));
|
||||||
|
|
||||||
MemoryStream backupCopy = new MemoryStream();
|
MemoryStream backupCopy = new MemoryStream();
|
||||||
if (createBackup)
|
if (createBackup)
|
||||||
{
|
{
|
||||||
|
|
||||||
memoryStream.CopyTo(backupCopy);
|
memoryStream.CopyTo(backupCopy);
|
||||||
backupCopy.Seek(0, SeekOrigin.Begin);
|
backupCopy.Seek(0, SeekOrigin.Begin);
|
||||||
//reset stream if we need to reuse it later:
|
//reset stream if we need to reuse it later:
|
||||||
memoryStream.Seek(0, SeekOrigin.Begin);
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Database openDb in _openDatabases)
|
if (!statusLogger.ContinueWork())
|
||||||
{
|
{
|
||||||
if (openDb.Ioc.IsSameFileAs(ioConnectionInfo))
|
throw new Java.Lang.InterruptedException();
|
||||||
{
|
}
|
||||||
//TODO check this earlier and simply open the database's root group
|
|
||||||
throw new Exception("Database already loaded!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_openAttempts.Add(ioConnectionInfo);
|
_openAttempts.Add(ioConnectionInfo);
|
||||||
var newDb = new Database(new DrawableFactory(), this);
|
var newDb = new Database(new DrawableFactory(), this);
|
||||||
newDb.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat);
|
newDb.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat);
|
||||||
|
|
||||||
|
modificationWatcher.BeforeModifyDatabases();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
if ((_currentDatabase == null) || makeCurrent)
|
|
||||||
_currentDatabase = newDb;
|
|
||||||
_openDatabases.Add(newDb);
|
|
||||||
|
|
||||||
|
if ((_currentDatabase == null) || makeCurrent) _currentDatabase = newDb;
|
||||||
|
|
||||||
|
bool replacedOpenDatabase = false;
|
||||||
|
for (int i = 0; i < _openDatabases.Count; i++)
|
||||||
|
{
|
||||||
|
if (_openDatabases[i].Ioc.IsSameFileAs(ioConnectionInfo))
|
||||||
|
{
|
||||||
|
if (_currentDatabase == _openDatabases[i])
|
||||||
|
{
|
||||||
|
_currentDatabase = newDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
replacedOpenDatabase = true;
|
||||||
|
_openDatabases[i] = newDb;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replacedOpenDatabase)
|
||||||
|
{
|
||||||
|
_openDatabases.Add(newDb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
modificationWatcher.AfterModifyDatabases();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (createBackup)
|
if (createBackup)
|
||||||
@@ -276,25 +312,28 @@ namespace keepass2android
|
|||||||
return newDb;
|
return newDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void CloseDatabase(Database db)
|
public void CloseDatabase(Database db)
|
||||||
{
|
{
|
||||||
if (!_openDatabases.Contains(db))
|
|
||||||
throw new Exception("Cannot close database which is not open!");
|
|
||||||
if (_openDatabases.Count == 1)
|
|
||||||
{
|
|
||||||
Lock(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (LastOpenedEntry != null && db.EntriesById.ContainsKey(LastOpenedEntry.Uuid))
|
|
||||||
{
|
|
||||||
LastOpenedEntry = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_openDatabases.Remove(db);
|
if (!_openDatabases.Contains(db))
|
||||||
if (_currentDatabase == db)
|
throw new Exception("Cannot close database which is not open!");
|
||||||
_currentDatabase = _openDatabases.First();
|
if (_openDatabases.Count == 1)
|
||||||
|
{
|
||||||
|
Lock(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LastOpenedEntry != null && db.EntriesById.ContainsKey(LastOpenedEntry.Uuid))
|
||||||
|
{
|
||||||
|
LastOpenedEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_openDatabases.Remove(db);
|
||||||
|
if (_currentDatabase == db)
|
||||||
|
_currentDatabase = _openDatabases.First();
|
||||||
|
|
||||||
UpdateOngoingNotification();
|
UpdateOngoingNotification();
|
||||||
//TODO broadcast event so affected activities can close/update?
|
//TODO broadcast event so affected activities can close/update?
|
||||||
}
|
}
|
||||||
@@ -371,12 +410,16 @@ namespace keepass2android
|
|||||||
|
|
||||||
private readonly List<IOConnectionInfo> _openAttempts = new List<IOConnectionInfo>(); //stores which files have been attempted to open. Used to avoid that we repeatedly try to load files which failed to load.
|
private readonly List<IOConnectionInfo> _openAttempts = new List<IOConnectionInfo>(); //stores which files have been attempted to open. Used to avoid that we repeatedly try to load files which failed to load.
|
||||||
private readonly List<Database> _openDatabases = new List<Database>();
|
private readonly List<Database> _openDatabases = new List<Database>();
|
||||||
|
|
||||||
private readonly List<IOConnectionInfo> _childDatabases = new List<IOConnectionInfo>(); //list of databases which were opened as child databases
|
private readonly List<IOConnectionInfo> _childDatabases = new List<IOConnectionInfo>(); //list of databases which were opened as child databases
|
||||||
private Database _currentDatabase;
|
private Database _currentDatabase;
|
||||||
|
|
||||||
public IEnumerable<Database> OpenDatabases
|
public IEnumerable<Database> OpenDatabases
|
||||||
{
|
{
|
||||||
get { return _openDatabases; }
|
get
|
||||||
|
{
|
||||||
|
return _openDatabases;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ChallengeXCKey _currentlyWaitingXcKey;
|
internal ChallengeXCKey _currentlyWaitingXcKey;
|
||||||
@@ -410,8 +453,9 @@ namespace keepass2android
|
|||||||
DirtyGroups.Add(group);
|
DirtyGroups.Add(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var intent = new Intent(Intents.DataUpdated);
|
||||||
}
|
App.Context.SendBroadcast(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -477,6 +521,8 @@ namespace keepass2android
|
|||||||
// Whether the app is currently showing a dialog that requires user input, like a yesNoCancel dialog
|
// Whether the app is currently showing a dialog that requires user input, like a yesNoCancel dialog
|
||||||
private bool _isShowingUserInputDialog = false;
|
private bool _isShowingUserInputDialog = false;
|
||||||
private IMessagePresenter? _messagePresenter;
|
private IMessagePresenter? _messagePresenter;
|
||||||
|
private YesNoCancelQuestion? _currentlyPendingYesNoCancelQuestion = null;
|
||||||
|
private Context? _activeContext;
|
||||||
|
|
||||||
private void AskForReload(Activity activity, Action<bool> actionOnResult)
|
private void AskForReload(Activity activity, Action<bool> actionOnResult)
|
||||||
{
|
{
|
||||||
@@ -573,123 +619,194 @@ namespace keepass2android
|
|||||||
return LocaleManager.LocalizedAppContext.Resources.GetDrawable((int)field.GetValue(null));
|
return LocaleManager.LocalizedAppContext.Resources.GetDrawable((int)field.GetValue(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> noHandler, EventHandler<DialogClickEventArgs> cancelHandler, Context ctx, string messageSuffix)
|
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey, EventHandler<DialogClickEventArgs> yesHandler, EventHandler<DialogClickEventArgs> noHandler, EventHandler<DialogClickEventArgs> cancelHandler, string messageSuffix)
|
||||||
{
|
{
|
||||||
AskYesNoCancel(titleKey, messageKey, UiStringKey.yes, UiStringKey.no,
|
AskYesNoCancel(titleKey, messageKey, UiStringKey.yes, UiStringKey.no,
|
||||||
yesHandler, noHandler, cancelHandler, ctx, messageSuffix);
|
yesHandler, noHandler, cancelHandler, messageSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
|
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
|
||||||
UiStringKey yesString, UiStringKey noString,
|
UiStringKey yesString, UiStringKey noString,
|
||||||
EventHandler<DialogClickEventArgs> yesHandler,
|
EventHandler<DialogClickEventArgs> yesHandler,
|
||||||
EventHandler<DialogClickEventArgs> noHandler,
|
EventHandler<DialogClickEventArgs> noHandler,
|
||||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
EventHandler<DialogClickEventArgs> cancelHandler, string messageSuffix = "")
|
||||||
Context ctx, string messageSuffix = "")
|
|
||||||
{
|
{
|
||||||
AskYesNoCancel(titleKey, messageKey, yesString, noString, yesHandler, noHandler, cancelHandler, null, ctx, messageSuffix);
|
AskYesNoCancel(titleKey, messageKey, yesString, noString, yesHandler, noHandler, cancelHandler, null, messageSuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
|
class YesNoCancelQuestion
|
||||||
|
{
|
||||||
|
private AlertDialog? _dialog;
|
||||||
|
public UiStringKey TitleKey { get; set; }
|
||||||
|
public UiStringKey MessageKey { get; set; }
|
||||||
|
public UiStringKey YesString { get; set; }
|
||||||
|
public UiStringKey NoString { get; set; }
|
||||||
|
public EventHandler<DialogClickEventArgs>? YesHandler { get; set; }
|
||||||
|
public EventHandler<DialogClickEventArgs>? NoHandler { get; set; }
|
||||||
|
public EventHandler<DialogClickEventArgs>? CancelHandler { get; set; }
|
||||||
|
public EventHandler DismissHandler { get; set; }
|
||||||
|
public string MessageSuffix { get; set; }
|
||||||
|
|
||||||
|
public bool TryShow(IKp2aApp app, Action onUserInputDialogClose, Action onUserInputDialogShow)
|
||||||
|
{
|
||||||
|
if (app.ActiveContext is Activity activity)
|
||||||
|
{
|
||||||
|
if (_dialog is { IsShowing: true })
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_dialog.Dismiss();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
|
_dialog = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
|
||||||
|
builder.SetTitle(app.GetResourceString(TitleKey));
|
||||||
|
|
||||||
|
builder.SetMessage(app.GetResourceString(MessageKey) +
|
||||||
|
(MessageSuffix != "" ? " " + MessageSuffix : ""));
|
||||||
|
|
||||||
|
// _____handlerWithShow are wrappers around given handlers to update _isSHowingYesNoCancelDialog
|
||||||
|
// and to show progress dialog after yesNoCancel dialog is closed
|
||||||
|
EventHandler<DialogClickEventArgs> yesHandlerWithShow = (sender, args) =>
|
||||||
|
{
|
||||||
|
onUserInputDialogClose();
|
||||||
|
YesHandler.Invoke(sender, args);
|
||||||
|
};
|
||||||
|
string yesText = app.GetResourceString(YesString);
|
||||||
|
builder.SetPositiveButton(yesText, yesHandlerWithShow);
|
||||||
|
string noText = "";
|
||||||
|
if (NoHandler != null)
|
||||||
|
{
|
||||||
|
EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) =>
|
||||||
|
{
|
||||||
|
onUserInputDialogClose();
|
||||||
|
NoHandler.Invoke(sender, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
noText = app.GetResourceString(NoString);
|
||||||
|
builder.SetNegativeButton(noText, noHandlerWithShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
string cancelText = "";
|
||||||
|
if (CancelHandler != null)
|
||||||
|
{
|
||||||
|
EventHandler<DialogClickEventArgs> cancelHandlerWithShow = (sender, args) =>
|
||||||
|
{
|
||||||
|
onUserInputDialogClose();
|
||||||
|
CancelHandler.Invoke(sender, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelText = App.Context.GetString(Android.Resource.String.Cancel);
|
||||||
|
builder.SetNeutralButton(cancelText,
|
||||||
|
cancelHandlerWithShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dialog = builder.Create();
|
||||||
|
if (DismissHandler != null)
|
||||||
|
{
|
||||||
|
_dialog.SetOnDismissListener(new Util.DismissListener(() =>
|
||||||
|
{
|
||||||
|
onUserInputDialogClose();
|
||||||
|
DismissHandler(_dialog, EventArgs.Empty);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_dialog.SetCancelable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onUserInputDialogShow();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_dialog.Show();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (yesText.Length + noText.Length + cancelText.Length >= 20)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Button button = _dialog.GetButton((int)DialogButtonType.Positive);
|
||||||
|
LinearLayout linearLayout = (LinearLayout)button.Parent;
|
||||||
|
linearLayout.Orientation = Orientation.Vertical;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OperationRunner.Instance.StatusLogger?.UpdateSubMessage(App.Context.GetString(Resource.String.user_interaction_required));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
|
||||||
UiStringKey yesString, UiStringKey noString,
|
UiStringKey yesString, UiStringKey noString,
|
||||||
EventHandler<DialogClickEventArgs> yesHandler,
|
EventHandler<DialogClickEventArgs> yesHandler,
|
||||||
EventHandler<DialogClickEventArgs> noHandler,
|
EventHandler<DialogClickEventArgs> noHandler,
|
||||||
EventHandler<DialogClickEventArgs> cancelHandler,
|
EventHandler<DialogClickEventArgs> cancelHandler,
|
||||||
EventHandler dismissHandler,
|
EventHandler dismissHandler,string messageSuffix = "")
|
||||||
Context ctx, string messageSuffix = "")
|
|
||||||
{
|
{
|
||||||
Handler handler = new Handler(Looper.MainLooper);
|
if (Java.Lang.Thread.Interrupted())
|
||||||
handler.Post(() =>
|
{
|
||||||
{
|
throw new Java.Lang.InterruptedException();
|
||||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(ctx);
|
}
|
||||||
builder.SetTitle(GetResourceString(titleKey));
|
_currentlyPendingYesNoCancelQuestion = new YesNoCancelQuestion()
|
||||||
|
{
|
||||||
builder.SetMessage(GetResourceString(messageKey) + (messageSuffix != "" ? " " + messageSuffix : ""));
|
TitleKey = titleKey,
|
||||||
|
MessageKey = messageKey,
|
||||||
// _____handlerWithShow are wrappers around given handlers to update _isSHowingYesNoCancelDialog
|
YesString = yesString,
|
||||||
// and to show progress dialog after yesNoCancel dialog is closed
|
NoString = noString,
|
||||||
EventHandler<DialogClickEventArgs> yesHandlerWithShow = (sender, args) =>
|
YesHandler = yesHandler,
|
||||||
{
|
NoHandler = noHandler,
|
||||||
OnUserInputDialogClose();
|
CancelHandler = cancelHandler,
|
||||||
yesHandler.Invoke(sender, args);
|
DismissHandler = dismissHandler,
|
||||||
};
|
MessageSuffix = messageSuffix
|
||||||
string yesText = GetResourceString(yesString);
|
};
|
||||||
builder.SetPositiveButton(yesText, yesHandlerWithShow);
|
|
||||||
string noText = "";
|
UiThreadHandler.Post( () =>
|
||||||
if (noHandler != null)
|
{
|
||||||
{
|
_currentlyPendingYesNoCancelQuestion.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow);
|
||||||
EventHandler<DialogClickEventArgs> noHandlerWithShow = (sender, args) =>
|
});
|
||||||
{
|
|
||||||
OnUserInputDialogClose();
|
|
||||||
noHandler.Invoke(sender, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
noText = GetResourceString(noString);
|
|
||||||
builder.SetNegativeButton(noText, noHandlerWithShow);
|
|
||||||
}
|
|
||||||
string cancelText = "";
|
|
||||||
if (cancelHandler != null)
|
|
||||||
{
|
|
||||||
EventHandler<DialogClickEventArgs> cancelHandlerWithShow = (sender, args) =>
|
|
||||||
{
|
|
||||||
OnUserInputDialogClose();
|
|
||||||
cancelHandler.Invoke(sender, args);
|
|
||||||
};
|
|
||||||
|
|
||||||
cancelText = ctx.GetString(Android.Resource.String.Cancel);
|
|
||||||
builder.SetNeutralButton(cancelText,
|
|
||||||
cancelHandlerWithShow);
|
|
||||||
}
|
|
||||||
|
|
||||||
var dialog = builder.Create();
|
|
||||||
if (dismissHandler != null)
|
|
||||||
{
|
|
||||||
dialog.SetOnDismissListener(new Util.DismissListener(() => {
|
|
||||||
OnUserInputDialogClose();
|
|
||||||
dismissHandler(dialog, EventArgs.Empty);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
OnUserInputDialogShow();
|
|
||||||
dialog.Show();
|
|
||||||
|
|
||||||
if (yesText.Length + noText.Length + cancelText.Length >= 20)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Button button = dialog.GetButton((int)DialogButtonType.Positive);
|
|
||||||
LinearLayout linearLayout = (LinearLayout)button.Parent;
|
|
||||||
linearLayout.Orientation = Orientation.Vertical;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shows all non-dismissed progress dialogs.
|
/// Shows all non-dismissed progress dialogs.
|
||||||
/// If there are multiple progressDialogs active, they all will be showing.
|
/// If there are multiple progressDialogs active, they all will be showing.
|
||||||
/// There probably will never be multiple dialogs at the same time because only one ProgressTask can run at a time.
|
/// There probably will never be multiple dialogs at the same time because only one BlockingOperationStarter can run at a time.
|
||||||
/// Even if multiple dialogs show at the same time, it shouldn't be too much of an issue
|
/// Even if multiple dialogs show at the same time, it shouldn't be too much of an issue
|
||||||
/// because they are just progress indicators.
|
/// because they are just progress indicators.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ShowAllActiveProgressDialogs()
|
private void ShowAllActiveProgressDialogs()
|
||||||
{
|
{
|
||||||
foreach (RealProgressDialog progressDialog in _activeProgressDialogs)
|
foreach (RealProgressDialog progressDialog in _activeProgressDialogs)
|
||||||
{
|
{
|
||||||
progressDialog.Show();
|
progressDialog.Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideAllActiveProgressDialogs()
|
private void HideAllActiveProgressDialogs()
|
||||||
{
|
{
|
||||||
foreach (RealProgressDialog progressDialog in _activeProgressDialogs)
|
foreach (RealProgressDialog progressDialog in _activeProgressDialogs)
|
||||||
{
|
{
|
||||||
progressDialog.Hide();
|
progressDialog.Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,13 +826,15 @@ namespace keepass2android
|
|||||||
private void OnUserInputDialogClose()
|
private void OnUserInputDialogClose()
|
||||||
{
|
{
|
||||||
_isShowingUserInputDialog = false;
|
_isShowingUserInputDialog = false;
|
||||||
ShowAllActiveProgressDialogs();
|
_currentlyPendingYesNoCancelQuestion = null;
|
||||||
|
|
||||||
|
ShowAllActiveProgressDialogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Handler UiThreadHandler
|
public Handler UiThreadHandler
|
||||||
{
|
{
|
||||||
get { return new Handler(); }
|
get { return _uiThreadHandler; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simple wrapper around ProgressDialog implementing IProgressDialog
|
/// Simple wrapper around ProgressDialog implementing IProgressDialog
|
||||||
@@ -760,8 +879,15 @@ namespace keepass2android
|
|||||||
_app._activeProgressDialogs.Add(this);
|
_app._activeProgressDialogs.Add(this);
|
||||||
// Only show if asking dialog not also showing
|
// Only show if asking dialog not also showing
|
||||||
if (!_app._isShowingUserInputDialog)
|
if (!_app._isShowingUserInputDialog)
|
||||||
{
|
{
|
||||||
_pd.Show();
|
try
|
||||||
|
{
|
||||||
|
_pd.Show();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -771,10 +897,25 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IProgressDialog CreateProgressDialog(Context ctx)
|
public IProgressDialog CreateProgressDialog(Context ctx)
|
||||||
{
|
{
|
||||||
return new RealProgressDialog(ctx, this);
|
try
|
||||||
}
|
{
|
||||||
|
|
||||||
|
|
||||||
|
var pd = new RealProgressDialog(ctx, this);
|
||||||
|
pd.SetTitle(GetResourceString(UiStringKey.progress_title));
|
||||||
|
return pd;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//may happen if the activity is (being) destroyed
|
||||||
|
Kp2aLog.Log("CreateProgressDialog failed with " + e.ToString());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
|
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
|
||||||
{
|
{
|
||||||
@@ -798,9 +939,14 @@ namespace keepass2android
|
|||||||
fileStorage = innerFileStorage;
|
fileStorage = innerFileStorage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fileStorage is IOfflineSwitchable)
|
if (fileStorage is IOfflineSwitchable switchable)
|
||||||
{
|
{
|
||||||
((IOfflineSwitchable)fileStorage).IsOffline = App.Kp2a.OfflineMode;
|
switchable.IsOffline = App.Kp2a.OfflineMode;
|
||||||
|
if (switchable.IsOffline)
|
||||||
|
{
|
||||||
|
//users of the file storage can set this to false, but the default is to show a warning:
|
||||||
|
switchable.TriggerWarningWhenFallingBackToCache = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return fileStorage;
|
return fileStorage;
|
||||||
}
|
}
|
||||||
@@ -836,7 +982,7 @@ namespace keepass2android
|
|||||||
new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this),
|
new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||||
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveFileStorage(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,
|
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveAppDataFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||||
new OneDriveFileStorage(),
|
new OneDriveFileStorage(this),
|
||||||
new OneDrive2FullFileStorage(),
|
new OneDrive2FullFileStorage(),
|
||||||
new OneDrive2MyFilesFileStorage(),
|
new OneDrive2MyFilesFileStorage(),
|
||||||
new OneDrive2AppFolderFileStorage(),
|
new OneDrive2AppFolderFileStorage(),
|
||||||
@@ -918,14 +1064,16 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
||||||
|
|
||||||
ValidationMode validationMode = ValidationMode.Warn;
|
ValidationMode validationMode = ValidationMode.Error;
|
||||||
|
|
||||||
string strValMode = prefs.GetString(LocaleManager.LocalizedAppContext.Resources.GetString(Resource.String.AcceptAllServerCertificates_key),
|
string strValMode = prefs.GetString(LocaleManager.LocalizedAppContext.Resources.GetString(Resource.String.AcceptAllServerCertificates_key),
|
||||||
LocaleManager.LocalizedAppContext.Resources.GetString(Resource.String.AcceptAllServerCertificates_default));
|
LocaleManager.LocalizedAppContext.Resources.GetString(Resource.String.AcceptAllServerCertificates_default));
|
||||||
|
|
||||||
if (strValMode == "IGNORE")
|
if (strValMode == "IGNORE")
|
||||||
validationMode = ValidationMode.Ignore;
|
validationMode = ValidationMode.Ignore;
|
||||||
else if (strValMode == "ERROR")
|
else if (strValMode == "WARN")
|
||||||
|
validationMode = ValidationMode.Warn;
|
||||||
|
else if (strValMode == "ERROR")
|
||||||
validationMode = ValidationMode.Error;
|
validationMode = ValidationMode.Error;
|
||||||
return validationMode;
|
return validationMode;
|
||||||
}
|
}
|
||||||
@@ -1010,6 +1158,8 @@ namespace keepass2android
|
|||||||
FileDbHelper = new FileDbHelper(app);
|
FileDbHelper = new FileDbHelper(app);
|
||||||
FileDbHelper.Open();
|
FileDbHelper.Open();
|
||||||
|
|
||||||
|
_uiThreadHandler = new Handler(Looper.MainLooper);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
foreach (UiStringKey key in Enum.GetValues(typeof(UiStringKey)))
|
foreach (UiStringKey key in Enum.GetValues(typeof(UiStringKey)))
|
||||||
{
|
{
|
||||||
@@ -1043,9 +1193,9 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
private string GetErrorMessageForFileStorageException(Exception e)
|
private string GetErrorMessageForFileStorageException(Exception e)
|
||||||
{
|
{
|
||||||
string errorMessage = e.Message;
|
var errorMessage = Util.GetErrorMessage(e);
|
||||||
if (e is OfflineModeException)
|
if (e is OfflineModeException)
|
||||||
errorMessage = GetResourceString(UiStringKey.InOfflineMode);
|
errorMessage = GetResourceString(UiStringKey.InOfflineMode);
|
||||||
if (e is DocumentAccessRevokedException)
|
if (e is DocumentAccessRevokedException)
|
||||||
errorMessage = GetResourceString(UiStringKey.DocumentAccessRevoked);
|
errorMessage = GetResourceString(UiStringKey.DocumentAccessRevoked);
|
||||||
@@ -1054,7 +1204,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex)
|
public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex)
|
||||||
{
|
{
|
||||||
var errorMessage = GetErrorMessageForFileStorageException(ex);
|
var errorMessage = GetErrorMessageForFileStorageException(ex);
|
||||||
ShowToast(LocaleManager.LocalizedAppContext.GetString(Resource.String.CouldNotLoadFromRemote, errorMessage), MessageSeverity.Error);
|
ShowToast(LocaleManager.LocalizedAppContext.GetString(Resource.String.CouldNotLoadFromRemote, errorMessage), MessageSeverity.Error);
|
||||||
@@ -1149,10 +1299,35 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// true if the app is used in offline mode
|
public bool SyncInBackgroundPreference
|
||||||
/// </summary>
|
{
|
||||||
public bool OfflineMode
|
get
|
||||||
|
{
|
||||||
|
var prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
||||||
|
return prefs.GetBoolean(LocaleManager.LocalizedAppContext.GetString(Resource.String.SyncOfflineCacheInBackground_key), false);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext);
|
||||||
|
ISharedPreferencesEditor edit = prefs.Edit();
|
||||||
|
edit.PutBoolean(LocaleManager.LocalizedAppContext.GetString(Resource.String.SyncOfflineCacheInBackground_key), value);
|
||||||
|
edit.Commit();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartBackgroundSyncService()
|
||||||
|
{
|
||||||
|
Intent intent = new Intent(App.Context, typeof(BackgroundSyncService));
|
||||||
|
intent.SetAction(BackgroundSyncService.ActionStart);
|
||||||
|
App.Context.StartService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// true if the app is used in offline mode
|
||||||
|
/// </summary>
|
||||||
|
public bool OfflineMode
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
@@ -1248,13 +1423,22 @@ namespace keepass2android
|
|||||||
|
|
||||||
public Database TryFindDatabaseForElement(IStructureItem element)
|
public Database TryFindDatabaseForElement(IStructureItem element)
|
||||||
{
|
{
|
||||||
foreach (var db in OpenDatabases)
|
try
|
||||||
{
|
{
|
||||||
//we compare UUIDs and not by reference. this is more robust and works with history items as well
|
foreach (var db in OpenDatabases)
|
||||||
if (db.Elements.Any(e => e.Uuid?.Equals(element.Uuid) == true))
|
{
|
||||||
return db;
|
//we compare UUIDs and not by reference. this is more robust and works with history items as well
|
||||||
|
if (db.Elements.Any(e => e.Uuid?.Equals(element.Uuid) == true))
|
||||||
|
{
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterChildDatabase(IOConnectionInfo ioc)
|
public void RegisterChildDatabase(IOConnectionInfo ioc)
|
||||||
@@ -1327,10 +1511,92 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Context ActiveContext
|
||||||
|
{
|
||||||
|
get => _activeContext ?? Application.Context;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_activeContext = value;
|
||||||
|
OperationRunner.Instance.SetNewActiveContext(App.Kp2a);
|
||||||
|
_currentlyPendingYesNoCancelQuestion?.TryShow(this, OnUserInputDialogClose, OnUserInputDialogShow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the database is updated from a background operation, that operation needs to acquire a writer lock on this.
|
||||||
|
/// </summary>
|
||||||
|
/// Activities can acquire a reader lock if they want to make sure that no background operation is modifying the database while they are open.
|
||||||
|
public ReaderWriterLockSlim DatabasesBackgroundModificationLock { get; } = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||||
|
|
||||||
|
public bool CancelBackgroundOperations()
|
||||||
|
{
|
||||||
|
if (!DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OperationRunner.Instance.CancelAll();
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DatabasesBackgroundModificationLock.ExitReadLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<int, List<ActionOnOperationFinished>> _pendingActionsForContextInstances = new();
|
||||||
|
private readonly object _pendingActionsForContextInstancesLock = new();
|
||||||
|
private Handler _uiThreadHandler;
|
||||||
|
|
||||||
|
public void RegisterPendingActionForContextInstance(int contextInstanceId,
|
||||||
|
ActionOnOperationFinished actionToPerformWhenContextIsResumed)
|
||||||
|
{
|
||||||
|
lock (_pendingActionsForContextInstancesLock)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!_pendingActionsForContextInstances.TryGetValue(contextInstanceId, out var actions))
|
||||||
|
{
|
||||||
|
actions = new List<ActionOnOperationFinished>();
|
||||||
|
_pendingActionsForContextInstances[contextInstanceId] = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.Add(actionToPerformWhenContextIsResumed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PerformPendingActions(int instanceId)
|
||||||
|
{
|
||||||
|
lock (_pendingActionsForContextInstancesLock)
|
||||||
|
{
|
||||||
|
if (_pendingActionsForContextInstances.TryGetValue(instanceId, out var actions))
|
||||||
|
{
|
||||||
|
foreach (var action in actions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action.Run();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingActionsForContextInstances.Remove(instanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
|
|
||||||
|
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
|
||||||
#if NoNet
|
#if NoNet
|
||||||
[Application(Debuggable=false, Label=AppNames.AppName)]
|
[Application(Debuggable=false, Label=AppNames.AppName)]
|
||||||
#else
|
#else
|
||||||
@@ -1340,7 +1606,8 @@ namespace keepass2android
|
|||||||
[Application(Debuggable = true, Label = AppNames.AppName)]
|
[Application(Debuggable = true, Label = AppNames.AppName)]
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
public class App : Application {
|
public class App : Application, ILifecycleObserver
|
||||||
|
{
|
||||||
|
|
||||||
public override void OnConfigurationChanged(Android.Content.Res.Configuration newConfig)
|
public override void OnConfigurationChanged(Android.Content.Res.Configuration newConfig)
|
||||||
{
|
{
|
||||||
@@ -1373,8 +1640,9 @@ namespace keepass2android
|
|||||||
InitThaiCalendarCrashFix();
|
InitThaiCalendarCrashFix();
|
||||||
|
|
||||||
base.OnCreate();
|
base.OnCreate();
|
||||||
|
ProcessLifecycleOwner.Get().Lifecycle.AddObserver(this);
|
||||||
|
|
||||||
Kp2aLog.Log("Creating application "+PackageName+". Version=" + PackageManager.GetPackageInfo(PackageName, 0).VersionCode);
|
Kp2aLog.Log("Creating application "+PackageName+". Version=" + PackageManager.GetPackageInfo(PackageName, 0).VersionCode);
|
||||||
|
|
||||||
CreateNotificationChannels();
|
CreateNotificationChannels();
|
||||||
|
|
||||||
@@ -1386,12 +1654,33 @@ namespace keepass2android
|
|||||||
intentFilter.AddAction(Intents.LockDatabaseByTimeout);
|
intentFilter.AddAction(Intents.LockDatabaseByTimeout);
|
||||||
intentFilter.AddAction(Intents.CloseDatabase);
|
intentFilter.AddAction(Intents.CloseDatabase);
|
||||||
ContextCompat.RegisterReceiver(Context, broadcastReceiver, intentFilter, (int)ReceiverFlags.Exported);
|
ContextCompat.RegisterReceiver(Context, broadcastReceiver, intentFilter, (int)ReceiverFlags.Exported);
|
||||||
|
|
||||||
//ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApplicationBroadcastReceiver broadcastReceiver = new ApplicationBroadcastReceiver();
|
private ApplicationBroadcastReceiver broadcastReceiver = new ApplicationBroadcastReceiver();
|
||||||
|
|
||||||
|
[Lifecycle.Event.OnStop]
|
||||||
|
[Export]
|
||||||
|
public void OnAppBackgrounded()
|
||||||
|
{
|
||||||
|
Kp2aLog.Log("Going to background");
|
||||||
|
Kp2a.ActiveContext = null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Lifecycle.Event.OnStart]
|
||||||
|
[Export]
|
||||||
|
public void OnAppForegrounded()
|
||||||
|
{
|
||||||
|
Kp2aLog.Log("Going to foreground");
|
||||||
|
StopBackgroundSyncService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopBackgroundSyncService()
|
||||||
|
{
|
||||||
|
Intent stopServiceIntent = new Intent(Context, typeof(BackgroundSyncService));
|
||||||
|
stopServiceIntent.SetAction(BackgroundSyncService.ActionStop);
|
||||||
|
Context.StartService(stopServiceIntent);
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateNotificationChannels()
|
private void CreateNotificationChannels()
|
||||||
{
|
{
|
||||||
|
@@ -71,7 +71,7 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (errorMessageBuilder != null)
|
if (errorMessageBuilder != null)
|
||||||
errorMessageBuilder.Append(e.Message);
|
errorMessageBuilder.Append(Util.GetErrorMessage(e));
|
||||||
Kp2aLog.Log(e.ToString());
|
Kp2aLog.Log(e.ToString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -487,7 +487,7 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
App.Kp2a.ShowMessage(this, "Error: " + e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(this, "Error: " + Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
Finish();
|
Finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,8 +37,13 @@ namespace keepass2android
|
|||||||
/// <summary>This intent will be broadcast once the database has been locked. Sensitive information displayed should be hidden and unloaded.</summary>
|
/// <summary>This intent will be broadcast once the database has been locked. Sensitive information displayed should be hidden and unloaded.</summary>
|
||||||
public const String DatabaseLocked = "keepass2android." + AppNames.PackagePart + ".database_locked";
|
public const String DatabaseLocked = "keepass2android." + AppNames.PackagePart + ".database_locked";
|
||||||
|
|
||||||
/// <summary>This intent will be broadcast once the keyboard data has been cleared</summary>
|
/// <summary>
|
||||||
public const String KeyboardCleared = "keepass2android." + AppNames.PackagePart + ".keyboard_cleared";
|
/// Signals that the loaded data was updated, e.g. by reloading during sync. All UI elements should be refreshed.
|
||||||
|
/// </summary>
|
||||||
|
public const String DataUpdated = "keepass2android." + AppNames.PackagePart + ".data_updated";
|
||||||
|
|
||||||
|
/// <summary>This intent will be broadcast once the keyboard data has been cleared</summary>
|
||||||
|
public const String KeyboardCleared = "keepass2android." + AppNames.PackagePart + ".keyboard_cleared";
|
||||||
|
|
||||||
public const String CopyUsername = "keepass2android.copy_username";
|
public const String CopyUsername = "keepass2android.copy_username";
|
||||||
public const String CopyPassword = "keepass2android.copy_password";
|
public const String CopyPassword = "keepass2android.copy_password";
|
||||||
|
@@ -48,7 +48,15 @@ namespace keepass2android.search
|
|||||||
{
|
{
|
||||||
protected override void OnCreate (Bundle bundle)
|
protected override void OnCreate (Bundle bundle)
|
||||||
{
|
{
|
||||||
base.OnCreate (bundle);
|
//we don't want any background thread to update/reload the database while we're in this activity. We're showing a temporary group, so background updating doesn't work well.
|
||||||
|
if (!App.Kp2a.DatabasesBackgroundModificationLock.TryEnterReadLock(TimeSpan.FromSeconds(5)))
|
||||||
|
{
|
||||||
|
App.Kp2a.ShowMessage(this, GetString(Resource.String.failed_to_access_database), MessageSeverity.Error);
|
||||||
|
Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnCreate (bundle);
|
||||||
|
|
||||||
if ( IsFinishing ) {
|
if ( IsFinishing ) {
|
||||||
return;
|
return;
|
||||||
@@ -59,7 +67,13 @@ namespace keepass2android.search
|
|||||||
ProcessIntent(Intent);
|
ProcessIntent(Intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool EntriesBelongToCurrentDatabaseOnly
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
App.Kp2a.DatabasesBackgroundModificationLock.ExitReadLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool EntriesBelongToCurrentDatabaseOnly
|
||||||
{
|
{
|
||||||
get { return false; }
|
get { return false; }
|
||||||
}
|
}
|
||||||
@@ -142,7 +156,7 @@ namespace keepass2android.search
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
App.Kp2a.ShowMessage(this,e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -96,7 +96,7 @@ namespace keepass2android.search
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(e);
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
App.Kp2a.ShowMessage(this, e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(this, Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -156,7 +156,7 @@ namespace keepass2android.services.AutofillBase
|
|||||||
catch (Java.Lang.SecurityException e)
|
catch (Java.Lang.SecurityException e)
|
||||||
{
|
{
|
||||||
Log.Warn(CommonUtil.Tag, "Security exception handling request");
|
Log.Warn(CommonUtil.Tag, "Security exception handling request");
|
||||||
callback.OnFailure(e.Message);
|
callback.OnFailure(Util.GetErrorMessage(e));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ namespace keepass2android.services.AutofillBase
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
callback.OnFailure(e.Message);
|
callback.OnFailure(Util.GetErrorMessage(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
183
src/keepass2android-app/services/BackgroundSyncService.cs
Normal file
183
src/keepass2android-app/services/BackgroundSyncService.cs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Util;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AndroidX.Core.App;
|
||||||
|
using Android.Content.PM;
|
||||||
|
using Microsoft.Graph.Models;
|
||||||
|
|
||||||
|
|
||||||
|
namespace keepass2android.services
|
||||||
|
{
|
||||||
|
[Service(ForegroundServiceType = ForegroundService.TypeDataSync)]
|
||||||
|
public class BackgroundSyncService : Service, IProgressUiProvider, IProgressUi
|
||||||
|
{
|
||||||
|
private const string ChannelId = "BackgroundSyncServiceChannel";
|
||||||
|
private const int NotificationId = 1;
|
||||||
|
private const string Tag = "BackgroundSyncService";
|
||||||
|
private CancellationTokenSource _cts;
|
||||||
|
private string _message;
|
||||||
|
private string _submessage;
|
||||||
|
|
||||||
|
public override void OnCreate()
|
||||||
|
{
|
||||||
|
base.OnCreate();
|
||||||
|
Log.Debug(Tag, "OnCreate");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
|
||||||
|
{
|
||||||
|
if (intent.Action == ActionStop)
|
||||||
|
{
|
||||||
|
Log.Debug(Tag, "OnStartCommand: STOP");
|
||||||
|
StopForeground(StopForegroundFlags.Remove);
|
||||||
|
StopSelf();
|
||||||
|
return StartCommandResult.NotSticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug(Tag, "OnStartCommand");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
CreateNotificationChannel();
|
||||||
|
StartForeground(NotificationId, BuildNotification());
|
||||||
|
App.Kp2a.ActiveContext = this;
|
||||||
|
return StartCommandResult.Sticky;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(Tag, ex.ToString());
|
||||||
|
StopForeground(StopForegroundFlags.Remove);
|
||||||
|
StopSelf();
|
||||||
|
return StartCommandResult.NotSticky;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Notification BuildNotification()
|
||||||
|
{
|
||||||
|
Intent notificationIntent = PackageManager.GetLaunchIntentForPackage(PackageName);
|
||||||
|
if (notificationIntent == null)
|
||||||
|
{
|
||||||
|
notificationIntent = new Intent(this, typeof(FileSelectActivity));
|
||||||
|
notificationIntent.SetFlags(ActivityFlags.BroughtToFront | ActivityFlags.SingleTop |
|
||||||
|
ActivityFlags.ReorderToFront | ActivityFlags.NewTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent,
|
||||||
|
PendingIntentFlags.Immutable);
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, ChannelId)
|
||||||
|
.SetSmallIcon(Resource.Drawable.ic_launcher_gray)
|
||||||
|
.SetPriority(NotificationCompat.PriorityLow)
|
||||||
|
.SetSilent(true)
|
||||||
|
.SetContentIntent(pendingIntent)
|
||||||
|
.SetProgress(100,0, true)
|
||||||
|
.SetOngoing(true);
|
||||||
|
if (!string.IsNullOrEmpty(_message))
|
||||||
|
{
|
||||||
|
builder.SetContentTitle(_message);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(_submessage))
|
||||||
|
{
|
||||||
|
builder.SetContentText(_submessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateNotificationChannel()
|
||||||
|
{
|
||||||
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||||
|
{
|
||||||
|
var channelName = GetString(Resource.String.BackgroundSyncChannel_name);
|
||||||
|
var channelDescription = GetString(Resource.String.BackgroundSyncChannel_desc);
|
||||||
|
var channelImportance = NotificationImportance.Default;
|
||||||
|
var channel = new NotificationChannel(ChannelId, channelName, channelImportance)
|
||||||
|
{
|
||||||
|
Description = channelDescription
|
||||||
|
};
|
||||||
|
channel.EnableLights(false);
|
||||||
|
channel.EnableVibration(false);
|
||||||
|
channel.SetSound(null, null);
|
||||||
|
channel.SetShowBadge(false);
|
||||||
|
|
||||||
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||||
|
notificationManager.CreateNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IBinder OnBind(Intent intent)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDestroy()
|
||||||
|
{
|
||||||
|
base.OnDestroy();
|
||||||
|
Log.Debug(Tag, "OnDestroy");
|
||||||
|
if (_cts != null)
|
||||||
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_cts.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IProgressUi? ProgressUi
|
||||||
|
{
|
||||||
|
get { return this; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ActionStop => "STOP";
|
||||||
|
|
||||||
|
public static string ActionStart => "START";
|
||||||
|
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
CloseNotification();
|
||||||
|
StopSelf();
|
||||||
|
StopForeground(StopForegroundFlags.Remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseNotification()
|
||||||
|
{
|
||||||
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||||
|
notificationManager.Cancel(NotificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMessage(string message)
|
||||||
|
{
|
||||||
|
_message = message;
|
||||||
|
UpdateNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateNotification()
|
||||||
|
{
|
||||||
|
var notification = BuildNotification();
|
||||||
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||||
|
notificationManager.Notify(NotificationId, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSubMessage(string submessage)
|
||||||
|
{
|
||||||
|
_submessage = submessage;
|
||||||
|
UpdateNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -117,16 +117,16 @@ namespace keepass2android
|
|||||||
var previousUsername = db.KpDatabase.DefaultUserName;
|
var previousUsername = db.KpDatabase.DefaultUserName;
|
||||||
db.KpDatabase.DefaultUserName = e.NewValue.ToString();
|
db.KpDatabase.DefaultUserName = e.NewValue.ToString();
|
||||||
|
|
||||||
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
|
SaveDb save = new SaveDb(App.Kp2a, App.Kp2a.CurrentDb, new ActionOnOperationFinished(App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
db.KpDatabase.DefaultUserName = previousUsername;
|
db.KpDatabase.DefaultUserName = previousUsername;
|
||||||
db.KpDatabase.DefaultUserNameChanged = previousUsernameChanged;
|
db.KpDatabase.DefaultUserNameChanged = previousUsernameChanged;
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, save);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -144,8 +144,8 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
pref.PreferenceClick += (sender, args) =>
|
pref.PreferenceClick += (sender, args) =>
|
||||||
{
|
{
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, Activity,
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a,
|
||||||
new AddTemplateEntries(Activity, App.Kp2a, new ActionOnFinish(Activity,
|
new AddTemplateEntries(App.Kp2a, new ActionOnOperationFinished(App.Kp2a,
|
||||||
delegate
|
delegate
|
||||||
{
|
{
|
||||||
pref.Enabled = false;
|
pref.Enabled = false;
|
||||||
@@ -183,13 +183,13 @@ namespace keepass2android
|
|||||||
String previousName = db.KpDatabase.Name;
|
String previousName = db.KpDatabase.Name;
|
||||||
db.KpDatabase.Name = e.NewValue.ToString();
|
db.KpDatabase.Name = e.NewValue.ToString();
|
||||||
|
|
||||||
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
|
SaveDb save = new SaveDb(App.Kp2a, App.Kp2a.CurrentDb, new ActionOnOperationFinished(App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
db.KpDatabase.Name = previousName;
|
db.KpDatabase.Name = previousName;
|
||||||
db.KpDatabase.NameChanged = previousNameChanged;
|
db.KpDatabase.NameChanged = previousNameChanged;
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -197,7 +197,7 @@ namespace keepass2android
|
|||||||
App.Kp2a.UpdateOngoingNotification();
|
App.Kp2a.UpdateOngoingNotification();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, save);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
App.Kp2a.ShowMessage(Activity, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(Activity, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
App.Kp2a.ShowMessage(Activity, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(Activity, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + Util.GetErrorMessage(e), MessageSeverity.Error);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,18 +410,18 @@ namespace keepass2android
|
|||||||
var previousCipher = db.KpDatabase.DataCipherUuid;
|
var previousCipher = db.KpDatabase.DataCipherUuid;
|
||||||
db.KpDatabase.DataCipherUuid = new PwUuid(MemUtil.HexStringToByteArray((string)preferenceChangeEventArgs.NewValue));
|
db.KpDatabase.DataCipherUuid = new PwUuid(MemUtil.HexStringToByteArray((string)preferenceChangeEventArgs.NewValue));
|
||||||
|
|
||||||
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
|
SaveDb save = new SaveDb(App.Kp2a, App.Kp2a.CurrentDb, new ActionOnOperationFinished(App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
db.KpDatabase.DataCipherUuid = previousCipher;
|
db.KpDatabase.DataCipherUuid = previousCipher;
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
preferenceChangeEventArgs.Preference.Summary =
|
preferenceChangeEventArgs.Preference.Summary =
|
||||||
CipherPool.GlobalPool.GetCipher(db.KpDatabase.DataCipherUuid).DisplayName;
|
CipherPool.GlobalPool.GetCipher(db.KpDatabase.DataCipherUuid).DisplayName;
|
||||||
}));
|
}));
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, save);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +628,7 @@ namespace keepass2android
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Kp2aLog.LogUnexpectedError(ex);
|
Kp2aLog.LogUnexpectedError(ex);
|
||||||
App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext, ex.Message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(LocaleManager.LocalizedAppContext, Util.GetErrorMessage(ex), MessageSeverity.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1071,18 +1071,18 @@ namespace keepass2android
|
|||||||
|
|
||||||
Kp2aLog.Log("--new kdf: " + KdfPool.Get(db.KpDatabase.KdfParameters.KdfUuid) + " " + db.KpDatabase.KdfParameters.KdfUuid.ToHexString());
|
Kp2aLog.Log("--new kdf: " + KdfPool.Get(db.KpDatabase.KdfParameters.KdfUuid) + " " + db.KpDatabase.KdfParameters.KdfUuid.ToHexString());
|
||||||
|
|
||||||
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
|
SaveDb save = new SaveDb(App.Kp2a, App.Kp2a.CurrentDb, new ActionOnOperationFinished(App.Kp2a, (success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
db.KpDatabase.KdfParameters = previousKdfParams;
|
db.KpDatabase.KdfParameters = previousKdfParams;
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateKdfScreen();
|
UpdateKdfScreen();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, save);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -13,13 +13,13 @@ namespace keepass2android
|
|||||||
public class ExportKeyfileActivity : LockCloseActivity
|
public class ExportKeyfileActivity : LockCloseActivity
|
||||||
{
|
{
|
||||||
|
|
||||||
public class ExportKeyfile : RunnableOnFinish
|
public class ExportKeyfile : OperationWithFinishHandler
|
||||||
{
|
{
|
||||||
private readonly IKp2aApp _app;
|
private readonly IKp2aApp _app;
|
||||||
private IOConnectionInfo _targetIoc;
|
private IOConnectionInfo _targetIoc;
|
||||||
|
|
||||||
public ExportKeyfile(Activity activity, IKp2aApp app, OnFinish onFinish, IOConnectionInfo targetIoc) : base(
|
public ExportKeyfile(IKp2aApp app, OnOperationFinishedHandler onOperationFinishedHandler, IOConnectionInfo targetIoc) : base(
|
||||||
activity, onFinish)
|
App.Kp2a, onOperationFinishedHandler)
|
||||||
{
|
{
|
||||||
_app = app;
|
_app = app;
|
||||||
_targetIoc = targetIoc;
|
_targetIoc = targetIoc;
|
||||||
@@ -53,7 +53,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Finish(false, ex.Message);
|
Finish(false, Util.GetErrorMessage(ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -63,25 +63,25 @@ namespace keepass2android
|
|||||||
|
|
||||||
public class ExportKeyfileProcessManager : FileSaveProcessManager
|
public class ExportKeyfileProcessManager : FileSaveProcessManager
|
||||||
{
|
{
|
||||||
public ExportKeyfileProcessManager(int requestCode, Activity activity) : base(requestCode, activity)
|
public ExportKeyfileProcessManager(int requestCode, LifecycleAwareActivity activity) : base(requestCode, activity)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SaveFile(IOConnectionInfo ioc)
|
protected override void SaveFile(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
var exportKeyfile = new ExportKeyfile(_activity, App.Kp2a, new ActionOnFinish(_activity,
|
var exportKeyfile = new ExportKeyfile(App.Kp2a, new ActionInContextInstanceOnOperationFinished(_activity.ContextInstanceId, App.Kp2a,
|
||||||
(success, message, activity) =>
|
(success, message, context) =>
|
||||||
{
|
{
|
||||||
if (!success)
|
if (!success)
|
||||||
App.Kp2a.ShowMessage(activity, message, MessageSeverity.Error);
|
App.Kp2a.ShowMessage(context, message, MessageSeverity.Error);
|
||||||
else
|
else
|
||||||
App.Kp2a.ShowMessage(activity, _activity.GetString(Resource.String.export_keyfile_successful),
|
App.Kp2a.ShowMessage(context, _activity.GetString(Resource.String.export_keyfile_successful),
|
||||||
MessageSeverity.Info);
|
MessageSeverity.Info);
|
||||||
activity.Finish();
|
(context as Activity)?.Finish();
|
||||||
}
|
}
|
||||||
), ioc);
|
), ioc);
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, _activity, exportKeyfile);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, exportKeyfile);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -113,8 +113,8 @@ namespace keepass2android.settings
|
|||||||
ParamValue = paramValue;
|
ParamValue = paramValue;
|
||||||
|
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
SaveDb save = new SaveDb((Activity)Context, App.Kp2a, App.Kp2a.CurrentDb, new AfterSave((Activity)Context, handler, oldValue, this));
|
SaveDb save = new SaveDb(App.Kp2a, App.Kp2a.CurrentDb, new AfterSave(App.Kp2a, handler, oldValue, this));
|
||||||
ProgressTask pt = new ProgressTask(App.Kp2a, (Activity)Context, save);
|
BlockingOperationStarter pt = new BlockingOperationStarter(App.Kp2a, save);
|
||||||
pt.Run();
|
pt.Run();
|
||||||
});
|
});
|
||||||
db.SetNegativeButton(Android.Resource.String.Cancel, ((sender, args) => { }));
|
db.SetNegativeButton(Android.Resource.String.Cancel, ((sender, args) => { }));
|
||||||
@@ -129,15 +129,14 @@ namespace keepass2android.settings
|
|||||||
|
|
||||||
public abstract ulong ParamValue { get; set; }
|
public abstract ulong ParamValue { get; set; }
|
||||||
|
|
||||||
private class AfterSave : OnFinish {
|
private class AfterSave : OnOperationFinishedHandler {
|
||||||
private readonly ulong _oldParamValue;
|
private readonly ulong _oldParamValue;
|
||||||
private readonly Context _ctx;
|
private readonly IKp2aApp _app;
|
||||||
private readonly KdfNumberDialogPreference _pref;
|
private readonly KdfNumberDialogPreference _pref;
|
||||||
|
|
||||||
public AfterSave(Activity ctx, Handler handler, ulong oldParamValue, KdfNumberDialogPreference pref):base(ctx, handler) {
|
public AfterSave(IKp2aApp app, Handler handler, ulong oldParamValue, KdfNumberDialogPreference pref):base(app, handler) {
|
||||||
|
_app = app;
|
||||||
_pref = pref;
|
_pref = pref;
|
||||||
_ctx = ctx;
|
|
||||||
_oldParamValue = oldParamValue;
|
_oldParamValue = oldParamValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +147,7 @@ namespace keepass2android.settings
|
|||||||
_pref.OnPreferenceChangeListener.OnPreferenceChange(_pref, null);
|
_pref.OnPreferenceChangeListener.OnPreferenceChange(_pref, null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DisplayMessage(_ctx);
|
DisplayMessage(_app.ActiveContext);
|
||||||
|
|
||||||
_pref.ParamValue = _oldParamValue;
|
_pref.ParamValue = _oldParamValue;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,96 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using Android.Util;
|
||||||
|
using Android.Views;
|
||||||
|
|
||||||
|
namespace keepass2android.views;
|
||||||
|
|
||||||
|
public class BackgroundOperationContainer : LinearLayout, IProgressUi
|
||||||
|
{
|
||||||
|
protected BackgroundOperationContainer(IntPtr javaReference, JniHandleOwnership transfer) : base(
|
||||||
|
javaReference, transfer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackgroundOperationContainer(Context context) : base(context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackgroundOperationContainer(Context context, IAttributeSet attrs) : base(context, attrs)
|
||||||
|
{
|
||||||
|
Initialize(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BackgroundOperationContainer(Context context, IAttributeSet attrs, int defStyle) : base(context,
|
||||||
|
attrs, defStyle)
|
||||||
|
{
|
||||||
|
Initialize(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize(IAttributeSet attrs)
|
||||||
|
{
|
||||||
|
|
||||||
|
LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
|
||||||
|
inflater.Inflate(Resource.Layout.background_operation_container, this);
|
||||||
|
|
||||||
|
FindViewById(Resource.Id.cancel_background).Click += (obj,args) =>
|
||||||
|
{
|
||||||
|
App.Kp2a.CancelBackgroundOperations();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Show()
|
||||||
|
{
|
||||||
|
App.Kp2a.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
Visibility = ViewStates.Visible;
|
||||||
|
FindViewById<TextView>(Resource.Id.background_ops_message)!.Visibility = ViewStates.Gone;
|
||||||
|
FindViewById<TextView>(Resource.Id.background_ops_submessage)!.Visibility = ViewStates.Gone;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Hide()
|
||||||
|
{
|
||||||
|
App.Kp2a.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
String activityType = Context.GetType().FullName;
|
||||||
|
Visibility = ViewStates.Gone;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMessage(string message)
|
||||||
|
{
|
||||||
|
App.Kp2a.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
TextView messageTextView = FindViewById<TextView>(Resource.Id.background_ops_message)!;
|
||||||
|
if (string.IsNullOrEmpty(message))
|
||||||
|
{
|
||||||
|
messageTextView.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
messageTextView.Visibility = ViewStates.Visible;
|
||||||
|
messageTextView.Text = message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSubMessage(string submessage)
|
||||||
|
{
|
||||||
|
App.Kp2a.UiThreadHandler.Post(() =>
|
||||||
|
{
|
||||||
|
TextView subMessageTextView = FindViewById<TextView>(Resource.Id.background_ops_submessage)!;
|
||||||
|
if (string.IsNullOrEmpty(submessage))
|
||||||
|
{
|
||||||
|
subMessageTextView.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
subMessageTextView.Visibility = ViewStates.Visible;
|
||||||
|
subMessageTextView.Text = submessage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -7,7 +7,6 @@ using Android.App;
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Content.Res;
|
using Android.Content.Res;
|
||||||
using Android.Graphics;
|
using Android.Graphics;
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Text;
|
using Android.Text;
|
||||||
using Android.Text.Method;
|
using Android.Text.Method;
|
||||||
@@ -18,6 +17,7 @@ using Android.Views;
|
|||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Google.Android.Material.Dialog;
|
using Google.Android.Material.Dialog;
|
||||||
using keepass2android;
|
using keepass2android;
|
||||||
|
using KeePassLib.Interfaces;
|
||||||
|
|
||||||
namespace keepass2android.views
|
namespace keepass2android.views
|
||||||
{
|
{
|
||||||
|
@@ -125,94 +125,103 @@ namespace keepass2android.view
|
|||||||
|
|
||||||
if (_groupBaseActivity.IsFinishing)
|
if (_groupBaseActivity.IsFinishing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_entry = pw;
|
_entry = pw;
|
||||||
_pos = pos;
|
_pos = pos;
|
||||||
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible;
|
try
|
||||||
ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible;
|
{
|
||||||
|
|
||||||
_db = App.Kp2a.FindDatabaseForElement(_entry);
|
|
||||||
|
|
||||||
ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon);
|
|
||||||
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
|
|
||||||
if (isExpired)
|
|
||||||
{
|
|
||||||
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, PwIcon.Expired, PwUuid.Zero, false);
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, pw.IconId, pw.CustomIconUuid, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
String title = pw.Strings.ReadSafe(PwDefs.TitleField);
|
|
||||||
title = SprEngine.Compile(title, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
|
|
||||||
var str = new SpannableString(title);
|
|
||||||
|
|
||||||
if (isExpired)
|
|
||||||
{
|
|
||||||
str.SetSpan(new StrikethroughSpan(), 0, title.Length, SpanTypes.ExclusiveExclusive);
|
|
||||||
}
|
|
||||||
_textView.TextFormatted = str;
|
|
||||||
|
|
||||||
if (_defaultTextColor == null)
|
|
||||||
_defaultTextColor = _textView.TextColors.DefaultColor;
|
|
||||||
|
|
||||||
if (_groupActivity.IsBeingMoved(_entry.Uuid))
|
|
||||||
{
|
|
||||||
int elementBeingMoved = Context.Resources.GetColor(Resource.Color.md_theme_inversePrimary);
|
|
||||||
_textView.SetTextColor(new Color(elementBeingMoved));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
_textView.SetTextColor(new Color((int)_defaultTextColor));
|
|
||||||
|
|
||||||
String detail = pw.Strings.ReadSafe(PwDefs.UserNameField);
|
|
||||||
detail = SprEngine.Compile(detail, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
|
|
||||||
|
|
||||||
if ((_showDetail == false) || (String.IsNullOrEmpty(detail)))
|
|
||||||
{
|
|
||||||
_textviewDetails.Visibility = ViewStates.Gone;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var strDetail = new SpannableString(detail);
|
|
||||||
|
|
||||||
if (isExpired)
|
|
||||||
{
|
|
||||||
strDetail.SetSpan(new StrikethroughSpan(), 0, detail.Length, SpanTypes.ExclusiveExclusive);
|
|
||||||
}
|
|
||||||
_textviewDetails.TextFormatted = strDetail;
|
|
||||||
|
|
||||||
_textviewDetails.Visibility = ViewStates.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( (!_showGroupFullPath) || (!_isSearchResult) ) {
|
|
||||||
_textgroupFullPath.Visibility = ViewStates.Gone;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
String groupDetail = pw.ParentGroup.GetFullPath();
|
|
||||||
if (App.Kp2a.OpenDatabases.Count() > 1)
|
|
||||||
{
|
|
||||||
groupDetail += "(" + App.Kp2a.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
var strGroupDetail = new SpannableString (groupDetail);
|
|
||||||
|
|
||||||
if (isExpired) {
|
|
||||||
strGroupDetail.SetSpan (new StrikethroughSpan (), 0, groupDetail.Length, SpanTypes.ExclusiveExclusive);
|
|
||||||
}
|
|
||||||
_textgroupFullPath.TextFormatted = strGroupDetail;
|
|
||||||
|
|
||||||
_textgroupFullPath.Visibility = ViewStates.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
//try to get totp data
|
|
||||||
UpdateTotp();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible;
|
||||||
|
ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible;
|
||||||
|
|
||||||
|
_db = App.Kp2a.FindDatabaseForElement(_entry);
|
||||||
|
|
||||||
|
ImageView iv = (ImageView)ev.FindViewById(Resource.Id.icon);
|
||||||
|
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
|
||||||
|
if (isExpired)
|
||||||
|
{
|
||||||
|
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, PwIcon.Expired, PwUuid.Zero, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_db.DrawableFactory.AssignDrawableTo(iv, Context, _db.KpDatabase, pw.IconId, pw.CustomIconUuid, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
String title = pw.Strings.ReadSafe(PwDefs.TitleField);
|
||||||
|
title = SprEngine.Compile(title, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
|
||||||
|
var str = new SpannableString(title);
|
||||||
|
|
||||||
|
if (isExpired)
|
||||||
|
{
|
||||||
|
str.SetSpan(new StrikethroughSpan(), 0, title.Length, SpanTypes.ExclusiveExclusive);
|
||||||
|
}
|
||||||
|
_textView.TextFormatted = str;
|
||||||
|
|
||||||
|
if (_defaultTextColor == null)
|
||||||
|
_defaultTextColor = _textView.TextColors.DefaultColor;
|
||||||
|
|
||||||
|
if (_groupActivity.IsBeingMoved(_entry.Uuid))
|
||||||
|
{
|
||||||
|
int elementBeingMoved = Context.Resources.GetColor(Resource.Color.md_theme_inversePrimary);
|
||||||
|
_textView.SetTextColor(new Color(elementBeingMoved));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_textView.SetTextColor(new Color((int)_defaultTextColor));
|
||||||
|
|
||||||
|
String detail = pw.Strings.ReadSafe(PwDefs.UserNameField);
|
||||||
|
detail = SprEngine.Compile(detail, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
|
||||||
|
|
||||||
|
if ((_showDetail == false) || (String.IsNullOrEmpty(detail)))
|
||||||
|
{
|
||||||
|
_textviewDetails.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var strDetail = new SpannableString(detail);
|
||||||
|
|
||||||
|
if (isExpired)
|
||||||
|
{
|
||||||
|
strDetail.SetSpan(new StrikethroughSpan(), 0, detail.Length, SpanTypes.ExclusiveExclusive);
|
||||||
|
}
|
||||||
|
_textviewDetails.TextFormatted = strDetail;
|
||||||
|
|
||||||
|
_textviewDetails.Visibility = ViewStates.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!_showGroupFullPath) || (!_isSearchResult))
|
||||||
|
{
|
||||||
|
_textgroupFullPath.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String groupDetail = pw.ParentGroup.GetFullPath();
|
||||||
|
if (App.Kp2a.OpenDatabases.Count() > 1)
|
||||||
|
{
|
||||||
|
groupDetail += "(" + App.Kp2a.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
var strGroupDetail = new SpannableString(groupDetail);
|
||||||
|
|
||||||
|
if (isExpired)
|
||||||
|
{
|
||||||
|
strGroupDetail.SetSpan(new StrikethroughSpan(), 0, groupDetail.Length, SpanTypes.ExclusiveExclusive);
|
||||||
|
}
|
||||||
|
_textgroupFullPath.TextFormatted = strGroupDetail;
|
||||||
|
|
||||||
|
_textgroupFullPath.Visibility = ViewStates.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
//try to get totp data
|
||||||
|
UpdateTotp();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.LogUnexpectedError(e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user