Merge branch 'master' of https://git01.codeplex.com/forks/alexvallat/keepass2androidperfopt into AlexVallat/Keepass2AndroidPerfOpt

Conflicts:
	src/keepass2android/QuickUnlock.cs
This commit is contained in:
Philipp Crocoll 2013-08-09 21:25:54 +02:00
commit 3b1b10385f
11 changed files with 640 additions and 615 deletions

View File

@ -109,7 +109,7 @@ namespace keepass2android
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo); var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
try try
{ {
pwDatabase.Open(databaseData, filename, iocInfo, compositeKey, status); pwDatabase.Open(databaseData ?? fileStorage.OpenFileForRead(iocInfo), filename, iocInfo, compositeKey, status);
} }
catch (Exception) catch (Exception)
{ {
@ -118,8 +118,11 @@ namespace keepass2android
//if we don't get a password, we don't know whether this means "empty password" or "no password" //if we don't get a password, we don't know whether this means "empty password" or "no password"
//retry without password: //retry without password:
compositeKey.RemoveUserKey(compositeKey.GetUserKey(typeof (KcpPassword))); compositeKey.RemoveUserKey(compositeKey.GetUserKey(typeof (KcpPassword)));
databaseData.Seek(0, SeekOrigin.Begin); if (databaseData != null)
pwDatabase.Open(databaseData, filename, iocInfo, compositeKey, status); {
databaseData.Seek(0, SeekOrigin.Begin);
}
pwDatabase.Open(databaseData ?? fileStorage.OpenFileForRead(iocInfo), filename, iocInfo, compositeKey, status);
} }
else throw; else throw;
} }

View File

@ -17,19 +17,20 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks;
using KeePassLib.Serialization; using KeePassLib.Serialization;
namespace keepass2android namespace keepass2android
{ {
public class LoadDb : RunnableOnFinish { public class LoadDb : RunnableOnFinish {
private readonly IOConnectionInfo _ioc; private readonly IOConnectionInfo _ioc;
private readonly MemoryStream _databaseData; private readonly Task<MemoryStream> _databaseData;
private readonly String _pass; private readonly String _pass;
private readonly String _key; private readonly String _key;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile; private readonly bool _rememberKeyfile;
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, MemoryStream databaseData, String pass, String key, OnFinish finish): base(finish) public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, String pass, String key, OnFinish finish): base(finish)
{ {
_app = app; _app = app;
_ioc = ioc; _ioc = ioc;
@ -47,7 +48,7 @@ namespace keepass2android
try try
{ {
StatusLogger.UpdateMessage(UiStringKey.loading_database); StatusLogger.UpdateMessage(UiStringKey.loading_database);
_app.LoadDatabase(_ioc, _databaseData, _pass, _key, StatusLogger); _app.LoadDatabase(_ioc, _databaseData == null ? null : _databaseData.Result, _pass, _key, StatusLogger);
SaveFileData (_ioc, _key); SaveFileData (_ioc, _key);
} catch (KeyFileException) { } catch (KeyFileException) {

View File

@ -351,7 +351,7 @@ namespace keepass2android
Toast.MakeText(_act, "Unrecoverable error: " + Message, ToastLength.Long).Show(); Toast.MakeText(_act, "Unrecoverable error: " + Message, ToastLength.Long).Show();
}); });
App.Kp2a.LockDatabase(); App.Kp2a.LockDatabase(false);
} }
} }

View File

@ -37,12 +37,9 @@ namespace keepass2android
public const Result ExitLock = Result.FirstUser+1; public const Result ExitLock = Result.FirstUser+1;
public const Result ExitRefresh = Result.FirstUser+2; public const Result ExitRefresh = Result.FirstUser+2;
public const Result ExitRefreshTitle = Result.FirstUser+3; public const Result ExitRefreshTitle = Result.FirstUser+3;
public const Result ExitForceLock = Result.FirstUser+4; public const Result ExitCloseAfterTaskComplete = Result.FirstUser+4;
public const Result ExitQuickUnlock = Result.FirstUser+5; public const Result ExitChangeDb = Result.FirstUser+5; // NOTE: Nothing is currently using this, but in the future a "Change Database" menu option might.
public const Result ExitCloseAfterTaskComplete = Result.FirstUser+6; public const Result ExitReloadDb = Result.FirstUser+6;
public const Result ExitChangeDb = Result.FirstUser+7;
public const Result ExitForceLockAndChangeDb = Result.FirstUser+8;
public const Result ExitReloadDb = Result.FirstUser+9;
AppTask _appTask; AppTask _appTask;

View File

@ -59,6 +59,8 @@ namespace keepass2android
private bool _rememberKeyfile; private bool _rememberKeyfile;
ISharedPreferences _prefs; ISharedPreferences _prefs;
private bool _started;
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer) public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) : base(javaReference, transfer)
{ {
@ -128,53 +130,32 @@ namespace keepass2android
public void LaunchNextActivity() public void LaunchNextActivity()
{ {
AppTask.AfterUnlockDatabase(this); AppTask.AfterUnlockDatabase(this);
} }
void TryStartQuickUnlock()
{
if (App.Kp2a.QuickUnlockEnabled && App.Kp2a.QuickLocked)
{
Intent i = new Intent(this, typeof(QuickUnlock));
PutIoConnectionToIntent(_ioConnection, i);
Kp2aLog.Log("Starting QuickUnlock");
StartActivityForResult(i, 0);
}
}
bool _startedWithActivityResult;
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{ {
base.OnActivityResult(requestCode, resultCode, data); base.OnActivityResult(requestCode, resultCode, data);
_startedWithActivityResult = true;
Kp2aLog.Log("PasswordActivity.OnActivityResult "+resultCode+"/"+requestCode); Kp2aLog.Log("PasswordActivity.OnActivityResult "+resultCode+"/"+requestCode);
//NOTE: original code from k eepassdroid used switch ((Android.App.Result)requestCode) { (but doesn't work here, although k eepassdroid works) //NOTE: original code from k eepassdroid used switch ((Android.App.Result)requestCode) { (but doesn't work here, although k eepassdroid works)
switch(resultCode) { switch(resultCode) {
case KeePass.ExitNormal: // Returned to this screen using the Back key, treat as locking the database
App.Kp2a.LockDatabase();
break;
case KeePass.ExitLock: case KeePass.ExitLock:
// The database has already been locked, just show the quick unlock screen if appropriate // The database has already been locked, and the quick unlock screen will be shown if appropriate
TryStartQuickUnlock();
break; break;
case KeePass.ExitForceLock: case KeePass.ExitChangeDb:
App.Kp2a.LockDatabase(false);
break;
case KeePass.ExitForceLockAndChangeDb:
case KeePass.ExitChangeDb: // What's the difference between this and ExitForceLockAndChangeDb?
case KeePass.ExitNormal: // Returned to this screen using the Back key, treat as exiting the database
App.Kp2a.LockDatabase(false); App.Kp2a.LockDatabase(false);
Finish(); Finish();
break; break;
case KeePass.ExitCloseAfterTaskComplete: case KeePass.ExitCloseAfterTaskComplete:
// Do not lock the database
SetResult(KeePass.ExitCloseAfterTaskComplete); SetResult(KeePass.ExitCloseAfterTaskComplete);
Finish(); Finish();
break; break;
case KeePass.ExitQuickUnlock:
App.Kp2a.UnlockDatabase();
LaunchNextActivity();
break;
case KeePass.ExitReloadDb: case KeePass.ExitReloadDb:
//if the activity was killed, fill password/keyfile so the user can directly hit load again //if the activity was killed, fill password/keyfile so the user can directly hit load again
if (App.Kp2a.GetDb().Loaded) if (App.Kp2a.GetDb().Loaded)
@ -196,8 +177,9 @@ namespace keepass2android
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path); SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
} }
} }
App.Kp2a.LockDatabase(false);
break; break;
case Result.Ok: case Result.Ok: // Key file browse dialog OK'ed.
if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) { if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) {
string filename = Util.IntentToFilename(data); string filename = Util.IntentToFilename(data);
if (filename != null) { if (filename != null) {
@ -274,6 +256,13 @@ namespace keepass2android
} }
} }
if (App.Kp2a.GetDb().Loaded && App.Kp2a.GetDb().Ioc != null &&
App.Kp2a.GetDb().Ioc.GetDisplayName() != _ioConnection.GetDisplayName())
{
// A different database is currently loaded, unload it before loading the new one requested
App.Kp2a.LockDatabase(false);
}
AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
SetContentView(Resource.Layout.password); SetContentView(Resource.Layout.password);
@ -286,8 +275,7 @@ namespace keepass2android
Window.SetSoftInputMode(SoftInput.StateVisible); Window.SetSoftInputMode(SoftInput.StateVisible);
Button confirmButton = (Button)FindViewById(Resource.Id.pass_ok); Button confirmButton = (Button)FindViewById(Resource.Id.pass_ok);
confirmButton.Click += (sender, e) => confirmButton.Click += (sender, e) => {
{
String pass = GetEditText(Resource.Id.password); String pass = GetEditText(Resource.Id.password);
String key = GetEditText(Resource.Id.pass_keyfile); String key = GetEditText(Resource.Id.pass_keyfile);
if (pass.Length == 0 && key.Length == 0) if (pass.Length == 0 && key.Length == 0)
@ -298,13 +286,12 @@ namespace keepass2android
CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock); CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked); App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
Handler handler = new Handler(); Handler handler = new Handler();
var stream = _loadDbTask.Result; LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbTask, pass, key, new AfterLoad(handler, this));
_loadDbTask = null; // prevent accidental re-use _loadDbTask = null; // prevent accidental re-use
LoadDb task = new LoadDb(App.Kp2a, _ioConnection, stream, pass, key, new AfterLoad(handler, this));
ProgressTask pt = new ProgressTask(App.Kp2a, this, task); new ProgressTask(App.Kp2a, this, task).Run();
pt.Run();
}; };
/*CheckBox checkBox = (CheckBox) FindViewById(Resource.Id.show_password); /*CheckBox checkBox = (CheckBox) FindViewById(Resource.Id.show_password);
@ -381,9 +368,7 @@ namespace keepass2android
protected override void OnStart() protected override void OnStart()
{ {
base.OnStart(); base.OnStart();
_started = true;
// Create task to kick off file loading while the user enters the password
_loadDbTask = Task.Factory.StartNew<MemoryStream>(LoadDbFile);
} }
private MemoryStream LoadDbFile() private MemoryStream LoadDbFile()
@ -402,7 +387,7 @@ namespace keepass2android
capacity = (int)stream.Length; capacity = (int)stream.Length;
} }
memoryStream = new MemoryStream(capacity); memoryStream = new MemoryStream(capacity);
MemUtil.CopyStream(stream, memoryStream); stream.CopyTo(memoryStream);
stream.Close(); stream.Close();
memoryStream.Seek(0, System.IO.SeekOrigin.Begin); memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
} }
@ -418,22 +403,30 @@ namespace keepass2android
AppTask.ToBundle(outState); AppTask.ToBundle(outState);
} }
protected override void OnResume() { protected override void OnResume()
{
base.OnResume(); base.OnResume();
if (_startedWithActivityResult)
return;
if (App.Kp2a.GetDb().Loaded && (App.Kp2a.GetDb().Ioc != null) // OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
&& (_ioConnection != null) && (App.Kp2a.GetDb().Ioc.GetDisplayName() == _ioConnection.GetDisplayName())) // be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult)
if (_started)
{ {
if (App.Kp2a.QuickLocked == false) _started = false;
if (App.Kp2a.DatabaseIsUnlocked)
{ {
LaunchNextActivity(); LaunchNextActivity();
} }
else else if (App.Kp2a.QuickUnlockEnabled && App.Kp2a.QuickLocked)
{ {
TryStartQuickUnlock(); var i = new Intent(this, typeof(QuickUnlock));
PutIoConnectionToIntent(_ioConnection, i);
Kp2aLog.Log("Starting QuickUnlock");
StartActivityForResult(i, 0);
}
else if (_loadDbTask == null && _prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
{
// Create task to kick off file loading while the user enters the password
_loadDbTask = Task.Factory.StartNew<MemoryStream>(LoadDbFile);
} }
} }
} }
@ -516,17 +509,24 @@ namespace keepass2android
private class AfterLoad : OnFinish { private class AfterLoad : OnFinish {
readonly PasswordActivity _act; readonly PasswordActivity _act;
public AfterLoad(Handler handler, PasswordActivity act):base(handler) {
public AfterLoad(Handler handler, PasswordActivity act):base(handler)
{
_act = act; _act = act;
} }
public override void Run() { public override void Run() {
if ( Success ) { if ( Success )
{
_act.SetEditText(Resource.Id.password, ""); _act.SetEditText(Resource.Id.password, "");
_act.LaunchNextActivity(); _act.LaunchNextActivity();
} else {
GC.Collect(); // Ensure temporary memory used while loading is collected - it will contain sensitive data such as username and password, and also the large data of the encrypted database file
}
else
{
DisplayMessage(_act); DisplayMessage(_act);
} }
} }

View File

@ -65,11 +65,10 @@ namespace keepass2android
txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[]{quickUnlockLength}); txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[]{quickUnlockLength});
SetResult(KeePass.ExitChangeDb); EditText pwd= (EditText)FindViewById(Resource.Id.QuickUnlock_password);
EditText pwd = (EditText)FindViewById(Resource.Id.QuickUnlock_password);
pwd.SetEms(quickUnlockLength); pwd.SetEms(quickUnlockLength);
Button btnUnlock = (Button)FindViewById(Resource.Id.QuickUnlock_button); Button btnUnlock = (Button)FindViewById(Resource.Id.QuickUnlock_button);
btnUnlock.Click += (object sender, EventArgs e) => btnUnlock.Click += (object sender, EventArgs e) =>
{ {
@ -78,11 +77,11 @@ namespace keepass2android
String expectedPasswordPart = password.Substring(Math.Max(0,password.Length-quickUnlockLength),Math.Min(password.Length, quickUnlockLength)); String expectedPasswordPart = password.Substring(Math.Max(0,password.Length-quickUnlockLength),Math.Min(password.Length, quickUnlockLength));
if (pwd.Text == expectedPasswordPart) if (pwd.Text == expectedPasswordPart)
{ {
SetResult(KeePass.ExitQuickUnlock); App.Kp2a.UnlockDatabase();
} }
else else
{ {
SetResult(KeePass.ExitForceLock); App.Kp2a.LockDatabase(false);
Toast.MakeText(this, GetString(Resource.String.QuickUnlock_fail), ToastLength.Long).Show(); Toast.MakeText(this, GetString(Resource.String.QuickUnlock_fail), ToastLength.Long).Show();
} }
Finish(); Finish();
@ -91,29 +90,21 @@ namespace keepass2android
Button btnLock = (Button)FindViewById(Resource.Id.QuickUnlock_buttonLock); Button btnLock = (Button)FindViewById(Resource.Id.QuickUnlock_buttonLock);
btnLock.Click += (object sender, EventArgs e) => btnLock.Click += (object sender, EventArgs e) =>
{ {
SetResult(KeePass.ExitForceLockAndChangeDb); App.Kp2a.LockDatabase(false);
Finish(); Finish();
}; };
} }
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();
if ( ! App.Kp2a.GetDb().Loaded ) {
SetResult(KeePass.ExitChangeDb);
Finish();
return;
}
EditText pwd = (EditText)FindViewById(Resource.Id.QuickUnlock_password); EditText pwd = (EditText)FindViewById(Resource.Id.QuickUnlock_password);
pwd.PostDelayed(() => pwd.PostDelayed(() =>
{ {
InputMethodManager keyboard = (InputMethodManager)GetSystemService(Context.InputMethodService); InputMethodManager keyboard = (InputMethodManager)GetSystemService(Context.InputMethodService);
keyboard.ShowSoftInput(pwd, 0); keyboard.ShowSoftInput(pwd, 0);
}, 50); }, 50);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -92,4 +92,7 @@
<string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string> <string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string>
<bool name="ShowUnlockedNotification_default">true</bool> <bool name="ShowUnlockedNotification_default">true</bool>
<string name="PreloadDatabaseEnabled_key">PreloadDatabaseEnabled</string>
<bool name="PreloadDatabaseEnabled_default">true</bool>
</resources> </resources>

View File

@ -216,7 +216,7 @@
<string name="add_extra_string">Add additional string</string> <string name="add_extra_string">Add additional string</string>
<string name="delete_extra_string">Delete additional string</string> <string name="delete_extra_string">Delete additional string</string>
<string name="database_loaded_quickunlock_enabled">%1$s: Locked. QuickUnlock enabled.</string> <string name="database_loaded_quickunlock_enabled">%1$s: Locked. QuickUnlock enabled.</string>
<string name="database_loaded_unlocked">%1$s: Unlocked. Tap to lock.</string> <string name="database_loaded_unlocked">%1$s: Unlocked.</string>
<string name="credentials_dialog_title">Enter server credentials</string> <string name="credentials_dialog_title">Enter server credentials</string>
<string name="UseFileTransactions_title">File transactions</string> <string name="UseFileTransactions_title">File transactions</string>
<string name="UseFileTransactions_summary">Use file transactions for writing databases</string> <string name="UseFileTransactions_summary">Use file transactions for writing databases</string>
@ -232,8 +232,11 @@
<string name="ShowUnlockedNotification_title">Notification while unlocked</string> <string name="ShowUnlockedNotification_title">Notification while unlocked</string>
<string name="ShowUnlockedNotification_summary">Show an ongoing notification while the database is unlocked.</string> <string name="ShowUnlockedNotification_summary">Show an ongoing notification while the database is unlocked.</string>
<string name="AskOverwriteBinary">Do you want to overwrite the existing binary with the same name?</string> <string name="PreloadDatabaseEnabled_title">Pre-load database file</string>
<string name="PreloadDatabaseEnabled_summary">Start background loading or downloading of the database file during password entry.</string>
<string name="AskOverwriteBinary">Do you want to overwrite the existing binary with the same name?</string>
<string name="AskOverwriteBinary_title">Overwrite existing binary?</string> <string name="AskOverwriteBinary_title">Overwrite existing binary?</string>
<string name="AskOverwriteBinary_yes">Overwrite</string> <string name="AskOverwriteBinary_yes">Overwrite</string>
<string name="AskOverwriteBinary_no">Rename</string> <string name="AskOverwriteBinary_no">Rename</string>

View File

@ -163,6 +163,12 @@
android:defaultValue="true" android:defaultValue="true"
android:title="@string/CheckForFileChangesOnSave_title" android:title="@string/CheckForFileChangesOnSave_title"
android:key="@string/CheckForFileChangesOnSave_key" /> android:key="@string/CheckForFileChangesOnSave_key" />
<CheckBoxPreference
android:enabled="true"
android:persistent="true"
android:summary="@string/PreloadDatabaseEnabled_summary"
android:defaultValue="@bool/PreloadDatabaseEnabled_default"
android:title="@string/PreloadDatabaseEnabled_title"
android:key="@string/PreloadDatabaseEnabled_key" />
</PreferenceScreen> </PreferenceScreen>
</PreferenceScreen> </PreferenceScreen>

View File

@ -128,22 +128,19 @@ namespace keepass2android
new NotificationCompat.Builder(this) new NotificationCompat.Builder(this)
.SetSmallIcon(Resource.Drawable.ic_launcher_gray) .SetSmallIcon(Resource.Drawable.ic_launcher_gray)
.SetLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.LauncherIcon)) .SetLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.LauncherIcon))
.SetContentTitle(GetText(Resource.String.app_name)) .SetContentTitle(GetString(Resource.String.app_name))
.SetContentText(GetString(Resource.String.database_loaded_quickunlock_enabled, GetDatabaseName())); .SetContentText(GetString(Resource.String.database_loaded_quickunlock_enabled, GetDatabaseName()));
Intent startKp2aIntent = new Intent(this, typeof(KeePass)); var startKp2APendingIntent = GetSwitchToAppPendingIntent();
startKp2aIntent.SetAction(Intent.ActionMain);
startKp2aIntent.AddCategory(Intent.CategoryLauncher);
PendingIntent startKp2APendingIntent =
PendingIntent.GetActivity(this, 0, startKp2aIntent, PendingIntentFlags.UpdateCurrent);
builder.SetContentIntent(startKp2APendingIntent); builder.SetContentIntent(startKp2APendingIntent);
return builder.Build(); return builder.Build();
} }
#endregion #endregion
#region Unlocked Warning #region Unlocked Warning
private Notification GetUnlockedNotification() private Notification GetUnlockedNotification()
{ {
NotificationCompat.Builder builder = NotificationCompat.Builder builder =
@ -151,14 +148,26 @@ namespace keepass2android
.SetOngoing(true) .SetOngoing(true)
.SetSmallIcon(Resource.Drawable.ic_unlocked_gray) .SetSmallIcon(Resource.Drawable.ic_unlocked_gray)
.SetLargeIcon(BitmapFactory.DecodeResource(Resources, Resource.Drawable.ic_launcher_red)) .SetLargeIcon(BitmapFactory.DecodeResource(Resources, Resource.Drawable.ic_launcher_red))
.SetContentTitle(GetText(Resource.String.app_name)) .SetContentTitle(GetString(Resource.String.app_name))
.SetContentText(GetString(Resource.String.database_loaded_unlocked, GetDatabaseName())); .SetContentText(GetString(Resource.String.database_loaded_unlocked, GetDatabaseName()));
builder.SetContentIntent(PendingIntent.GetBroadcast(this, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent)); // Default action is to show Kp2A
builder.SetContentIntent(GetSwitchToAppPendingIntent());
// Additional action to allow locking the database
builder.AddAction(Android.Resource.Drawable.IcLockLock, GetString(Resource.String.menu_lock), PendingIntent.GetBroadcast(this, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent));
return builder.Build(); return builder.Build();
} }
private PendingIntent GetSwitchToAppPendingIntent()
{
var startKp2aIntent = new Intent(this, typeof(KeePass));
startKp2aIntent.SetAction(Intent.ActionMain);
startKp2aIntent.AddCategory(Intent.CategoryLauncher);
return PendingIntent.GetActivity(this, 0, startKp2aIntent, PendingIntentFlags.UpdateCurrent);
}
private static string GetDatabaseName() private static string GetDatabaseName()
{ {