diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index ad4099a..6e50db1 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -2,7 +2,7 @@ name: Changelog Generation on: release: - types: [published, released] + types: [published] workflow_dispatch: jobs: @@ -16,6 +16,7 @@ jobs: - uses: rhysd/changelog-from-release/action@v3 with: file: CHANGELOG.md + pull_request: true github_token: ${{ secrets.GITHUB_TOKEN }} commit_summary_template: 'update changelog for %s changes' args: -l 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e60fe6..c1426ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,5 +45,23 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Run lint check + run: bash ./gradlew lint + + - name: Upload lint result + uses: actions/upload-artifact@v4 + with: + name: lint-results-debug + path: BackupApp/build/reports/lint-results-debug.html + - name: Build the app run: bash ./gradlew build --stacktrace + + - name: Build debug apk + run: bash ./gradlew assembleDebug + + - name: Upload debug apk + uses: actions/upload-artifact@v4 + with: + name: debug-apk + path: BackupApp/build/outputs/apk/debug/*.apk \ No newline at end of file diff --git a/.gitignore b/.gitignore index aa9c63f..e9d0871 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,37 @@ +# Built application files +/*/build/ +build/* + +# Local configuration file (sdk path, etc) +local.properties + +# Gradle generated files +.gradle/ + +# Signing files +.signing/ + +# User-specific configurations +.idea/* *.iml -.gradle -/local.properties -.idea/ -misc.xml + +# OS-specific files .DS_Store -/build -/captures -.externalNativeBuild -.cxx +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Documents +*.pdf +*.doc +*.docx +*.odt + +# APKs +*.apk /pfa.properties +/.idea/ +/BackupApp/release diff --git a/.gitmodules b/.gitmodules index 3c35039..cdb3b42 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "BackupAPI"] path = BackupAPI url = https://github.com/SecUSo/privacy-friendly-backup-api.git +[submodule "openpgp-api"] + path = openpgp-api + url = https://github.com/open-keychain/openpgp-api diff --git a/BackupApp/build.gradle b/BackupApp/build.gradle index e10153a..508f9b9 100644 --- a/BackupApp/build.gradle +++ b/BackupApp/build.gradle @@ -24,17 +24,17 @@ android { applicationId "org.secuso.privacyfriendlybackup" minSdkVersion 21 targetSdkVersion 34 - versionCode 5 - versionName "1.3.1" + versionCode 6 + versionName "1.3.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions { annotationProcessorOptions { arguments += [ - "room.schemaLocation": "$projectDir/schemas".toString(), - "room.incremental":"true", - "room.expandProjection":"true" + "room.schemaLocation" : "$projectDir/schemas".toString(), + "room.incremental" : "true", + "room.expandProjection": "true" ] } } @@ -106,10 +106,10 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' // OpenPGP API - implementation 'org.sufficientlysecure:openpgp-api:12.0' - + //implementation 'org.sufficientlysecure:openpgp-api:12.0' + implementation(project(':openpgp-api')) // WorkManager - def work_version = "2.9.0" + def work_version = "2.9.1" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" @@ -123,10 +123,10 @@ dependencies { implementation "androidx.room:room-ktx:$roomVersion" // Lifecycle - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3" - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.3' + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6" + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.3' + implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.6' // Preferences def preference_version = "1.2.1" @@ -136,7 +136,7 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.11.0' // JSON Viewer - implementation 'com.yuyh.json:jsonviewer:1.0.6' + implementation 'com.github.smuyyh:JsonViewer:1.0.7' // Retrofit for Google Drive Test implementation 'com.squareup.retrofit2:retrofit:2.9.0' diff --git a/BackupApp/src/androidTest/java/org/secuso/privacyfriendlybackup/ConnectToPFATest.kt b/BackupApp/src/androidTest/java/org/secuso/privacyfriendlybackup/ConnectToPFATest.kt deleted file mode 100644 index ad3cae4..0000000 --- a/BackupApp/src/androidTest/java/org/secuso/privacyfriendlybackup/ConnectToPFATest.kt +++ /dev/null @@ -1,173 +0,0 @@ -package org.secuso.privacyfriendlybackup - -import android.util.Log -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.work.Configuration -import androidx.work.WorkManager -import androidx.work.testing.SynchronousExecutor -import androidx.work.testing.WorkManagerTestInitHelper - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* -import org.junit.Before -import org.junit.BeforeClass -import org.secuso.privacyfriendlybackup.api.IPFAService -import org.secuso.privacyfriendlybackup.api.common.PfaApi.ACTION_CONNECT -import org.secuso.privacyfriendlybackup.api.common.PfaError -import org.secuso.privacyfriendlybackup.backupapi.PfaApiConnection - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ConnectToPFATest { - val TAG = "PFABackup" - - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - - @Before - fun setUp() { - val config = Configuration.Builder().setMinimumLoggingLevel(Log.DEBUG).build() - WorkManagerTestInitHelper.initializeTestWorkManager(appContext, config) - WorkManager.getInstance(appContext) - } - - @Test - fun useAppContext() { - // Context of the app under test. - assertEquals("org.secuso.privacyfriendlybackup", appContext.packageName) - } - - @Test - fun bindToService() { - var testEnd = false - var connection : PfaApiConnection? = null - var valid = false - - val listener = object : PfaApiConnection.IPfaApiListener { - override fun onBound(service: IPFAService?) { - Log.d("$TAG ConnectToPFATest","Bound service successfully.") - connection!!.send(ACTION_CONNECT) - } - override fun onError(error: PfaError) { - Log.d("$TAG ConnectToPFATest","Error: ${error.errorMessage}") - testEnd = true - } - override fun onSuccess() { - Log.d("$TAG ConnectToPFATest","Command sent successfully.") - connection!!.disconnect() - valid = true - testEnd = true - } - override fun onDisconnected() { - Log.d("$TAG ConnectToPFATest","Disconnected") - testEnd = true - } - } - - connection = PfaApiConnection(appContext, "org.secuso.example", listener) - connection.connect() - - try { - do { - Thread.sleep(1000) - } while(!testEnd) - } catch (e : InterruptedException) { - if(connection.isBound()) { - connection.disconnect() - } - } - assertTrue(valid) - } - - @Test - fun bindToServiceUnknownCommand() { - var testEnd = false - var connection : PfaApiConnection? = null - var valid = false - - val listener = object : PfaApiConnection.IPfaApiListener { - override fun onBound(service: IPFAService?) { - Log.d("$TAG ConnectToPFATest","Bound service successfully.") - connection!!.send("UNKNOWN") - } - override fun onError(error: PfaError) { - Log.d("$TAG ConnectToPFATest","Error: ${error.errorMessage}") - assertEquals(error.code, PfaError.PfaErrorCode.ACTION_ERROR) - valid = true - testEnd = true - } - override fun onSuccess() { - Log.d("$TAG ConnectToPFATest","Command sent successfully.") - connection!!.disconnect() - testEnd = true - } - override fun onDisconnected() { - Log.d("$TAG ConnectToPFATest","Disconnected") - testEnd = true - } - } - - connection = PfaApiConnection(appContext, "org.secuso.example", listener) - connection.connect() - - try { - do { - Thread.sleep(1000) - } while(!testEnd) - } catch (e : InterruptedException) { - if(connection.isBound()) { - connection.disconnect() - } - } - assertTrue(valid) - } - - @Test - fun bindToServiceAPIUnsupported() { - var testEnd = false - var connection : PfaApiConnection? = null - var valid = false - - val listener = object : PfaApiConnection.IPfaApiListener { - override fun onBound(service: IPFAService?) { - println("Bound service successfully.") - connection!!.send(ACTION_CONNECT) - } - override fun onError(error: PfaError) { - println("Error: ${error.errorMessage}") - assertEquals(error.code, PfaError.PfaErrorCode.API_VERSION_UNSUPPORTED) - valid = true - testEnd = true - } - override fun onSuccess() { - println("Command sent successfully.") - connection!!.disconnect() - testEnd = true - } - override fun onDisconnected() { - println("Disconnected") - testEnd = true - } - } - - connection = PfaApiConnection(appContext, "org.secuso.example", listener, 0) - connection.connect() - - try { - do { - Thread.sleep(1000) - } while(!testEnd) - } catch (e : InterruptedException) { - if(connection.isBound()) { - connection.disconnect() - } - } - assertTrue(valid) - } -} \ No newline at end of file diff --git a/BackupApp/src/main/AndroidManifest.xml b/BackupApp/src/main/AndroidManifest.xml index 9900b86..61df407 100644 --- a/BackupApp/src/main/AndroidManifest.xml +++ b/BackupApp/src/main/AndroidManifest.xml @@ -27,14 +27,7 @@ - - - + tools:node="remove" /> + if (newValue.equals(true)) { + ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE_POST_NOTIFICATION) + } + return@setOnPreferenceChangeListener true + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionActivity.kt b/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionActivity.kt index 034c526..44aab13 100644 --- a/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionActivity.kt +++ b/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionActivity.kt @@ -1,5 +1,6 @@ package org.secuso.privacyfriendlybackup.ui.inspection +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.widget.Toast @@ -53,6 +54,7 @@ class DataInspectionActivity : AppCompatActivity() { } } + @SuppressLint("MissingSuperCall") override fun onBackPressed() { finish() } diff --git a/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionViewModel.kt b/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionViewModel.kt index d6d67f5..3bb3d82 100644 --- a/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionViewModel.kt +++ b/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/ui/inspection/DataInspectionViewModel.kt @@ -5,7 +5,11 @@ import android.net.Uri import android.text.TextUtils import android.util.Log import android.widget.Toast -import androidx.lifecycle.* +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import androidx.preference.PreferenceManager import androidx.work.ExistingWorkPolicy import androidx.work.WorkInfo @@ -26,7 +30,6 @@ import org.secuso.privacyfriendlybackup.data.room.model.BackupJob import org.secuso.privacyfriendlybackup.data.room.model.enums.BackupJobAction import org.secuso.privacyfriendlybackup.data.room.model.enums.StorageType import org.secuso.privacyfriendlybackup.worker.BackupJobManagerWorker -import java.util.* class DataInspectionViewModel(app : Application) : AndroidViewModel(app) { val TAG : String = "DataInspectionViewModel" @@ -147,9 +150,7 @@ class DataInspectionViewModel(app : Application) : AndroidViewModel(app) { } private fun setDecryptionMetaData(decryptionMetaData: DecryptionMetaData?) { - decryptionMetaData ?: return - - decryptionMetaLiveData.postValue(decryptionMetaData) + decryptionMetaData?.let { decryptionMetaLiveData.postValue(it) } } private fun getUnencryptedFilename(filename: String) = diff --git a/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/worker/EncryptionWorker.kt b/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/worker/EncryptionWorker.kt index 9f6934b..78a96f9 100644 --- a/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/worker/EncryptionWorker.kt +++ b/BackupApp/src/main/java/org/secuso/privacyfriendlybackup/worker/EncryptionWorker.kt @@ -1,9 +1,12 @@ package org.secuso.privacyfriendlybackup.worker +import android.Manifest import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.util.Log +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat @@ -387,7 +390,9 @@ class EncryptionWorker(val context: Context, params: WorkerParameters) : Corouti }.build() with(NotificationManagerCompat.from(context)) { - notify(NOTIFICATION_ID, notification) + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) { + notify(NOTIFICATION_ID, notification) + } } // end this worker and wait for user interaction diff --git a/BackupApp/src/main/res/values-de/strings.xml b/BackupApp/src/main/res/values-de/strings.xml index 1fd6454..c0aa2df 100644 --- a/BackupApp/src/main/res/values-de/strings.xml +++ b/BackupApp/src/main/res/values-de/strings.xml @@ -3,7 +3,7 @@ Backup Privacy Friendly Backup Standard - Standard Benachrichtigungskanal + Standard-Benachrichtigungskanal Suche Alle markieren Sortieren @@ -11,7 +11,7 @@ Backup Übersicht Verfügbare Apps Verschlüsselungsoptionen - Backup Einstellungen + Backup-Einstellungen Hilfe Über Verschlüsselungsoptionen @@ -20,7 +20,7 @@ Schlüssel auswählen Verschlüsselung ist aktiviert Daten werden unverschlüsselt gespeichert - Tippen um Schlüssel auszuwählen + Tippen, um Schlüssel auszuwählen Krypto-Anbieter auswählen Verschlüsselungsoptionen Sind Sie sicher, dass Sie die markierten Backups löschen möchten? Diese Operation ist permanent und kann nicht wieder rückgängig gemacht werden. @@ -50,18 +50,18 @@ Automatische Backups Externer Speicher Wiederherstellen - Fehler: Id ist nicht korrekt + Fehler: ID ist nicht korrekt Exportieren Unbekannt Lade… Entschlüssle… Unbekannter Fehler. - Ein unbekannter entschlüsselungsfehler ist aufgetreten. Bitte überprüfen Sie ob in den Verschlüsselungsoptionen der korrekte Krypto-Anbieter ausgewählt ist und der benötigte Schlüssel noch verfügbar ist. + Ein unbekannter Entschlüsselungsfehler ist aufgetreten. Bitte überprüfen Sie, ob in den Verschlüsselungsoptionen der korrekte Krypto-Anbieter ausgewählt ist und der benötigte Schlüssel noch verfügbar ist. Fertig Backup importieren Backup importieren Exportieren - Datenimport Abgeschlossen + Datenimport abgeschlossen Das Backup wurde erfolgreich importiert.\n App: %s \n Datum: %s Okay Okay @@ -77,7 +77,7 @@ Möchten Sie dieses Backup exportieren? Backup verschlüsseln Durch unverschlüsseltes Speichern des Backups in den externen Speicher sind ihre Daten ungeschützt. - Dieses Backup scheint beschädigt zu sein. Versuchen Sie ein neues Backup zu erstellen oder versuchen Sie es erneut zu importieren. + Dieses Backup scheint beschädigt zu sein. Versuchen Sie, ein neues Backup zu erstellen, oder versuchen Sie, es erneut zu importieren. Ja Nein Importieren des Backups @@ -88,27 +88,27 @@ Autoren: und Mitwirkende In Zusammenarbeit mit - Diese App gehört zur Gruppe der Privacy Friendly Apps entwickelt von der Forschungsgruppe SECUSO am Karlsruher Institut für Technologie (KIT). Quelltext lizenziert unter GPLv3. Bilder copyright SECUSO und Google Inc. + Diese App gehört zur Gruppe der Privacy Friendly Apps, entwickelt von der Forschungsgruppe SECUSO am Karlsruher Institut für Technologie (KIT). Quelltext lizenziert unter GPLv3. Bilder copyright SECUSO und Google Inc. Mehr Informationen können gefunden werden auf: Signiert: %s - Schlüssel ID: %s + Schlüssel-ID: %s Keine Signatur Gültig Ungültig Unsicher Abgelaufen - Wiederrufen + Widerrufen Unbestätigt Schlüssel fehlt Es existieren zur Zeit noch keine Backups. Erstellen Sie neue Backups über die Applikationsübersicht oder importieren Sie existierende Backups. - Zur Zeit sind keine Applikationen installiert, welche das Backup Protokoll unterstützen. Stellen Sie sicher, dass die Privacy Friendly Apps aktualisiert oder passende Apps installiert sind. + Zurzeit sind keine Applikationen installiert, welche das Backup-Protokoll unterstützen. Stellen Sie sicher, dass die Privacy Friendly Apps aktualisiert oder passende Apps installiert sind. Aufmerksamkeit erforderlich - Einstellungen für die Verschlüsselung im Hintergrund fehlen. Tippen Sie auf diese Benachrichtigung um fortzufahren. - Was ist die Privacy Friendly Backup App? + Einstellungen für die Verschlüsselung im Hintergrund fehlen. Tippen Sie auf diese Benachrichtigung, um fortzufahren. + Was ist die Privacy-Friendly-Backup-App? Wie funktioniert die Verschlüsselung? - Die Privacy Friendly Backup App unterstützt Anwender beim Erstellen von Backups anderer Privacy Friendly Apps. Die Apps kommunizieren untereinander, senden und empfangen Daten, um App-Daten zu sichern und wiederherzustellen. - Die Verschlüsselung erfolgt über einen externen Kryptographie-Anbieter. Für weitere Informationen können Sie sich die Openpgp-api ansehen, die von dieser Anwendung verwendet wird. - Um die Verschlüsselung nutzen zu können, müssen Sie eine Kryptoanbieter-Anwendung installieren. Wir empfehlen die OpenKeyChain-Anwendung, da sie quelloffen ist und sowohl im Google Play Store als auch im f-Droid-Store verfügbar ist. + Die Privacy-Friendly-Backup-App unterstützt Anwender beim Erstellen von Backups anderer Privacy Friendly Apps. Die Apps kommunizieren untereinander, senden und empfangen Daten, um App-Daten zu sichern und wiederherzustellen. + Die Verschlüsselung erfolgt über einen externen Kryptographie-Anbieter. Für weitere Informationen können Sie sich die OpenPGP-API ansehen, die von dieser Anwendung verwendet wird. + Um die Verschlüsselung nutzen zu können, müssen Sie eine Kryptoanbieter-Anwendung installieren. Wir empfehlen die OpenKeyChain-Anwendung, da sie quelloffen ist und sowohl im Google Play Store als auch im F-Droid-Store verfügbar ist. Wie viele und welche Apps werden zur Zeit unterstützt? - Wir arbeiten stetig daran neue Apps zu unterstützten. Zur Zeit wird nur die Notizen App unterstützt. + Wir arbeiten stetig daran neue Apps zu unterstützen. Zurzeit wird nur die Notizen-App unterstützt. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7e98f85..5cae6c7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.9.10" + ext.kotlin_version = "1.9.20" repositories { google() - jcenter() + mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { - classpath 'com.android.tools.build:gradle:8.1.4' + classpath 'com.android.tools.build:gradle:8.6.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -22,10 +23,11 @@ plugins { allprojects { repositories { google() - jcenter() + mavenCentral() + maven { url 'https://jitpack.io' } } } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 510e968..ee70b1d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/openpgp-api b/openpgp-api new file mode 160000 index 0000000..f91db6b --- /dev/null +++ b/openpgp-api @@ -0,0 +1 @@ +Subproject commit f91db6be300e8a777c49bbcb8ad0b3a9a583ef08 diff --git a/settings.gradle b/settings.gradle index 13d6585..4650b51 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,7 @@ include ':BackupApp' rootProject.name = "Backup" include ':BackupAPI' -project(':BackupAPI').projectDir = new File('BackupAPI/BackupAPI') \ No newline at end of file +project(':BackupAPI').projectDir = new File('BackupAPI/BackupAPI') + +include ':openpgp-api' +project(':openpgp-api').projectDir = new File('openpgp-api/openpgp-api') \ No newline at end of file