allow to turn off saving of credentials through Autofill for the whole app. Closes https://github.com/PhilippC/keepass2android/issues/488. Allow to re-enable disabled autofill targets in the app preferences. Closes https://github.com/PhilippC/keepass2android/issues/715

This commit is contained in:
Philipp Crocoll
2019-03-21 04:13:29 +01:00
parent c11731541c
commit 80ba3b969d
9 changed files with 482 additions and 211 deletions

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/image_list_view_row_table_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingLeft="6dip"
android:paddingRight="?android:attr/scrollbarSize" >
<TextView
android:id="@+id/disabled_query_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:ellipsize="end"
android:gravity="center_vertical"
android:paddingRight="40dip"
android:singleLine="true"
android:textColor="@android:color/black"
/>
<CheckBox
android:id="@+id/disabled_query_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:checked="false"
android:gravity="right" />
</RelativeLayout>

View File

@@ -90,6 +90,9 @@
<string name="DebugLog_key">DebugLog_key</string>
<string name="DebugLog_prefs_key">DebugLog_prefs_key</string>
<string name="DebugLog_send_key">DebugLog_send</string>
<string name="AutofillDisabledQueriesPreference_key">AutofillDisabledQueriesPreference_key</string>
<string name="OfferSaveCredentials_key">OfferSaveCredentials_key</string>
<string name="AutoFill_prefs_screen_key">AutoFill_prefs_screen_key</string>
<string name="password_access_prefs_key">password_access_prefs_key</string>
<string name="security_prefs_key">security_prefs_key</string>

View File

@@ -31,6 +31,10 @@
<string name="ShowGroupnameInSearchResult_title">Display group name in search result</string>
<string name="ShowGroupnameInSearchResult_resume">Display group name below entry titles in search results. Useful if several entries have the same name.</string>
<string name="NavigationToGroupCompleted_message">Display group is now: %1$s</string>
<string name="AutofillDisabledQueriesPreference_title">Disabled AutoFill targets</string>
<string name="AutofillDisabledQueriesPreference_summary">Views a list of apps and websites for which AutoFill has been disabled</string>
<string name="OfferSaveCredentials_summary">If enabled, Android will ask if you want to save credentials after you manually entered data into auto-fillable fields.</string>
<string name="OfferSaveCredentials_title">Offer saving of credentials</string>
<string name="ShowGroupInEntry_title">Show group name in entry view</string>

View File

@@ -403,11 +403,35 @@
android:data="https://philippc.github.io/keepass2android/AccServiceAutoFill.html" />
</Preference>
<PreferenceScreen
android:key="@string/AutoFill_prefs_screen_key"
android:title="@string/AutoFill_prefs"
>
<keepass2android.ToolbarPreference
android:key="@string/AutoFill_prefs_screen_key"
android:title="@string/AutoFill_prefs" />
<Preference
android:key="@string/AutoFill_prefs_key"
android:title="@string/AutoFill_prefs">
</Preference>
<CheckBoxPreference
android:enabled="true"
android:persistent="true"
android:summary="@string/OfferSaveCredentials_summary"
android:defaultValue="true"
android:title="@string/OfferSaveCredentials_title"
android:key="@string/OfferSaveCredentials_key" />
<keepass2android.AutofillDisabledQueriesPreference
android:title="@string/AutofillDisabledQueriesPreference_title"
android:summary="@string/AutofillDisabledQueriesPreference_summary"
android:persistent="false"
android:key="AutofillDisabledQueriesPreference_key"/>
</PreferenceScreen>
<CheckBoxPreference
android:enabled="true"

View File

@@ -260,9 +260,11 @@
<Compile Include="services\Kp2aAutofill\Kp2aAutofillService.cs" />
<Compile Include="services\OngoingNotificationsService.cs" />
<Compile Include="settings\Argon2Preference.cs" />
<Compile Include="settings\AutofillDisabledQueriesPreference.cs" />
<Compile Include="settings\DatabaseSettingsActivity.cs" />
<Compile Include="intents\Intents.cs" />
<Compile Include="SelectCurrentDbActivity.cs" />
<Compile Include="settings\IconSetPreference.cs" />
<Compile Include="SwitchImeActivity.cs" />
<Compile Include="timeout\TimeoutHelper.cs" />
<Compile Include="GroupActivity.cs" />
@@ -379,6 +381,9 @@
<AndroidResource Include="Resources\layout\httpcredentials.axml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\layout\disabled_queries_preference_row.axml">
<SubType>Designer</SubType>
</AndroidResource>
<None Include="settings\RoundsPreference %28Kopie%29.cs">
<Visible>False</Visible>
</None>

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Preferences;
using Android.Runtime;
@@ -80,8 +81,9 @@ namespace keepass2android.services.AutofillBase
AddQueryDataset(query, isManual, autofillIds, responseBuilder, !hasEntryDataset);
AddDisableDataset(query, autofillIds, responseBuilder, isManual);
responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType,
parser.AutofillFields.GetAutofillIds()).Build());
if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.OfferSaveCredentials_key), true))
responseBuilder.SetSaveInfo(new SaveInfo.Builder(parser.AutofillFields.SaveType,
parser.AutofillFields.GetAutofillIds()).Build());
callback.OnSuccess(responseBuilder.Build());
}
@@ -120,7 +122,35 @@ namespace keepass2android.services.AutofillBase
responseBuilder.AddDataset(datasetBuilder.Build());
}
public static string GetDisplayNameForQuery(string str, Context Context)
{
string displayName = str;
try
{
string appPrefix = "androidapp://";
if (str.StartsWith(appPrefix))
{
str = str.Substring(appPrefix.Length);
PackageManager pm = Context.PackageManager;
ApplicationInfo ai;
try
{
ai = pm.GetApplicationInfo(str, 0);
}
catch (PackageManager.NameNotFoundException e)
{
ai = null;
}
displayName = ai != null ? pm.GetApplicationLabel(ai) : str;
}
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
return displayName;
}
private void AddDisableDataset(string query, AutofillId[] autofillIds, FillResponse.Builder responseBuilder, bool isManual)
{
@@ -131,7 +161,7 @@ namespace keepass2android.services.AutofillBase
var sender = IntentBuilder.GetDisableIntentSenderForResponse(this, query, isManual, isForDisable);
RemoteViews presentation = AutofillHelper.NewRemoteViews(PackageName,
GetString(isForDisable ? Resource.String.autofill_disable : Resource.String.autofill_enable_for, new Java.Lang.Object[] { query}), Resource.Drawable.ic_menu_close_grey);
GetString(isForDisable ? Resource.String.autofill_disable : Resource.String.autofill_enable_for, new Java.Lang.Object[] { GetDisplayNameForQuery(query, this)}), Resource.Drawable.ic_menu_close_grey);
var datasetBuilder = new Dataset.Builder(presentation);
datasetBuilder.SetAuthentication(sender);

View File

@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Preferences;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using keepass2android.services.AutofillBase;
namespace keepass2android
{
public class AutofillDisabledQueriesPreference : ListPreference
{
private class DisabledQuery
{
public string Query { get; set; }
public string DisplayName { get; set; }
}
private class DisabledQueryPreferenceScreenAdapter : BaseAdapter
{
public Dictionary<string, bool> DisabledQueriesValues = new Dictionary<string, bool>();
private readonly AutofillDisabledQueriesPreference _pref;
public DisabledQueryPreferenceScreenAdapter(AutofillDisabledQueriesPreference pref, Context context)
{
_pref = pref;
}
private class CustomHolder : Java.Lang.Object
{
private TextView text = null;
private CheckBox checkbox = null;
public CustomHolder(View row, int position, AutofillDisabledQueriesPreference pref)
{
text = (TextView) row.FindViewById(Resource.Id.disabled_query_text);
text.Text = pref.DisabledQueries[position].DisplayName;
checkbox = (CheckBox) row.FindViewById(Resource.Id.disabled_query_checkbox);
checkbox.Id = position;
checkbox.Clickable = true;
checkbox.Checked = true;
}
public CheckBox Checkbox
{
get { return checkbox; }
}
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
CustomHolder holder = null;
int p = position;
row = LayoutInflater.From(_pref.Context)
.Inflate(Resource.Layout.disabled_queries_preference_row, parent, false);
holder = new CustomHolder(row, position, _pref);
row.Tag = holder;
/*row.Clickable = true;
row.Focusable = true;
row.FocusableInTouchMode = true;
*/
((CustomHolder) row.Tag).Checkbox.CheckedChange += (sender, args) =>
{
DisabledQueriesValues[_pref.DisabledQueries[p].Query] = args.IsChecked;
};
return row;
}
public override int Count
{
get { return _pref.DisabledQueries.Count; }
}
}
List<DisabledQuery> _disabledQueries = null;
private List<DisabledQuery> DisabledQueries
{
get
{
var prefs = PreferenceManager.GetDefaultSharedPreferences(this.Context);
_disabledQueries = prefs.GetStringSet("AutoFillDisabledQueries", new List<string>()).Select(str =>
new DisabledQuery() {Query = str, DisplayName = AutofillServiceBase.GetDisplayNameForQuery(str, Context)}).ToList();
return _disabledQueries;
}
}
protected AutofillDisabledQueriesPreference(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
private readonly Task _populatorTask;
public AutofillDisabledQueriesPreference(Context context, IAttributeSet attrs)
: base(context, attrs)
{
_populatorTask = Task.Factory.StartNew(() =>
{
SetEntries(DisabledQueries.Select(s => s.DisplayName).ToArray());
SetEntryValues(DisabledQueries.Select(s => s.Query).ToArray());
});
}
protected override void OnPrepareDialogBuilder(AlertDialog.Builder builder)
{
_populatorTask.Wait();
base.OnPrepareDialogBuilder(builder);
var adapter = new DisabledQueryPreferenceScreenAdapter(this, Context);
builder.SetAdapter(adapter, (sender, args) => { });
builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) =>
{
List<string> newList = adapter.DisabledQueriesValues.Where(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();
var prefs = PreferenceManager.GetDefaultSharedPreferences(this.Context);
prefs.Edit().PutStringSet("AutoFillDisabledQueries", newList).Commit();
});
}
}
}

View File

@@ -16,24 +16,16 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
*/
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Widget;
using Android.Preferences;
using Android.Provider;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Views.Autofill;
using Java.IO;
using KeePassLib.Cryptography.Cipher;
@@ -47,203 +39,6 @@ using KeePassLib.Cryptography.KeyDerivation;
namespace keepass2android
{
public class IconSetPreference : ListPreference
{
private int selectedEntry;
private class IconSet
{
public string PackageName { get; set; }
public string DisplayName { get; set; }
public Drawable GetIcon(Context context)
{
if (PackageName == context.PackageName)
return context.Resources.GetDrawable(Resource.Drawable.ic00);
Resources res = context.PackageManager.GetResourcesForApplication(PackageName);
return res.GetDrawable(res.GetIdentifier("ic00", "drawable", PackageName));
}
}
private class IconListPreferenceScreenAdapter : BaseAdapter
{
private readonly IconSetPreference _pref;
public IconListPreferenceScreenAdapter(IconSetPreference pref, Context context)
{
_pref = pref;
}
private class CustomHolder : Java.Lang.Object
{
private TextView text = null;
private RadioButton rButton = null;
public CustomHolder(View row, int position, IconSetPreference pref)
{
text = (TextView)row.FindViewById(Resource.Id.image_list_view_row_text_view);
text.Text = pref.IconSets[position].DisplayName;
rButton = (RadioButton)row.FindViewById(Resource.Id.image_list_view_row_radio_button);
rButton.Id = position;
rButton.Clickable = false;
rButton.Checked = (pref.selectedEntry == position);
try
{
Drawable dr = pref.IconSets[position].GetIcon(row.Context);
var bitmap = ((BitmapDrawable)dr).Bitmap;
Drawable d = new BitmapDrawable(row.Resources, Bitmap.CreateScaledBitmap(bitmap, 64, 64, true));
text.SetCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
text.Text = (" " + text.Text);
}
catch (Exception)
{
}
}
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
CustomHolder holder = null;
int p = position;
row = LayoutInflater.From(_pref.Context).Inflate(Resource.Layout.image_list_preference_row, parent, false);
holder = new CustomHolder(row, position, _pref);
row.Tag = holder;
// row.setClickable(true);
// row.setFocusable(true);
// row.setFocusableInTouchMode(true);
row.Click += (sender, args) =>
{
((View)sender).RequestFocus();
Dialog mDialog = _pref.Dialog;
mDialog.Dismiss();
_pref.CallChangeListener(_pref.IconSets[p].PackageName);
ISharedPreferences pref = PreferenceManager.GetDefaultSharedPreferences(_pref.Context);
var edit = pref.Edit();
edit.PutString(_pref.Key, _pref.IconSets[p].PackageName);
edit.Commit();
_pref.selectedEntry = p;
};
return row;
}
public override int Count
{
get { return _pref.IconSets.Count; }
}
}
List<IconSet> _iconSets = null;
List<IconSet> IconSets
{
get
{
if (_iconSets != null)
return _iconSets;
_iconSets = new List<IconSet>();
_iconSets.Add(new IconSet()
{
DisplayName = Context.GetString(AppNames.AppNameResource),
PackageName = Context.PackageName
});
foreach (var p in Context.PackageManager.GetInstalledPackages(0))
{
try
{
string packageName = p.PackageName;
Resources res = Context.PackageManager.GetResourcesForApplication(packageName);
int nameId = res.GetIdentifier("kp2a_iconset_name", "string", packageName);
_iconSets.Add(new IconSet()
{
DisplayName = res.GetString(nameId),
PackageName = packageName
});
}
catch (Exception)
{
}
}
return _iconSets;
}
}
protected IconSetPreference(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
private readonly Task _populatorTask;
public IconSetPreference(Context context, IAttributeSet attrs)
: base(context, attrs)
{
_populatorTask = Task.Factory.StartNew(() =>
{
SetEntries(IconSets.Select(s => s.DisplayName).ToArray());
SetEntryValues(IconSets.Select(s => s.PackageName).ToArray());
});
}
protected override void OnPrepareDialogBuilder(AlertDialog.Builder builder)
{
_populatorTask.Wait();
base.OnPrepareDialogBuilder(builder);
var iconListPreferenceAdapter = new IconListPreferenceScreenAdapter(this, Context);
String selectedValue = PreferenceManager.GetDefaultSharedPreferences(Context).GetString(Key, "");
for (int i = 0; i < IconSets.Count; i++)
{
if (selectedValue == IconSets[i].PackageName)
{
selectedEntry = i;
break;
}
}
builder.SetAdapter(iconListPreferenceAdapter, (sender, args) => { });
builder.SetNeutralButton(Resource.String.IconSet_install, (sender, args) =>
{
Util.GotoUrl(Context, "market://search?q=keepass2android icon set");
});
}
}
//http://stackoverflow.com/a/27422401/292233
public class SettingsFragment : PreferenceFragment
{
@@ -406,7 +201,7 @@ namespace keepass2android
new AlertDialog.Builder(Context)
.SetTitle(Resource.String.autofill_enable)
.SetMessage(Resource.String.autofill_enable_failed)
.SetPositiveButton(Resource.String.ok, (o, eventArgs) => { })
.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => { })
.Show();
}
@@ -517,7 +312,10 @@ namespace keepass2android
private void UpdateAutofillPref()
{
var autofillScreen = FindPreference(GetString(Resource.String.AutoFill_prefs_screen_key));
var autofillPref = FindPreference(GetString(Resource.String.AutoFill_prefs_key));
var autofillDisabledPref = FindPreference(GetString(Resource.String.AutofillDisabledQueriesPreference_key));
var autofillSavePref = FindPreference(GetString(Resource.String.OfferSaveCredentials_key));
if (autofillPref == null)
return;
if ((Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.O) ||
@@ -526,19 +324,23 @@ namespace keepass2android
{
var passwordAccessScreen =
(PreferenceScreen) FindPreference(Activity.GetString(Resource.String.password_access_prefs_key));
passwordAccessScreen.RemovePreference(autofillPref);
passwordAccessScreen.RemovePreference(autofillScreen);
}
else
{
if (((AutofillManager) Activity.GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager))))
.HasEnabledAutofillServices)
{
autofillDisabledPref.Enabled = true;
autofillSavePref.Enabled = true;
autofillPref.Summary = Activity.GetString(Resource.String.plugin_enabled);
autofillPref.Intent = new Intent(Intent.ActionView);
autofillPref.Intent.SetData(Android.Net.Uri.Parse("https://philippc.github.io/keepass2android/OreoAutoFill.html"));
}
else
{
autofillDisabledPref.Enabled = false;
autofillSavePref.Enabled = false;
autofillPref.Summary = Activity.GetString(Resource.String.not_enabled);
}

View File

@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Preferences;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
namespace keepass2android
{
public class IconSetPreference : ListPreference
{
private int selectedEntry;
private class IconSet
{
public string PackageName { get; set; }
public string DisplayName { get; set; }
public Drawable GetIcon(Context context)
{
if (PackageName == context.PackageName)
return context.Resources.GetDrawable(Resource.Drawable.ic00);
Resources res = context.PackageManager.GetResourcesForApplication(PackageName);
return res.GetDrawable(res.GetIdentifier("ic00", "drawable", PackageName));
}
}
private class IconListPreferenceScreenAdapter : BaseAdapter
{
private readonly IconSetPreference _pref;
public IconListPreferenceScreenAdapter(IconSetPreference pref, Context context)
{
_pref = pref;
}
private class CustomHolder : Java.Lang.Object
{
private TextView text = null;
private RadioButton rButton = null;
public CustomHolder(View row, int position, IconSetPreference pref)
{
text = (TextView)row.FindViewById(Resource.Id.image_list_view_row_text_view);
text.Text = pref.IconSets[position].DisplayName;
rButton = (RadioButton)row.FindViewById(Resource.Id.image_list_view_row_radio_button);
rButton.Id = position;
rButton.Clickable = false;
rButton.Checked = (pref.selectedEntry == position);
try
{
Drawable dr = pref.IconSets[position].GetIcon(row.Context);
var bitmap = ((BitmapDrawable)dr).Bitmap;
Drawable d = new BitmapDrawable(row.Resources, Bitmap.CreateScaledBitmap(bitmap, 64, 64, true));
text.SetCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
text.Text = (" " + text.Text);
}
catch (Exception)
{
}
}
}
public override Java.Lang.Object GetItem(int position)
{
return null;
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
CustomHolder holder = null;
int p = position;
row = LayoutInflater.From(_pref.Context).Inflate(Resource.Layout.image_list_preference_row, parent, false);
holder = new CustomHolder(row, position, _pref);
row.Tag = holder;
// row.setClickable(true);
// row.setFocusable(true);
// row.setFocusableInTouchMode(true);
row.Click += (sender, args) =>
{
((View)sender).RequestFocus();
Dialog mDialog = _pref.Dialog;
mDialog.Dismiss();
_pref.CallChangeListener(_pref.IconSets[p].PackageName);
ISharedPreferences pref = PreferenceManager.GetDefaultSharedPreferences(_pref.Context);
var edit = pref.Edit();
edit.PutString(_pref.Key, _pref.IconSets[p].PackageName);
edit.Commit();
_pref.selectedEntry = p;
};
return row;
}
public override int Count
{
get { return _pref.IconSets.Count; }
}
}
List<IconSet> _iconSets = null;
List<IconSet> IconSets
{
get
{
if (_iconSets != null)
return _iconSets;
_iconSets = new List<IconSet>();
_iconSets.Add(new IconSet()
{
DisplayName = Context.GetString(AppNames.AppNameResource),
PackageName = Context.PackageName
});
foreach (var p in Context.PackageManager.GetInstalledPackages(0))
{
try
{
string packageName = p.PackageName;
Resources res = Context.PackageManager.GetResourcesForApplication(packageName);
int nameId = res.GetIdentifier("kp2a_iconset_name", "string", packageName);
_iconSets.Add(new IconSet()
{
DisplayName = res.GetString(nameId),
PackageName = packageName
});
}
catch (Exception)
{
}
}
return _iconSets;
}
}
protected IconSetPreference(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
private readonly Task _populatorTask;
public IconSetPreference(Context context, IAttributeSet attrs)
: base(context, attrs)
{
_populatorTask = Task.Factory.StartNew(() =>
{
SetEntries(IconSets.Select(s => s.DisplayName).ToArray());
SetEntryValues(IconSets.Select(s => s.PackageName).ToArray());
});
}
protected override void OnPrepareDialogBuilder(AlertDialog.Builder builder)
{
_populatorTask.Wait();
base.OnPrepareDialogBuilder(builder);
var iconListPreferenceAdapter = new IconListPreferenceScreenAdapter(this, Context);
String selectedValue = PreferenceManager.GetDefaultSharedPreferences(Context).GetString(Key, "");
for (int i = 0; i < IconSets.Count; i++)
{
if (selectedValue == IconSets[i].PackageName)
{
selectedEntry = i;
break;
}
}
builder.SetAdapter(iconListPreferenceAdapter, (sender, args) => { });
builder.SetNeutralButton(Resource.String.IconSet_install, (sender, args) =>
{
Util.GotoUrl(Context, "market://search?q=keepass2android icon set");
});
}
}
}