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 a15d493..68c288e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' android { compileSdkVersion 33 @@ -47,7 +48,6 @@ android { } } } - dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') // android @@ -66,9 +66,9 @@ dependencies { testImplementation 'pl.pragmatists:JUnitParams:0.3.6' // dependency injection implementation 'com.squareup.dagger:dagger:1.2.5' - annotationProcessor 'com.squareup.dagger:dagger-compiler:1.2.5' compileOnly 'com.squareup.dagger:dagger-compiler:1.2.5' + // persistence implementation 'com.j256.ormlite:ormlite-android:5.0' // reactive x @@ -80,4 +80,17 @@ dependencies { implementation 'com.github.PhilJay:MPAndroidChart:v3.0.0-beta1' // image zoom implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.5.0' + + implementation 'androidx.sqlite:sqlite:2.3.0' + implementation 'androidx.sqlite:sqlite-ktx:2.3.0' + + // 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" + + implementation 'androidx.core:core-ktx:1.7.20' } + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4134f53..c86f5c8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ - + @@ -13,11 +15,12 @@ android:xlargeScreens="true"/> + android:name=".PFAShoppingListApplication" + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/PFAShoppingListApplication.kt b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/PFAShoppingListApplication.kt new file mode 100644 index 0000000..b1abfa5 --- /dev/null +++ b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/PFAShoppingListApplication.kt @@ -0,0 +1,22 @@ +package privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist + + +import android.app.Application +import android.util.Log +import androidx.work.Configuration +import org.secuso.privacyfriendlybackup.api.pfa.BackupManager +import privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.backup.BackupCreator +import privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.backup.BackupRestorer + +class PFAShoppingListApplication : 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/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/BackupCreator.kt b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/BackupCreator.kt new file mode 100644 index 0000000..1c3813e --- /dev/null +++ b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/BackupCreator.kt @@ -0,0 +1,54 @@ +package privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.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 privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.framework.persistence.DB +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, DB.APP.dbName).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/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/BackupRestorer.kt b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/BackupRestorer.kt new file mode 100644 index 0000000..8838c51 --- /dev/null +++ b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/BackupRestorer.kt @@ -0,0 +1,122 @@ +package privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.backup + +import android.content.Context +import android.content.SharedPreferences +import android.preference.PreferenceManager +import android.util.JsonReader +import android.util.Log +import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil +import org.secuso.privacyfriendlybackup.api.backup.FileUtil +import org.secuso.privacyfriendlybackup.api.pfa.IBackupRestorer +import privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.framework.persistence.DB +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(DB.APP.dbName) + + DatabaseUtil.deleteRoomDatabase(context, DB.APP.dbName) + + 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", + "privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.pref.sort_asc_dec_key" -> preferences.putBoolean(name, reader.nextBoolean()) + "privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.pref.sort_by_key" -> preferences.putString(name, reader.nextString()) + 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/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/PFABackupService.kt b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/PFABackupService.kt new file mode 100644 index 0000000..dda4598 --- /dev/null +++ b/app/src/main/java/privacyfriendlyshoppinglist/secuso/org/privacyfriendlyshoppinglist/backup/PFABackupService.kt @@ -0,0 +1,5 @@ +package privacyfriendlyshoppinglist.secuso.org.privacyfriendlyshoppinglist.backup + +import org.secuso.privacyfriendlybackup.api.pfa.PFAAuthService + +class PFABackupService : PFAAuthService() \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6a02c77..46b473a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,12 @@ buildscript { mavenCentral() google() } + + ext.kotlin_version = "1.7.20" dependencies { - classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.android.tools.build:gradle:7.3.1' classpath 'me.tatarka:gradle-retrolambda:3.2.5' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7064403..8cfe1e4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jan 12 20:25:41 CET 2023 +#Fri Jan 13 12:17:28 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.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