Broadcast backup improvements
* Add setting to enable backup types as discusses in #157 * Add different notification channels
This commit is contained in:
parent
8563679b17
commit
76edcb900a
11 changed files with 160 additions and 74 deletions
|
@ -60,6 +60,7 @@ import org.shadowice.flocke.andotp.R;
|
|||
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||
import org.shadowice.flocke.andotp.Utilities.EncryptionHelper;
|
||||
import org.shadowice.flocke.andotp.Utilities.KeyStoreHelper;
|
||||
import org.shadowice.flocke.andotp.Utilities.NotificationHelper;
|
||||
import org.shadowice.flocke.andotp.Utilities.TokenCalculator;
|
||||
import org.shadowice.flocke.andotp.View.EntriesCardAdapter;
|
||||
import org.shadowice.flocke.andotp.View.FloatingActionMenu;
|
||||
|
@ -222,6 +223,7 @@ public class MainActivity extends BaseActivity
|
|||
ItemTouchHelper touchHelper = new ItemTouchHelper(touchHelperCallback);
|
||||
touchHelper.attachToRecyclerView(recList);
|
||||
|
||||
NotificationHelper.initializeNotificationChannels(this);
|
||||
restoreSortMode();
|
||||
|
||||
float durationScale = android.provider.Settings.Global.getFloat(this.getContentResolver(), android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 0);
|
||||
|
|
|
@ -24,33 +24,28 @@
|
|||
package org.shadowice.flocke.andotp.Receivers;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
||||
import org.shadowice.flocke.andotp.R;
|
||||
import org.shadowice.flocke.andotp.Utilities.Constants;
|
||||
import org.shadowice.flocke.andotp.Utilities.NotificationHelper;
|
||||
import org.shadowice.flocke.andotp.Utilities.Settings;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public abstract class BackupBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
protected boolean canSaveBackup(Context context) {
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_read_permission_failed);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_read_permission_failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_write_permission_failed);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_write_permission_failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,19 +43,23 @@ import java.util.ArrayList;
|
|||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
// Use the following command to test in the dev version:
|
||||
// adb shell am broadcast -a org.shadowice.flocke.andotp.broadcast.ENCRYPTED_BACKUP org.shadowice.flocke.andotp.dev
|
||||
public class EncryptedBackupBroadcastReceiver extends BackupBroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if(!canSaveBackup(context))
|
||||
Settings settings = new Settings(context);
|
||||
|
||||
if (settings.isEncryptedBackupBroadcastEnabled()) {
|
||||
if (!canSaveBackup(context))
|
||||
return;
|
||||
|
||||
Settings settings = new Settings(context);
|
||||
Uri savePath = Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(context, Constants.BackupType.ENCRYPTED));
|
||||
|
||||
String password = settings.getBackupPasswordEnc();
|
||||
|
||||
if (password.isEmpty()) {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_crypt_password_not_set);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_crypt_password_not_set);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -64,7 +68,7 @@ public class EncryptedBackupBroadcastReceiver extends BackupBroadcastReceiver {
|
|||
if (settings.getEncryption() == Constants.EncryptionType.KEYSTORE) {
|
||||
encryptionKey = KeyStoreHelper.loadEncryptionKeyFromKeyStore(context, false);
|
||||
} else {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_custom_encryption_failed );
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_custom_encryption_failed);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -72,19 +76,20 @@ public class EncryptedBackupBroadcastReceiver extends BackupBroadcastReceiver {
|
|||
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey);
|
||||
String plain = DatabaseHelper.entriesToString(entries);
|
||||
|
||||
boolean success = true;
|
||||
|
||||
try {
|
||||
SecretKey key = EncryptionHelper.generateSymmetricKeyFromPassword(password);
|
||||
byte[] encrypted = EncryptionHelper.encrypt(key, plain.getBytes(StandardCharsets.UTF_8));
|
||||
FileHelper.writeBytesToFile(context, savePath, encrypted);
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_success, savePath.getPath());
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, savePath.getPath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_export_failed);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_export_failed);
|
||||
}
|
||||
} else {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_storage_not_accessible);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_storage_not_accessible);
|
||||
}
|
||||
} else {
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_encrypted_disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,17 +41,17 @@ import java.util.ArrayList;
|
|||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
//Test with: adb shell am broadcast -n org.shadowice.flocke.andotp/.Receivers.PlainTextBackupBroadcastReceiver
|
||||
// Use the following command to test in the dev version:
|
||||
// adb shell am broadcast -a org.shadowice.flocke.andotp.broadcast.PLAIN_TEXT_BACKUP org.shadowice.flocke.andotp.dev
|
||||
public class PlainTextBackupBroadcastReceiver extends BackupBroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_disabled);
|
||||
|
||||
/*if(!canSaveBackup(context))
|
||||
return;
|
||||
|
||||
Settings settings = new Settings(context);
|
||||
|
||||
if (settings.isPlainTextBackupBroadcastEnabled()) {
|
||||
if (!canSaveBackup(context))
|
||||
return;
|
||||
|
||||
Uri savePath = Tools.buildUri(settings.getBackupDir(), FileHelper.backupFilename(context, Constants.BackupType.PLAIN_TEXT));
|
||||
|
||||
SecretKey encryptionKey = null;
|
||||
|
@ -59,7 +59,7 @@ public class PlainTextBackupBroadcastReceiver extends BackupBroadcastReceiver {
|
|||
if (settings.getEncryption() == Constants.EncryptionType.KEYSTORE) {
|
||||
encryptionKey = KeyStoreHelper.loadEncryptionKeyFromKeyStore(context, false);
|
||||
} else {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_custom_encryption_failed);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_custom_encryption_failed);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -67,12 +67,15 @@ public class PlainTextBackupBroadcastReceiver extends BackupBroadcastReceiver {
|
|||
ArrayList<Entry> entries = DatabaseHelper.loadDatabase(context, encryptionKey);
|
||||
|
||||
if (FileHelper.writeStringToFile(context, savePath, DatabaseHelper.entriesToString(entries))) {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_success, savePath.getPath());
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_SUCCESS, R.string.backup_receiver_title_backup_success, savePath.getPath());
|
||||
} else {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_export_failed);
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_export_failed);
|
||||
}
|
||||
} else {
|
||||
NotificationHelper.notify(context, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_storage_not_accessible);
|
||||
}*/
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_toast_storage_not_accessible);
|
||||
}
|
||||
} else {
|
||||
NotificationHelper.notify(context, Constants.NotificationChannel.BACKUP_FAILED, R.string.backup_receiver_title_backup_failed, R.string.backup_receiver_plain_disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,10 @@ public class Constants {
|
|||
OR, AND, SINGLE
|
||||
}
|
||||
|
||||
public enum NotificationChannel {
|
||||
BACKUP_FAILED, BACKUP_SUCCESS
|
||||
}
|
||||
|
||||
// Intents (Format: A0x with A = parent Activity, x = number of the intent)
|
||||
public final static int INTENT_MAIN_AUTHENTICATE = 100;
|
||||
public final static int INTENT_MAIN_SETTINGS = 101;
|
||||
|
|
|
@ -34,31 +34,63 @@ import org.shadowice.flocke.andotp.R;
|
|||
import static android.content.Context.NOTIFICATION_SERVICE;
|
||||
|
||||
public class NotificationHelper {
|
||||
public static void notify(Context context, int resIdTitle, int resIdBody) {
|
||||
notify(context, resIdTitle, context.getText(resIdBody).toString());
|
||||
private static String channelId(Constants.NotificationChannel channel) {
|
||||
return "andOTP_" + channel.name().toLowerCase();
|
||||
}
|
||||
|
||||
public static void notify(Context context, int resIdTitle, String resBody) {
|
||||
String channelId = "andOTP_channel";
|
||||
NotificationChannel channel = null;
|
||||
private static void createNotificationChannel(Context context, Constants.NotificationChannel channel) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel notificationChannel = new NotificationChannel(channelId(channel), context.getString(R.string.app_name), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
|
||||
switch(channel) {
|
||||
case BACKUP_FAILED:
|
||||
notificationChannel.setName(context.getString(R.string.notification_channel_name_backup_failed));
|
||||
notificationChannel.setDescription(context.getString(R.string.notification_channel_desc_backup_failed));
|
||||
notificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
|
||||
break;
|
||||
case BACKUP_SUCCESS:
|
||||
notificationChannel.setName(context.getString(R.string.notification_channel_name_backup_success));
|
||||
notificationChannel.setDescription(context.getString(R.string.notification_channel_desc_backup_success));
|
||||
notificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
notificationManager.createNotificationChannel(notificationChannel);
|
||||
}
|
||||
}
|
||||
|
||||
public static void initializeNotificationChannels(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
for (Constants.NotificationChannel channel : Constants.NotificationChannel.values()) {
|
||||
NotificationHelper.createNotificationChannel(context, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void notify(Context context, Constants.NotificationChannel channel, int resIdTitle, int resIdBody) {
|
||||
notify(context, channel, resIdTitle, context.getText(resIdBody).toString());
|
||||
}
|
||||
|
||||
public static void notify(Context context, Constants.NotificationChannel channel , int resIdTitle, String resBody) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, null)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setContentTitle(context.getText(resIdTitle))
|
||||
.setContentText(resBody);
|
||||
.setStyle(new NotificationCompat.BigTextStyle()
|
||||
.bigText(resBody));
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
channel = new NotificationChannel(channelId, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
|
||||
notificationManager.createNotificationChannel(channel);
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||
}
|
||||
|
||||
builder.setChannelId(channelId);
|
||||
createNotificationChannel(context, channel);
|
||||
builder.setChannelId(channelId(channel));
|
||||
|
||||
int notificationId = 1;
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(notificationId, builder.build());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -391,6 +391,18 @@ public class Settings {
|
|||
return password;
|
||||
}
|
||||
|
||||
public Set<String> getBackupBroadcasts() {
|
||||
return settings.getStringSet(getResString(R.string.settings_key_backup_broadcasts), Collections.<String>emptySet());
|
||||
}
|
||||
|
||||
public boolean isPlainTextBackupBroadcastEnabled() {
|
||||
return getBackupBroadcasts().contains("plain");
|
||||
}
|
||||
|
||||
public boolean isEncryptedBackupBroadcastEnabled() {
|
||||
return getBackupBroadcasts().contains("encrypted");
|
||||
}
|
||||
|
||||
public String getOpenPGPProvider() {
|
||||
return getString(R.string.settings_key_openpgp_provider, "");
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<string name="settings_key_backup_directory" translatable="false">pref_backup_directory</string>
|
||||
<string name="settings_key_backup_password" translatable="false">pref_backup_password</string> <!-- Deprecated -->
|
||||
<string name="settings_key_backup_password_enc" translatable="false">pref_backup_password_enc</string>
|
||||
<string name="settings_key_backup_broadcasts" translatable="false">pref_backup_broadcasts</string>
|
||||
<string name="settings_key_openpgp_provider" translatable="false">pref_openpgp_provider</string>
|
||||
<string name="settings_key_openpgp_keyid" translatable="false">pref_openpgp_keyid</string>
|
||||
<string name="settings_key_openpgp_sign" translatable="false">pref_openpgp_sign</string>
|
||||
|
@ -123,6 +124,11 @@
|
|||
<item>single</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="settings_values_backup_broadcasts" translatable="false">
|
||||
<item>plain</item>
|
||||
<item>encrypted</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="settings_entries_lang" translatable="false">
|
||||
<item>@string/settings_lang_sys_default</item>
|
||||
<item>Català</item>
|
||||
|
|
|
@ -48,8 +48,10 @@
|
|||
<string name="backup_receiver_title_backup_failed">Backup failed</string>
|
||||
<string name="backup_receiver_title_backup_success">Backup successful</string>
|
||||
|
||||
<string name="backup_receiver_disabled">Plain text backups are temporary disabled for security
|
||||
reasons. Please wait for the next release.</string>
|
||||
<string name="backup_receiver_plain_disabled">Plain-text backups are currently not allowed,
|
||||
please go to the Settings to enable them</string>
|
||||
<string name="backup_receiver_encrypted_disabled">Encrypted backups are currently not allowed,
|
||||
please go to the Settings to enable them</string>
|
||||
|
||||
<string name="backup_receiver_read_permission_failed">Read permission not granted, please do
|
||||
this before attempting backup</string>
|
||||
|
@ -58,6 +60,14 @@
|
|||
<string name="backup_receiver_custom_encryption_failed">Password/PIN based encryption not
|
||||
supported with broadcast backup</string>
|
||||
|
||||
<!-- Notification channels -->
|
||||
<string name="notification_channel_name_backup_failed">Automatic backup failed</string>
|
||||
<string name="notification_channel_name_backup_success">Automatic backup successful</string>
|
||||
|
||||
<string name="notification_channel_desc_backup_failed">These notifications are shown when an
|
||||
automatic backup has failed for some reason</string>
|
||||
<string name="notification_channel_desc_backup_success">These notifications are shown when an
|
||||
automatic backup was successful</string>
|
||||
|
||||
<!-- Toast messages -->
|
||||
<string name="backup_toast_mkdir_failed">Failed to create backup directory</string>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<string name="settings_title_backup_ask">Ask for filename</string>
|
||||
<string name="settings_title_backup_directory">Backup directory</string>
|
||||
<string name="settings_title_backup_password">Backup password</string>
|
||||
<string name="settings_title_backup_broadcasts">Backup Broadcasts</string>
|
||||
<string name="settings_title_openpgp_provider">Select OpenPGP provider</string>
|
||||
<string name="settings_title_openpgp_keyid">Select OpenPGP key</string>
|
||||
<string name="settings_title_openpgp_sign">Sign encrypted backups</string>
|
||||
|
@ -57,6 +58,9 @@
|
|||
<string name="settings_desc_backup_password">Set the password that is used to encrypt the
|
||||
backups</string>
|
||||
|
||||
<string name="settings_desc_backup_broadcasts">Select which backup types can be triggered by
|
||||
3rd-party apps using Broadcasts</string>
|
||||
|
||||
<string name="settings_desc_openpgp_sign">Every encrypted backup is additionally signed with
|
||||
your key (requires password)</string>
|
||||
<string name="settings_desc_openpgp_verify">Encrypted backups are only imported if they are
|
||||
|
@ -166,6 +170,11 @@
|
|||
<item>Switch between tags (only allow one tag to be selected at a time)</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="settings_entries_backup_broadcasts">
|
||||
<item>Plain-text backups</item>
|
||||
<item>Encrypted backups</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Special -->
|
||||
<string name="settings_lang_sys_default">System default</string>
|
||||
|
||||
|
|
|
@ -134,6 +134,14 @@
|
|||
android:title="@string/settings_title_backup_password"
|
||||
android:summary="@string/settings_desc_backup_password" />
|
||||
|
||||
<MultiSelectListPreference
|
||||
android:key="@string/settings_key_backup_broadcasts"
|
||||
android:title="@string/settings_title_backup_broadcasts"
|
||||
android:summary="@string/settings_desc_backup_broadcasts"
|
||||
android:entries="@array/settings_entries_backup_broadcasts"
|
||||
android:entryValues="@array/settings_values_backup_broadcasts"
|
||||
android:defaultValue="@array/settings_empty_array" />
|
||||
|
||||
<org.openintents.openpgp.util.OpenPgpAppPreference
|
||||
android:key="@string/settings_key_openpgp_provider"
|
||||
android:title="@string/settings_title_openpgp_provider" />
|
||||
|
|
Loading…
Reference in a new issue