keepass2android/src/keepass2android/services/OngoingNotificationsService.cs

250 lines
8.2 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.Graphics;
using Android.OS;
using Android.Preferences;
using Android.Support.V4.App;
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]
public class OngoingNotificationsService : Service
{
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);
}
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 current foreground status and QuickUnlock icon
StopForeground(true);
//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.LockDatabase();
}
public override void OnDestroy()
{
base.OnDestroy();
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.Cancel(UnlockedWarningId);
// Quick Unlock notification should be removed automatically by the service (if present), as it was the foreground notification.
Kp2aLog.Log("OngoingNotificationsService.OnDestroy");
// If the service is killed, then lock the database immediately
if (App.Kp2a.DatabaseIsUnlocked)
{
App.Kp2a.LockDatabase();
}
//also remove any notifications of the app
notificationManager.CancelAll();
UnregisterReceiver(_screenOffReceiver);
}
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)
.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(Android.Resource.Drawable.IcLockLock, GetString(Resource.String.QuickUnlock_lockButton),
PendingIntent.GetBroadcast(this, 0, new Intent(Intents.CloseDatabase), PendingIntentFlags.UpdateCurrent));
return builder.Build();
}
private Bitmap MakeLargeIcon(Bitmap unscaled)
{
int height = (int)(0.9*Resources.GetDimension(Android.Resource.Dimension.NotificationLargeIconHeight));
int width = (int)(0.9*Resources.GetDimension(Android.Resource.Dimension.NotificationLargeIconWidth));
return Bitmap.CreateScaledBitmap(unscaled, width, height, true);
}
#endregion
#region Unlocked Warning
private Notification GetUnlockedNotification()
{
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.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.ic_action_lock, GetString(Resource.String.menu_lock), PendingIntent.GetBroadcast(this, 0, new Intent(Intents.LockDatabase), PendingIntentFlags.UpdateCurrent));
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, PendingIntentFlags.UpdateCurrent);
}
private static string GetDatabaseName()
{
var db = App.Kp2a.GetDb().KpDatabase;
var name = db.Name;
if (String.IsNullOrEmpty(name))
{
//todo: if paranoid ("don't remember recent files") return "***"
name = App.Kp2a.GetFileStorage(db.IOConnectionInfo).GetFilenameWithoutPathAndExt(db.IOConnectionInfo);
}
return name;
}
#endregion
class ScreenOffReceiver: BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
App.Kp2a.OnScreenOff();
}
}
}
}