
(This is required because it is allowed that plugins send intents and also intents from system notifications are not received in NonExported mode)
271 lines
9.6 KiB
C#
271 lines
9.6 KiB
C#
/*
|
|
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
|
|
|
|
Keepass2Android is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Keepass2Android is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using Android.App;
|
|
using Android.Content;
|
|
using Android.Content.PM;
|
|
using Android.Graphics;
|
|
using Android.OS;
|
|
using Android.Preferences;
|
|
using AndroidX.Core.App;
|
|
using keepass2android;
|
|
using KeePassLib.Utility;
|
|
|
|
namespace keepass2android
|
|
{
|
|
/// <summary>
|
|
/// Service for showing ongoing notifications
|
|
///
|
|
/// Shows database unlocked warning persistent notification
|
|
/// Shows Quick-Unlock notification
|
|
/// </summary>
|
|
/// This service is running as foreground service to keep the app alive even when it's not currently
|
|
/// used by the user. This ensures the database is kept in memory (until Android kills it due to low memory).
|
|
/// It is important to also have a foreground service also for the "unlocked" state because it's really
|
|
/// irritating if the db is closed while switching between apps.
|
|
[Service(ForegroundServiceType = ForegroundService.TypeSpecialUse )]
|
|
[MetaData("android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE", Value = " This service is running as foreground service to keep the app alive even when it's not currently used by the user. This ensures the database is kept in memory (until Android kills it due to low memory). It is important to also have a foreground service also for the \"unlocked\" state because it's really irritating if the db is closed while switching between apps.")]
|
|
|
|
public class OngoingNotificationsService : Service
|
|
{
|
|
protected override void AttachBaseContext(Context baseContext)
|
|
{
|
|
base.AttachBaseContext(LocaleManager.setLocale(baseContext));
|
|
}
|
|
private ScreenOffReceiver _screenOffReceiver;
|
|
|
|
#region Service
|
|
private const int QuickUnlockId = 100;
|
|
private const int UnlockedWarningId = 200;
|
|
|
|
public override void OnCreate()
|
|
{
|
|
base.OnCreate();
|
|
|
|
_screenOffReceiver = new ScreenOffReceiver();
|
|
IntentFilter filter = new IntentFilter();
|
|
filter.AddAction(Intent.ActionScreenOff);
|
|
RegisterReceiver(_screenOffReceiver, filter, ReceiverFlags.Exported);
|
|
}
|
|
|
|
|
|
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
|
|
{
|
|
Kp2aLog.Log("Starting/Updating OngoingNotificationsService. Database " + (App.Kp2a.DatabaseIsUnlocked ? "Unlocked" : (App.Kp2a.QuickLocked ? "QuickLocked" : "Locked")));
|
|
|
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
|
|
|
// Set the icon to reflect the current state
|
|
if (App.Kp2a.DatabaseIsUnlocked)
|
|
{
|
|
// Clear QuickUnlock icon
|
|
notificationManager.Cancel(QuickUnlockId);
|
|
|
|
//use foreground again to let the app not be killed too easily.
|
|
StartForeground(UnlockedWarningId, GetUnlockedNotification());
|
|
}
|
|
else
|
|
{
|
|
notificationManager.Cancel(UnlockedWarningId);
|
|
|
|
if (App.Kp2a.QuickLocked)
|
|
{
|
|
// Show the Quick Unlock notification
|
|
StartForeground(QuickUnlockId, GetQuickUnlockNotification());
|
|
}
|
|
else
|
|
{
|
|
// Not showing any notification, database is locked, no point in keeping running
|
|
StopSelf();
|
|
}
|
|
}
|
|
|
|
return StartCommandResult.NotSticky;
|
|
}
|
|
|
|
public override void OnTaskRemoved(Intent rootIntent)
|
|
{
|
|
base.OnTaskRemoved(rootIntent);
|
|
|
|
Kp2aLog.Log("OngoingNotificationsService.OnTaskRemoved: " + rootIntent.Action);
|
|
|
|
// If the user has closed the task (probably by swiping it out of the recent apps list) then lock the database
|
|
App.Kp2a.Lock();
|
|
}
|
|
|
|
public override void OnDestroy()
|
|
{
|
|
base.OnDestroy();
|
|
|
|
CancelNotifications(this);
|
|
|
|
Kp2aLog.Log("OngoingNotificationsService.OnDestroy");
|
|
|
|
// If the service is killed, then lock the database immediately
|
|
if (App.Kp2a.DatabaseIsUnlocked)
|
|
{
|
|
App.Kp2a.Lock(false);
|
|
}
|
|
|
|
UnregisterReceiver(_screenOffReceiver);
|
|
}
|
|
|
|
public static void CancelNotifications(Context ctx)
|
|
{
|
|
var notificationManager = (NotificationManager) ctx.GetSystemService(NotificationService);
|
|
notificationManager.Cancel(UnlockedWarningId);
|
|
// Quick Unlock notification should be removed automatically by the service (if present), as it was the foreground notification.
|
|
|
|
//also remove any notifications of the app
|
|
notificationManager.CancelAll();
|
|
}
|
|
|
|
public override IBinder OnBind(Intent intent)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region QuickUnlock
|
|
|
|
private Notification GetQuickUnlockNotification()
|
|
{
|
|
int grayIconResouceId = Resource.Drawable.ic_launcher_gray;
|
|
if ((int)Android.OS.Build.VERSION.SdkInt < 16)
|
|
if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(GetString(Resource.String.QuickUnlockIconHidden_key), false))
|
|
{
|
|
grayIconResouceId = Resource.Drawable.transparent;
|
|
}
|
|
NotificationCompat.Builder builder =
|
|
new NotificationCompat.Builder(this, App.NotificationChannelIdQuicklocked)
|
|
.SetSmallIcon(grayIconResouceId)
|
|
.SetLargeIcon(MakeLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.NotificationLockedIcon)))
|
|
.SetVisibility((int)Android.App.NotificationVisibility.Secret)
|
|
.SetContentTitle(GetString(Resource.String.app_name))
|
|
.SetContentText(GetString(Resource.String.database_loaded_quickunlock_enabled, GetDatabaseName()));
|
|
|
|
if ((int)Build.VERSION.SdkInt >= 16)
|
|
{
|
|
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
|
.GetBoolean(GetString(Resource.String.QuickUnlockIconHidden16_key), true))
|
|
{
|
|
builder.SetPriority((int) NotificationPriority.Min);
|
|
}
|
|
else
|
|
{
|
|
builder.SetPriority((int)NotificationPriority.Default);
|
|
}
|
|
}
|
|
|
|
// Default action is to show Kp2A
|
|
builder.SetContentIntent(GetSwitchToAppPendingIntent());
|
|
// Additional action to allow locking the database
|
|
builder.AddAction(Resource.Drawable.baseline_lock_24, GetString(Resource.String.QuickUnlock_lockButton),
|
|
PendingIntent.GetBroadcast(this, 0, new Intent(this, typeof(ApplicationBroadcastReceiver)).SetAction(Intents.CloseDatabase), Util.AddMutabilityFlag(PendingIntentFlags.UpdateCurrent, PendingIntentFlags.Immutable)));
|
|
|
|
|
|
return builder.Build();
|
|
}
|
|
|
|
private Bitmap MakeLargeIcon(Bitmap unscaled)
|
|
{
|
|
return Util.MakeLargeIcon(unscaled, this);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Unlocked Warning
|
|
|
|
private Notification GetUnlockedNotification()
|
|
{
|
|
NotificationCompat.Builder builder =
|
|
new NotificationCompat.Builder(this, App.NotificationChannelIdUnlocked)
|
|
.SetOngoing(true)
|
|
.SetSmallIcon(Resource.Drawable.ic_notify)
|
|
.SetLargeIcon(MakeLargeIcon(BitmapFactory.DecodeResource(Resources, AppNames.NotificationUnlockedIcon)))
|
|
.SetVisibility((int)Android.App.NotificationVisibility.Public)
|
|
.SetContentTitle(GetString(Resource.String.app_name))
|
|
.SetContentText(GetString(Resource.String.database_loaded_unlocked, GetDatabaseName()));
|
|
|
|
if ((int)Build.VERSION.SdkInt >= 16)
|
|
{
|
|
if (PreferenceManager.GetDefaultSharedPreferences(this)
|
|
.GetBoolean(GetString(Resource.String.ShowUnlockedNotification_key),
|
|
Resources.GetBoolean(Resource.Boolean.ShowUnlockedNotification_default)))
|
|
{
|
|
builder.SetPriority((int)NotificationPriority.Default);
|
|
}
|
|
else
|
|
{
|
|
builder.SetPriority((int) NotificationPriority.Min);
|
|
}
|
|
}
|
|
|
|
// Default action is to show Kp2A
|
|
builder.SetContentIntent(GetSwitchToAppPendingIntent());
|
|
// Additional action to allow locking the database
|
|
builder.AddAction(Resource.Drawable.baseline_lock_24, GetString(Resource.String.menu_lock), PendingIntent.GetBroadcast(this, 0, new Intent(this, typeof(ApplicationBroadcastReceiver)).SetAction(Intents.LockDatabase), Util.AddMutabilityFlag(PendingIntentFlags.UpdateCurrent, PendingIntentFlags.Immutable)));
|
|
|
|
return builder.Build();
|
|
}
|
|
|
|
private PendingIntent GetSwitchToAppPendingIntent()
|
|
{
|
|
var startKp2aIntent = new Intent(this, typeof(KeePass));
|
|
startKp2aIntent.SetAction(Intent.ActionMain);
|
|
startKp2aIntent.AddCategory(Intent.CategoryLauncher);
|
|
|
|
return PendingIntent.GetActivity(this, 0, startKp2aIntent, Util.AddMutabilityFlag(PendingIntentFlags.UpdateCurrent, PendingIntentFlags.Immutable));
|
|
}
|
|
|
|
private static string GetDatabaseName()
|
|
{
|
|
string displayString = "";
|
|
foreach (Database db in App.Kp2a.OpenDatabases)
|
|
{
|
|
var kpDatabase = db.KpDatabase;
|
|
var dbname = kpDatabase.Name;
|
|
if (String.IsNullOrEmpty(dbname))
|
|
{
|
|
//if paranoid ("don't remember recent files")return "***"
|
|
if (!App.Kp2a.GetBooleanPreference(PreferenceKey.remember_keyfile))
|
|
return "***";
|
|
dbname = UrlUtil.StripExtension(
|
|
UrlUtil.GetFileName(App.Kp2a.GetFileStorage(kpDatabase.IOConnectionInfo).GetDisplayName(kpDatabase.IOConnectionInfo)));
|
|
}
|
|
if (displayString != "")
|
|
displayString = displayString + ", ";
|
|
displayString += dbname;
|
|
}
|
|
|
|
return displayString;
|
|
}
|
|
#endregion
|
|
|
|
class ScreenOffReceiver: BroadcastReceiver
|
|
{
|
|
public override void OnReceive(Context context, Intent intent)
|
|
{
|
|
App.Kp2a.OnScreenOff();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|