diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5fb4a5c
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libs/privacy-friendly-backup-api"]
+ path = libs/privacy-friendly-backup-api
+ url = https://github.com/SecUSo/privacy-friendly-backup-api.git
diff --git a/app/build.gradle b/app/build.gradle
index 2c8f868..6f61968 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 32
@@ -41,4 +42,17 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
testImplementation 'junit:junit:4.13.2'
+
+ def room_version = "2.4.3"
+ implementation "androidx.room:room-runtime:$room_version"
+ annotationProcessor "androidx.room:room-compiler:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "androidx.room:room-ktx:$room_version"
+
+ // Backup API
+ implementation project(':backup-api')
+ def work_version = '2.7.1'
+ implementation "androidx.work:work-runtime:$work_version"
+ implementation "androidx.work:work-runtime-ktx:$work_version"
+ androidTestImplementation "androidx.work:work-testing:$work_version"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a531918..d6cfb70 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,5 +1,6 @@
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/PFCircuitTrainingApplication.kt b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/PFCircuitTrainingApplication.kt
new file mode 100644
index 0000000..5417598
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/PFCircuitTrainingApplication.kt
@@ -0,0 +1,21 @@
+package org.secuso.privacyfriendlycircuittraining
+
+import android.app.Application
+import android.util.Log
+import androidx.work.Configuration
+import org.secuso.privacyfriendlybackup.api.pfa.BackupManager
+import org.secuso.privacyfriendlycircuittraining.backup.BackupCreator
+import org.secuso.privacyfriendlycircuittraining.backup.BackupRestorer
+
+class PFCircuitTrainingApplication : Application(), Configuration.Provider {
+
+ override fun onCreate() {
+ super.onCreate()
+ BackupManager.backupCreator = BackupCreator()
+ BackupManager.backupRestorer = BackupRestorer()
+ }
+
+ override fun getWorkManagerConfiguration(): Configuration {
+ return Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/MainActivity.java b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/MainActivity.java
index 0a51572..0e40a09 100644
--- a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/MainActivity.java
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/MainActivity.java
@@ -14,6 +14,7 @@
package org.secuso.privacyfriendlycircuittraining.activities;
+import android.app.AlarmManager;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
@@ -21,6 +22,7 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceActivity;
@@ -40,6 +42,7 @@
import org.secuso.privacyfriendlycircuittraining.R;
import org.secuso.privacyfriendlycircuittraining.database.PFASQLiteHelper;
+import org.secuso.privacyfriendlycircuittraining.fragments.GrantExactAlarmPermissionDialogFragment;
import org.secuso.privacyfriendlycircuittraining.helpers.NotificationHelper;
import org.secuso.privacyfriendlycircuittraining.models.Exercise;
import org.secuso.privacyfriendlycircuittraining.models.ExerciseSet;
@@ -153,7 +156,16 @@ protected void onCreate(Bundle savedInstanceState) {
//Schedule the next motivation notification (necessary if permission was not granted)
if(NotificationHelper.isMotivationAlertEnabled(this)){
- NotificationHelper.setMotivationAlert(this);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ AlarmManager am = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
+ if (!am.canScheduleExactAlarms()) { //Check permission to schedule exact alarm on versions >= Android S
+ new GrantExactAlarmPermissionDialogFragment().show(getFragmentManager(), GrantExactAlarmPermissionDialogFragment.TAG);
+ } else {
+ NotificationHelper.setMotivationAlert(this);
+ }
+ } else {
+ NotificationHelper.setMotivationAlert(this);
+ }
}
//Set the change listener for the switch button to turn block periodization on and off
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/SettingsActivity.java b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/SettingsActivity.java
index 739aca7..92e8e65 100644
--- a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/SettingsActivity.java
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/activities/SettingsActivity.java
@@ -15,6 +15,7 @@
package org.secuso.privacyfriendlycircuittraining.activities;
import android.annotation.TargetApi;
+import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -31,6 +32,7 @@
import android.view.MenuItem;
import org.secuso.privacyfriendlycircuittraining.R;
+import org.secuso.privacyfriendlycircuittraining.fragments.GrantExactAlarmPermissionDialogFragment;
import org.secuso.privacyfriendlycircuittraining.helpers.NotificationHelper;
import java.util.List;
@@ -225,7 +227,17 @@ public void onCreate(Bundle savedInstanceState) {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if((Boolean) newValue){
- NotificationHelper.setMotivationAlert(preference.getContext());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ if (!am.canScheduleExactAlarms()) { //Check permission to schedule exact alarm on versions >= Android S
+ new GrantExactAlarmPermissionDialogFragment().show(getChildFragmentManager(), GrantExactAlarmPermissionDialogFragment.TAG);
+ return false;
+ } else {
+ NotificationHelper.setMotivationAlert(preference.getContext());
+ }
+ } else {
+ NotificationHelper.setMotivationAlert(preference.getContext());
+ }
}
else{
NotificationHelper.cancelMotivationAlert(preference.getContext());
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/BackupCreator.kt b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/BackupCreator.kt
new file mode 100644
index 0000000..3771504
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/BackupCreator.kt
@@ -0,0 +1,54 @@
+package org.secuso.privacyfriendlycircuittraining.backup
+
+import android.content.Context
+import android.preference.PreferenceManager
+import android.util.JsonWriter
+import android.util.Log
+import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil.getSupportSQLiteOpenHelper
+import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil.writeDatabase
+import org.secuso.privacyfriendlybackup.api.backup.PreferenceUtil.writePreferences
+import org.secuso.privacyfriendlybackup.api.pfa.IBackupCreator
+import org.secuso.privacyfriendlycircuittraining.database.PFASQLiteHelper
+import java.io.OutputStream
+import java.io.OutputStreamWriter
+
+class BackupCreator : IBackupCreator {
+ override fun writeBackup(context: Context, outputStream: OutputStream): Boolean {
+ Log.d(TAG, "createBackup() started")
+ val outputStreamWriter = OutputStreamWriter(outputStream, Charsets.UTF_8)
+ val writer = JsonWriter(outputStreamWriter)
+ writer.setIndent("")
+
+ try {
+ writer.beginObject()
+
+ Log.d(TAG, "Writing database")
+ writer.name("database")
+
+ val database = getSupportSQLiteOpenHelper(context, PFASQLiteHelper.DATABASE_NAME).readableDatabase
+
+ writeDatabase(writer, database)
+ database.close()
+
+ Log.d(TAG, "Writing preferences")
+ writer.name("preferences")
+
+ val pref = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
+ writePreferences(writer, pref)
+
+ writer.endObject()
+ writer.close()
+ } catch (e: Exception) {
+ Log.e(TAG, "Error occurred", e)
+ e.printStackTrace()
+ return false
+ }
+
+ Log.d(TAG, "Backup created successfully")
+ return true
+ }
+
+ companion object {
+ const val TAG = "PFABackupCreator"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/BackupRestorer.kt b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/BackupRestorer.kt
new file mode 100644
index 0000000..c5d0c40
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/BackupRestorer.kt
@@ -0,0 +1,138 @@
+package org.secuso.privacyfriendlycircuittraining.backup
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.preference.PreferenceManager
+import android.util.JsonReader
+import android.util.Log
+import androidx.annotation.NonNull
+import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil
+import org.secuso.privacyfriendlybackup.api.backup.FileUtil
+import org.secuso.privacyfriendlybackup.api.pfa.IBackupRestorer
+import org.secuso.privacyfriendlycircuittraining.database.PFASQLiteHelper
+import java.io.IOException
+import java.io.InputStream
+import java.io.InputStreamReader
+import kotlin.system.exitProcess
+
+
+class BackupRestorer : IBackupRestorer {
+ @Throws(IOException::class)
+ private fun readDatabase(reader: JsonReader, context: Context) {
+ reader.beginObject()
+ val n1: String = reader.nextName()
+ if (n1 != "version") {
+ throw RuntimeException("Unknown value $n1")
+ }
+ val version: Int = reader.nextInt()
+ val n2: String = reader.nextName()
+ if (n2 != "content") {
+ throw RuntimeException("Unknown value $n2")
+ }
+
+ Log.d(TAG, "Restoring database...")
+ val restoreDatabaseName = "restoreDatabase"
+
+ // delete if file already exists
+ val restoreDatabaseFile = context.getDatabasePath(restoreDatabaseName)
+ if (restoreDatabaseFile.exists()) {
+ DatabaseUtil.deleteRoomDatabase(context, restoreDatabaseName)
+ }
+
+ // create new restore database
+ val db = DatabaseUtil.getSupportSQLiteOpenHelper(context, restoreDatabaseName, version).writableDatabase
+
+ db.beginTransaction()
+ db.version = version
+
+ Log.d(TAG, "Copying database contents...")
+ DatabaseUtil.readDatabaseContent(reader, db)
+ db.setTransactionSuccessful()
+ db.endTransaction()
+ db.close()
+
+ reader.endObject()
+
+ // copy file to correct location
+ val actualDatabaseFile = context.getDatabasePath(PFASQLiteHelper.DATABASE_NAME)
+
+ DatabaseUtil.deleteRoomDatabase(context, PFASQLiteHelper.DATABASE_NAME)
+
+ FileUtil.copyFile(restoreDatabaseFile, actualDatabaseFile)
+ Log.d(TAG, "Database Restored")
+
+ // delete restore database
+ DatabaseUtil.deleteRoomDatabase(context, restoreDatabaseName)
+ }
+
+ @Throws(IOException::class)
+ private fun readPreferences(reader: JsonReader, preferences: SharedPreferences.Editor) {
+ reader.beginObject()
+ while (reader.hasNext()) {
+ val name: String = reader.nextName()
+ when (name) {
+ "workoutMode",
+ "org.secuso.privacyfriendlytraining.pref_start_timer_switch_enabled",
+ "org.secuso.privacyfriendlytraining.pref.motivation_alert_enabled",
+ "org.secuso.privacyfriendlytraining.pref.calories",
+ "org.secuso.privacyfriendlytraining.pref.voicecountdownworkout",
+ "org.secuso.privacyfriendlytraining.pref.voicecountdownrest",
+ "Blinking progress bar",
+ "org.secuso.privacyfriendlytraining.pref.cancel_workout_check",
+ "org.secuso.privacyfriendlytraining.pref.soundrythm",
+ "org.secuso.privacyfriendlytraining.pref.voicehalftime",
+ "org.secuso.privacyfriendlytraining.pref_keep_screen_on_switch_enabled" -> preferences.putBoolean(name, reader.nextBoolean())
+ "org.secuso.privacyfriendlytraining.pref.weight",
+ "org.secuso.privacyfriendlytraining.pref.gender",
+ "org.secuso.privacyfriendlytraining.pref.age",
+ "org.secuso.privacyfriendlytraining.pref.height" -> preferences.putString(name, reader.nextString())
+ "org.secuso.privacyfriendlytraining.pref.timer_set" -> preferences.putInt(name, reader.nextInt())
+ "org.secuso.privacyfriendlytraining.pref.motivation_alert_time" -> preferences.putLong(name, reader.nextLong())
+ "Motivation texts" -> preferences.putStringSet(name, readPreferenceSet(reader))
+ else -> throw RuntimeException("Unknown preference $name")
+ }
+ }
+ reader.endObject()
+ }
+
+ private fun readPreferenceSet(reader: JsonReader): Set {
+ val preferenceSet = mutableSetOf()
+
+ reader.beginArray()
+ while (reader.hasNext()) {
+ preferenceSet.add(reader.nextString());
+ }
+ reader.endArray()
+ return preferenceSet
+ }
+
+ override fun restoreBackup(context: Context, restoreData: InputStream): Boolean {
+ return try {
+ val isReader = InputStreamReader(restoreData)
+ val reader = JsonReader(isReader)
+ val preferences = PreferenceManager.getDefaultSharedPreferences(context).edit()
+
+ // START
+ reader.beginObject()
+ while (reader.hasNext()) {
+ val type: String = reader.nextName()
+ when (type) {
+ "database" -> readDatabase(reader, context)
+ "preferences" -> readPreferences(reader, preferences)
+ else -> throw RuntimeException("Can not parse type $type")
+ }
+ }
+ reader.endObject()
+ preferences.commit()
+
+ exitProcess(0)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ false
+ }
+ }
+
+ companion object {
+ const val TAG = "PFABackupRestorer"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/PFABackupService.kt b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/PFABackupService.kt
new file mode 100644
index 0000000..d55949c
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/backup/PFABackupService.kt
@@ -0,0 +1,5 @@
+package org.secuso.privacyfriendlycircuittraining.backup
+
+import org.secuso.privacyfriendlybackup.api.pfa.PFAAuthService
+
+class PFABackupService : PFAAuthService()
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/fragments/GrantExactAlarmPermissionDialogFragment.kt b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/fragments/GrantExactAlarmPermissionDialogFragment.kt
new file mode 100644
index 0000000..290913f
--- /dev/null
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/fragments/GrantExactAlarmPermissionDialogFragment.kt
@@ -0,0 +1,38 @@
+package org.secuso.privacyfriendlycircuittraining.fragments
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import android.app.DialogFragment
+import org.secuso.privacyfriendlycircuittraining.R
+
+class GrantExactAlarmPermissionDialogFragment : DialogFragment() {
+ @RequiresApi(Build.VERSION_CODES.S)
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ return activity.let {
+ val builder = AlertDialog.Builder(it)
+ builder.setMessage(R.string.request_schedule_exact_alarm_permission)
+ .setPositiveButton(
+ R.string.okay
+ ) { dialog, _ ->
+ val askForExactAlarmIntent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
+ dialog.dismiss()
+ context.startActivity(askForExactAlarmIntent)
+ }
+ .setNegativeButton(
+ R.string.cancel
+ ) { dialog, _ ->
+ dialog.dismiss()
+ }
+ builder.create()
+ } ?: throw IllegalStateException("Activity cannot be null")
+ }
+
+ companion object {
+ const val TAG = "GrantExactAlarmPermissionDialog"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/helpers/NotificationHelper.java b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/helpers/NotificationHelper.java
index a339c0d..204d64b 100644
--- a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/helpers/NotificationHelper.java
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/helpers/NotificationHelper.java
@@ -21,6 +21,7 @@
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
+import android.provider.Settings;
import android.util.Log;
import org.secuso.privacyfriendlycircuittraining.R;
@@ -41,13 +42,14 @@ public class NotificationHelper {
/**
* Schedules (or updates) the motivation alert notification alarm
+ *
* @param context The application context
*/
- public static void setMotivationAlert(Context context){
+ public static void setMotivationAlert(Context context) {
Log.i(LOG_CLASS, "Setting motivation alert alarm");
Intent motivationAlertIntent = new Intent(context, MotivationAlertReceiver.class);
- PendingIntent motivationAlertPendingIntent = PendingIntent.getBroadcast(context, 1, motivationAlertIntent, 0);
+ PendingIntent motivationAlertPendingIntent = PendingIntent.getBroadcast(context, 1, motivationAlertIntent, PendingIntent.FLAG_IMMUTABLE);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
@@ -62,29 +64,36 @@ public static void setMotivationAlert(Context context){
calendar.set(Calendar.DAY_OF_MONTH, Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
- if(calendar.before(Calendar.getInstance())){
+ if (calendar.before(Calendar.getInstance())) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
// Set alarm
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), motivationAlertPendingIntent);
- }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (!am.canScheduleExactAlarms()) {
+ Log.i(LOG_CLASS, "Motivation alert cannot be scheduled because of missing permission.");
+ } else {
+ am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), motivationAlertPendingIntent);
+ }
+ } else {
+ am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), motivationAlertPendingIntent);
+ }
+ } else {
am.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), motivationAlertPendingIntent);
- }else{
- am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), motivationAlertPendingIntent);
}
Log.i(LOG_CLASS, "Scheduled motivation alert at start time " + calendar.toString());
}
/**
* Checks if the motivation alert is enabled in the settings
+ *
* @param context The application context
*/
- public static boolean isMotivationAlertEnabled(Context context){
+ public static boolean isMotivationAlertEnabled(Context context) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
- if(sharedPref != null){
+ if (sharedPref != null) {
return sharedPref.getBoolean(context.getString(R.string.pref_notification_motivation_alert_enabled), false);
}
return false;
@@ -92,12 +101,13 @@ public static boolean isMotivationAlertEnabled(Context context){
/**
* Cancels the motivation alert
+ *
* @param context The application context
*/
- public static void cancelMotivationAlert(Context context){
+ public static void cancelMotivationAlert(Context context) {
Log.i(LOG_CLASS, "Canceling motivation alert alarm");
Intent motivationAlertIntent = new Intent(context, MotivationAlertReceiver.class);
- PendingIntent motivationAlertPendingIntent = PendingIntent.getBroadcast(context, 1, motivationAlertIntent, 0);
+ PendingIntent motivationAlertPendingIntent = PendingIntent.getBroadcast(context, 1, motivationAlertIntent, PendingIntent.FLAG_IMMUTABLE);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.cancel(motivationAlertPendingIntent);
}
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/receivers/MotivationAlertReceiver.java b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/receivers/MotivationAlertReceiver.java
index 4788ba4..d2046d2 100644
--- a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/receivers/MotivationAlertReceiver.java
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/receivers/MotivationAlertReceiver.java
@@ -14,11 +14,13 @@
package org.secuso.privacyfriendlycircuittraining.receivers;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.os.Build;
import android.preference.PreferenceManager;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
@@ -52,15 +54,17 @@ public class MotivationAlertReceiver extends WakefulBroadcastReceiver {
private Context context;
public static final int NOTIFICATION_ID = 0;
+ private static final String NOTIFICATION_CHANNEL = "Circuit_Training_Notifications";
+
public void onReceive(Context context, Intent intent) {
this.context = context;
if(NotificationHelper.isMotivationAlertEnabled(context)){
- motivate();
+ motivate(context);
}
}
- private void motivate() {
+ private void motivate(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
@@ -89,9 +93,15 @@ private void motivate() {
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setDescription(context.getString(R.string.app_name));
+ notificationManager.createNotificationChannel(notificationChannel);
+ }
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context.getApplicationContext())
+ NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context.getApplicationContext(), NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.ic_circletraining_logo_white_24dp)
.setContentTitle(context.getString(R.string.reminder_notification_title))
.setContentText(motivationText)
diff --git a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/services/TimerService.java b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/services/TimerService.java
index 21b888b..cd73b5c 100644
--- a/app/src/main/java/org/secuso/privacyfriendlycircuittraining/services/TimerService.java
+++ b/app/src/main/java/org/secuso/privacyfriendlycircuittraining/services/TimerService.java
@@ -704,10 +704,10 @@ public Notification buildNotification(int time) {
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Intent buttonIntent = new Intent(NOTIFICATION_BROADCAST);
- PendingIntent buttonPendingIntent = PendingIntent.getBroadcast(this, 4, buttonIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent buttonPendingIntent = PendingIntent.getBroadcast(this, 4, buttonIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
notiBuilder.setContentIntent(pendingIntent);
String message = "";
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 864eb89..f07311d 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -91,7 +91,7 @@
Was macht die App privatsphäre-freundlich?
Diese App beinhaltet keinerlei Werbung oder Tracking-Mechanismen. Außerdem werden keine Nutzerinformationen, die in der App konfiguriert oder gesammelt wurden, weitergegeben.
Welche Berechtigungen braucht die App?
- Privacy Friendly Training benötigt die Berechtigung "Startvorgang abgeschlossen". Diese wird verwendet um die täglichen Motivationsbenachrichtigungen, nach einem Neustart, wieder einzuplannen. Wird die Berechtigung nicht gewährt so muss die App, nach einem Neustart, erst wieder einmal gestartet werden.
+ Privacy Friendly Training benötigt die Berechtigung \"Startvorgang abgeschlossen\". Diese wird verwendet um die täglichen Motivationsbenachrichtigungen, nach einem Neustart, wieder einzuplannen. Wird die Berechtigung nicht gewährt so muss die App, nach einem Neustart, erst wieder einmal gestartet werden.\n\nPrivacy Friendly Training benötigt die Berechtigung \"Genauen Wecker planen\". Diese wird verwendet um die Motivationsbenachrichtigungen zu planen.
@@ -227,5 +227,6 @@
Bitte wähle eine Übung aus
Möchten Sie die ausgewählten Elemente löschen?
Das gewählte Übungsset enthält keine Übungen.
+ Um Ihnen Motivationsbenachrichtigungen senden zu können, wird die Erlaubnis benötigt, einen Alarm einzustellen. Möchten Sie die Erlaubnis jetzt erteilen?
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e98865b..baa52be 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -89,7 +89,7 @@
What makes the app privacy-friendly?
This app does not include any tracking mechanisms or advertisement. It further does not share any user information which may be collected during the usage of this app.
Which permissions are required by the app?
- Privacy Friendly Training requires the permission "Receive Boot Completed". It is used to reschedule the motivation alert after a reboot. If the permission is not granted, the app has to be launched first to reschedule the daily reminder notification.
+ Privacy Friendly Training requires the permission \"Receive Boot Completed\". It is used to reschedule the motivation alert after a reboot. If the permission is not granted, the app has to be launched first to reschedule the daily reminder notification.\n\nPrivacy Friendly Training requires the permission \"Schedule Exact Alarm\". It is used to schedule the motivation alerts.
About
@@ -258,5 +258,7 @@
Please choose an exercise
Do you want to delete the selected items?
Chosen exercise set has no exercises.
+
+ In order to send you motivation alerts, the permission to set an alarm is needed. Do you want to grant the permission now?
diff --git a/build.gradle b/build.gradle
index e1663dc..61ce192 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.7.10'
+ ext.kotlin_version = '1.7.20'
repositories {
mavenCentral()
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.2.2'
+ classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4a88a51..5fa02d8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Sep 15 14:42:43 CEST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/libs/privacy-friendly-backup-api b/libs/privacy-friendly-backup-api
new file mode 160000
index 0000000..d267b0d
--- /dev/null
+++ b/libs/privacy-friendly-backup-api
@@ -0,0 +1 @@
+Subproject commit d267b0d5e899fe12f41a6b7aac2081b8d4ea71af
diff --git a/settings.gradle b/settings.gradle
index e7b4def..7264140 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,3 @@
include ':app'
+include ':backup-api'
+project(':backup-api').projectDir = new File('libs/privacy-friendly-backup-api/BackupAPI')
\ No newline at end of file