Merge branch 'master' into upgrade-fluentftp

This commit is contained in:
Rick Brown
2023-11-02 15:19:28 -04:00
9 changed files with 211 additions and 18 deletions

View File

@@ -292,8 +292,20 @@ namespace keepass2android.Io
{
using (var client = GetClient(ioc))
{
/*
* For some reason GetListing(path) does not always return the contents of the directory.
* However, calling SetWorkingDirectory(path) followed by GetListing(null, options) to
* list the contents of the working directory does consistently work.
*
* Similar behavior was confirmed using ncftp client. I suspect this is a strange
* bug/nuance in the server's implementation of the LIST command?
*
* [bug #2423]
*/
client.SetWorkingDirectory(IocToLocalPath(ioc));
List<FileDescription> files = new List<FileDescription>();
foreach (FtpListItem item in client.GetListing(IocToLocalPath(ioc),
foreach (FtpListItem item in client.GetListing(null,
FtpListOption.SizeModify | FtpListOption.AllFiles))
{
switch (item.Type)

View File

@@ -48,6 +48,8 @@ using KeePassLib.Serialization;
using PluginTOTP;
using File = Java.IO.File;
using Uri = Android.Net.Uri;
using keepass2android.fileselect;
using Boolean = Java.Lang.Boolean;
namespace keepass2android
{
@@ -554,21 +556,90 @@ namespace keepass2android
}
}
internal void StartNotificationsService(bool activateKeyboard)
{
Intent showNotIntent = new Intent(this, typeof (CopyToClipboardService));
showNotIntent.SetAction(Intents.ShowNotification);
showNotIntent.PutExtra(KeyEntry, new ElementAndDatabaseId(App.Kp2a.CurrentDb, Entry).FullId);
AppTask.PopulatePasswordAccessServiceIntent(showNotIntent);
showNotIntent.PutExtra(KeyActivateKeyboard, activateKeyboard);
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
if (permissions.Length == 1 && permissions.First() == Android.Manifest.Permission.PostNotifications &&
grantResults.First() == Permission.Granted)
{
StartNotificationsServiceAfterPermissionsCheck(requestCode == 1 /*requestCode is used to transfer this flag*/);
}
StartService(showNotIntent);
}
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
internal void StartNotificationsService(bool activateKeyboard)
{
if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(
GetString(Resource.String.CopyToClipboardNotification_key),
Resources.GetBoolean(Resource.Boolean.CopyToClipboardNotification_default)) == false
&& PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(
GetString(Resource.String.UseKp2aKeyboard_key),
Resources.GetBoolean(Resource.Boolean.UseKp2aKeyboard_default)) == false)
{
//notifications are disabled
return;
}
if ((int)Build.VERSION.SdkInt < 33 || CheckSelfPermission(Android.Manifest.Permission.PostNotifications) ==
Permission.Granted)
{
StartNotificationsServiceAfterPermissionsCheck(activateKeyboard);
return;
}
//user has not yet granted Android 13's POST_NOTIFICATONS permission for the app.
//check if we should ask them to grant:
if (!ShouldShowRequestPermissionRationale(Android.Manifest.Permission.PostNotifications) //this menthod returns false if we haven't asked yet or if the user has denied permission too often
&& PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean("RequestedPostNotificationsPermission", false))//use a preference to tell the difference between "haven't asked yet" and "have asked too often"
{
//user has denied permission before. Do not show the dialog. User must give permission in the Android App settings.
return;
}
new AlertDialog.Builder(this)
.SetTitle(Resource.String.post_notifications_dialog_title)
.SetMessage(Resource.String.post_notifications_dialog_message)
.SetNegativeButton(Resource.String.post_notifications_dialog_disable, (sender, args) =>
{
//disable this dialog for the future by disabling the notification preferences
var edit= PreferenceManager.GetDefaultSharedPreferences(this).Edit();
edit.PutBoolean(GetString(Resource.String.CopyToClipboardNotification_key), false);
edit.PutBoolean(GetString(Resource.String.UseKp2aKeyboard_key), false);
edit.Commit();
})
.SetPositiveButton(Resource.String.post_notifications_dialog_allow, (sender, args) =>
{
//remember that we did ask for permission at least once:
var edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
edit.PutBoolean("RequestedPostNotificationsPermission", true);
edit.Commit();
//request permission. user must grant, we'll show notifications in the OnRequestPermissionResults() callback
Android.Support.V4.App.ActivityCompat.RequestPermissions(this, new[] { Android.Manifest.Permission.PostNotifications }, activateKeyboard ? 1 : 0 /*use requestCode to transfer the flag*/);
private String getDateTime(DateTime dt)
})
.SetNeutralButton(Resource.String.post_notifications_dialog_notnow, (sender, args) => { })
.Show();
}
private void StartNotificationsServiceAfterPermissionsCheck(bool activateKeyboard)
{
Intent showNotIntent = new Intent(this, typeof(CopyToClipboardService));
showNotIntent.SetAction(Intents.ShowNotification);
showNotIntent.PutExtra(KeyEntry, new ElementAndDatabaseId(App.Kp2a.CurrentDb, Entry).FullId);
AppTask.PopulatePasswordAccessServiceIntent(showNotIntent);
showNotIntent.PutExtra(KeyActivateKeyboard, activateKeyboard);
StartService(showNotIntent);
}
private String getDateTime(DateTime dt)
{
return dt.ToLocalTime().ToString("g", CultureInfo.CurrentUICulture);
}

View File

@@ -21,6 +21,7 @@ using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Views;
@@ -57,8 +58,9 @@ namespace keepass2android
{ Resource.Id.child_db_infotext, 13 },
{ Resource.Id.fingerprint_infotext, 12 },
{ Resource.Id.autofill_infotext, 11 },
{ Resource.Id.notification_info_android8_infotext, 10 },
{ Resource.Id.infotext, 9 },
{ Resource.Id.notification_permission_infotext, 10 },
{ Resource.Id.notification_info_android8_infotext, 9 },
{ Resource.Id.infotext, 8 },
{ Resource.Id.select_other_entry, 20},
{ Resource.Id.add_url_entry, 20},
};
@@ -273,6 +275,7 @@ namespace keepass2android
UpdateFingerprintInfo();
UpdateAutofillInfo();
UpdateAndroid8NotificationInfo();
UpdatePostNotificationsPermissionInfo();
UpdateInfotexts();
RefreshIfDirty();
@@ -280,6 +283,7 @@ namespace keepass2android
SetSearchItemVisibility();
}
private void UpdateInfotexts()
{
@@ -385,6 +389,31 @@ namespace keepass2android
hasCalledOtherActivity = false;
}
private void UpdatePostNotificationsPermissionInfo(bool hideForever=false)
{
const string prefsKey = "DidShowNotificationPermissionInfo";
bool canShowNotificationInfo = ((int)Build.VERSION.SdkInt >= 33)
&& (!_prefs.GetBoolean(prefsKey, false)
&& (CheckSelfPermission(Android.Manifest.Permission.PostNotifications) !=
Permission.Granted)
&& (ShouldShowRequestPermissionRationale(Android.Manifest.Permission.PostNotifications) //this menthod returns false if we haven't asked yet or if the user has denied permission too often
|| !PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean("RequestedPostNotificationsPermission", false))//use a preference to tell the difference between "haven't asked yet" and "have asked too often"
);
if ((canShowNotificationInfo) && hideForever)
{
_prefs.Edit().PutBoolean(prefsKey, true).Commit();
canShowNotificationInfo = false;
}
if (canShowNotificationInfo)
{
RegisterInfoTextDisplay("NotificationPermissionInfo"); //this ensures that we don't show the general info texts too soon
}
UpdateBottomBarElementVisibility(Resource.Id.notification_permission_infotext, canShowNotificationInfo);
}
private void UpdateAndroid8NotificationInfo(bool hideForever = false)
{
const string prefsKey = "DidShowAndroid8NotificationInfo";
@@ -606,6 +635,25 @@ namespace keepass2android
}
if (FindViewById(Resource.Id.post_notification_button_allow) != null)
{
FindViewById(Resource.Id.post_notification_button_allow).Click += (sender, args) =>
{
//remember that we did ask for permission at least once:
var edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit();
edit.PutBoolean("RequestedPostNotificationsPermission", true);
edit.Commit();
Android.Support.V4.App.ActivityCompat.RequestPermissions(this, new[] { Android.Manifest.Permission.PostNotifications }, 0);
UpdatePostNotificationsPermissionInfo(true);
};
FindViewById(Resource.Id.post_notification_button_dont_show_again).Click += (sender, args) =>
{
UpdatePostNotificationsPermissionInfo(true);
};
}

View File

@@ -43,7 +43,7 @@
</queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_notify_locked" android:label="KP2A entry search" android:name="keepass2android.keepass2android_debug.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_debug.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application
@@ -258,6 +258,7 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<uses-permission android:name="keepass2android.keepass2android_debug.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<!-- READ_PHONE_STATE seems to come from some library or so, not clear where. We don't want to have it, remove it: -->

View File

@@ -42,8 +42,8 @@
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="31" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher" android:label="KP2A entry search" android:name="keepass2android.keepass2android.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
@@ -270,6 +270,10 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<uses-permission android:name="keepass2android.keepass2android.permission.KP2aInternalSearch" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Samsung Pass permission -->
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />

View File

@@ -40,7 +40,7 @@
</intent>
</queries>
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="31" />
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="33" />
<permission android:description="@string/permission_desc2" android:icon="@drawable/ic_launcher_offline" android:label="KP2A entry search" android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" android:protectionLevel="signature" />
<permission android:description="@string/permission_desc3" android:icon="@drawable/ic_launcher_offline" android:label="KP2A choose autofill dataset" android:name="keepass2android.keepass2android_nonet.permission.Kp2aChooseAutofill" android:protectionLevel="signature" />
<application
@@ -243,6 +243,9 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalFileBrowsing" />
<uses-permission android:name="keepass2android.keepass2android_nonet.permission.KP2aInternalSearch" />

View File

@@ -245,6 +245,50 @@
style="@style/BottomBarButton" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/notification_permission_infotext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:orientation="vertical">
<TextView android:id="@+id/infotext" android:text="@string/PostNotificationsPermissionInfo_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_margin="6dp"
android:layout_marginBottom="2dp"
/>
<RelativeLayout
android:id="@+id/notification_permissions_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
<Button
android:id="@+id/post_notification_button_allow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:paddingTop="4dp"
android:text="@string/post_notifications_dialog_allow"
style="@style/BottomBarButton" />
<Button
android:id="@+id/post_notification_button_dont_show_again"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:paddingTop="4dp"
android:text="@string/dont_show_again"
style="@style/BottomBarButton" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/infotext"
android:layout_width="match_parent"

View File

@@ -484,6 +484,10 @@
<string name="IconVisibilityInfo_Android8_text">Android 8 has introduced new behavior for notifications. If you want to hide the icon for Keepass2Android\'s notifications, please configure this through the system settings. Set the importance of the notification category to Minimum.</string>
<string name="IconVisibilityInfo_Android8_btnSettings">Open settings</string>
<string name="PostNotificationsPermissionInfo_text">Keepass2Android can display a system notification while your database is not locked. For this to work, please grant permission.</string>
<string name="DontCare">I don\'t care</string>
<string name="DocumentAccessRevoked">The file is no longer accessible to Keepass2Android. Either it was removed or the access permissions have been revoked. Please use re-open the file, e.g. using Change database.</string>
@@ -1369,6 +1373,12 @@ Initial public release
<string name="enable_fingerprint_hint">Keepass2Android has detected biometric hardware. Do you want to enable Biometric Unlock for this database?</string>
<string name="post_notifications_dialog_title">Allow notifications</string>
<string name="post_notifications_dialog_message">Keepass2Android can show notifications with buttons to copy values like passwords and TOTPs to clipboard, or to bring up the built-in keyboard. This is useful to transfer values into other apps without switching to Keepass2Android repeatedly. Do you want to enable such notifications?</string>
<string name="post_notifications_dialog_allow">Allow notifications</string>
<string name="post_notifications_dialog_disable">Disable this feature</string>
<string name="post_notifications_dialog_notnow">Not now</string>
<string name="understand">I understand</string>
<string name="dont_show_again">Do not show again</string>

View File

@@ -315,7 +315,7 @@ namespace keepass2android
}
else
{
//Anrdoid 8 requires that we call StartForeground() shortly after starting the service with StartForegroundService.
//Android 8 requires that we call StartForeground() shortly after starting the service with StartForegroundService.
//This is not possible when we're closing the service. In this case we don't use the StopSelf in the OngoingNotificationsService.OnStartCommand() anymore but directly stop the service.
OngoingNotificationsService.CancelNotifications(ctx); //The docs are not 100% clear if OnDestroy() will be called immediately. So make sure the notifications are up to date.