Merge branch 'master' into 2507--dialog-behind-keyboard

This commit is contained in:
PhilippC
2024-01-13 11:47:12 +01:00
committed by GitHub
27 changed files with 642 additions and 203 deletions

View File

@@ -91,6 +91,28 @@ namespace keepass2android
} }
public PwGroup SearchForUuid(Database database, string uuid)
{
SearchParameters sp = SearchParameters.None;
sp.SearchInUuids = true;
sp.SearchString = uuid;
if (sp.RegularExpression) // Validate regular expression
{
new Regex(sp.SearchString);
}
string strGroupName = _app.GetResourceString(UiStringKey.search_results);
PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch) { IsVirtual = true };
PwObjectList<PwEntry> listResults = pgResults.Entries;
database.Root.SearchEntries(sp, listResults, new NullStatusLogger());
return pgResults;
}
private static String ExtractHost(String url) private static String ExtractHost(String url)
{ {
return UrlUtil.GetHost(url.Trim()); return UrlUtil.GetHost(url.Trim());

View File

@@ -175,6 +175,13 @@ namespace keepass2android
return group; return group;
}
public PwGroup SearchForUuid(String uuid)
{
PwGroup group = SearchHelper.SearchForUuid(this, uuid);
return group;
} }
public PwGroup SearchForHost(String url, bool allowSubdomains) { public PwGroup SearchForHost(String url, bool allowSubdomains) {

View File

@@ -32,6 +32,7 @@ using Android.Text.Method;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks;
using Android.Content.PM; using Android.Content.PM;
using Android.Webkit; using Android.Webkit;
using Android.Graphics; using Android.Graphics;
@@ -49,7 +50,9 @@ using PluginTOTP;
using File = Java.IO.File; using File = Java.IO.File;
using Uri = Android.Net.Uri; using Uri = Android.Net.Uri;
using keepass2android.fileselect; using keepass2android.fileselect;
using KeeTrayTOTP.Libraries;
using Boolean = Java.Lang.Boolean; using Boolean = Java.Lang.Boolean;
using Android.Util;
namespace keepass2android namespace keepass2android
{ {
@@ -286,6 +289,8 @@ namespace keepass2android
extraGroup.AddView(view.View); extraGroup.AddView(view.View);
} }
SetPasswordStyle();
//update the Entry output in the App database and notify the CopyToClipboard service //update the Entry output in the App database and notify the CopyToClipboard service
if (App.Kp2a.LastOpenedEntry != null) if (App.Kp2a.LastOpenedEntry != null)
@@ -488,10 +493,11 @@ namespace keepass2android
_pluginFieldReceiver = new PluginFieldReceiver(this); _pluginFieldReceiver = new PluginFieldReceiver(this);
RegisterReceiver(_pluginFieldReceiver, new IntentFilter(Strings.ActionSetEntryField)); RegisterReceiver(_pluginFieldReceiver, new IntentFilter(Strings.ActionSetEntryField));
new Thread(NotifyPluginsOnOpen).Start(); var notifyPluginsOnOpenThread = new Thread(NotifyPluginsOnOpen);
notifyPluginsOnOpenThread.Start();
//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); AppTask.CompleteOnCreateEntryActivity(this, notifyPluginsOnOpenThread);
} }
private void RemoveFromHistory() private void RemoveFromHistory()
@@ -664,7 +670,7 @@ namespace keepass2android
EditModeBase editMode = new DefaultEdit(); EditModeBase editMode = new DefaultEdit();
if (KpEntryTemplatedEdit.IsTemplated(App.Kp2a.CurrentDb, this.Entry)) if (KpEntryTemplatedEdit.IsTemplated(App.Kp2a.CurrentDb, this.Entry))
editMode = new KpEntryTemplatedEdit(App.Kp2a.CurrentDb, this.Entry); editMode = new KpEntryTemplatedEdit(App.Kp2a.CurrentDb, this.Entry);
foreach (var key in editMode.SortExtraFieldKeys(Entry.Strings.GetKeys().Where(key=> !PwDefs.IsStandardField(key)))) foreach (var key in editMode.SortExtraFieldKeys(Entry.Strings.GetKeys().Where(key=> !PwDefs.IsStandardField(key) && key != Kp2aTotp.TotpKey)))
{ {
if (editMode.IsVisible(key)) if (editMode.IsVisible(key))
{ {
@@ -840,7 +846,7 @@ namespace keepass2android
{ {
if (!_showPassword.ContainsKey(protectedTextView)) if (!_showPassword.ContainsKey(protectedTextView))
{ {
_showPassword[protectedTextView] = fieldKey == UpdateTotpTimerTask.TotpKey ? _showTotpDefault : _showPasswordDefault; _showPassword[protectedTextView] = fieldKey == Kp2aTotp.TotpKey ? _showTotpDefault : _showPasswordDefault;
} }
var protectedTextviewGroup = new ProtectedTextviewGroup { ProtectedField = protectedTextView, VisibleProtectedField = visibleTextView}; var protectedTextviewGroup = new ProtectedTextviewGroup { ProtectedField = protectedTextView, VisibleProtectedField = visibleTextView};
_protectedTextViews.Add(protectedTextviewGroup); _protectedTextViews.Add(protectedTextviewGroup);
@@ -946,9 +952,11 @@ namespace keepass2android
PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField); PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField);
PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField); PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField);
PopulateStandardText(new List<int> { Resource.Id.entry_totp, Resource.Id.entry_totp_visible }, Resource.Id.entryfield_container_totp, Kp2aTotp.TotpKey);
PopulateStandardText(new List<int> { Resource.Id.entry_password, Resource.Id.entry_password_visible}, Resource.Id.entryfield_container_password, PwDefs.PasswordField); PopulateStandardText(new List<int> { Resource.Id.entry_password, Resource.Id.entry_password_visible}, Resource.Id.entryfield_container_password, PwDefs.PasswordField);
RegisterProtectedTextView(PwDefs.PasswordField, FindViewById<TextView>(Resource.Id.entry_password), FindViewById<TextView>(Resource.Id.entry_password_visible)); RegisterProtectedTextView(PwDefs.PasswordField, FindViewById<TextView>(Resource.Id.entry_password), FindViewById<TextView>(Resource.Id.entry_password_visible));
RegisterProtectedTextView(Kp2aTotp.TotpKey, FindViewById<TextView>(Resource.Id.entry_totp), FindViewById<TextView>(Resource.Id.entry_totp_visible));
RegisterTextPopup(FindViewById<RelativeLayout> (Resource.Id.groupname_container), RegisterTextPopup(FindViewById<RelativeLayout> (Resource.Id.groupname_container),
FindViewById (Resource.Id.entry_group_name), KeyGroupFullPath); FindViewById (Resource.Id.entry_group_name), KeyGroupFullPath);
@@ -961,6 +969,8 @@ namespace keepass2android
.Add(new GotoUrlMenuItem(this, PwDefs.UrlField)); .Add(new GotoUrlMenuItem(this, PwDefs.UrlField));
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.password_container), RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.password_container),
FindViewById(Resource.Id.password_vdots), PwDefs.PasswordField); FindViewById(Resource.Id.password_vdots), PwDefs.PasswordField);
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.totp_container),
FindViewById(Resource.Id.totp_vdots), Kp2aTotp.TotpKey);
PopulateText(Resource.Id.entry_created, Resource.Id.entryfield_container_created, getDateTime(Entry.CreationTime)); PopulateText(Resource.Id.entry_created, Resource.Id.entryfield_container_created, getDateTime(Entry.CreationTime));
@@ -991,6 +1001,40 @@ namespace keepass2android
SetPasswordStyle(); SetPasswordStyle();
} }
private async Task UpdateTotpCountdown()
{
if (App.Kp2a.LastOpenedEntry == null)
return;
var totpData = new Kp2aTotp().TryGetTotpData(App.Kp2a.LastOpenedEntry);
if (totpData == null || !totpData.IsTotpEntry)
return;
var totpProvider = new TOTPProvider(totpData);
var progressBar = FindViewById<ProgressBar>(Resource.Id.TotpCountdownProgressBar);
int lastSecondsLeft = -1;
while (!isPaused && progressBar != null)
{
int secondsLeft = totpProvider.Timer;
if (secondsLeft != lastSecondsLeft)
{
lastSecondsLeft = secondsLeft;
// Update the progress bar on the UI thread
RunOnUiThread(() =>
{
progressBar.Progress = secondsLeft;
progressBar.Max = totpProvider.Duration;
});
}
await Task.Delay(1000);
}
}
private void PopulatePreviousVersions() private void PopulatePreviousVersions()
{ {
ViewGroup historyGroup = (ViewGroup)FindViewById(Resource.Id.previous_versions); ViewGroup historyGroup = (ViewGroup)FindViewById(Resource.Id.previous_versions);
@@ -1042,7 +1086,7 @@ namespace keepass2android
} }
private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey) private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey)
{ {
return RegisterTextPopup(container, anchor, fieldKey, Entry.Strings.GetSafe(fieldKey).IsProtected); return RegisterTextPopup(container, anchor, fieldKey, Entry.Strings.GetSafe(fieldKey).IsProtected || fieldKey == Kp2aTotp.TotpKey);
} }
private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey, bool isProtected) private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey, bool isProtected)
@@ -1055,7 +1099,12 @@ namespace keepass2android
popupItems.Add(new CopyToClipboardPopupMenuIcon(this, _stringViews[fieldKey], isProtected)); popupItems.Add(new CopyToClipboardPopupMenuIcon(this, _stringViews[fieldKey], isProtected));
if (isProtected) if (isProtected)
{ {
var valueView = container.FindViewById<TextView>(fieldKey == PwDefs.PasswordField ? Resource.Id.entry_password : Resource.Id.entry_extra); var valueView = container.FindViewById<TextView>(fieldKey switch
{
PwDefs.PasswordField => Resource.Id.entry_password,
Kp2aTotp.TotpKey => Resource.Id.entry_totp,
_ => Resource.Id.entry_extra
});
popupItems.Add(new ToggleVisibilityPopupMenuItem(this, valueView)); popupItems.Add(new ToggleVisibilityPopupMenuItem(this, valueView));
} }
@@ -1282,8 +1331,13 @@ namespace keepass2android
return base.OnPrepareOptionsMenu(menu); return base.OnPrepareOptionsMenu(menu);
} }
bool isPaused = false;
protected override void OnPause()
{
base.OnPause();
isPaused = true;
}
private void UpdateTogglePasswordMenu() private void UpdateTogglePasswordMenu()
@@ -1323,6 +1377,8 @@ namespace keepass2android
ClearCache(); ClearCache();
base.OnResume(); base.OnResume();
_activityDesign.ReapplyTheme(); _activityDesign.ReapplyTheme();
isPaused = false;
Task.Run(UpdateTotpCountdown);
} }
public void ClearCache() public void ClearCache()

View File

@@ -140,7 +140,7 @@ namespace keepass2android
//will return the results later //will return the results later
Intent i = new Intent(this, typeof (SelectCurrentDbActivity)); Intent i = new Intent(this, typeof (SelectCurrentDbActivity));
//don't show user notifications when an entry is opened. //don't show user notifications when an entry is opened.
var task = new SearchUrlTask() {UrlToSearchFor = _requestedUrl, ShowUserNotifications = ShowUserNotificationsMode.WhenTotp}; var task = new SearchUrlTask() {UrlToSearchFor = _requestedUrl, ShowUserNotifications = ActivationCondition.WhenTotp, ActivateKeyboard = ActivationCondition.Never };
task.ToIntent(i); task.ToIntent(i);
StartActivityForResult(i, RequestCodeQuery); StartActivityForResult(i, RequestCodeQuery);
_startedQuery = true; _startedQuery = true;

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -184,6 +184,68 @@
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/entryfield_container_totp"
style="@style/EntryEditSingleLine_container">
<ImageView
style="@style/EntryEditSingleLine_ImageView"
android:src="@drawable/ic_entry_totp" />
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!-- TOTP -->
<TextView
android:id="@+id/entry_totp_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/TOTP"
style="@style/EntryFieldHeader" />
<RelativeLayout
android:id="@+id/totp_container"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:clickable="true"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/totp_vdots"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:src="@drawable/vdots"
android:gravity="right|bottom"
android:layout_alignParentRight="true" />
<TextView
android:id="@+id/entry_totp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:password="true"
android:typeface="monospace"
android:layout_toLeftOf="@id/totp_vdots"
style="@style/EntryItem" />
<TextView
android:id="@+id/entry_totp_visible"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/totp_vdots"
style="@style/EntryItem" />
</RelativeLayout>
<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>
<LinearLayout <LinearLayout
android:id="@+id/entryfield_container_comment" android:id="@+id/entryfield_container_comment"
style="@style/EntryEditSingleLine_container"> style="@style/EntryEditSingleLine_container">

View File

@@ -47,6 +47,7 @@
<string name="oi_filemanager_web">https://openintents.googlecode.com/files/FileManager-2.0.2.apk</string> <string name="oi_filemanager_web">https://openintents.googlecode.com/files/FileManager-2.0.2.apk</string>
<string name="permission_desc2">KP2A Search</string> <string name="permission_desc2">KP2A Search</string>
<string name="permission_desc3">KP2A Choose autofill dataset</string> <string name="permission_desc3">KP2A Choose autofill dataset</string>
<string name="AutoFillTotp_prefs_screen_key">AutoFillTotp_prefs_screen_key</string>
<!-- Preference settings --> <!-- Preference settings -->

View File

@@ -402,6 +402,16 @@
<string name="ShowSeparateNotifications_summary">Show separate notifications for copying username and password to clipboard and activating the keyboard.</string> <string name="ShowSeparateNotifications_summary">Show separate notifications for copying username and password to clipboard and activating the keyboard.</string>
<string name="AccServiceAutoFill_prefs">AutoFill Accessibility-Service</string> <string name="AccServiceAutoFill_prefs">AutoFill Accessibility-Service</string>
<string name="AutoFill_prefs">AutoFill Service</string> <string name="AutoFill_prefs">AutoFill Service</string>
<string name="AutoFillTotp_prefs_ShowNotification_summary">When autofilling an entry with TOTP, show the entry notification with a Copy TOTP button</string>
<string name="AutoFillTotp_prefs_ShowNotification_title">Show entry notification</string>
<string name="AutoFillTotp_prefs_title">Autofill for TOTP entries</string>
<string name="AutoFillTotp_prefs_CopyTotpToClipboard_title">Copy TOTP to clipboard</string>
<string name="AutoFillTotp_prefs_CopyTotpToClipboard_summary">When autofilling an entry with TOTP, copy the TOTP to the clipboard</string>
<string name="AutoFillTotp_prefs_ActivateKeyboard_summary">When autofilling an entry with TOTP, activate the built-in keyboard. The keyboard has a TOTP button.</string>
<string name="AutoFillTotp_prefs_ActivateKeyboard_title">Activate built-in keyboard</string>
<string name="TotpCopiedToClipboard">Copied TOTP to clipboard</string>
<string name="ShowKp2aKeyboardNotification_title">KP2A keyboard notification</string> <string name="ShowKp2aKeyboardNotification_title">KP2A keyboard notification</string>
<string name="ShowKp2aKeyboardNotification_summary">Make full entry accessible through the KP2A keyboard (recommended).</string> <string name="ShowKp2aKeyboardNotification_summary">Make full entry accessible through the KP2A keyboard (recommended).</string>
<string name="OpenKp2aKeyboardAutomatically_title">Switch keyboard</string> <string name="OpenKp2aKeyboardAutomatically_title">Switch keyboard</string>
@@ -589,6 +599,7 @@
<string name="CouldntLoadChalAuxFile_Hint">Please use the KeeChallenge plugin in KeePass 2.x (PC) to configure your database for use with challenge-response!</string> <string name="CouldntLoadChalAuxFile_Hint">Please use the KeeChallenge plugin in KeePass 2.x (PC) to configure your database for use with challenge-response!</string>
<string name="ErrorUpdatingChalAuxFile">Error updating OTP auxiliary file!</string> <string name="ErrorUpdatingChalAuxFile">Error updating OTP auxiliary file!</string>
<string name="TrayTotp_SeedField_title">TOTP Seed field name</string> <string name="TrayTotp_SeedField_title">TOTP Seed field name</string>
<string name="TOTP">TOTP</string>
<string name="TrayTotp_SeedField_summary">If you are using the Keepass 2 plugin "TrayTotp" with non-default settings, enter the field name for the seed field here according to the settings on the PC.</string> <string name="TrayTotp_SeedField_summary">If you are using the Keepass 2 plugin "TrayTotp" with non-default settings, enter the field name for the seed field here according to the settings on the PC.</string>
<string name="TrayTotp_SettingsField_title">TOTP Settings field name</string> <string name="TrayTotp_SettingsField_title">TOTP Settings field name</string>
<string name="TrayTotp_SettingsField_summary">Enter the field name of the settings field for TrayTotp here.</string> <string name="TrayTotp_SettingsField_summary">Enter the field name of the settings field for TrayTotp here.</string>

View File

@@ -463,6 +463,7 @@
android:key="@string/LogAutofillView_key" /> android:key="@string/LogAutofillView_key" />
<CheckBoxPreference <CheckBoxPreference
android:enabled="true" android:enabled="true"
android:persistent="true" android:persistent="true"
@@ -477,6 +478,38 @@
android:persistent="false" android:persistent="false"
android:key="AutofillDisabledQueriesPreference_key"/> android:key="AutofillDisabledQueriesPreference_key"/>
<PreferenceScreen
android:key="@string/AutoFillTotp_prefs_screen_key"
android:title="@string/AutoFillTotp_prefs_title"
>
<keepass2android.ToolbarPreference
android:key="@string/AutoFillTotp_prefs_screen_key"
android:title="@string/AutoFillTotp_prefs_title" />
<CheckBoxPreference android:key="AutoFillTotp_prefs_ShowNotification_key"
android:enabled="true"
android:persistent="true"
android:summary="@string/AutoFillTotp_prefs_ShowNotification_summary"
android:defaultValue="true"
android:title="@string/AutoFillTotp_prefs_ShowNotification_title"
/>
<CheckBoxPreference android:key="AutoFillTotp_prefs_CopyTotpToClipboard_key"
android:enabled="true"
android:persistent="true"
android:summary="@string/AutoFillTotp_prefs_CopyTotpToClipboard_summary"
android:defaultValue="true"
android:title="@string/AutoFillTotp_prefs_CopyTotpToClipboard_title"
/>
<CheckBoxPreference android:key="AutoFillTotp_prefs_ActivateKeyboard_key"
android:enabled="true"
android:persistent="true"
android:summary="@string/AutoFillTotp_prefs_ActivateKeyboard_summary"
android:defaultValue="false"
android:title="@string/AutoFillTotp_prefs_ActivateKeyboard_title"
/>
</PreferenceScreen>
</PreferenceScreen> </PreferenceScreen>
<CheckBoxPreference <CheckBoxPreference

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -306,7 +307,27 @@ namespace keepass2android
} }
else if (Intent.Action == Intent.ActionSend) else if (Intent.Action == Intent.ActionSend)
{ {
AppTask = new SearchUrlTask { UrlToSearchFor = Intent.GetStringExtra(Intent.ExtraText) }; ActivationCondition activationCondition = ActivationCondition.Never;
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
if (prefs.GetBoolean("kp2a_switch_rooted", false))
{
activationCondition = ActivationCondition.Always;
}
else
{
//if the app is about to be closed again (e.g. after searching for a URL and returning to the browser:
// automatically bring up the Keyboard selection dialog
if (prefs.GetBoolean(this.GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), this.Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default)))
{
activationCondition = ActivationCondition.Always;
}
}
AppTask = new SearchUrlTask()
{
UrlToSearchFor = Intent.GetStringExtra(Intent.ExtraText),
ActivateKeyboard = activationCondition
};
} }
} }

View File

@@ -63,6 +63,12 @@ namespace keepass2android
launchMode.Launch(act, i); launchMode.Launch(act, i);
} }
public static void Launch(Activity act, OpenSpecificEntryTask task, ActivityLaunchMode launchMode)
{
Intent i = new Intent(act, typeof(ShareUrlResults));
task.ToIntent(i);
launchMode.Launch(act, i);
}
public override bool IsSearchResult public override bool IsSearchResult
{ {
@@ -76,21 +82,15 @@ namespace keepass2android
//if user presses back to leave this activity: //if user presses back to leave this activity:
SetResult(Result.Canceled); SetResult(Result.Canceled);
UpdateBottomBarElementVisibility(Resource.Id.select_other_entry, true); UpdateBottomBarElementVisibility(Resource.Id.select_other_entry, true);
UpdateBottomBarElementVisibility(Resource.Id.add_url_entry, true); UpdateBottomBarElementVisibility(Resource.Id.add_url_entry, true);
if (App.Kp2a.DatabaseIsUnlocked) if (App.Kp2a.DatabaseIsUnlocked)
{ {
var searchUrlTask = ((SearchUrlTask)AppTask); Query();
String searchUrl = searchUrlTask.UrlToSearchFor;
Query(searchUrl, searchUrlTask.AutoReturnFromQuery);
} }
// else: LockCloseListActivity.OnResume will trigger a broadcast (LockDatabase) which will cause the activity to be finished. // else: LockCloseListActivity.OnResume will trigger a broadcast (LockDatabase) which will cause the activity to be finished.
} }
protected override void OnSaveInstanceState(Bundle outState) protected override void OnSaveInstanceState(Bundle outState)
@@ -99,12 +99,25 @@ namespace keepass2android
AppTask.ToBundle(outState); AppTask.ToBundle(outState);
} }
private void Query(string url, bool autoReturnFromQuery) private void Query()
{ {
bool canAutoReturnFromQuery = true;
bool shouldAutoReturnFromQuery = true;
try try
{ {
Group = GetSearchResultsForUrl(url); if (AppTask is SearchUrlTask searchUrlTask)
{
String searchUrl = searchUrlTask.UrlToSearchFor;
canAutoReturnFromQuery = searchUrlTask.AutoReturnFromQuery;
shouldAutoReturnFromQuery = PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.AutoReturnFromQuery_key), true);
Group = GetSearchResultsForUrl(searchUrl);
}
else if (AppTask is OpenSpecificEntryTask openEntryTask)
{
Group = GetSearchResultsForUuid(openEntryTask.EntryUuid);
}
} catch (Exception e) } catch (Exception e)
{ {
Toast.MakeText(this, e.Message, ToastLength.Long).Show(); Toast.MakeText(this, e.Message, ToastLength.Long).Show();
@@ -114,7 +127,7 @@ namespace keepass2android
} }
//if there is exactly one match: open the entry //if there is exactly one match: open the entry
if ((Group.Entries.Count() == 1) && autoReturnFromQuery && PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.AutoReturnFromQuery_key),true)) if ((Group.Entries.Count() == 1) && canAutoReturnFromQuery && shouldAutoReturnFromQuery)
{ {
LaunchActivityForEntry(Group.Entries.Single(),0); LaunchActivityForEntry(Group.Entries.Single(),0);
return; return;
@@ -131,25 +144,45 @@ namespace keepass2android
FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment).ListAdapter = new PwGroupListAdapter(this, Group); FragmentManager.FindFragmentById<GroupListFragment>(Resource.Id.list_fragment).ListAdapter = new PwGroupListAdapter(this, Group);
View selectOtherEntry = FindViewById (Resource.Id.select_other_entry); View selectOtherEntry = FindViewById (Resource.Id.select_other_entry);
View createUrlEntry = FindViewById(Resource.Id.add_url_entry);
var newTask = new SearchUrlTask() {AutoReturnFromQuery = false, UrlToSearchFor = url}; if (AppTask is OpenSpecificEntryTask)
{
selectOtherEntry.Visibility = ViewStates.Gone;
createUrlEntry.Visibility = ViewStates.Gone;
}
else
{
var searchUrlTask = AppTask as SearchUrlTask;
String searchUrl = searchUrlTask.UrlToSearchFor;
selectOtherEntry.Visibility = ViewStates.Visible;
SearchUrlTask newTask;
if (AppTask is SelectEntryTask currentSelectTask) if (AppTask is SelectEntryTask currentSelectTask)
{
newTask = new SearchUrlTask() { AutoReturnFromQuery = false, UrlToSearchFor = searchUrl, ActivateKeyboard = currentSelectTask.ActivateKeyboard };
newTask.ShowUserNotifications = currentSelectTask.ShowUserNotifications; newTask.ShowUserNotifications = currentSelectTask.ShowUserNotifications;
newTask.ActivateKeyboard = currentSelectTask.ActivateKeyboard;
newTask.CopyTotpToClipboard = currentSelectTask.CopyTotpToClipboard;
}
else
newTask = new SearchUrlTask() { AutoReturnFromQuery = false, UrlToSearchFor = searchUrl, ActivateKeyboard = ActivationCondition.Never };
selectOtherEntry.Click += (sender, e) => { selectOtherEntry.Click += (sender, e) => {
GroupActivity.Launch (this, newTask, new ActivityLaunchModeRequestCode(0)); GroupActivity.Launch(this, newTask, new ActivityLaunchModeRequestCode(0));
}; };
View createUrlEntry = FindViewById (Resource.Id.add_url_entry);
if (App.Kp2a.OpenDatabases.Any(db => db.CanWrite)) if (App.Kp2a.OpenDatabases.Any(db => db.CanWrite))
{ {
createUrlEntry.Visibility = ViewStates.Visible; createUrlEntry.Visibility = ViewStates.Visible;
createUrlEntry.Click += (sender, e) => createUrlEntry.Click += (sender, e) =>
{ {
GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = url, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ShowUserNotificationsMode.Always }, new ActivityLaunchModeRequestCode(0)); GroupActivity.Launch(this, new CreateEntryThenCloseTask { Url = searchUrl, ShowUserNotifications = (AppTask as SelectEntryTask)?.ShowUserNotifications ?? ActivationCondition.Always }, new ActivityLaunchModeRequestCode(0));
Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show(); Toast.MakeText(this, GetString(Resource.String.select_group_then_add, new Java.Lang.Object[] { GetString(Resource.String.add_entry) }), ToastLength.Long).Show();
}; };
} }
@@ -158,6 +191,11 @@ namespace keepass2android
createUrlEntry.Visibility = ViewStates.Gone; createUrlEntry.Visibility = ViewStates.Gone;
} }
}
Util.MoveBottomBarButtons(Resource.Id.select_other_entry, Resource.Id.add_url_entry, Resource.Id.bottom_bar, this); Util.MoveBottomBarButtons(Resource.Id.select_other_entry, Resource.Id.add_url_entry, Resource.Id.bottom_bar, this);
} }
@@ -201,6 +239,31 @@ namespace keepass2android
return resultsGroup; return resultsGroup;
} }
public static PwGroup GetSearchResultsForUuid(string uuid)
{
PwGroup resultsGroup = null;
foreach (var db in App.Kp2a.OpenDatabases)
{
var resultsForThisDb = db.SearchForUuid(uuid);
if (resultsGroup == null)
{
resultsGroup = resultsForThisDb;
}
else
{
foreach (var entry in resultsForThisDb.Entries)
{
resultsGroup.AddEntry(entry, false, false);
}
}
}
return resultsGroup;
}
public override bool OnSearchRequested() public override bool OnSearchRequested()
{ {
Intent i = new Intent(this, typeof(SearchActivity)); Intent i = new Intent(this, typeof(SearchActivity));

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using Android.Content; using Android.Content;
using keepass2android;
using KeePassLib.Collections; using KeePassLib.Collections;
namespace PluginTOTP namespace PluginTOTP
@@ -39,16 +40,21 @@ namespace PluginTOTP
{ {
TotpData res = new TotpData(); TotpData res = new TotpData();
string data; string data;
if (!_entryFields.TryGetValue("otp", out data)) var otpKey = "otp";
if (!_entryFields.TryGetValue(otpKey, out data))
{ {
return res; return res;
} }
NameValueCollection parameters = ParseQueryString(data); NameValueCollection parameters = ParseQueryString(data);
res.InternalFields.Add(otpKey);
if (parameters[KeyParameter] == null) if (parameters[KeyParameter] == null)
{ {
return res; return res;
} }
try
{
res.TotpSeed = parameters[KeyParameter]; res.TotpSeed = parameters[KeyParameter];
@@ -56,6 +62,12 @@ namespace PluginTOTP
res.Length = GetIntOrDefault(parameters, SizeParameter, 6).ToString(); res.Length = GetIntOrDefault(parameters, SizeParameter, 6).ToString();
res.IsTotpEntry = true; res.IsTotpEntry = true;
}
catch (Exception e)
{
Kp2aLog.Log("Cannot parse seed");
}
return res; return res;
} }

View File

@@ -16,6 +16,7 @@ namespace keepass2android
{ {
return res; return res;
} }
res.InternalFields.Add("otp");
string otpUriStart = "otpauth://totp/"; string otpUriStart = "otpauth://totp/";

View File

@@ -14,14 +14,18 @@ namespace PluginTOTP
public TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings) public TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings)
{ {
TotpData res = new TotpData(); TotpData res = new TotpData();
byte[] pbSecret = (GetOtpSecret(entryFields, "TimeOtp-") ?? MemUtil.EmptyByteArray); byte[] pbSecret = (GetOtpSecret(entryFields, "TimeOtp-", out string secretFieldKey) ?? MemUtil.EmptyByteArray);
if (pbSecret.Length == 0) if (pbSecret.Length == 0)
return res; return res;
res.InternalFields.Add(secretFieldKey);
string strPeriod; string strPeriod;
uint uPeriod = 0; uint uPeriod = 0;
if (entryFields.TryGetValue("TimeOtp-Period", out strPeriod)) if (entryFields.TryGetValue("TimeOtp-Period", out strPeriod))
{ {
res.InternalFields.Add("TimeOtp-Period");
uint.TryParse(strPeriod, out uPeriod); uint.TryParse(strPeriod, out uPeriod);
} }
@@ -34,6 +38,7 @@ namespace PluginTOTP
uint uLength = 0; uint uLength = 0;
if (entryFields.TryGetValue("TimeOtp-Length", out strLength)) if (entryFields.TryGetValue("TimeOtp-Length", out strLength))
{ {
res.InternalFields.Add("TimeOtp-Length");
uint.TryParse(strLength, out uLength); uint.TryParse(strLength, out uLength);
} }
@@ -42,6 +47,8 @@ namespace PluginTOTP
string strAlg; string strAlg;
entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg); entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg);
if (!string.IsNullOrEmpty(strAlg))
res.InternalFields.Add("TimeOtp-Algorithm");
res.HashAlgorithm = strAlg; res.HashAlgorithm = strAlg;
res.TotpSecret = pbSecret; res.TotpSecret = pbSecret;
@@ -52,32 +59,37 @@ namespace PluginTOTP
} }
private static byte[] GetOtpSecret(IDictionary<string, string> entryFields, string strPrefix) private static byte[] GetOtpSecret(IDictionary<string, string> entryFields, string strPrefix, out string secretFieldKey)
{ {
try try
{ {
string str; string str;
entryFields.TryGetValue(strPrefix + "Secret", out str); secretFieldKey = strPrefix + "Secret";
entryFields.TryGetValue(secretFieldKey, out str);
if (!string.IsNullOrEmpty(str)) if (!string.IsNullOrEmpty(str))
return StrUtil.Utf8.GetBytes(str); return StrUtil.Utf8.GetBytes(str);
entryFields.TryGetValue(strPrefix + "Secret-Hex", out str); secretFieldKey = strPrefix + "Secret-Hex";
entryFields.TryGetValue(secretFieldKey, out str);
if (!string.IsNullOrEmpty(str)) if (!string.IsNullOrEmpty(str))
return MemUtil.HexStringToByteArray(str); return MemUtil.HexStringToByteArray(str);
entryFields.TryGetValue(strPrefix + "Secret-Base32", out str); secretFieldKey = strPrefix + "Secret-Base32";
entryFields.TryGetValue(secretFieldKey, out str);
if (!string.IsNullOrEmpty(str)) if (!string.IsNullOrEmpty(str))
return Base32.Decode(str); return Base32.Decode(str);
entryFields.TryGetValue(strPrefix + "Secret-Base64", out str); secretFieldKey = strPrefix + "Secret-Base64";
entryFields.TryGetValue(secretFieldKey, out str);
if (!string.IsNullOrEmpty(str)) if (!string.IsNullOrEmpty(str))
return Convert.FromBase64String(str); return Convert.FromBase64String(str);
} }
catch (Exception e) catch (Exception e)
{ {
Kp2aLog.LogUnexpectedError(e); Kp2aLog.LogUnexpectedError(e);
} }
secretFieldKey = null;
return null; return null;
} }
} }

View File

@@ -10,6 +10,7 @@ namespace keepass2android
{ {
class Kp2aTotp class Kp2aTotp
{ {
public const string TotpKey = "TOTP";
readonly ITotpPluginAdapter[] _pluginAdapters = new ITotpPluginAdapter[] readonly ITotpPluginAdapter[] _pluginAdapters = new ITotpPluginAdapter[]
{ {
@@ -46,7 +47,7 @@ namespace keepass2android
foreach (ITotpPluginAdapter adapter in _pluginAdapters) foreach (ITotpPluginAdapter adapter in _pluginAdapters)
{ {
TotpData totpData = adapter.GetTotpData( TotpData totpData = adapter.GetTotpData(
App.Kp2a.LastOpenedEntry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key),
pair => pair.Value.ReadString()), LocaleManager.LocalizedAppContext, false); pair => pair.Value.ReadString()), LocaleManager.LocalizedAppContext, false);
if (totpData.IsTotpEntry) if (totpData.IsTotpEntry)
{ {

View File

@@ -37,6 +37,8 @@ namespace PluginTOTP
get { return Length == "6" && Duration == "30" && (HashAlgorithm == null || HashAlgorithm == HashSha1); } get { return Length == "6" && Duration == "30" && (HashAlgorithm == null || HashAlgorithm == HashSha1); }
} }
public List<string> InternalFields { get; set; } = new List<string>();
public static TotpData MakeDefaultRfc6238() public static TotpData MakeDefaultRfc6238()
{ {
return new TotpData() return new TotpData()

View File

@@ -106,6 +106,8 @@ namespace PluginTOTP
{ {
bool NoTimeCorrection = false; bool NoTimeCorrection = false;
string[] Settings = SettingsGet(entryFields); string[] Settings = SettingsGet(entryFields);
res.InternalFields.Add(SettingsFieldName);
res.InternalFields.Add(SeedFieldName);
res.Duration = Settings[0]; res.Duration = Settings[0];
res.Length = Settings[1]; res.Length = Settings[1];
if (res.Length == "S") if (res.Length == "S")

View File

@@ -13,7 +13,7 @@ namespace PluginTOTP
{ {
class UpdateTotpTimerTask: TimerTask class UpdateTotpTimerTask: TimerTask
{ {
public const string TotpKey = "TOTP"; public const string TotpKey = Kp2aTotp.TotpKey;
private readonly Context _context; private readonly Context _context;
private readonly ITotpPluginAdapter _adapter; private readonly ITotpPluginAdapter _adapter;

View File

@@ -6,9 +6,13 @@ using Android.OS;
using Android.Widget; using Android.Widget;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using KeePassLib; using KeePassLib;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility; using KeePassLib.Utility;
using KeeTrayTOTP.Libraries;
using Android.Content.Res;
using Android.Preferences;
namespace keepass2android namespace keepass2android
{ {
@@ -339,9 +343,17 @@ namespace keepass2android
} }
public virtual void CompleteOnCreateEntryActivity(EntryActivity activity) public virtual void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
{ {
activity.StartNotificationsService(false); //this default implementation is executed when we're opening an entry manually, i.e. without search/autofill.
//We only activate the keyboard if this is enabled in "silent mode"
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(activity);
bool activateKeyboard = prefs.GetBoolean("kp2a_switch_rooted", false) &&
!prefs.GetBoolean(
activity.GetString(Resource.String
.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false);
activity.StartNotificationsService(activateKeyboard);
} }
public virtual void PopulatePasswordAccessServiceIntent(Intent intent) public virtual void PopulatePasswordAccessServiceIntent(Intent intent)
@@ -353,7 +365,8 @@ namespace keepass2android
protected static bool GetBoolFromBundle(Bundle b, string key, bool defaultValue) protected static bool GetBoolFromBundle(Bundle b, string key, bool defaultValue)
{ {
bool boolValue; bool boolValue;
if (!Boolean.TryParse(b.GetString(key), out boolValue)) string stringValue = b.GetString(key);
if (!Boolean.TryParse(stringValue, out boolValue))
{ {
boolValue = defaultValue; boolValue = defaultValue;
} }
@@ -363,7 +376,8 @@ namespace keepass2android
protected static int GetIntFromBundle(Bundle b, string key, int defaultValue) protected static int GetIntFromBundle(Bundle b, string key, int defaultValue)
{ {
int intValue; int intValue;
if (!Int32.TryParse(b.GetString(key), out intValue)) var strValue = b.GetString(key);
if (!Int32.TryParse(strValue, out intValue))
{ {
intValue = defaultValue; intValue = defaultValue;
} }
@@ -383,7 +397,7 @@ namespace keepass2android
/// User is about to search an entry for a given URL /// User is about to search an entry for a given URL
/// </summary> /// </summary>
/// Derive from SelectEntryTask. This means that as soon as an Entry is opened, we're returning with /// Derive from SelectEntryTask. This means that as soon as an Entry is opened, we're returning with
/// ExitAfterTaskComplete. This also allows te specify the flag if we need to display the user notifications. /// ExitAfterTaskComplete. This also allows to specify the flag if we need to display the user notifications.
public class SearchUrlTask: SelectEntryTask public class SearchUrlTask: SelectEntryTask
{ {
public SearchUrlTask() public SearchUrlTask()
@@ -392,6 +406,7 @@ namespace keepass2android
} }
public const String UrlToSearchKey = "UrlToSearch"; public const String UrlToSearchKey = "UrlToSearch";
public const String AutoReturnFromQueryKey = "AutoReturnFromQuery";
public string UrlToSearchFor public string UrlToSearchFor
{ {
@@ -416,7 +431,7 @@ namespace keepass2android
} }
} }
public const String AutoReturnFromQueryKey = "AutoReturnFromQuery";
public bool AutoReturnFromQuery { get; set; } public bool AutoReturnFromQuery { get; set; }
@@ -424,14 +439,18 @@ namespace keepass2android
{ {
if (String.IsNullOrEmpty(UrlToSearchFor)) if (String.IsNullOrEmpty(UrlToSearchFor))
{ {
GroupActivity.Launch(act, new SelectEntryTask() { ShowUserNotifications = ShowUserNotifications}, new ActivityLaunchModeRequestCode(0)); GroupActivity.Launch(act, new SelectEntryTask() {
ShowUserNotifications = ShowUserNotifications,
CopyTotpToClipboard = CopyTotpToClipboard,
ActivateKeyboard = ActivateKeyboard
},
new ActivityLaunchModeRequestCode(0));
} }
else else
{ {
ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0)); ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0));
} }
//removed. this causes an issue in the following workflow: //removed. this causes an issue in the following workflow:
//When the user wants to find an entry for a URL but has the wrong database open he needs //When the user wants to find an entry for a URL but has the wrong database open he needs
//to switch to another database. But the Task is removed already the first time when going through PasswordActivity //to switch to another database. But the Task is removed already the first time when going through PasswordActivity
@@ -453,7 +472,7 @@ namespace keepass2android
intent.PutExtra(UrlToSearchKey, UrlToSearchFor); intent.PutExtra(UrlToSearchKey, UrlToSearchFor);
} }
public override void CompleteOnCreateEntryActivity(EntryActivity activity) public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
{ {
if (App.Kp2a.LastOpenedEntry != null) if (App.Kp2a.LastOpenedEntry != null)
App.Kp2a.LastOpenedEntry.SearchUrl = UrlToSearchFor; App.Kp2a.LastOpenedEntry.SearchUrl = UrlToSearchFor;
@@ -462,18 +481,18 @@ namespace keepass2android
//if the database is readonly (or no URL exists), don't offer to modify the URL //if the database is readonly (or no URL exists), don't offer to modify the URL
if ((App.Kp2a.CurrentDb.CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor) || keepass2android.ShareUrlResults.GetSearchResultsForUrl(UrlToSearchFor).Entries.Any(e => e == activity.Entry) )) if ((App.Kp2a.CurrentDb.CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor) || keepass2android.ShareUrlResults.GetSearchResultsForUrl(UrlToSearchFor).Entries.Any(e => e == activity.Entry) ))
{ {
base.CompleteOnCreateEntryActivity(activity); base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
return; return;
} }
AskAddUrlThenCompleteCreate(activity, UrlToSearchFor); AskAddUrlThenCompleteCreate(activity, UrlToSearchFor, notifyPluginsOnOpenThread);
} }
/// <summary> /// <summary>
/// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding /// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
/// </summary> /// </summary>
public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url) public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url, Thread notifyPluginsOnOpenThread)
{ {
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetTitle(activity.GetString(Resource.String.AddUrlToEntryDialog_title)); builder.SetTitle(activity.GetString(Resource.String.AddUrlToEntryDialog_title));
@@ -482,12 +501,13 @@ namespace keepass2android
builder.SetPositiveButton(activity.GetString(Resource.String.yes), (dlgSender, dlgEvt) => builder.SetPositiveButton(activity.GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
{ {
activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity)); activity.AddUrlToEntry(url, (EntryActivity thenActiveActivity) => base.CompleteOnCreateEntryActivity(thenActiveActivity, notifyPluginsOnOpenThread
));
}); });
builder.SetNegativeButton(activity.GetString(Resource.String.no), (dlgSender, dlgEvt) => builder.SetNegativeButton(activity.GetString(Resource.String.no), (dlgSender, dlgEvt) =>
{ {
base.CompleteOnCreateEntryActivity(activity); base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
}); });
Dialog dialog = builder.Create(); Dialog dialog = builder.Create();
@@ -495,8 +515,46 @@ namespace keepass2android
} }
} }
public class OpenSpecificEntryTask : SelectEntryTask
{
public OpenSpecificEntryTask()
{
}
public enum ShowUserNotificationsMode public const String EntryUuidKey = "EntryUuid";
public string EntryUuid
{
get;
set;
}
public override void Setup(Bundle b)
{
base.Setup(b);
EntryUuid = b.GetString(EntryUuidKey);
}
public override IEnumerable<IExtra> Extras
{
get
{
foreach (IExtra e in base.Extras)
yield return e;
yield return new StringExtra { Key = EntryUuidKey, Value = EntryUuid };
}
}
public override void LaunchFirstGroupActivity(Activity act)
{
ShareUrlResults.Launch(act, this, new ActivityLaunchModeRequestCode(0));
}
}
public enum ActivationCondition
{ {
Never, Never,
WhenTotp, WhenTotp,
@@ -509,29 +567,34 @@ namespace keepass2android
{ {
public SelectEntryTask() public SelectEntryTask()
{ {
ShowUserNotifications = ShowUserNotificationsMode.Always; ShowUserNotifications = ActivationCondition.Always;
CloseAfterCreate = true; CloseAfterCreate = true;
ActivateKeyboard = true; ActivateKeyboard = ActivationCondition.Never;
CopyTotpToClipboard = false;
} }
public const String ShowUserNotificationsKey = "ShowUserNotifications"; public const String ShowUserNotificationsKey = "ShowUserNotifications";
public ShowUserNotificationsMode ShowUserNotifications { get; set; } public ActivationCondition ShowUserNotifications { get; set; }
public const String CloseAfterCreateKey = "CloseAfterCreate"; public const String CloseAfterCreateKey = "CloseAfterCreate";
public const String ActivateKeyboardKey = "ActivateKeyboard"; public const String ActivateKeyboardKey = "ActivateKeyboard";
public const String CopyTotpToClipboardKey = "CopyTotpToClipboard";
public bool CloseAfterCreate { get; set; } public bool CloseAfterCreate { get; set; }
public bool ActivateKeyboard { get; set; } public ActivationCondition ActivateKeyboard { get; set; }
public bool CopyTotpToClipboard { get; set; }
public override void Setup(Bundle b) public override void Setup(Bundle b)
{ {
ShowUserNotifications = (ShowUserNotificationsMode) GetIntFromBundle(b, ShowUserNotificationsKey, (int)ShowUserNotificationsMode.Always); ShowUserNotifications = (ActivationCondition) GetIntFromBundle(b, ShowUserNotificationsKey, (int)ActivationCondition.Always);
CloseAfterCreate = GetBoolFromBundle(b, CloseAfterCreateKey, true); CloseAfterCreate = GetBoolFromBundle(b, CloseAfterCreateKey, true);
ActivateKeyboard = GetBoolFromBundle(b, ActivateKeyboardKey, true); ActivateKeyboard = (ActivationCondition)GetIntFromBundle(b, ActivateKeyboardKey, (int)ActivationCondition.Always);
CopyTotpToClipboard = GetBoolFromBundle(b, CopyTotpToClipboardKey, false);
} }
@@ -541,29 +604,57 @@ namespace keepass2android
{ {
yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ((int)ShowUserNotifications).ToString() }; yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ((int)ShowUserNotifications).ToString() };
yield return new StringExtra { Key = CloseAfterCreateKey, Value = CloseAfterCreate.ToString() }; yield return new StringExtra { Key = CloseAfterCreateKey, Value = CloseAfterCreate.ToString() };
yield return new StringExtra { Key = ActivateKeyboardKey, Value = ActivateKeyboard.ToString() }; yield return new StringExtra { Key = ActivateKeyboardKey, Value = ((int)ActivateKeyboard).ToString() };
yield return new StringExtra { Key = CopyTotpToClipboardKey, Value = CopyTotpToClipboard.ToString() };
} }
} }
public override void CompleteOnCreateEntryActivity(EntryActivity activity) public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
{ {
Context ctx = activity; Context ctx = activity;
if (ctx == null) if (ctx == null)
ctx = LocaleManager.LocalizedAppContext; ctx = LocaleManager.LocalizedAppContext;
if ((ShowUserNotifications == ShowUserNotificationsMode.Always) var pwEntryOutput = new PwEntryOutput(activity.Entry, App.Kp2a.CurrentDb);
|| ((ShowUserNotifications == ShowUserNotificationsMode.WhenTotp) && new Kp2aTotp().TryGetAdapter(new PwEntryOutput(activity.Entry, App.Kp2a.CurrentDb)) != null)) var totpPluginAdapter = new Kp2aTotp().TryGetAdapter(pwEntryOutput);
bool isTotpEntry = totpPluginAdapter != null;
bool activateKeyboard = ActivateKeyboard == ActivationCondition.Always || (ActivateKeyboard == ActivationCondition.WhenTotp && isTotpEntry);
if ((ShowUserNotifications == ActivationCondition.Always)
|| ((ShowUserNotifications == ActivationCondition.WhenTotp) && isTotpEntry)
|| activateKeyboard)
{ {
//show the notifications //show the notifications
activity.StartNotificationsService(ActivateKeyboard); activity.StartNotificationsService(activateKeyboard);
} }
else else
{ {
//to avoid getting into inconsistent state (LastOpenedEntry and Notifications): clear notifications: //to avoid getting into inconsistent state (LastOpenedEntry and Notifications): clear notifications:
CopyToClipboardService.CancelNotifications(activity); CopyToClipboardService.CancelNotifications(activity);
} }
if (CopyTotpToClipboard && isTotpEntry)
{
Dictionary<string, string> entryFields = pwEntryOutput.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString());
var totpData= totpPluginAdapter.GetTotpData(entryFields, activity, true);
if (totpData.IsTotpEntry)
{
TOTPProvider prov = new TOTPProvider(totpData);
string totp = prov.GenerateByByte(totpData.TotpSecret);
CopyToClipboardService.CopyValueToClipboardWithTimeout(activity, totp, true);
Toast.MakeText(activity, activity.GetString(Resource.String.TotpCopiedToClipboard),
ToastLength.Long).Show();
}
}
if (CloseAfterCreate) if (CloseAfterCreate)
{ {
//give plugins and TOTP time to do their work:
notifyPluginsOnOpenThread.Join(TimeSpan.FromSeconds(1));
//close //close
activity.CloseAfterTaskComplete(); activity.CloseAfterTaskComplete();
} }
@@ -630,7 +721,7 @@ namespace keepass2android
{ {
public CreateEntryThenCloseTask() public CreateEntryThenCloseTask()
{ {
ShowUserNotifications = ShowUserNotificationsMode.Always; ShowUserNotifications = ActivationCondition.Always;
} }
public override bool CanActivateSearchViewOnStart public override bool CanActivateSearchViewOnStart
@@ -670,13 +761,13 @@ namespace keepass2android
public IList<string> ProtectedFieldsList { get; set; } public IList<string> ProtectedFieldsList { get; set; }
public ShowUserNotificationsMode ShowUserNotifications { get; set; } public ActivationCondition ShowUserNotifications { get; set; }
public override void Setup(Bundle b) public override void Setup(Bundle b)
{ {
ShowUserNotifications = (ShowUserNotificationsMode)GetIntFromBundle(b,ShowUserNotificationsKey, (int)ShowUserNotificationsMode.Always); ShowUserNotifications = (ActivationCondition)GetIntFromBundle(b,ShowUserNotificationsKey, (int)ActivationCondition.Always);
Url = b.GetString(UrlKey); Url = b.GetString(UrlKey);
AllFields = b.GetString(AllFieldsKey); AllFields = b.GetString(AllFieldsKey);
@@ -724,15 +815,15 @@ namespace keepass2android
public override void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry) public override void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry)
{ {
EntryActivity.Launch(entryEditActivity, newEntry, -1, EntryActivity.Launch(entryEditActivity, newEntry, -1,
new SelectEntryTask { ShowUserNotifications = this.ShowUserNotifications}, new SelectEntryTask() { ShowUserNotifications = this.ShowUserNotifications, ActivateKeyboard = ActivationCondition.Never },
ActivityFlags.ForwardResult); ActivityFlags.ForwardResult);
//no need to call Finish here, that's done in EntryEditActivity ("closeOrShowError") //no need to call Finish here, that's done in EntryEditActivity ("closeOrShowError")
} }
public override void CompleteOnCreateEntryActivity(EntryActivity activity) public override void CompleteOnCreateEntryActivity(EntryActivity activity, Thread notifyPluginsOnOpenThread)
{ {
//if the user selects an entry before creating the new one, we're not closing the app //if the user selects an entry before creating the new one, we're not closing the app
base.CompleteOnCreateEntryActivity(activity); base.CompleteOnCreateEntryActivity(activity, notifyPluginsOnOpenThread);
} }
} }

View File

@@ -1985,6 +1985,9 @@
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable-mdpi\ic_storage_pcloudall.png" /> <AndroidResource Include="Resources\drawable-mdpi\ic_storage_pcloudall.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\ic_entry_totp.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <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. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -20,6 +20,7 @@ using AndroidX.AutoFill.Inline;
using AndroidX.AutoFill.Inline.V1; using AndroidX.AutoFill.Inline.V1;
using Java.Util.Concurrent.Atomic; using Java.Util.Concurrent.Atomic;
using keepass2android.services.AutofillBase.model; using keepass2android.services.AutofillBase.model;
using KeePassLib;
using Kp2aAutofillParser; using Kp2aAutofillParser;
namespace keepass2android.services.AutofillBase namespace keepass2android.services.AutofillBase
@@ -29,7 +30,7 @@ namespace keepass2android.services.AutofillBase
PendingIntent GetAuthPendingIntentForResponse(Context context, string query, string queryDomain, string queryPackage, PendingIntent GetAuthPendingIntentForResponse(Context context, string query, string queryDomain, string queryPackage,
bool isManualRequest, bool autoReturnFromQuery, AutofillServiceBase.DisplayWarning warning); bool isManualRequest, bool autoReturnFromQuery, AutofillServiceBase.DisplayWarning warning);
PendingIntent GetAuthPendingIntentForWarning(Context context, string query, string queryDomain, string queryPackage, AutofillServiceBase.DisplayWarning warning); PendingIntent GetAuthPendingIntentForWarning(Context context, PwUuid entryUuid, AutofillServiceBase.DisplayWarning warning);
PendingIntent GetDisablePendingIntentForResponse(Context context, string query, PendingIntent GetDisablePendingIntentForResponse(Context context, string query,
bool isManualRequest, bool isDisable); bool isManualRequest, bool isDisable);
@@ -262,20 +263,27 @@ namespace keepass2android.services.AutofillBase
{ {
List<Dataset> result = new List<Dataset>(); List<Dataset> result = new List<Dataset>();
Kp2aLog.Log("AF: BuildEntryDatasets"); Kp2aLog.Log("AF: BuildEntryDatasets");
var suggestedEntries = GetSuggestedEntries(query).ToDictionary(e => e.DatasetName, e => e); Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>> suggestedEntries = GetSuggestedEntries(query);
Kp2aLog.Log("AF: BuildEntryDatasets found " + suggestedEntries.Count + " entries"); Kp2aLog.Log("AF: BuildEntryDatasets found " + suggestedEntries.Count + " entries");
int count = 0; int count = 0;
foreach (var filledAutofillFieldCollection in suggestedEntries.Values)
var totpHelper = new Kp2aTotp();
foreach (var kvp in suggestedEntries)
{ {
var filledAutofillFieldCollection = kvp.Value;
PwEntryOutput entry = kvp.Key;
if (filledAutofillFieldCollection == null) if (filledAutofillFieldCollection == null)
continue; continue;
var inlinePresentationSpec = AutofillHelper.ExtractSpec(inlinePresentationSpecs, count); var inlinePresentationSpec = AutofillHelper.ExtractSpec(inlinePresentationSpecs, count);
if (warning == DisplayWarning.None) if ((warning == DisplayWarning.None)
&& (totpHelper.TryGetAdapter(entry) == null))
{ {
//no special dataset, we can immediately return the field collection
FilledAutofillFieldCollection<ViewNodeInputField> partitionData = FilledAutofillFieldCollection<ViewNodeInputField> partitionData =
AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, parser.AutofillFields.FocusedAutofillCanonicalHints); AutofillHintsHelper.FilterForPartition(filledAutofillFieldCollection, parser.AutofillFields.FocusedAutofillCanonicalHints);
@@ -286,9 +294,11 @@ namespace keepass2android.services.AutofillBase
} }
else else
{ {
//return an "auth" dataset (actually for just warning the user in case domain/package dont match)
//return an "auth" dataset (actually for just warning the user in case domain/package dont match and/or to make sure that we open the EntryActivity,
// thus opening the entry notification in case of TOTP)
PendingIntent pendingIntent = PendingIntent pendingIntent =
IntentBuilder.GetAuthPendingIntentForWarning(this, query, queryDomain, queryPackage, warning); IntentBuilder.GetAuthPendingIntentForWarning(this, entry.Uuid, warning);
var datasetName = filledAutofillFieldCollection.DatasetName; var datasetName = filledAutofillFieldCollection.DatasetName;
if (datasetName == null) if (datasetName == null)
{ {
@@ -320,7 +330,7 @@ namespace keepass2android.services.AutofillBase
} }
protected abstract List<FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query); protected abstract Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query);
public enum DisplayWarning public enum DisplayWarning
{ {

View File

@@ -24,7 +24,7 @@ namespace keepass2android.services.AutofillBase
{ {
protected Intent ReplyIntent; protected Intent ReplyIntent;
public static string ExtraUuidString => "EXTRA_UUID_STRING";
public static string ExtraQueryString => "EXTRA_QUERY_STRING"; public static string ExtraQueryString => "EXTRA_QUERY_STRING";
public static string ExtraQueryPackageString => "EXTRA_QUERY_PACKAGE_STRING"; public static string ExtraQueryPackageString => "EXTRA_QUERY_PACKAGE_STRING";
public static string ExtraQueryDomainString => "EXTRA_QUERY_DOMAIN_STRING"; public static string ExtraQueryDomainString => "EXTRA_QUERY_DOMAIN_STRING";
@@ -50,9 +50,10 @@ namespace keepass2android.services.AutofillBase
} }
string requestedUrl = Intent.GetStringExtra(ExtraQueryString); string requestedUrl = Intent.GetStringExtra(ExtraQueryString);
if (requestedUrl == null) string requestedUuid = Intent.GetStringExtra(ExtraUuidString);
if (requestedUrl == null && requestedUuid == null)
{ {
Kp2aLog.Log("ChooseForAutofillActivityBase: no requestedUrl "); Kp2aLog.Log("ChooseForAutofillActivityBase: no requestedUrl and no requestedUuid");
Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show(); Toast.MakeText(this, "Cannot execute query for null.", ToastLength.Long).Show();
RestartApp(); RestartApp();
return; return;
@@ -134,7 +135,15 @@ namespace keepass2android.services.AutofillBase
private void Proceed() private void Proceed()
{ {
string requestedUrl = Intent.GetStringExtra(ExtraQueryString); string requestedUrl = Intent.GetStringExtra(ExtraQueryString);
string requestedUuid = Intent.GetStringExtra(ExtraUuidString);
if (requestedUuid != null)
{
var i = GetOpenEntryIntent(requestedUuid);
StartActivityForResult(i, RequestCodeQuery);
}
else
{
var i = GetQueryIntent(requestedUrl, Intent.GetBooleanExtra(ExtraAutoReturnFromQuery, true), Intent.GetBooleanExtra(ExtraUseLastOpenedEntry, false)); var i = GetQueryIntent(requestedUrl, Intent.GetBooleanExtra(ExtraAutoReturnFromQuery, true), Intent.GetBooleanExtra(ExtraUseLastOpenedEntry, false));
if (i == null) if (i == null)
{ {
@@ -145,7 +154,11 @@ namespace keepass2android.services.AutofillBase
StartActivityForResult(i, RequestCodeQuery); StartActivityForResult(i, RequestCodeQuery);
} }
}
protected abstract Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery, bool useLastOpenedEntry); protected abstract Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery, bool useLastOpenedEntry);
protected abstract Intent GetOpenEntryIntent(string entryUuid);
protected void RestartApp() protected void RestartApp()
{ {

View File

@@ -501,24 +501,8 @@ namespace keepass2android
if (hasKeyboardDataNow) if (hasKeyboardDataNow)
{ {
notBuilder.AddKeyboardAccess(); notBuilder.AddKeyboardAccess();
if (prefs.GetBoolean("kp2a_switch_rooted", false)) if (activateKeyboard)
{
//switch rooted
bool onlySwitchOnSearch = prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false);
if (activateKeyboard || (!onlySwitchOnSearch))
{
ActivateKp2aKeyboard(); ActivateKp2aKeyboard();
}
}
else
{
//if the app is about to be closed again (e.g. after searching for a URL and returning to the browser:
// automatically bring up the Keyboard selection dialog
if ((activateKeyboard) && prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default)))
{
ActivateKp2aKeyboard();
}
}
} }
@@ -548,31 +532,6 @@ namespace keepass2android
} }
public void ActivateKeyboardIfAppropriate(bool closeAfterCreate, ISharedPreferences prefs)
{
if (prefs.GetBoolean("kp2a_switch_rooted", false))
{
//switch rooted
bool onlySwitchOnSearch = prefs.GetBoolean(
GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false);
if (closeAfterCreate || (!onlySwitchOnSearch))
{
ActivateKp2aKeyboard();
}
}
else
{
//if the app is about to be closed again (e.g. after searching for a URL and returning to the browser:
// automatically bring up the Keyboard selection dialog
if ((closeAfterCreate) &&
prefs.GetBoolean(GetString(Resource.String.OpenKp2aKeyboardAutomatically_key),
Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default)))
{
ActivateKp2aKeyboard();
}
}
}
private bool ClearNotifications() private bool ClearNotifications()
{ {
// Notification Manager // Notification Manager
@@ -587,6 +546,7 @@ namespace keepass2android
return hadKeyboardData; return hadKeyboardData;
} }
bool MakeAccessibleForKeyboard(PwEntryOutput entry, string searchUrl) bool MakeAccessibleForKeyboard(PwEntryOutput entry, string searchUrl)
{ {
#if EXCLUDE_KEYBOARD #if EXCLUDE_KEYBOARD
@@ -595,38 +555,41 @@ namespace keepass2android
bool hasData = false; bool hasData = false;
Keepass2android.Kbbridge.KeyboardDataBuilder kbdataBuilder = new Keepass2android.Kbbridge.KeyboardDataBuilder(); Keepass2android.Kbbridge.KeyboardDataBuilder kbdataBuilder = new Keepass2android.Kbbridge.KeyboardDataBuilder();
String[] keys = {PwDefs.UserNameField, String[] standardKeys = {PwDefs.UserNameField,
PwDefs.PasswordField, PwDefs.PasswordField,
Kp2aTotp.TotpKey,
PwDefs.UrlField, PwDefs.UrlField,
PwDefs.NotesField, PwDefs.NotesField,
PwDefs.TitleField PwDefs.TitleField
}; };
int[] resIds = {Resource.String.entry_user_name, int[] resIds = {Resource.String.entry_user_name,
Resource.String.entry_password, Resource.String.entry_password,
0,
Resource.String.entry_url, Resource.String.entry_url,
Resource.String.entry_comment, Resource.String.entry_comment,
Resource.String.entry_title }; Resource.String.entry_title };
//add standard fields: //add standard fields:
int i = 0; int i = 0;
foreach (string key in keys) foreach (string key in standardKeys)
{ {
String value = entry.OutputStrings.ReadSafe(key); String value = entry.OutputStrings.ReadSafe(key);
if (value.Length > 0) if (value.Length > 0)
{ {
kbdataBuilder.AddString(key, GetString(resIds[i]), value); kbdataBuilder.AddString(key, resIds[i] > 0 ? GetString(resIds[i]) : key, value);
hasData = true; hasData = true;
} }
i++; i++;
} }
//add additional fields: //add additional fields:
var totpData = new Kp2aTotp().TryGetTotpData(entry);
foreach (var pair in entry.OutputStrings) foreach (var pair in entry.OutputStrings)
{ {
var key = pair.Key; var key = pair.Key;
var value = pair.Value.ReadString(); var value = pair.Value.ReadString();
if (!PwDefs.IsStandardField(key)) if (!standardKeys.Contains(key) && totpData?.InternalFields.Contains(key) != true)
{ {
kbdataBuilder.AddString(pair.Key, pair.Key, value); kbdataBuilder.AddString(pair.Key, pair.Key, value);
hasData = true; hasData = true;
@@ -865,14 +828,9 @@ namespace keepass2android
{ {
//let's bring up the keyboard switching dialog. //let's bring up the keyboard switching dialog.
//Unfortunately this no longer works starting with Android 9 if our app is not in foreground. //Unfortunately this no longer works starting with Android 9 if our app is not in foreground.
bool mustUseHelperActivity = false; //first it seemed to be required for Samsung mostly, but there are use cases where it is required for other devices as well.
if ((int)Build.VERSION.SdkInt >= 28) //Let's be sure and use the helper activity.
{ bool mustUseHelperActivity = (int)Build.VERSION.SdkInt >= 28;
ActivityManager.RunningAppProcessInfo appProcessInfo = new ActivityManager.RunningAppProcessInfo();
ActivityManager.GetMyMemoryState(appProcessInfo);
//at least on Samsung devices, we always need the helper activity
mustUseHelperActivity = (appProcessInfo.Importance != Importance.Foreground) || (Build.Manufacturer != "Google");
}
if (mustUseHelperActivity) if (mustUseHelperActivity)
{ {
try try

View File

@@ -10,6 +10,7 @@ using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using AndroidX.Preference;
using KeePass.Util.Spr; using KeePass.Util.Spr;
using keepass2android.services.AutofillBase; using keepass2android.services.AutofillBase;
using keepass2android.services.AutofillBase.model; using keepass2android.services.AutofillBase.model;
@@ -27,6 +28,32 @@ namespace keepass2android.services.Kp2aAutofill
Permission = "keepass2android." + AppNames.PackagePart + ".permission.Kp2aChooseAutofill")] Permission = "keepass2android." + AppNames.PackagePart + ".permission.Kp2aChooseAutofill")]
public class ChooseForAutofillActivity : ChooseForAutofillActivityBase public class ChooseForAutofillActivity : ChooseForAutofillActivityBase
{ {
public bool ActivateKeyboardWhenTotpPreference
{
get
{
return PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean("AutoFillTotp_prefs_ActivateKeyboard_key", false);
}
}
public bool CopyTotpToClipboardPreference
{
get
{
return PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean("AutoFillTotp_prefs_CopyTotpToClipboard_key", true);
}
}
public bool ShowNotificationPreference
{
get
{
return PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean("AutoFillTotp_prefs_ShowNotification_key", true);
}
}
protected override Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery, bool useLastOpenedEntry) protected override Intent GetQueryIntent(string requestedUrl, bool autoReturnFromQuery, bool useLastOpenedEntry)
{ {
if (useLastOpenedEntry && (App.Kp2a.LastOpenedEntry?.SearchUrl == requestedUrl)) if (useLastOpenedEntry && (App.Kp2a.LastOpenedEntry?.SearchUrl == requestedUrl))
@@ -37,7 +64,33 @@ namespace keepass2android.services.Kp2aAutofill
//will return the results later //will return the results later
Intent i = new Intent(this, typeof(SelectCurrentDbActivity)); Intent i = new Intent(this, typeof(SelectCurrentDbActivity));
//don't show user notifications when an entry is opened. //don't show user notifications when an entry is opened.
var task = new SearchUrlTask() { UrlToSearchFor = requestedUrl, ShowUserNotifications = ShowUserNotificationsMode.WhenTotp, AutoReturnFromQuery = autoReturnFromQuery, ActivateKeyboard = false }; var task = new SearchUrlTask()
{
UrlToSearchFor = requestedUrl,
AutoReturnFromQuery = autoReturnFromQuery
};
SetTotpDependantActionsOnTask(task);
task.ToIntent(i);
return i;
}
private void SetTotpDependantActionsOnTask(SelectEntryTask task)
{
task.ShowUserNotifications =
ShowNotificationPreference ? ActivationCondition.WhenTotp : ActivationCondition.Never;
task.CopyTotpToClipboard = CopyTotpToClipboardPreference;
task.ActivateKeyboard = ActivateKeyboardWhenTotpPreference
? ActivationCondition.WhenTotp
: ActivationCondition.Never;
}
protected override Intent GetOpenEntryIntent(string entryUuid)
{
Intent i = new Intent(this, typeof(SelectCurrentDbActivity));
//don't show user notifications when an entry is opened.
var task = new OpenSpecificEntryTask() { EntryUuid = entryUuid };
SetTotpDependantActionsOnTask(task);
task.ToIntent(i); task.ToIntent(i);
return i; return i;
} }

View File

@@ -4,6 +4,7 @@ using System.Linq;
using Android; using Android;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Preferences;
using Android.Runtime; using Android.Runtime;
using keepass2android.services.AutofillBase; using keepass2android.services.AutofillBase;
using keepass2android.services.AutofillBase.model; using keepass2android.services.AutofillBase.model;
@@ -34,24 +35,29 @@ namespace keepass2android.services
{ {
} }
protected override List<FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query) protected override Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>> GetSuggestedEntries(string query)
{ {
if (!App.Kp2a.DatabaseIsUnlocked) if (!App.Kp2a.DatabaseIsUnlocked)
return new List<FilledAutofillFieldCollection<ViewNodeInputField>>(); return new Dictionary<PwEntryOutput, FilledAutofillFieldCollection<ViewNodeInputField>>();
var foundEntries = (ShareUrlResults.GetSearchResultsForUrl(query)?.Entries ?? new PwObjectList<PwEntry>()) var foundEntries = (ShareUrlResults.GetSearchResultsForUrl(query)?.Entries ?? new PwObjectList<PwEntry>())
.Select(e => new PwEntryOutput(e, App.Kp2a.FindDatabaseForElement(e)))
.ToList(); .ToList();
if (App.Kp2a.LastOpenedEntry?.SearchUrl == query) if (App.Kp2a.LastOpenedEntry?.SearchUrl == query)
{ {
foundEntries.Clear(); foundEntries.Clear();
foundEntries.Add(App.Kp2a.LastOpenedEntry); foundEntries.Add(App.Kp2a.LastOpenedEntry?.Entry);
} }
//it seems like at least with Firefox we can have at most 3 datasets. Reserve space for the disable/enable dataset and the "fill with KP2A" which allows to select another item int numDisableDatasets = 0;
//so take only 1: if (!PreferenceManager.GetDefaultSharedPreferences(this)
return foundEntries.Take(1).Select(e => ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry(e, this)) .GetBoolean(GetString(Resource.String.NoAutofillDisabling_key), false))
.ToList(); numDisableDatasets = 1;
//it seems like at least with Firefox we can have at most 3 datasets. Reserve space for the disable dataset and the "fill with KP2A" which allows to select another item
return foundEntries.Take(2-numDisableDatasets)
.Select(e => new PwEntryOutput(e, App.Kp2a.FindDatabaseForElement(e)))
.ToDictionary(e => e,
e => ChooseForAutofillActivity.GetFilledAutofillFieldCollectionFromEntry(e, this));
} }
protected override void HandleSaveRequest(StructureParser parser, StructureParser.AutofillTargetId query) protected override void HandleSaveRequest(StructureParser parser, StructureParser.AutofillTargetId query)

View File

@@ -9,6 +9,7 @@ using Android.Views;
using Android.Widget; using Android.Widget;
using keepass2android.services.AutofillBase; using keepass2android.services.AutofillBase;
using keepass2android.services.Kp2aAutofill; using keepass2android.services.Kp2aAutofill;
using KeePassLib;
namespace keepass2android.services namespace keepass2android.services
{ {
@@ -29,16 +30,14 @@ namespace keepass2android.services
return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Mutable)); return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Mutable));
} }
public PendingIntent GetAuthPendingIntentForWarning(Context context, string query, string queryDomain, string queryPackage, public PendingIntent GetAuthPendingIntentForWarning(Context context,PwUuid entryUuid,
AutofillServiceBase.DisplayWarning warning) AutofillServiceBase.DisplayWarning warning)
{ {
Intent intent = new Intent(context, typeof(ChooseForAutofillActivity)); Intent intent = new Intent(context, typeof(ChooseForAutofillActivity));
intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryString, query); intent.PutExtra(ChooseForAutofillActivityBase.ExtraUuidString, entryUuid.ToHexString());
intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryDomainString, queryDomain);
intent.PutExtra(ChooseForAutofillActivityBase.ExtraQueryPackageString, queryPackage);
intent.PutExtra(ChooseForAutofillActivityBase.ExtraDisplayWarning, (int)warning); intent.PutExtra(ChooseForAutofillActivityBase.ExtraDisplayWarning, (int)warning);
intent.PutExtra(ChooseForAutofillActivityBase.ExtraUseLastOpenedEntry, true); intent.PutExtra(ChooseForAutofillActivityBase.ExtraUseLastOpenedEntry, true);
return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Immutable)); return PendingIntent.GetActivity(context, _pendingIntentRequestCode++, intent, Util.AddMutabilityFlag(PendingIntentFlags.CancelCurrent, PendingIntentFlags.Mutable));
} }
public PendingIntent GetDisablePendingIntentForResponse(Context context, string query, public PendingIntent GetDisablePendingIntentForResponse(Context context, string query,