Merge pull request #2516 from PhilippC/803--totp-overview

TOTP overview
This commit is contained in:
PhilippC
2024-01-17 18:34:29 +01:00
committed by GitHub
11 changed files with 393 additions and 16 deletions

View File

@@ -72,8 +72,9 @@ namespace keepass2android
}
private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default);
private bool? _hasTotpEntries;
public bool ReloadRequested { get; set; }
public bool ReloadRequested { get; set; }
public bool DidOpenFileChange()
{
@@ -104,8 +105,9 @@ namespace keepass2android
SearchHelper = new SearchDbHelper(app);
_databaseFormat = databaseFormat;
_hasTotpEntries = null;
CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo);
CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo);
}
/// <summary>
@@ -200,8 +202,21 @@ namespace keepass2android
trans.CommitWrite();
}
}
_hasTotpEntries = null;
}
public bool HasTotpEntries
{
get
{
if (_hasTotpEntries == null)
{
_hasTotpEntries = true;
}
return _hasTotpEntries.Value;
}
}
private void PopulateGlobals(PwGroup currentGroup, bool checkForDuplicateUuids )
{

View File

@@ -39,6 +39,9 @@ using Android.Support.V4.View;
using Android.Views.Autofill;
using CursorAdapter = Android.Support.V4.Widget.CursorAdapter;
using Object = Java.Lang.Object;
using Android.Text;
using keepass2android.search;
using KeeTrayTOTP.Libraries;
namespace keepass2android
{
@@ -115,6 +118,8 @@ namespace keepass2android
FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabAddNew).Visibility = (showAddGroup || showAddEntry) ? ViewStates.Visible : ViewStates.Gone;
FindViewById(Resource.Id.fabSearch).Visibility = (showAddGroup || showAddEntry) ? ViewStates.Visible : ViewStates.Gone;
FindViewById(Resource.Id.fabTotpOverview).Visibility = CanShowTotpFab() ? ViewStates.Visible : ViewStates.Gone;
}
UpdateBottomBarElementVisibility(Resource.Id.insert_element, false);
@@ -262,6 +267,7 @@ namespace keepass2android
private bool hasCalledOtherActivity = false;
private IMenuItem searchItem;
private IMenuItem searchItemDummy;
private bool isPaused;
protected override void OnResume()
{
@@ -281,8 +287,39 @@ namespace keepass2android
RefreshIfDirty();
SetSearchItemVisibility();
}
isPaused = false;
System.Threading.Tasks.Task.Run(UpdateTotpCountdown);
}
private async System.Threading.Tasks.Task UpdateTotpCountdown()
{
while (!isPaused )
{
RunOnUiThread(() =>
{
var listView = FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment)
.ListView;
if (listView != null)
{
int count = listView.Count;
for (int i = 0; i < count; i++)
{
var item = listView.GetChildAt(i);
if (item is PwEntryView)
{
var entryView = (PwEntryView)item;
entryView.UpdateTotp();
}
}
}
});
await System.Threading.Tasks.Task.Delay(1000);
}
}
private void UpdateInfotexts()
{
@@ -390,6 +427,13 @@ namespace keepass2android
}
protected override void OnPause()
{
base.OnPause();
isPaused = true;
}
private void UpdatePostNotificationsPermissionInfo(bool hideForever=false)
{
const string prefsKey = "DidShowNotificationPermissionInfo";
@@ -572,6 +616,25 @@ namespace keepass2android
};
}
if (FindViewById(Resource.Id.fabSearch) != null)
{
FindViewById(Resource.Id.fabSearch).Click += (sender, args) =>
{
if (searchView?.Iconified != false)
ActivateSearchView();
else
searchView.Iconified = true;
};
}
if (FindViewById(Resource.Id.fabTotpOverview) != null)
{
FindViewById(Resource.Id.fabTotpOverview).Click += (sender, args) =>
{
SearchTotpResults.Launch(this, this.AppTask);
};
}
if (FindViewById(Resource.Id.fabCancelAddNew) != null)
{
FindViewById(Resource.Id.fabAddNew).Click += (sender, args) =>
@@ -580,6 +643,8 @@ namespace keepass2android
FindViewById(Resource.Id.fabAddNewGroup).Visibility = AddGroupEnabled ? ViewStates.Visible : ViewStates.Gone;
FindViewById(Resource.Id.fabAddNewEntry).Visibility = AddEntryEnabled ? ViewStates.Visible : ViewStates.Gone;
FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabSearch).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabTotpOverview).Visibility = ViewStates.Gone;
};
FindViewById(Resource.Id.fabCancelAddNew).Click += (sender, args) =>
@@ -588,6 +653,8 @@ namespace keepass2android
FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.fabSearch).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.fabTotpOverview).Visibility = CanShowTotpFab() ? ViewStates.Visible : ViewStates.Gone;
};
@@ -667,7 +734,12 @@ namespace keepass2android
}
protected virtual bool CanShowTotpFab()
{
return App.Kp2a.CurrentDb.HasTotpEntries && Group == App.Kp2a.CurrentDb.Root;
}
private bool IsTimeForInfotext(out string lastInfoText)
{
DateTime lastDisplayTime = new DateTime(_prefs.GetLong("LastInfoTextTime", 0));
@@ -1028,6 +1100,7 @@ namespace keepass2android
searchView.Iconified = false;
AppTask.CanActivateSearchViewOnStart = false;
}
@@ -1067,6 +1140,15 @@ namespace keepass2android
public abstract ElementAndDatabaseId FullGroupId { get; }
public virtual bool MayPreviewTotp
{
get
{
return !PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.masktotp_key),
Resources.GetBoolean(Resource.Boolean.masktotp_default));
}
}
public override bool OnPrepareOptionsMenu(IMenu menu)
{
@@ -1267,6 +1349,7 @@ namespace keepass2android
FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.fabSearch).Visibility = ViewStates.Gone;
UpdateBottomBarElementVisibility(Resource.Id.insert_element, true);
UpdateBottomBarElementVisibility(Resource.Id.cancel_insert_element, true);
@@ -1330,6 +1413,7 @@ namespace keepass2android
}
ListView.ItemClick += (sender, args) => ((GroupListItemView)args.View).OnClick();
StyleListView();

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -61,6 +61,28 @@
android:layout_width="wrap_content"
android:text="group detail"
style="@style/GroupDetailInSearchResult" />
<LinearLayout
android:id="@+id/totp_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/totp_text"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:text=""/>
<ProgressBar
android:id="@+id/TotpCountdownProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="30dp" />
</LinearLayout>
</LinearLayout>
<ImageView android:id="@+id/right_arrow"

View File

@@ -400,5 +400,23 @@
android:layout_marginRight="16dp"
android:layout_marginBottom="160dp"
android:visibility="gone"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right"
android:src="@drawable/ic_fab_search"
android:layout_marginRight="16dp"
android:layout_marginBottom="88dp"
android:visibility="gone"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabTotpOverview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right"
android:src="@drawable/ic_fab_totp"
android:layout_marginRight="16dp"
android:layout_marginBottom="160dp"
android:visibility="gone"/>
</android.support.design.widget.CoordinatorLayout>
</RelativeLayout>

View File

@@ -227,6 +227,7 @@
<Compile Include="fileselect\FileSelectActivity.cs" />
<Compile Include="fileselect\FileDbHelper.cs" />
<Compile Include="search\SearchProvider.cs" />
<Compile Include="search\SearchTotpResults.cs" />
<Compile Include="SelectStorageLocationActivity.cs" />
<Compile Include="services\AutofillBase\AutofillFieldMetadata.cs" />
<Compile Include="services\AutofillBase\AutofillFieldMetadataCollection.cs" />
@@ -1988,6 +1989,18 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\ic_entry_totp.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-mdpi\ic_fab_search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\ic_fab_search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-mdpi\ic_fab_totp.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\ic_fab_totp.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -0,0 +1,162 @@
/*
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 3 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 System.Linq;
using System.Text.RegularExpressions;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Preferences;
using Android.Views;
using Android.Widget;
using keepass2android.view;
using KeePassLib;
namespace keepass2android.search
{
/// <summary>
/// Activity to show search results
/// </summary>
[Activity(Label = "@string/app_name", Theme = "@style/MyTheme_ActionBar", LaunchMode = Android.Content.PM.LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)]
public class SearchTotpResults : GroupBaseActivity
{
public static void Launch(Activity act, AppTask appTask, ActivityFlags? flags = null)
{
Intent i = new Intent(act, typeof(SearchTotpResults));
if (flags != null)
i.SetFlags((ActivityFlags)flags);
appTask.ToIntent(i);
if (flags != null && (((ActivityFlags)flags) | ActivityFlags.ForwardResult) == ActivityFlags.ForwardResult)
act.StartActivity(i);
else
act.StartActivityForResult(i, 0);
}
public override bool MayPreviewTotp
{
get
{
return true;
}
}
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
if ( IsFinishing ) {
return;
}
SetResult(KeePass.ExitNormal);
// Likely the app has been killed exit the activity
if (!App.Kp2a.DatabaseIsUnlocked)
{
Finish();
}
Group = new PwGroup()
{
Name = GetString(Resource.String.TOTP)
};
try
{
foreach (var db in App.Kp2a.OpenDatabases)
{
foreach (var entry in db.EntriesById.Values)
{
var totpData = new Kp2aTotp().TryGetTotpData(new PwEntryOutput(entry, db));
if (totpData?.IsTotpEntry == true)
Group.AddEntry(entry, false);
}
}
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
Toast.MakeText(this, e.Message, ToastLength.Long).Show();
Finish();
return;
}
if (Group == null || (!Group.Entries.Any()))
{
SetContentView(Resource.Layout.group_empty);
}
SetGroupTitle();
FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment).ListAdapter = new PwGroupListAdapter(this, Group);
}
public override bool EntriesBelongToCurrentDatabaseOnly
{
get { return false; }
}
public override ElementAndDatabaseId FullGroupId
{
get { return null; }
}
public override void OnCreateContextMenu(IContextMenu menu, View v,
IContextMenuContextMenuInfo menuInfo)
{
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
ClickView cv = (ClickView) acmi.TargetView;
cv.OnCreateMenu(menu, menuInfo);
}
public override bool OnContextItemSelected(IMenuItem item) {
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo)item.MenuInfo;
ClickView cv = (ClickView) acmi.TargetView;
bool result;
return cv.OnContextItemSelected(item);
}
public override bool OnSearchRequested()
{
Intent i = new Intent(this, typeof(SearchActivity));
this.AppTask.ToIntent(i);
i.SetFlags(ActivityFlags.ForwardResult);
StartActivity(i);
return true;
}
public override bool IsSearchResult
{
get { return true; }
}
}
}

View File

@@ -26,6 +26,10 @@ using Android.Text;
using Android.Text.Style;
using Android.Preferences;
using KeePass.Util.Spr;
using KeeTrayTOTP.Libraries;
using PluginTOTP;
using Android.Content;
using System.ComponentModel;
namespace keepass2android.view
@@ -37,8 +41,11 @@ namespace keepass2android.view
private readonly TextView _textView;
private readonly TextView _textviewDetails;
private readonly TextView _textgroupFullPath;
private readonly ProgressBar _totpCountdown;
private readonly TextView _totpText;
private readonly LinearLayout _totpLayout;
private int _pos;
private int _pos;
private int? _defaultTextColor;
@@ -82,7 +89,18 @@ namespace keepass2android.view
_textgroupFullPath = (TextView)ev.FindViewById(Resource.Id.group_detail);
_textgroupFullPath.TextSize = PrefsUtil.GetListDetailTextSize(groupActivity);
_showDetail = PreferenceManager.GetDefaultSharedPreferences(groupActivity).GetBoolean(
_totpCountdown = ev.FindViewById<ProgressBar>(Resource.Id.TotpCountdownProgressBar);
_totpText = ev.FindViewById<TextView>(Resource.Id.totp_text);
_totpLayout = ev.FindViewById<LinearLayout>(Resource.Id.totp_layout);
_totpLayout.LongClick += (sender, args) =>
{
string totp = UpdateTotp();
if (!String.IsNullOrEmpty(totp))
CopyToClipboardService.CopyValueToClipboardWithTimeout(_groupActivity, totp, true);
};
_showDetail = PreferenceManager.GetDefaultSharedPreferences(groupActivity).GetBoolean(
groupActivity.GetString(Resource.String.ShowUsernameInList_key),
Resources.GetBoolean(Resource.Boolean.ShowUsernameInList_default));
@@ -112,20 +130,20 @@ namespace keepass2android.view
ev.FindViewById(Resource.Id.icon).Visibility = ViewStates.Visible;
ev.FindViewById(Resource.Id.check_mark).Visibility = ViewStates.Invisible;
Database db = App.Kp2a.FindDatabaseForElement(_entry);
_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);
_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);
_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));
title = SprEngine.Compile(title, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
var str = new SpannableString(title);
if (isExpired)
@@ -146,7 +164,7 @@ namespace keepass2android.view
_textView.SetTextColor(new Color((int)_defaultTextColor));
String detail = pw.Strings.ReadSafe(PwDefs.UserNameField);
detail = SprEngine.Compile(detail, new SprContext(_entry, db.KpDatabase, SprCompileFlags.All));
detail = SprEngine.Compile(detail, new SprContext(_entry, _db.KpDatabase, SprCompileFlags.All));
if ((_showDetail == false) || (String.IsNullOrEmpty(detail)))
{
@@ -173,7 +191,7 @@ namespace keepass2android.view
String groupDetail = pw.ParentGroup.GetFullPath();
if (App.Kp2a.OpenDatabases.Count() > 1)
{
groupDetail += "(" + App.Kp2a.GetFileStorage(db.Ioc).GetDisplayName(db.Ioc) + ")";
groupDetail += "(" + App.Kp2a.GetFileStorage(_db.Ioc).GetDisplayName(_db.Ioc) + ")";
}
var strGroupDetail = new SpannableString (groupDetail);
@@ -186,7 +204,16 @@ namespace keepass2android.view
_textgroupFullPath.Visibility = ViewStates.Visible;
}
}
//try to get totp data
UpdateTotp();
}
public void ConvertView(PwEntry pw, int pos)
{
@@ -248,6 +275,42 @@ namespace keepass2android.view
{
LaunchEntry();
}
}
private TotpData _totpData;
private Database _db;
public string UpdateTotp()
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(_groupActivity);
bool showTotpDefault = _groupActivity.MayPreviewTotp;
if (showTotpDefault)
_totpData = new Kp2aTotp().TryGetTotpData(new PwEntryOutput(_entry, _db));
else
_totpData = null;
if (_totpData?.IsTotpEntry != true)
{
_totpLayout.Visibility = ViewStates.Gone;
return null;
}
_totpLayout.Visibility = ViewStates.Visible;
TOTPProvider prov = new TOTPProvider(_totpData);
string totp = prov.GenerateByByte(_totpData.TotpSecret);
_totpText.Text = totp;
var progressBar = _totpCountdown;
progressBar.Progress = prov.Timer;
progressBar.Max = prov.Duration;
return totp;
}
}
}