From 2246a5d9ce01f52fefda7e51457f221f7d18782d Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 19 Apr 2023 23:07:36 +1000 Subject: [PATCH 01/53] feat: introduce new pns models and wire kotlinx serialization to apply in libsession --- app/build.gradle | 15 ------ .../securesms/ApplicationContext.java | 8 +-- ...nManager.kt => PushNotificationManager.kt} | 2 +- .../notifications/PushNotificationService.kt | 4 +- build.gradle | 6 ++- libsession/build.gradle | 2 + .../sending_receiving/notifications/Models.kt | 52 +++++++++++++++++++ .../notifications/PushNotificationAPI.kt | 4 +- 8 files changed, 67 insertions(+), 26 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/notifications/{LokiPushNotificationManager.kt => PushNotificationManager.kt} (99%) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt diff --git a/app/build.gradle b/app/build.gradle index b861220339..a2bb940922 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,18 +1,3 @@ -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath "com.android.tools.build:gradle:$gradlePluginVersion" - classpath files('libs/gradle-witness.jar') - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion" - classpath "com.google.gms:google-services:$googleServicesVersion" - classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" - } -} - apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'witness' diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index a605a84922..2fc53dc244 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -76,7 +76,7 @@ import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.notifications.BackgroundPollWorker; import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.FcmUtils; -import org.thoughtcrime.securesms.notifications.LokiPushNotificationManager; +import org.thoughtcrime.securesms.notifications.PushNotificationManager; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; import org.thoughtcrime.securesms.providers.BlobProvider; @@ -455,9 +455,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { if (TextSecurePreferences.isUsingFCM(this)) { - LokiPushNotificationManager.register(token, userPublicKey, this, force); + PushNotificationManager.register(token, userPublicKey, this, force); } else { - LokiPushNotificationManager.unregister(token, this); + PushNotificationManager.unregister(token, this); } }); @@ -536,7 +536,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO public void clearAllData(boolean isMigratingToV2KeyPair) { String token = TextSecurePreferences.getFCMToken(this); if (token != null && !token.isEmpty()) { - LokiPushNotificationManager.unregister(token, this); + PushNotificationManager.unregister(token, this); } if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt similarity index 99% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt index adaec0e17a..5a53f7af22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt @@ -15,7 +15,7 @@ import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.dependencies.DatabaseComponent -object LokiPushNotificationManager { +object PushNotificationManager { private val maxRetryCount = 4 private val tokenExpirationInterval = 12 * 60 * 60 * 1000 diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index fc399d293e..b7a92e3b76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -18,7 +18,7 @@ class PushNotificationService : FirebaseMessagingService() { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - LokiPushNotificationManager.register(token, userPublicKey, this, false) + PushNotificationManager.register(token, userPublicKey, this, false) } override fun onMessageReceived(message: RemoteMessage) { @@ -53,6 +53,6 @@ class PushNotificationService : FirebaseMessagingService() { super.onDeletedMessages() val token = TextSecurePreferences.getFCMToken(this)!! val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - LokiPushNotificationManager.register(token, userPublicKey, this, true) + PushNotificationManager.register(token, userPublicKey, this, true) } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7e7e14f00f..64a1fd4d02 100644 --- a/build.gradle +++ b/build.gradle @@ -5,9 +5,11 @@ buildscript { } dependencies { classpath "com.android.tools.build:gradle:$gradlePluginVersion" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "com.google.gms:google-services:$googleServicesVersion" classpath files('libs/gradle-witness.jar') + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion" + classpath "com.google.gms:google-services:$googleServicesVersion" + classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" } } diff --git a/libsession/build.gradle b/libsession/build.gradle index dd8959958e..2241cfb3c5 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'kotlin-android' + id 'kotlinx-serialization' } android { @@ -41,6 +42,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxJsonVersion" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" testImplementation "junit:junit:$junitVersion" testImplementation 'org.assertj:assertj-core:3.11.1' diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt new file mode 100644 index 0000000000..e37b5b0b60 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -0,0 +1,52 @@ +package org.session.libsession.messaging.sending_receiving.notifications + +import kotlinx.serialization.Serializable + +@Serializable +data class SubscriptionRequest( + /** the 33-byte account being subscribed to; typically a session ID */ + val pubkey: String, + /** when the pubkey starts with 05 (i.e. a session ID) this is the ed25519 32-byte pubkey associated with the session ID */ + val session_ed25519: String?, + /** 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth (new closed groups) */ + val subkey_tag: String?, + /** array of integer namespaces to subscribe to, **must be sorted in ascending order** */ + val namespaces: List, + /** if provided and true then notifications will include the body of the message (as long as it isn't too large) */ + val data: Boolean, + /** the signature unix timestamp in seconds, not ms */ + val sig_ts: Long, + /** the 64-byte ed25519 signature */ + val signature: String, + /** the string identifying the notification service, "firebase" for android (currently) */ + val service: String, + /** dict of service-specific data, currently just "token" field with device-specific token but different services might have other requirements */ + val service_info: Map, + /** 32-byte encryption key; notification payloads sent to the device will be encrypted with XChaCha20-Poly1305 via libsodium using this key. + * persist it on device */ + val enc_key: String +) + +@Serializable +data class SubscriptionResponse( + val error: Int?, + val message: String?, + val success: Boolean?, + val added: Boolean?, + val updated: Boolean?, +) { + companion object { + /** invalid values, missing reuqired arguments etc, details in message */ + const val UNPARSEABLE_ERROR = 1 + /** the "service" value is not active / valid */ + const val SERVICE_NOT_AVAILABLE = 2 + /** something getting wrong internally talking to the backend */ + const val SERVICE_TIMEOUT = 3 + /** other error processing the subscription (details in the message) */ + const val GENERIC_ERROR = 4 + } + fun isSuccess() = success == true && error == null + fun errorInfo() = if (success == false && error != null) { + true to message + } else false to null +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index f793cd6e4b..e49375babe 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -16,8 +16,8 @@ import org.session.libsignal.utilities.Log @SuppressLint("StaticFieldLeak") object PushNotificationAPI { val context = MessagingModuleConfiguration.shared.context - val server = "https://live.apns.getsession.org" - val serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" + val server = "https://push.getsession.org" + val serverPublicKey: String = TODO("get the new server pubkey here") private val maxRetryCount = 4 private val tokenExpirationInterval = 12 * 60 * 60 * 1000 From 8d4f2445f28f1f9042f28c4e8fe87b3761e58486 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:12:38 +1000 Subject: [PATCH 02/53] feat: add support for firebase and split out google services as a dependency for only the play version of the app. Add support for requests in new pn server --- .gitignore | 2 +- app/build.gradle | 268 ++++++++--------- app/src/main/AndroidManifest.xml | 8 - .../securesms/ApplicationContext.java | 36 +-- .../securesms/crypto/IdentityKeyUtil.java | 1 + .../dependencies/InjectableType.java | 4 - .../securesms/dependencies/PushComponent.kt | 12 + .../securesms/home/HomeActivity.kt | 6 +- .../securesms/jobs/JobManagerFactories.java | 1 - .../securesms/jobs/UpdateApkJob.java | 271 ------------------ .../securesms/notifications/PushManager.kt | 6 + .../notifications/PushNotificationService.kt | 9 +- .../securesms/onboarding/PNModeActivity.kt | 2 +- .../NotificationsPreferenceFragment.java | 2 +- .../service/UpdateApkRefreshListener.java | 47 --- app/src/play/AndroidManifest.xml | 16 ++ .../securesms/notifications/FcmUtils.kt | 4 +- .../notifications/FirebasePushManager.kt | 142 +++++++++ .../notifications/FirebasePushModule.kt | 21 ++ .../sending_receiving/notifications/Models.kt | 27 +- .../notifications/PushNotificationAPI.kt | 6 +- 21 files changed, 381 insertions(+), 510 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java create mode 100644 app/src/play/AndroidManifest.xml rename app/src/{main/java => play/kotlin}/org/thoughtcrime/securesms/notifications/FcmUtils.kt (90%) create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt diff --git a/.gitignore b/.gitignore index 01ec4c41ce..1fe35a0e7b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ signing.properties ffpr *.sh pkcs11.password -play +app/play diff --git a/app/build.gradle b/app/build.gradle index a2bb940922..a8a869fc5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,6 @@ apply plugin: 'kotlin-android' apply plugin: 'witness' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' -apply plugin: 'com.google.gms.google-services' apply plugin: 'kotlinx-serialization' apply plugin: 'dagger.hilt.android.plugin' @@ -11,6 +10,140 @@ configurations.all { exclude module: "commons-logging" } +def canonicalVersionCode = 335 +def canonicalVersionName = "1.16.7" + +def postFixSize = 10 +def abiPostFix = ['armeabi-v7a' : 1, + 'arm64-v8a' : 2, + 'x86' : 3, + 'x86_64' : 4, + 'universal' : 5] + +android { + compileSdkVersion androidCompileSdkVersion + namespace 'network.loki.messenger' + useLibrary 'org.apache.http.legacy' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + packagingOptions { + exclude 'LICENSE.txt' + exclude 'LICENSE' + exclude 'NOTICE' + exclude 'asm-license.txt' + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + exclude 'META-INF/proguard/androidx-annotations.pro' + } + + splits { + abi { + enable true + reset() + include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + universalApk true + } + } + + defaultConfig { + versionCode canonicalVersionCode * postFixSize + versionName canonicalVersionName + + minSdkVersion androidMinimumSdkVersion + targetSdkVersion androidTargetSdkVersion + + multiDexEnabled = true + + vectorDrawables.useSupportLibrary = true + project.ext.set("archivesBaseName", "session") + + buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" + buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" + buildConfigField "int", "CONTENT_PROXY_PORT", "443" + buildConfigField "String", "USER_AGENT", "\"OWA\"" + buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' + buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" + + resConfigs autoResConfig() + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + // The following argument makes the Android Test Orchestrator run its + // "pm clear" command after each test invocation. This command ensures + // that the app's state is completely cleared between tests. + testInstrumentationRunnerArguments clearPackageData: 'true' + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + } + } + + sourceSets { + String sharedTestDir = 'src/sharedTest/java' + test.java.srcDirs += sharedTestDir + androidTest.java.srcDirs += sharedTestDir + } + + buildTypes { + release { + minifyEnabled false + } + debug { + minifyEnabled false + } + } + + flavorDimensions "distribution" + productFlavors { + play { + dimension "distribution" + apply plugin: 'com.google.gms.google-services' + ext.websiteUpdateUrl = "null" + buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" + buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" + } + + website { + dimension "distribution" + ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" + buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" + buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" + } + } + + applicationVariants.all { variant -> + variant.outputs.each { output -> + def abiName = output.getFilter("ABI") ?: 'universal' + def postFix = abiPostFix.get(abiName, 0) + + if (postFix >= postFixSize) throw new AssertionError("postFix is too large") + output.outputFileName = output.outputFileName = "session-${variant.versionName}-${abiName}.apk" + output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix + } + } + + lintOptions { + abortOnError true + baseline file("lint-baseline.xml") + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } + + buildFeatures { + dataBinding true + viewBinding true + } +} + dependencies { implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.recyclerview:recyclerview:1.2.1' @@ -34,7 +167,7 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.5.3' implementation "androidx.core:core-ktx:$coreVersion" implementation "androidx.work:work-runtime-ktx:2.7.1" - implementation ("com.google.firebase:firebase-messaging:18.0.0") { + playImplementation ("com.google.firebase:firebase-messaging:18.0.0") { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -145,137 +278,6 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 335 -def canonicalVersionName = "1.16.7" - -def postFixSize = 10 -def abiPostFix = ['armeabi-v7a' : 1, - 'arm64-v8a' : 2, - 'x86' : 3, - 'x86_64' : 4, - 'universal' : 5] - -android { - compileSdkVersion androidCompileSdkVersion - namespace 'network.loki.messenger' - useLibrary 'org.apache.http.legacy' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - packagingOptions { - exclude 'LICENSE.txt' - exclude 'LICENSE' - exclude 'NOTICE' - exclude 'asm-license.txt' - exclude 'META-INF/LICENSE' - exclude 'META-INF/NOTICE' - exclude 'META-INF/proguard/androidx-annotations.pro' - } - - splits { - abi { - enable true - reset() - include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' - universalApk true - } - } - - defaultConfig { - versionCode canonicalVersionCode * postFixSize - versionName canonicalVersionName - - minSdkVersion androidMinimumSdkVersion - targetSdkVersion androidTargetSdkVersion - - multiDexEnabled = true - - vectorDrawables.useSupportLibrary = true - project.ext.set("archivesBaseName", "session") - - buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" - buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" - buildConfigField "int", "CONTENT_PROXY_PORT", "443" - buildConfigField "String", "USER_AGENT", "\"OWA\"" - buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' - buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" - - resConfigs autoResConfig() - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - } - - sourceSets { - String sharedTestDir = 'src/sharedTest/java' - test.java.srcDirs += sharedTestDir - androidTest.java.srcDirs += sharedTestDir - } - - buildTypes { - release { - minifyEnabled false - } - debug { - minifyEnabled false - } - } - - flavorDimensions "distribution" - productFlavors { - play { - ext.websiteUpdateUrl = "null" - buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" - buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" - } - - website { - ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" - buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" - buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" - } - } - - applicationVariants.all { variant -> - variant.outputs.each { output -> - def abiName = output.getFilter("ABI") ?: 'universal' - def postFix = abiPostFix.get(abiName, 0) - - if (postFix >= postFixSize) throw new AssertionError("postFix is too large") - output.outputFileName = output.outputFileName = "session-${variant.versionName}-${abiName}.apk" - output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix - } - } - - lintOptions { - abortOnError true - baseline file("lint-baseline.xml") - } - - testOptions { - unitTests { - includeAndroidResources = true - } - } - - buildFeatures { - dataBinding true - viewBinding true - } -} - static def getLastCommitTimestamp() { new ByteArrayOutputStream().withStream { os -> return os.toString() + "000" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6755addc03..7912ab9739 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -306,14 +306,6 @@ android:name="android.support.PARENT_ACTIVITY" android:value="org.thoughtcrime.securesms.home.HomeActivity" /> - - - - - { - if (!task.isSuccessful()) { - Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException()); - return Unit.INSTANCE; - } - String token = task.getResult().getToken(); - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (userPublicKey == null) return Unit.INSTANCE; - - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - if (TextSecurePreferences.isUsingFCM(this)) { - PushNotificationManager.register(token, userPublicKey, this, force); - } else { - PushNotificationManager.unregister(token, this); - } - }); - - return Unit.INSTANCE; - }); + public void registerForPnIfNeeded(final Boolean force) { + pushManager.register(force); } private void setUpPollingIfNeeded() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index a5333ef5d4..62aaf58f1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -52,6 +52,7 @@ public class IdentityKeyUtil { public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3"; public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key"; public static final String ED25519_SECRET_KEY = "pref_ed25519_secret_key"; + public static final String NOTIFICATION_KEY = "pref_notification_key"; public static final String LOKI_SEED = "loki_seed"; public static final String HAS_MIGRATED_KEY = "has_migrated_keys"; diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java deleted file mode 100644 index 033b3ef45a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.thoughtcrime.securesms.dependencies; - -public interface InjectableType { -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt new file mode 100644 index 0000000000..cd4e00416c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt @@ -0,0 +1,12 @@ +package org.thoughtcrime.securesms.dependencies + +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.thoughtcrime.securesms.notifications.PushManager + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface PushComponent { + fun providePushManager(): PushManager +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 9b1e01a54a..72df057985 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -1,11 +1,11 @@ package org.thoughtcrime.securesms.home import android.content.BroadcastReceiver +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.ClipData -import android.content.ClipboardManager import android.os.Bundle import android.text.SpannableString import android.widget.Toast @@ -199,7 +199,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // update things based on TextSecurePrefs (profile info etc) // Set up remaining components if needed val application = ApplicationContext.getInstance(this@HomeActivity) - application.registerForFCMIfNeeded(false) + application.registerForPnIfNeeded(false) if (textSecurePreferences.getLocalNumber() != null) { OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index ef73325f37..e660a774b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -31,7 +31,6 @@ public final class JobManagerFactories { put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application)); - put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory()); }}; factoryKeys.addAll(factoryHashMap.keySet()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java deleted file mode 100644 index 5b4ce8d13c..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java +++ /dev/null @@ -1,271 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - - -import android.app.DownloadManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.session.libsession.messaging.utilities.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.service.UpdateApkReadyListener; -import org.session.libsession.utilities.FileUtils; -import org.session.libsignal.utilities.Hex; -import org.session.libsignal.utilities.JsonUtil; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.io.FileInputStream; -import java.io.IOException; -import java.security.MessageDigest; - -import network.loki.messenger.BuildConfig; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public class UpdateApkJob extends BaseJob { - - public static final String KEY = "UpdateApkJob"; - - private static final String TAG = UpdateApkJob.class.getSimpleName(); - - public UpdateApkJob() { - this(new Job.Parameters.Builder() - .setQueue("UpdateApkJob") - .addConstraint(NetworkConstraint.KEY) - .setMaxAttempts(3) - .build()); - } - - private UpdateApkJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull - Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException, PackageManager.NameNotFoundException { - if (!BuildConfig.PLAY_STORE_DISABLED) return; - - Log.i(TAG, "Checking for APK update..."); - - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build(); - - Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) { - throw new IOException("Bad response: " + response.message()); - } - - UpdateDescriptor updateDescriptor = JsonUtil.fromJson(response.body().string(), UpdateDescriptor.class); - byte[] digest = Hex.fromStringCondensed(updateDescriptor.getDigest()); - - Log.i(TAG, "Got descriptor: " + updateDescriptor); - - if (updateDescriptor.getVersionCode() > getVersionCode()) { - DownloadStatus downloadStatus = getDownloadStatus(updateDescriptor.getUrl(), digest); - - Log.i(TAG, "Download status: " + downloadStatus.getStatus()); - - if (downloadStatus.getStatus() == DownloadStatus.Status.COMPLETE) { - Log.i(TAG, "Download status complete, notifying..."); - handleDownloadNotify(downloadStatus.getDownloadId()); - } else if (downloadStatus.getStatus() == DownloadStatus.Status.MISSING) { - Log.i(TAG, "Download status missing, starting download..."); - handleDownloadStart(updateDescriptor.getUrl(), updateDescriptor.getVersionName(), digest); - } - } - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - return e instanceof IOException; - } - - @Override - public void onCanceled() { - Log.w(TAG, "Update check failed"); - } - - private int getVersionCode() throws PackageManager.NameNotFoundException { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - - return packageInfo.versionCode; - } - - private DownloadStatus getDownloadStatus(String uri, byte[] theirDigest) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Query query = new DownloadManager.Query(); - - query.setFilterByStatus(DownloadManager.STATUS_PAUSED | DownloadManager.STATUS_PENDING | DownloadManager.STATUS_RUNNING | DownloadManager.STATUS_SUCCESSFUL); - - long pendingDownloadId = TextSecurePreferences.getUpdateApkDownloadId(context); - byte[] pendingDigest = getPendingDigest(context); - Cursor cursor = downloadManager.query(query); - - try { - DownloadStatus status = new DownloadStatus(DownloadStatus.Status.MISSING, -1); - - while (cursor != null && cursor.moveToNext()) { - int jobStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); - String jobRemoteUri = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI)); - long downloadId = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)); - byte[] digest = getDigestForDownloadId(downloadId); - - if (jobRemoteUri != null && jobRemoteUri.equals(uri) && downloadId == pendingDownloadId) { - - if (jobStatus == DownloadManager.STATUS_SUCCESSFUL && - digest != null && pendingDigest != null && - MessageDigest.isEqual(pendingDigest, theirDigest) && - MessageDigest.isEqual(digest, theirDigest)) - { - return new DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId); - } else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) { - status = new DownloadStatus(DownloadStatus.Status.PENDING, downloadId); - } - } - } - - return status; - } finally { - if (cursor != null) cursor.close(); - } - } - - private void handleDownloadStart(String uri, String versionName, byte[] digest) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(uri)); - - downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); - downloadRequest.setTitle("Downloading Signal update"); - downloadRequest.setDescription("Downloading Signal " + versionName); - downloadRequest.setVisibleInDownloadsUi(false); - downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk"); - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); - - long downloadId = downloadManager.enqueue(downloadRequest); - TextSecurePreferences.setUpdateApkDownloadId(context, downloadId); - TextSecurePreferences.setUpdateApkDigest(context, Hex.toStringCondensed(digest)); - } - - private void handleDownloadNotify(long downloadId) { - Intent intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE); - intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId); - - new UpdateApkReadyListener().onReceive(context, intent); - } - - private @Nullable byte[] getDigestForDownloadId(long downloadId) { - try { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - FileInputStream fin = new FileInputStream(downloadManager.openDownloadedFile(downloadId).getFileDescriptor()); - byte[] digest = FileUtils.getFileDigest(fin); - - fin.close(); - - return digest; - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - private @Nullable byte[] getPendingDigest(Context context) { - try { - String encodedDigest = TextSecurePreferences.getUpdateApkDigest(context); - - if (encodedDigest == null) return null; - - return Hex.fromStringCondensed(encodedDigest); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - private static class UpdateDescriptor { - @JsonProperty - private int versionCode; - - @JsonProperty - private String versionName; - - @JsonProperty - private String url; - - @JsonProperty - private String sha256sum; - - - public int getVersionCode() { - return versionCode; - } - - public String getVersionName() { - return versionName; - } - - public String getUrl() { - return url; - } - - public @NonNull String toString() { - return "[" + versionCode + ", " + versionName + ", " + url + "]"; - } - - public String getDigest() { - return sha256sum; - } - } - - private static class DownloadStatus { - enum Status { - PENDING, - COMPLETE, - MISSING - } - - private final Status status; - private final long downloadId; - - DownloadStatus(Status status, long downloadId) { - this.status = status; - this.downloadId = downloadId; - } - - public Status getStatus() { - return status; - } - - public long getDownloadId() { - return downloadId; - } - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull UpdateApkJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new UpdateApkJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt new file mode 100644 index 0000000000..36aca1c912 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.notifications + +interface PushManager { + fun register(force: Boolean) + fun unregister(token: String) +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index b7a92e3b76..6e4f8d104a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -4,6 +4,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage +import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters @@ -11,14 +12,18 @@ import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log +import javax.inject.Inject +@AndroidEntryPoint class PushNotificationService : FirebaseMessagingService() { + @Inject lateinit var pushManager: PushManager + override fun onNewToken(token: String) { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") - val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - PushNotificationManager.register(token, userPublicKey, this, false) + TextSecurePreferences.getLocalNumber(this) ?: return + pushManager.register(true) } override fun onMessageReceived(message: RemoteMessage) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index 9cf9c3d049..e440ba21d6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -160,7 +160,7 @@ class PNModeActivity : BaseActionBarActivity() { TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == binding.fcmOptionView)) val application = ApplicationContext.getInstance(this) application.startPollingIfNeeded() - application.registerForFCMIfNeeded(true) + application.registerForPnIfNeeded(true) val intent = Intent(this, HomeActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK show(intent) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 9ae78fc5cf..efc20aca4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -39,7 +39,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme this.findPreference(fcmKey) .setOnPreferenceChangeListener((preference, newValue) -> { TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue); - ApplicationContext.getInstance(getContext()).registerForFCMIfNeeded(true); + ApplicationContext.getInstance(getContext()).registerForPnIfNeeded(true); return true; }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java deleted file mode 100644 index 187713df95..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.thoughtcrime.securesms.service; - - -import android.content.Context; -import android.content.Intent; -import org.session.libsignal.utilities.Log; - -import org.thoughtcrime.securesms.ApplicationContext; -import network.loki.messenger.BuildConfig; -import org.thoughtcrime.securesms.jobs.UpdateApkJob; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.util.concurrent.TimeUnit; - -public class UpdateApkRefreshListener extends PersistentAlarmManagerListener { - - private static final String TAG = UpdateApkRefreshListener.class.getSimpleName(); - - private static final long INTERVAL = TimeUnit.HOURS.toMillis(6); - - @Override - protected long getNextScheduledExecutionTime(Context context) { - return TextSecurePreferences.getUpdateApkRefreshTime(context); - } - - @Override - protected long onAlarm(Context context, long scheduledTime) { - Log.i(TAG, "onAlarm..."); - - if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) { - Log.i(TAG, "Queueing APK update job..."); - ApplicationContext.getInstance(context) - .getJobManager() - .add(new UpdateApkJob()); - } - - long newTime = System.currentTimeMillis() + INTERVAL; - TextSecurePreferences.setUpdateApkRefreshTime(context, newTime); - - return newTime; - } - - public static void schedule(Context context) { - new UpdateApkRefreshListener().onReceive(context, new Intent()); - } - -} diff --git a/app/src/play/AndroidManifest.xml b/app/src/play/AndroidManifest.xml new file mode 100644 index 0000000000..f5b54fd47e --- /dev/null +++ b/app/src/play/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt similarity index 90% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt index 87a9efc0de..de3d14d430 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt @@ -13,7 +13,5 @@ fun getFcmInstanceId(body: (Task)->Unit): Job = MainScope().la // wait for task to complete while we are active } if (!isActive) return@launch // don't 'complete' task if we were canceled - withContext(Dispatchers.Main) { - body(task) - } + body(task) } \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt new file mode 100644 index 0000000000..0865c25c90 --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -0,0 +1,142 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import com.goterl.lazysodium.LazySodiumAndroid +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.interfaces.Sign +import com.goterl.lazysodium.utils.Key +import com.goterl.lazysodium.utils.KeyPair +import kotlinx.coroutines.Job +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.map +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.Version +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.Namespace +import org.session.libsignal.utilities.retryIfNeeded +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.crypto.KeyPairUtilities + +class FirebasePushManager(private val context: Context, private val prefs: TextSecurePreferences): PushManager { + + companion object { + private const val maxRetryCount = 4 + private const val tokenExpirationInterval = 12 * 60 * 60 * 1000 + } + + private var firebaseInstanceIdJob: Job? = null + private val sodium = LazySodiumAndroid(SodiumAndroid()) + + private fun getOrCreateNotificationKey(): Key { + if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) { + // generate the key and store it + val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) + IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) + } + return Key.fromHexString( + IdentityKeyUtil.retrieve( + context, + IdentityKeyUtil.NOTIFICATION_KEY + ) + ) + } + + override fun register(force: Boolean) { + val currentInstanceIdJob = firebaseInstanceIdJob + if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return + + if (force && currentInstanceIdJob != null) { + currentInstanceIdJob.cancel(null) + } + + firebaseInstanceIdJob = getFcmInstanceId { task -> + // context in here is Dispatchers.IO + if (!task.isSuccessful) { + Log.w( + "Loki", + "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception + ) + return@getFcmInstanceId + } + val token: String = task.result?.token ?: return@getFcmInstanceId + val userPublicKey = getLocalNumber(context) ?: return@getFcmInstanceId + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@getFcmInstanceId + if (prefs.isUsingFCM()) { + register(token, userPublicKey, userEdKey, force) + } else { + unregister(token) + } + } + } + + override fun unregister(token: String) { + TODO("Not yet implemented") + } + + fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { + val oldToken = TextSecurePreferences.getFCMToken(context) + val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) + if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } + + val pnKey = getOrCreateNotificationKey() + + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes) + val requestParameters = SubscriptionRequest ( + pubkey = publicKey, + session_ed25519 = userEd25519Key.publicKey.asHexString, + namespaces = listOf(Namespace.DEFAULT), + data = true, // only permit data subscription for now (?) + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + enc_key = pnKey.asHexString, + ) + + val url = "${PushNotificationAPI.server}/subscribe" + val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) + val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { + getResponseBody(request.build()).map { response -> + if (response.isSuccess()) { + TextSecurePreferences.setIsUsingFCM(context, true) + TextSecurePreferences.setFCMToken(context, token) + TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) + } else { + val (_, message) = response.errorInfo() + Log.d("Loki", "Couldn't register for FCM due to error: $message.") + } + }.fail { exception -> + Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") + } + } + } + + private fun getResponseBody(request: Request): Promise { + return OnionRequestAPI.sendOnionRequest(request, + PushNotificationAPI.server, + PushNotificationAPI.serverPublicKey, Version.V4).map { response -> + Json.decodeFromStream(response.body!!.inputStream()) + } + } + + +} \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt new file mode 100644 index 0000000000..983973d964 --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.session.libsession.utilities.TextSecurePreferences +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FirebasePushModule { + @Provides + @Singleton + fun provideFirebasePushManager( + @ApplicationContext context: Context, + prefs: TextSecurePreferences, + ): PushManager = FirebasePushManager(context, prefs) +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index e37b5b0b60..78b6cd4a13 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -1,7 +1,15 @@ package org.session.libsession.messaging.sending_receiving.notifications +import com.goterl.lazysodium.utils.Key +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * N.B. all of these variable names will be named the same as the actual JSON utf-8 request/responses expected from the server. + * Changing the variable names will break how data is serialized/deserialized. + * If it's less than ideally named we can use [SerialName] + */ + @Serializable data class SubscriptionRequest( /** the 33-byte account being subscribed to; typically a session ID */ @@ -9,7 +17,7 @@ data class SubscriptionRequest( /** when the pubkey starts with 05 (i.e. a session ID) this is the ed25519 32-byte pubkey associated with the session ID */ val session_ed25519: String?, /** 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth (new closed groups) */ - val subkey_tag: String?, + val subkey_tag: String? = null, /** array of integer namespaces to subscribe to, **must be sorted in ascending order** */ val namespaces: List, /** if provided and true then notifications will include the body of the message (as long as it isn't too large) */ @@ -46,7 +54,18 @@ data class SubscriptionResponse( const val GENERIC_ERROR = 4 } fun isSuccess() = success == true && error == null - fun errorInfo() = if (success == false && error != null) { - true to message - } else false to null + fun errorInfo() = if (success != true && error != null) { + error to message + } else null to null +} + +@Serializable +data class PushNotificationServerObject( + val enc_payload: String, + val spns: Int, +) { + fun decryptPayload(key: Key): Any { + + TODO() + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index e49375babe..cf037b019b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -9,15 +9,17 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.retryIfNeeded @SuppressLint("StaticFieldLeak") object PushNotificationAPI { val context = MessagingModuleConfiguration.shared.context val server = "https://push.getsession.org" val serverPublicKey: String = TODO("get the new server pubkey here") + private val legacyServer = "https://live.apns.getsession.org" + private val legacyServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private val maxRetryCount = 4 private val tokenExpirationInterval = 12 * 60 * 60 * 1000 @@ -94,7 +96,7 @@ object PushNotificationAPI { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2).map { response -> val code = response.info["code"] as? Int if (code == null || code == 0) { Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"] as? String ?: "null"}.") From 7762d534bbc8b54a13f3bdb0867e3f6cce73ba82 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:25:03 +1000 Subject: [PATCH 03/53] feat: add no op push manager for de-googled --- .../linkpreview/LinkPreviewRepository.java | 6 +++--- .../notifications/PushNotificationService.kt | 7 +++++++ .../securesms/notifications/NoOpPushManager.kt | 14 ++++++++++++++ .../securesms/notifications/NoOpPushModule.kt | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) rename app/src/{main/java => play/kotlin}/org/thoughtcrime/securesms/notifications/PushNotificationService.kt (88%) create mode 100644 app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt create mode 100644 app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 697f6718c2..6dcc928c99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.linkpreview; +import static org.session.libsession.utilities.Util.readFully; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -8,8 +10,6 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.gms.common.util.IOUtils; - import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment; @@ -148,7 +148,7 @@ public class LinkPreviewRepository { InputStream bodyStream = response.body().byteStream(); controller.setStream(bodyStream); - byte[] data = IOUtils.readInputStreamFully(bodyStream); + byte[] data = readFully(bodyStream); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Optional thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.JPEG, MediaTypes.IMAGE_JPEG); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt similarity index 88% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 6e4f8d104a..6bf18131e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -28,6 +28,13 @@ class PushNotificationService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { Log.d("Loki", "Received a push notification.") + if (message.data.containsKey("spns")) { + // assume this is the new push notification content + // deal with the enc payload (probably decrypting through the PushManager? + Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}") + pushManager.decrypt(message.data) + return + } val base64EncodedData = message.data?.get("ENCRYPTED_DATA") val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt new file mode 100644 index 0000000000..edbf7d710d --- /dev/null +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt @@ -0,0 +1,14 @@ +package org.thoughtcrime.securesms.notifications + +import org.session.libsignal.utilities.Log + +class NoOpPushManager: PushManager { + + override fun register(force: Boolean) { + Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") + } + + override fun unregister(token: String) { + Log.d("NoOpPushManager", "Push notifications not supported, not unregistering for push notifications") + } +} \ No newline at end of file diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt new file mode 100644 index 0000000000..1c8f2f936c --- /dev/null +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt @@ -0,0 +1,15 @@ +package org.thoughtcrime.securesms.notifications + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class NoOpPushModule { + @Provides + @Singleton + fun provideNoOpManager(): PushManager = NoOpPushManager() +} \ No newline at end of file From d2e80c3157c368d1cd9c7bc330a6bb21f295ec6b Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:17:18 +1100 Subject: [PATCH 04/53] feat: re-add bencode utility and fix tests to use bytearray instead of assuming utf-8 encoding for strings --- .../notifications/FirebasePushManager.kt | 36 ++++ .../notifications/FirebasePushModule.kt | 10 +- .../notifications/PushNotificationService.kt | 4 +- .../libsession/utilities/bencode/Bencode.kt | 169 ++++++++++++++++++ .../libsession/utilities/BencoderTest.kt | 107 +++++++++++ 5 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt create mode 100644 libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 0865c25c90..39705c75d6 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -19,11 +19,16 @@ import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber +import org.session.libsession.utilities.bencode.Bencode +import org.session.libsession.utilities.bencode.BencodeDict +import org.session.libsession.utilities.bencode.BencodeList +import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace @@ -55,6 +60,37 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS ) } + fun decrypt(encPayload: ByteArray) { + val encKey = getOrCreateNotificationKey() + val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val decrypted = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) + ?: return Log.e("Loki", "Failed to decrypt push notification") + val bencoded = Bencode.Decoder(decrypted) + val expectedList = (bencoded.decode() as? BencodeList) + ?: return Log.e("Loki", "Failed to decode bencoded list from payload") + + val (metadata, content) = expectedList.values + val metadataDict = (metadata as? BencodeDict)?.values + ?: return Log.e("Loki", "Failed to decode metadata dict") + + val push = """ + Push metadata received was: + @: ${metadataDict["@"]} + #: ${metadataDict["#"]} + n: ${metadataDict["n"]} + l: ${metadataDict["l"]} + B: ${metadataDict["B"]} + """.trimIndent() + + Log.d("Loki", "push") + + val contentBytes = (content as? BencodeString)?.value + ?: return Log.e("Loki", "Failed to decode content string") + + // TODO: something with contentBytes + } + override fun register(force: Boolean) { val currentInstanceIdJob = firebaseInstanceIdJob if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt index 983973d964..30045d6c25 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,5 +18,12 @@ object FirebasePushModule { fun provideFirebasePushManager( @ApplicationContext context: Context, prefs: TextSecurePreferences, - ): PushManager = FirebasePushManager(context, prefs) + ) = FirebasePushManager(context, prefs) +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class FirebaseBindingModule { + @Binds + abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager } \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 6bf18131e9..4c268ce180 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -17,7 +17,7 @@ import javax.inject.Inject @AndroidEntryPoint class PushNotificationService : FirebaseMessagingService() { - @Inject lateinit var pushManager: PushManager + @Inject lateinit var pushManager: FirebasePushManager override fun onNewToken(token: String) { super.onNewToken(token) @@ -32,7 +32,7 @@ class PushNotificationService : FirebaseMessagingService() { // assume this is the new push notification content // deal with the enc payload (probably decrypting through the PushManager? Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}") - pushManager.decrypt(message.data) + pushManager.decrypt(Base64.decode(message.data["enc_payload"])) return } val base64EncodedData = message.data?.get("ENCRYPTED_DATA") diff --git a/libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt b/libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt new file mode 100644 index 0000000000..427e80691a --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt @@ -0,0 +1,169 @@ +package org.session.libsession.utilities.bencode + +import java.util.LinkedList + +object Bencode { + class Decoder(source: ByteArray) { + + private val iterator = LinkedList().apply { + addAll(source.asIterable()) + } + + /** + * Decode an element based on next marker assumed to be string/int/list/dict or return null + */ + fun decode(): BencodeElement? { + val result = when (iterator.peek()?.toInt()?.toChar()) { + in NUMBERS -> decodeString() + INT_INDICATOR -> decodeInt() + LIST_INDICATOR -> decodeList() + DICT_INDICATOR -> decodeDict() + else -> { + null + } + } + return result + } + + /** + * Decode a string element from iterator assumed to have structure `{length}:{data}` + */ + private fun decodeString(): BencodeString? { + val lengthStrings = buildString { + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != SEPARATOR) { + append(iterator.pop().toInt().toChar()) + } + } + iterator.pop() // drop `:` + val length = lengthStrings.toIntOrNull(10) ?: return null + val remaining = (0 until length).map { iterator.pop() }.toByteArray() + return BencodeString(remaining) + } + + /** + * Decode an int element from iterator assumed to have structure `i{int}e` + */ + private fun decodeInt(): BencodeElement? { + iterator.pop() // drop `i` + val intString = buildString { + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) { + append(iterator.pop().toInt().toChar()) + } + } + val asInt = intString.toIntOrNull(10) ?: return null + iterator.pop() // drop `e` + return BencodeInteger(asInt) + } + + /** + * Decode a list element from iterator assumed to have structure `l{data}e` + */ + private fun decodeList(): BencodeElement { + iterator.pop() // drop `l` + val listElements = mutableListOf() + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) { + decode()?.let { nextElement -> + listElements += nextElement + } + } + iterator.pop() // drop `e` + return BencodeList(listElements) + } + + /** + * Decode a dict element from iterator assumed to have structure `d{data}e` + */ + private fun decodeDict(): BencodeElement? { + iterator.pop() // drop `d` + val dictElements = mutableMapOf() + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) { + val key = decodeString() ?: return null + val value = decode() ?: return null + dictElements += key.value.decodeToString() to value + } + iterator.pop() // drop `e` + return BencodeDict(dictElements) + } + + companion object { + private val NUMBERS = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') + private const val INT_INDICATOR = 'i' + private const val LIST_INDICATOR = 'l' + private const val DICT_INDICATOR = 'd' + private const val END_INDICATOR = 'e' + private const val SEPARATOR = ':' + } + + } + +} + +sealed class BencodeElement { + abstract fun encode(): ByteArray +} + +fun String.bencode() = BencodeString(this.encodeToByteArray()) +fun Int.bencode() = BencodeInteger(this) + +data class BencodeString(val value: ByteArray): BencodeElement() { + override fun encode(): ByteArray = buildString { + append(value.size.toString()) + append(':') + }.toByteArray() + value + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BencodeString + + if (!value.contentEquals(other.value)) return false + + return true + } + + override fun hashCode(): Int { + return value.contentHashCode() + } +} +data class BencodeInteger(val value: Int): BencodeElement() { + override fun encode(): ByteArray = buildString { + append('i') + append(value.toString()) + append('e') + }.toByteArray() +} +data class BencodeList(val values: List): BencodeElement() { + + constructor(vararg values: BencodeElement) : this(values.toList()) + + override fun encode(): ByteArray = "l".toByteArray() + + values.fold(byteArrayOf()) { array, element -> array + element.encode() } + + "e".toByteArray() +} +data class BencodeDict(val values: Map): BencodeElement() { + + constructor(vararg values: Pair) : this(values.toMap()) + + override fun encode(): ByteArray = "d".toByteArray() + + values.entries.fold(byteArrayOf()) { array, (key, value) -> + array + key.bencode().encode() + value.encode() + } + "e".toByteArray() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BencodeDict + + if (values != other.values) return false + + return true + } + + override fun hashCode(): Int { + return values.hashCode() + } + + +} \ No newline at end of file diff --git a/libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt b/libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt new file mode 100644 index 0000000000..d96fa6658f --- /dev/null +++ b/libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt @@ -0,0 +1,107 @@ +package org.session.libsession.utilities + +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Test +import org.session.libsession.utilities.bencode.Bencode +import org.session.libsession.utilities.bencode.BencodeDict +import org.session.libsession.utilities.bencode.BencodeInteger +import org.session.libsession.utilities.bencode.BencodeList +import org.session.libsession.utilities.bencode.bencode + +class BencoderTest { + + @Test + fun `it should decode a basic string`() { + val basicString = "5:howdy".toByteArray() + val bencoder = Bencode.Decoder(basicString) + val result = bencoder.decode() + assertEquals("howdy".bencode(), result) + } + + @Test + fun `it should decode a basic integer`() { + val basicInteger = "i3e".toByteArray() + val bencoder = Bencode.Decoder(basicInteger) + val result = bencoder.decode() + assertEquals(BencodeInteger(3), result) + } + + @Test + fun `it should decode a list of integers`() { + val basicIntList = "li1ei2ee".toByteArray() + val bencoder = Bencode.Decoder(basicIntList) + val result = bencoder.decode() + assertEquals( + BencodeList( + 1.bencode(), + 2.bencode() + ), + result + ) + } + + @Test + fun `it should decode a basic dict`() { + val basicDict = "d4:spaml1:a1:bee".toByteArray() + val bencoder = Bencode.Decoder(basicDict) + val result = bencoder.decode() + assertEquals( + BencodeDict( + "spam" to BencodeList( + "a".bencode(), + "b".bencode() + ) + ), + result + ) + } + + @Test + fun `it should encode a basic string`() { + val basicString = "5:howdy".toByteArray() + val element = "howdy".bencode() + assertArrayEquals(basicString, element.encode()) + } + + @Test + fun `it should encode a basic int`() { + val basicInt = "i3e".toByteArray() + val element = 3.bencode() + assertArrayEquals(basicInt, element.encode()) + } + + @Test + fun `it should encode a basic list`() { + val basicList = "li1ei2ee".toByteArray() + val element = BencodeList(1.bencode(),2.bencode()) + assertArrayEquals(basicList, element.encode()) + } + + @Test + fun `it should encode a basic dict`() { + val basicDict = "d4:spaml1:a1:bee".toByteArray() + val element = BencodeDict( + "spam" to BencodeList( + "a".bencode(), + "b".bencode() + ) + ) + assertArrayEquals(basicDict, element.encode()) + } + + @Test + fun `it should encode a more complex real world case`() { + val source = "d15:lastReadMessaged66:031122334455667788990011223344556677889900112233445566778899001122i1234568790e66:051122334455667788990011223344556677889900112233445566778899001122i1234568790ee5:seqNoi1ee".toByteArray() + val result = Bencode.Decoder(source).decode() + val expected = BencodeDict( + "lastReadMessage" to BencodeDict( + "051122334455667788990011223344556677889900112233445566778899001122" to 1234568790.bencode(), + "031122334455667788990011223344556677889900112233445566778899001122" to 1234568790.bencode() + ), + "seqNo" to BencodeInteger(1) + ) + assertEquals(expected, result) + } + +} \ No newline at end of file From 46acd7878d57f58f1a5c3ba19a5f1902afccbacd Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Thu, 18 May 2023 18:03:00 -0300 Subject: [PATCH 05/53] New SPNS subscription and notifications Finishes the WIP for subscribing to push notifications and handling the new-style pushes we get. --- .../notifications/FirebasePushManager.kt | 46 +++++++++---------- .../notifications/PushNotificationService.kt | 20 ++++---- .../sending_receiving/notifications/Models.kt | 39 +++++++++++++--- .../notifications/PushNotificationAPI.kt | 2 +- .../messaging/utilities/SodiumUtilities.kt | 2 +- 5 files changed, 69 insertions(+), 40 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 39705c75d6..12e62cee45 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -8,6 +8,7 @@ import com.goterl.lazysodium.interfaces.Sign import com.goterl.lazysodium.utils.Key import com.goterl.lazysodium.utils.KeyPair import kotlinx.coroutines.Job +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream @@ -17,6 +18,7 @@ import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse import org.session.libsession.messaging.utilities.SodiumUtilities @@ -26,7 +28,6 @@ import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.bencode.Bencode -import org.session.libsession.utilities.bencode.BencodeDict import org.session.libsession.utilities.bencode.BencodeList import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 @@ -60,35 +61,32 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS ) } - fun decrypt(encPayload: ByteArray) { + fun decrypt(encPayload: ByteArray): ByteArray? { val encKey = getOrCreateNotificationKey() val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() - val decrypted = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) - ?: return Log.e("Loki", "Failed to decrypt push notification") + val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) + ?: error("Failed to decrypt push notification") + val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray() val bencoded = Bencode.Decoder(decrypted) - val expectedList = (bencoded.decode() as? BencodeList) - ?: return Log.e("Loki", "Failed to decode bencoded list from payload") + val expectedList = (bencoded.decode() as? BencodeList)?.values + ?: error("Failed to decode bencoded list from payload") - val (metadata, content) = expectedList.values - val metadataDict = (metadata as? BencodeDict)?.values - ?: return Log.e("Loki", "Failed to decode metadata dict") + val metadataJson = (expectedList[0] as? BencodeString)?.value + ?: error("no metadata") + val metadata:PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) - val push = """ - Push metadata received was: - @: ${metadataDict["@"]} - #: ${metadataDict["#"]} - n: ${metadataDict["n"]} - l: ${metadataDict["l"]} - B: ${metadataDict["B"]} - """.trimIndent() + val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null + // null content is valid only if we got a "data_too_long" flag + if (content == null) + check(metadata.data_too_long) { "missing message data, but no too-long flag" } + else + check(metadata.data_len == content.size) { "wrong message data size" } - Log.d("Loki", "push") + Log.d("Loki", + "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") - val contentBytes = (content as? BencodeString)?.value - ?: return Log.e("Loki", "Failed to decode content string") - - // TODO: something with contentBytes + return content } override fun register(force: Boolean) { @@ -158,10 +156,10 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) } else { val (_, message) = response.errorInfo() - Log.d("Loki", "Couldn't register for FCM due to error: $message.") + Log.e("Loki", "Couldn't register for FCM due to error: $message.") } }.fail { exception -> - Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") + Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.") } } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 4c268ce180..bb625bc540 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -28,15 +28,19 @@ class PushNotificationService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { Log.d("Loki", "Received a push notification.") - if (message.data.containsKey("spns")) { - // assume this is the new push notification content - // deal with the enc payload (probably decrypting through the PushManager? - Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}") - pushManager.decrypt(Base64.decode(message.data["enc_payload"])) - return + val data: ByteArray? = if (message.data.containsKey("spns")) { + // this is a v2 push notification + try { + pushManager.decrypt(Base64.decode(message.data["enc_payload"])) + } catch(e: Exception) { + Log.e("Loki", "Invalid push notification: ${e.message}") + return + } + } else { + // old v1 push notification; we still need this for receiving legacy closed group notifications + val base64EncodedData = message.data?.get("ENCRYPTED_DATA") + base64EncodedData?.let { Base64.decode(it) } } - val base64EncodedData = message.data?.get("ENCRYPTED_DATA") - val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { try { val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index 78b6cd4a13..e7a0ad79a2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -7,7 +7,8 @@ import kotlinx.serialization.Serializable /** * N.B. all of these variable names will be named the same as the actual JSON utf-8 request/responses expected from the server. * Changing the variable names will break how data is serialized/deserialized. - * If it's less than ideally named we can use [SerialName] + * If it's less than ideally named we can use [SerialName], such as for the push metadata which uses + * single-letter keys to be as compact as possible. */ @Serializable @@ -37,11 +38,11 @@ data class SubscriptionRequest( @Serializable data class SubscriptionResponse( - val error: Int?, - val message: String?, - val success: Boolean?, - val added: Boolean?, - val updated: Boolean?, + val error: Int? = null, + val message: String? = null, + val success: Boolean? = null, + val added: Boolean? = null, + val updated: Boolean? = null, ) { companion object { /** invalid values, missing reuqired arguments etc, details in message */ @@ -59,6 +60,32 @@ data class SubscriptionResponse( } else null to null } +@Serializable +data class PushNotificationMetadata( + /** Account ID (such as Session ID or closed group ID) where the message arrived **/ + @SerialName("@") + val account: String, + + /** The hash of the message in the swarm. */ + @SerialName("#") + val msg_hash: String, + + /** The swarm namespace in which this message arrived. */ + @SerialName("n") + val namespace: Int, + + /** The length of the message data. This is always included, even if the message content + * itself was too large to fit into the push notification. */ + @SerialName("l") + val data_len: Int, + + /** This will be true if the data was omitted because it was too long to fit in a push + * notification (around 2.5kB of raw data), in which case the push notification includes + * only this metadata but not the message content itself. */ + @SerialName("B") + val data_too_long : Boolean = false +) + @Serializable data class PushNotificationServerObject( val enc_payload: String, diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index cf037b019b..9016f30ebe 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -17,7 +17,7 @@ import org.session.libsignal.utilities.retryIfNeeded object PushNotificationAPI { val context = MessagingModuleConfiguration.shared.context val server = "https://push.getsession.org" - val serverPublicKey: String = TODO("get the new server pubkey here") + val serverPublicKey: String = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" private val legacyServer = "https://live.apns.getsession.org" private val legacyServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private val maxRetryCount = 4 diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt index 9a1de4f2d5..079caee235 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt @@ -205,7 +205,7 @@ object SodiumUtilities { } fun decrypt(ciphertext: ByteArray, decryptionKey: ByteArray, nonce: ByteArray): ByteArray? { - val plaintextSize = ciphertext.size - AEAD.CHACHA20POLY1305_ABYTES + val plaintextSize = ciphertext.size - AEAD.XCHACHA20POLY1305_IETF_ABYTES val plaintext = ByteArray(plaintextSize) return if (sodium.cryptoAeadXChaCha20Poly1305IetfDecrypt( plaintext, From 95bb9ee4418e5eb718adb09d756bb25c9d268203 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 24 May 2023 11:56:38 +1000 Subject: [PATCH 06/53] refactor: remove some unnecessary code for legacy PN registration --- .../notifications/PushNotificationAPI.kt | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index 9016f30ebe..3548ab5816 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -61,27 +61,6 @@ object PushNotificationAPI { } fun register(token: String, publicKey: String, force: Boolean) { - val oldToken = TextSecurePreferences.getFCMToken(context) - val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } - val parameters = mapOf( "token" to token, "pubKey" to publicKey ) - val url = "$server/register" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, true) - TextSecurePreferences.setFCMToken(context, token) - TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) - } else { - Log.d("Loki", "Couldn't register for FCM due to error: ${response.info["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") - } - } // Subscribe to all closed groups val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() allClosedGroupPublicKeys.iterator().forEach { closedGroup -> @@ -92,7 +71,7 @@ object PushNotificationAPI { fun performOperation(operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { if (!TextSecurePreferences.isUsingFCM(context)) { return } val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) - val url = "$server/${operation.rawValue}" + val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { From ba6eca2443f06291a3d91e8df90cd65338020f34 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 09:52:11 +0930 Subject: [PATCH 07/53] Add FirebasePushManager#unregister --- .../securesms/notifications/PushManager.kt | 1 - .../notifications/FirebasePushManager.kt | 113 +++++++++++------- .../notifications/NoOpPushManager.kt | 6 +- .../sending_receiving/notifications/Models.kt | 26 ++++ .../utilities/TextSecurePreferences.kt | 2 +- 5 files changed, 100 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt index 36aca1c912..9d4ef23fe9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt @@ -2,5 +2,4 @@ package org.thoughtcrime.securesms.notifications interface PushManager { fun register(force: Boolean) - fun unregister(token: String) } \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 12e62cee45..4f9a6292f3 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import com.google.android.gms.tasks.Task +import com.google.firebase.iid.InstanceIdResult import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.interfaces.AEAD @@ -21,6 +23,8 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI @@ -74,7 +78,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") - val metadata:PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) + val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null // null content is valid only if we got a "data_too_long" flag @@ -90,41 +94,71 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS } override fun register(force: Boolean) { - val currentInstanceIdJob = firebaseInstanceIdJob - if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return - - if (force && currentInstanceIdJob != null) { - currentInstanceIdJob.cancel(null) + firebaseInstanceIdJob?.apply { + if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = getFcmInstanceId { task -> - // context in here is Dispatchers.IO - if (!task.isSuccessful) { - Log.w( - "Loki", - "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception - ) - return@getFcmInstanceId - } - val token: String = task.result?.token ?: return@getFcmInstanceId - val userPublicKey = getLocalNumber(context) ?: return@getFcmInstanceId - val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@getFcmInstanceId - if (prefs.isUsingFCM()) { - register(token, userPublicKey, userEdKey, force) - } else { - unregister(token) + firebaseInstanceIdJob = getFcmInstanceId { register(it, force) } + } + + private fun register(task: Task, force: Boolean) { + // context in here is Dispatchers.IO + if (!task.isSuccessful) { + Log.w( + "Loki", + "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception + ) + return + } + + val token: String = task.result?.token ?: return + val userPublicKey = getLocalNumber(context) ?: return + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return + + when { + prefs.isUsingFCM() -> register(token, userPublicKey, userEdKey, force) + prefs.getFCMToken() != null -> unregister(token, userPublicKey, userEdKey) + } + } + + private fun unregister(token: String, userPublicKey: String, userEdKey: KeyPair) { + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) + + val requestParameters = UnsubscriptionRequest( + pubkey = userPublicKey, + session_ed25519 = userEdKey.publicKey.asHexString, + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + ) + + val url = "${PushNotificationAPI.server}/unsubscribe" + val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) + val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { + getResponseBody(request.build()).map { response -> + if (response.success == true) { + TextSecurePreferences.setIsUsingFCM(context, false) + TextSecurePreferences.setFCMToken(context, null) + Log.d("Loki", "Unsubscribe FCM success") + } else { + Log.e("Loki", "Couldn't unregister for FCM due to error: ${response.message}") + } + }.fail { exception -> + Log.e("Loki", "Couldn't unregister for FCM due to error: ${exception}.", exception) } } } - override fun unregister(token: String) { - TODO("Not yet implemented") - } - - fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { + private fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } + if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) return val pnKey = getOrCreateNotificationKey() @@ -133,7 +167,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray() val signature = ByteArray(Sign.BYTES) sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes) - val requestParameters = SubscriptionRequest ( + val requestParameters = SubscriptionRequest( pubkey = publicKey, session_ed25519 = userEd25519Key.publicKey.asHexString, namespaces = listOf(Namespace.DEFAULT), @@ -149,9 +183,8 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { response -> + getResponseBody(request.build()).map { response -> if (response.isSuccess()) { - TextSecurePreferences.setIsUsingFCM(context, true) TextSecurePreferences.setFCMToken(context, token) TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) } else { @@ -159,18 +192,16 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS Log.e("Loki", "Couldn't register for FCM due to error: $message.") } }.fail { exception -> - Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.") + Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.", exception) } } } - private fun getResponseBody(request: Request): Promise { - return OnionRequestAPI.sendOnionRequest(request, + private inline fun getResponseBody(request: Request): Promise = + OnionRequestAPI.sendOnionRequest( + request, PushNotificationAPI.server, - PushNotificationAPI.serverPublicKey, Version.V4).map { response -> - Json.decodeFromStream(response.body!!.inputStream()) - } - } - - -} \ No newline at end of file + PushNotificationAPI.serverPublicKey, + Version.V4 + ).map { response -> Json.decodeFromStream(response.body!!.inputStream()) } +} diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt index edbf7d710d..59f1b9df4b 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt @@ -7,8 +7,4 @@ class NoOpPushManager: PushManager { override fun register(force: Boolean) { Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") } - - override fun unregister(token: String) { - Log.d("NoOpPushManager", "Push notifications not supported, not unregistering for push notifications") - } -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index e7a0ad79a2..141202ef66 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -36,6 +36,24 @@ data class SubscriptionRequest( val enc_key: String ) +@Serializable +data class UnsubscriptionRequest( + /** the 33-byte account being subscribed to; typically a session ID */ + val pubkey: String, + /** when the pubkey starts with 05 (i.e. a session ID) this is the ed25519 32-byte pubkey associated with the session ID */ + val session_ed25519: String?, + /** 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth (new closed groups) */ + val subkey_tag: String? = null, + /** the signature unix timestamp in seconds, not ms */ + val sig_ts: Long, + /** the 64-byte ed25519 signature */ + val signature: String, + /** the string identifying the notification service, "firebase" for android (currently) */ + val service: String, + /** dict of service-specific data, currently just "token" field with device-specific token but different services might have other requirements */ + val service_info: Map, +) + @Serializable data class SubscriptionResponse( val error: Int? = null, @@ -60,6 +78,14 @@ data class SubscriptionResponse( } else null to null } +@Serializable +data class UnsubscribeResponse( + val error: Int? = null, + val message: String? = null, + val success: Boolean? = null, + val removed: Boolean? = null, +) + @Serializable data class PushNotificationMetadata( /** Account ID (such as Session ID or closed group ID) where the message arrived **/ diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 1f05e4f135..b1ab1090f7 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -318,7 +318,7 @@ interface TextSecurePreferences { } @JvmStatic - fun setFCMToken(context: Context, value: String) { + fun setFCMToken(context: Context, value: String?) { setStringPreference(context, FCM_TOKEN, value) } From 3f6229f8412732deec3b531e5beeae12a7533c6d Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 14:50:45 +0930 Subject: [PATCH 08/53] Remove PushNotificationManager --- .../securesms/ApplicationContext.java | 6 +- .../securesms/notifications/PushManager.kt | 2 +- .../notifications/PushNotificationManager.kt | 121 ------------------ .../notifications/FirebasePushManager.kt | 6 +- .../notifications/PushNotificationService.kt | 13 +- .../notifications/NoOpPushManager.kt | 2 +- .../MessageSenderClosedGroupHandler.kt | 10 +- .../ReceivedMessageHandler.kt | 4 +- .../notifications/PushNotificationAPI.kt | 105 +++++++++------ 9 files changed, 87 insertions(+), 182 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 57187235c2..885c716638 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -36,6 +36,7 @@ import org.session.libsession.avatars.AvatarHelper; import org.session.libsession.database.MessageDataProvider; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI; import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2; import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.snode.SnodeModule; @@ -78,7 +79,6 @@ import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; import org.thoughtcrime.securesms.notifications.PushManager; -import org.thoughtcrime.securesms.notifications.PushNotificationManager; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.KeyCachingService; @@ -440,7 +440,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private static class ProviderInitializationException extends RuntimeException { } public void registerForPnIfNeeded(final Boolean force) { - pushManager.register(force); + pushManager.refresh(force); } private void setUpPollingIfNeeded() { @@ -514,7 +514,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO public void clearAllData(boolean isMigratingToV2KeyPair) { String token = TextSecurePreferences.getFCMToken(this); if (token != null && !token.isEmpty()) { - PushNotificationManager.unregister(token, this); + PushNotificationAPI.unregister(token); } if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt index 9d4ef23fe9..d094644c07 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt @@ -1,5 +1,5 @@ package org.thoughtcrime.securesms.notifications interface PushManager { - fun register(force: Boolean) + fun refresh(force: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt deleted file mode 100644 index 5a53f7af22..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt +++ /dev/null @@ -1,121 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import android.content.Context -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.map -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI -import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.snode.Version -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.retryIfNeeded -import org.thoughtcrime.securesms.dependencies.DatabaseComponent - -object PushNotificationManager { - private val maxRetryCount = 4 - private val tokenExpirationInterval = 12 * 60 * 60 * 1000 - - private val server by lazy { - PushNotificationAPI.server - } - private val pnServerPublicKey by lazy { - PushNotificationAPI.serverPublicKey - } - - enum class ClosedGroupOperation { - Subscribe, Unsubscribe; - - val rawValue: String - get() { - return when (this) { - Subscribe -> "subscribe_closed_group" - Unsubscribe -> "unsubscribe_closed_group" - } - } - } - - @JvmStatic - fun unregister(token: String, context: Context) { - val parameters = mapOf( "token" to token ) - val url = "$server/unregister" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { json -> - val code = json["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, false) - } else { - Log.d("Loki", "Couldn't disable FCM due to error: ${json["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") - } - } - // Unsubscribe from all closed groups - val allClosedGroupPublicKeys = DatabaseComponent.get(context).lokiAPIDatabase().getAllClosedGroupPublicKeys() - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) - } - } - - @JvmStatic - fun register(token: String, publicKey: String, context: Context, force: Boolean) { - val oldToken = TextSecurePreferences.getFCMToken(context) - val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } - val parameters = mapOf( "token" to token, "pubKey" to publicKey ) - val url = "$server/register" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { json -> - val code = json["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, true) - TextSecurePreferences.setFCMToken(context, token) - TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) - } else { - Log.d("Loki", "Couldn't register for FCM due to error: ${json["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") - } - } - // Subscribe to all closed groups - val allClosedGroupPublicKeys = DatabaseComponent.get(context).lokiAPIDatabase().getAllClosedGroupPublicKeys() - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey) - } - } - - @JvmStatic - fun performOperation(context: Context, operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { - if (!TextSecurePreferences.isUsingFCM(context)) { return } - val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) - val url = "$server/${operation.rawValue}" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { json -> - val code = json["code"] as? Int - if (code == null || code == 0) { - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") - } - } - } - - private fun getResponseBody(request: Request): Promise, Exception> { - return OnionRequestAPI.sendOnionRequest(request, server, pnServerPublicKey, Version.V2).map { response -> - JsonUtil.fromJson(response.body, Map::class.java) - } - } -} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 4f9a6292f3..c169f145c9 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -93,15 +93,15 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS return content } - override fun register(force: Boolean) { + override fun refresh(force: Boolean) { firebaseInstanceIdJob?.apply { if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = getFcmInstanceId { register(it, force) } + firebaseInstanceIdJob = getFcmInstanceId { refresh(it, force) } } - private fun register(task: Task, force: Boolean) { + private fun refresh(task: Task, force: Boolean) { // context in here is Dispatchers.IO if (!task.isSuccessful) { Log.w( diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index bb625bc540..0d9765d737 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 @@ -23,7 +24,7 @@ class PushNotificationService : FirebaseMessagingService() { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") TextSecurePreferences.getLocalNumber(this) ?: return - pushManager.register(true) + pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage) { @@ -53,22 +54,18 @@ class PushNotificationService : FirebaseMessagingService() { Log.d("Loki", "Failed to decode data for message.") val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER) .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) - .setColor(this.getResources().getColor(network.loki.messenger.R.color.textsecure_primary)) + .setColor(resources.getColor(network.loki.messenger.R.color.textsecure_primary)) .setContentTitle("Session") .setContentText("You've got a new message.") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) - with(NotificationManagerCompat.from(this)) { - notify(11111, builder.build()) - } + NotificationManagerCompat.from(this).notify(11111, builder.build()) } } override fun onDeletedMessages() { Log.d("Loki", "Called onDeletedMessages.") super.onDeletedMessages() - val token = TextSecurePreferences.getFCMToken(this)!! - val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - PushNotificationManager.register(token, userPublicKey, this, true) + PushNotificationAPI.register() } } \ No newline at end of file diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt index 59f1b9df4b..a817e59d5d 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt @@ -4,7 +4,7 @@ import org.session.libsignal.utilities.Log class NoOpPushManager: PushManager { - override fun register(force: Boolean) { + override fun refresh(force: Boolean) { Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index b8a903539f..39afec418b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -74,7 +74,7 @@ fun MessageSender.create(name: String, members: Collection): Promise, name: St // Update name if needed if (name != group.title) { setName(groupPublicKey, name) } // Add members if needed - val addedMembers = members - group.members.map { it.serialize() } - if (!addedMembers.isEmpty()) { addMembers(groupPublicKey, addedMembers) } + val addedMembers = members - group.members.map { it.serialize() }.toSet() + if (addedMembers.isNotEmpty()) { addMembers(groupPublicKey, addedMembers) } // Remove members if needed - val removedMembers = group.members.map { it.serialize() } - members + val removedMembers = group.members.map { it.serialize() } - members.toSet() if (removedMembers.isEmpty()) { removeMembers(groupPublicKey, removedMembers) } } @@ -195,7 +195,7 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List "subscribe_closed_group" - Unsubscribe -> "unsubscribe_closed_group" - } - } + private enum class ClosedGroupOperation(val rawValue: String) { + Subscribe("subscribe_closed_group"), + Unsubscribe("unsubscribe_closed_group"); } + fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { + token?.let(::unregisterV1) + subscribeGroups() + } + + @JvmStatic fun unregister(token: String) { + unregisterV1(token) + unsubscribeGroups() + } + + private fun unregisterV1(token: String) { val parameters = mapOf( "token" to token ) val url = "$server/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, false) - } else { - Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"] as? String ?: "null"}.") + when (response.info["code"]) { + null, 0 -> Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"]}.") } }.fail { exception -> Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") } } - // Unsubscribe from all closed groups - val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() - val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) - } } - fun register(token: String, publicKey: String, force: Boolean) { - // Subscribe to all closed groups - val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(ClosedGroupOperation.Subscribe, closedGroup, publicKey) - } + // Legacy Closed Groups + + fun subscribeGroup( + closedGroupPublicKey: String, + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) } - fun performOperation(operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { - if (!TextSecurePreferences.isUsingFCM(context)) { return } + private fun subscribeGroups( + closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperations(ClosedGroupOperation.Subscribe, closedGroupPublicKeys, publicKey) + } + + fun unsubscribeGroup( + closedGroupPublicKey: String, + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) + } + + private fun unsubscribeGroups( + closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperations(ClosedGroupOperation.Unsubscribe, closedGroupPublicKeys, publicKey) + } + + private fun performGroupOperations( + operation: ClosedGroupOperation, + closedGroupPublicKeys: Collection, + publicKey: String + ) { + closedGroupPublicKeys.forEach { performGroupOperation(operation, it, publicKey) } + } + + private fun performGroupOperation( + operation: ClosedGroupOperation, + closedGroupPublicKey: String, + publicKey: String + ) { + if (!TextSecurePreferences.isUsingFCM(context)) return + val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code == null || code == 0) { - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"] as? String ?: "null"}.") + when (response.info["code"]) { + null, 0 -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"]}.") } }.fail { exception -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") From 01d80ae54bcc095b976182c02c6504164731a5bb Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 15:56:24 +0930 Subject: [PATCH 09/53] cleanup PushNotificationAPI --- .../notifications/FirebasePushManager.kt | 1 - .../ReceivedMessageHandler.kt | 2 +- .../notifications/MessageNotifier.kt | 2 +- .../notifications/PushNotificationAPI.kt | 24 ++++--------------- .../utilities/TextSecurePreferences.kt | 1 - 5 files changed, 6 insertions(+), 24 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index c169f145c9..702d686122 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -143,7 +143,6 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS retryIfNeeded(maxRetryCount) { getResponseBody(request.build()).map { response -> if (response.success == true) { - TextSecurePreferences.setIsUsingFCM(context, false) TextSecurePreferences.setFCMToken(context, null) Log.d("Loki", "Unsubscribe FCM success") } else { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 0d24f630a0..7643f67b94 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -478,7 +478,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli // Set expiration timer storage.setExpirationTimer(groupID, expireTimer) // Notify the PN server - PushNotificationAPI.subscribeGroup(groupPublicKey, userPublicKey) + PushNotificationAPI.subscribeGroup(groupPublicKey) // Notify the user if (userPublicKey == sender && !groupExists) { val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt index 37480543b7..8de01ca53e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt @@ -14,4 +14,4 @@ interface MessageNotifier { fun updateNotification(context: Context, threadId: Long, signal: Boolean) fun updateNotification(context: Context, signal: Boolean, reminderCount: Int) fun clearReminder(context: Context) -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index a7c260ab94..aff4987f8a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -59,38 +59,22 @@ object PushNotificationAPI { fun subscribeGroup( closedGroupPublicKey: String, publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) - } + ) = performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) private fun subscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperations(ClosedGroupOperation.Subscribe, closedGroupPublicKeys, publicKey) - } + ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) } fun unsubscribeGroup( closedGroupPublicKey: String, publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) - } + ) = performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) private fun unsubscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperations(ClosedGroupOperation.Unsubscribe, closedGroupPublicKeys, publicKey) - } - - private fun performGroupOperations( - operation: ClosedGroupOperation, - closedGroupPublicKeys: Collection, - publicKey: String - ) { - closedGroupPublicKeys.forEach { performGroupOperation(operation, it, publicKey) } - } + ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) } private fun performGroupOperation( operation: ClosedGroupOperation, diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index b1ab1090f7..9c13699dda 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -986,7 +986,6 @@ interface TextSecurePreferences { fun clearAll(context: Context) { getDefaultSharedPreferences(context).edit().clear().commit() } - } } From be4d742e8467f01e14833ad4481c5f51642f6a4d Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 18:10:06 +0930 Subject: [PATCH 10/53] Unregister v1 push --- .../notifications/FirebasePushManager.kt | 26 ++++++++++++------- .../notifications/PushNotificationAPI.kt | 26 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 702d686122..8015030795 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -41,6 +41,8 @@ import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities +private const val TAG = "FirebasePushManager" + class FirebasePushManager(private val context: Context, private val prefs: TextSecurePreferences): PushManager { companion object { @@ -87,8 +89,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS else check(metadata.data_len == content.size) { "wrong message data size" } - Log.d("Loki", - "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") + Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") return content } @@ -102,11 +103,11 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS } private fun refresh(task: Task, force: Boolean) { + Log.d(TAG, "refresh") + // context in here is Dispatchers.IO if (!task.isSuccessful) { - Log.w( - "Loki", - "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception + Log.w(TAG, "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception ) return } @@ -143,18 +144,21 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS retryIfNeeded(maxRetryCount) { getResponseBody(request.build()).map { response -> if (response.success == true) { + Log.d(TAG, "Unsubscribe FCM success") TextSecurePreferences.setFCMToken(context, null) - Log.d("Loki", "Unsubscribe FCM success") + PushNotificationAPI.unregister(token) } else { - Log.e("Loki", "Couldn't unregister for FCM due to error: ${response.message}") + Log.e(TAG, "Couldn't unregister for FCM due to error: ${response.message}") } }.fail { exception -> - Log.e("Loki", "Couldn't unregister for FCM due to error: ${exception}.", exception) + Log.e(TAG, "Couldn't unregister for FCM due to error: ${exception}.", exception) } } } private fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { + Log.d(TAG, "register token: $token") + val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) return @@ -184,14 +188,16 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS retryIfNeeded(maxRetryCount) { getResponseBody(request.build()).map { response -> if (response.isSuccess()) { + Log.d(TAG, "Success $token") TextSecurePreferences.setFCMToken(context, token) TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) + PushNotificationAPI.register(token) } else { val (_, message) = response.errorInfo() - Log.e("Loki", "Couldn't register for FCM due to error: $message.") + Log.e(TAG, "Couldn't register for FCM due to error: $message.") } }.fail { exception -> - Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.", exception) + Log.e(TAG, "Couldn't register for FCM due to error: ${exception}.", exception) } } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index aff4987f8a..d9e4293cf7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -15,6 +15,8 @@ import org.session.libsignal.utilities.retryIfNeeded @SuppressLint("StaticFieldLeak") object PushNotificationAPI { + private const val TAG = "PushNotificationAPI" + val context = MessagingModuleConfiguration.shared.context const val server = "https://push.getsession.org" const val serverPublicKey: String = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" @@ -28,28 +30,33 @@ object PushNotificationAPI { } fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { + Log.d(TAG, "register: $token") + token?.let(::unregisterV1) subscribeGroups() } @JvmStatic fun unregister(token: String) { + Log.d(TAG, "unregister: $token") + unregisterV1(token) unsubscribeGroups() } private fun unregisterV1(token: String) { val parameters = mapOf( "token" to token ) - val url = "$server/unregister" + val url = "$legacyServer/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + val request = Request.Builder().url(url).post(body).build() retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { - null, 0 -> Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"]}.") + null, 0 -> Log.d(TAG, "Couldn't disable FCM due to error: ${response.info["message"]}.") + else -> Log.d(TAG, "unregisterV1 success token: $token") } }.fail { exception -> - Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") + Log.d(TAG, "Couldn't disable FCM due to error: ${exception}.") } } } @@ -86,15 +93,16 @@ object PushNotificationAPI { val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + val request = Request.Builder().url(url).post(body).build() retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2).map { response -> + OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { - null, 0 -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"]}.") + null, 0 -> Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${response.info["message"]}.") + else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") } }.fail { exception -> - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") + Log.d(TAG, "Couldn't ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") } } } From b2a1b5fe4694e5a1e7d1e9c5962fa3fe30c3f96b Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 10 Jun 2023 11:44:43 +0930 Subject: [PATCH 11/53] Add token to FCM logs --- .../sending_receiving/notifications/PushNotificationAPI.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index d9e4293cf7..32bd17c8c2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -52,11 +52,11 @@ object PushNotificationAPI { retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { - null, 0 -> Log.d(TAG, "Couldn't disable FCM due to error: ${response.info["message"]}.") + null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") else -> Log.d(TAG, "unregisterV1 success token: $token") } }.fail { exception -> - Log.d(TAG, "Couldn't disable FCM due to error: ${exception}.") + Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") } } } From 288b70bb1467bab0d957c3cd0970b56eeefdca27 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 13 Jun 2023 10:21:52 +0930 Subject: [PATCH 12/53] Store legacy fcm token to reduce unregister api calls --- .../notifications/PushNotificationAPI.kt | 16 ++++++++++++---- .../utilities/TextSecurePreferences.kt | 13 ++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index 32bd17c8c2..9d36ff0013 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -32,7 +32,7 @@ object PushNotificationAPI { fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { Log.d(TAG, "register: $token") - token?.let(::unregisterV1) + unregisterV1IfRequired() subscribeGroups() } @@ -40,11 +40,16 @@ object PushNotificationAPI { fun unregister(token: String) { Log.d(TAG, "unregister: $token") - unregisterV1(token) + unregisterV1IfRequired() unsubscribeGroups() } - private fun unregisterV1(token: String) { + /** + * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. + */ + private fun unregisterV1IfRequired() { + val token = TextSecurePreferences.getLegacyFCMToken(context) ?: return + val parameters = mapOf( "token" to token ) val url = "$legacyServer/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) @@ -53,7 +58,10 @@ object PushNotificationAPI { OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") - else -> Log.d(TAG, "unregisterV1 success token: $token") + else -> { + TextSecurePreferences.clearLegacyFCMToken(context) + Log.d(TAG, "unregisterV1 success token: $token") + } } }.fail { exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 9c13699dda..a1482d96b4 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -250,7 +250,8 @@ interface TextSecurePreferences { const val GIF_METADATA_WARNING = "has_seen_gif_metadata_warning" const val GIF_GRID_LAYOUT = "pref_gif_grid_layout" const val IS_USING_FCM = "pref_is_using_fcm" - const val FCM_TOKEN = "pref_fcm_token" + const val FCM_TOKEN_LEGACY = "pref_fcm_token" + const val FCM_TOKEN = "pref_fcm_token_2" const val LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time_2" const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" const val CONFIGURATION_SYNCED = "pref_configuration_synced" @@ -312,6 +313,16 @@ interface TextSecurePreferences { setBooleanPreference(context, IS_USING_FCM, value) } + @JvmStatic + fun getLegacyFCMToken(context: Context): String? { + return getStringPreference(context, FCM_TOKEN_LEGACY, "") + } + + @JvmStatic + fun clearLegacyFCMToken(context: Context) { + removePreference(context, FCM_TOKEN_LEGACY) + } + @JvmStatic fun getFCMToken(context: Context): String? { return getStringPreference(context, FCM_TOKEN, "") From 153aa4ceaa0d305e1c24bc10269c8083421ca8ba Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 14 Jun 2023 18:46:28 +0930 Subject: [PATCH 13/53] Reinstate push v1 --- .../securesms/ApplicationContext.java | 9 +--- .../notifications/FirebasePushManager.kt | 7 ++- .../notifications/PushNotificationService.kt | 4 +- .../notifications/PushNotificationAPI.kt | 44 +++++++++++++------ .../utilities/TextSecurePreferences.kt | 5 +++ 5 files changed, 45 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 885c716638..688e30d879 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -218,10 +218,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO broadcaster = new Broadcaster(this); LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase(); SnodeModule.Companion.configure(apiDB, broadcaster); - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (userPublicKey != null) { - registerForPnIfNeeded(false); - } initializeExpiringMessageManager(); initializeTypingStatusRepository(); initializeTypingStatusSender(); @@ -512,10 +508,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO } public void clearAllData(boolean isMigratingToV2KeyPair) { - String token = TextSecurePreferences.getFCMToken(this); - if (token != null && !token.isEmpty()) { - PushNotificationAPI.unregister(token); - } + PushNotificationAPI.unregister(); if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 8015030795..108b10f147 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -146,7 +146,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS if (response.success == true) { Log.d(TAG, "Unsubscribe FCM success") TextSecurePreferences.setFCMToken(context, null) - PushNotificationAPI.unregister(token) + PushNotificationAPI.unregister() } else { Log.e(TAG, "Couldn't unregister for FCM due to error: ${response.message}") } @@ -161,7 +161,10 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) return +// if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { +// Log.d(TAG, "not registering now... not forced or expired") +// return +// } val pnKey = getOrCreateNotificationKey() diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 0d9765d737..3e669dbf8c 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -24,7 +24,9 @@ class PushNotificationService : FirebaseMessagingService() { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") TextSecurePreferences.getLocalNumber(this) ?: return - pushManager.refresh(true) + if (TextSecurePreferences.getLocalNumber(this) != token) { + pushManager.refresh(true) + } } override fun onMessageReceived(message: RemoteMessage) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index 9d36ff0013..a2eaf353fd 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -29,25 +29,43 @@ object PushNotificationAPI { Unsubscribe("unsubscribe_closed_group"); } - fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { + fun register(token: String? = TextSecurePreferences.getLegacyFCMToken(context)) { Log.d(TAG, "register: $token") - unregisterV1IfRequired() + register(token, TextSecurePreferences.getLocalNumber(context)) subscribeGroups() } - @JvmStatic - fun unregister(token: String) { - Log.d(TAG, "unregister: $token") + fun register(token: String?, publicKey: String?) { + Log.d(TAG, "register($token)") - unregisterV1IfRequired() - unsubscribeGroups() + token ?: return + publicKey ?: return + val parameters = mapOf("token" to token, "pubKey" to publicKey) + val url = "$legacyServer/register" + val body = + RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { + OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2) + .map { response -> + val code = response.info["code"] as? Int + if (code != null && code != 0) { + TextSecurePreferences.setLegacyFCMToken(context, token) + } else { + Log.d(TAG, "Couldn't register for FCM due to error: ${response.info["message"] as? String ?: "null"}.") + } + }.fail { exception -> + Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") + } + } } /** * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ - private fun unregisterV1IfRequired() { + @JvmStatic + fun unregister() { val token = TextSecurePreferences.getLegacyFCMToken(context) ?: return val parameters = mapOf( "token" to token ) @@ -56,17 +74,17 @@ object PushNotificationAPI { val request = Request.Builder().url(url).post(body).build() retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> + TextSecurePreferences.clearLegacyFCMToken(context) when (response.info["code"]) { null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") - else -> { - TextSecurePreferences.clearLegacyFCMToken(context) - Log.d(TAG, "unregisterV1 success token: $token") - } + else -> Log.d(TAG, "unregisterV1 success token: $token") } }.fail { exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") } } + + unsubscribeGroups() } // Legacy Closed Groups @@ -110,7 +128,7 @@ object PushNotificationAPI { else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") } }.fail { exception -> - Log.d(TAG, "Couldn't ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") + Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") } } } diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index a1482d96b4..97fe9d3b49 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -318,6 +318,11 @@ interface TextSecurePreferences { return getStringPreference(context, FCM_TOKEN_LEGACY, "") } + @JvmStatic + fun setLegacyFCMToken(context: Context, token: String?) { + setStringPreference(context, FCM_TOKEN_LEGACY, token) + } + @JvmStatic fun clearLegacyFCMToken(context: Context) { removePreference(context, FCM_TOKEN_LEGACY) From 667af27bfb49164cdc5f7d7307b16d8fd7eaf7b3 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 16 Jun 2023 10:38:33 +0930 Subject: [PATCH 14/53] Utilise TokenManager and ExpiryManager --- .../securesms/ApplicationContext.java | 1 - .../notifications/FcmTokenManager.kt | 26 +++ .../securesms/notifications/ExpiryManager.kt | 25 +++ .../securesms/notifications/FcmUtils.kt | 7 +- .../notifications/FirebasePushManager.kt | 207 ++++++++++-------- .../notifications/FirebasePushModule.kt | 3 +- .../messaging/jobs/NotifyPNServerJob.kt | 18 +- .../sending_receiving/notifications/Models.kt | 48 ++-- .../notifications/PushNotificationAPI.kt | 104 ++++----- .../libsignal/utilities/PromiseUtilities.kt | 4 + .../session/libsignal/utilities/Retrying.kt | 1 + 11 files changed, 260 insertions(+), 184 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 688e30d879..e10a7a7dc7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -508,7 +508,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO } public void clearAllData(boolean isMigratingToV2KeyPair) { - PushNotificationAPI.unregister(); if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt new file mode 100644 index 0000000000..e1182a480b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt @@ -0,0 +1,26 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import org.session.libsession.utilities.TextSecurePreferences + +class FcmTokenManager( + private val context: Context, + private val expiryManager: ExpiryManager +) { + val isUsingFCM get() = TextSecurePreferences.isUsingFCM(context) + + var fcmToken + get() = TextSecurePreferences.getFCMToken(context) + set(value) { + TextSecurePreferences.setFCMToken(context, value) + if (value != null) markTime() else clearTime() + } + + val requiresUnregister get() = fcmToken != null + + private fun clearTime() = expiryManager.clearTime() + private fun markTime() = expiryManager.markTime() + private fun isExpired() = expiryManager.isExpired() + + fun isInvalid(): Boolean = fcmToken == null || isExpired() +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt new file mode 100644 index 0000000000..36c986f0ea --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt @@ -0,0 +1,25 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import org.session.libsession.utilities.TextSecurePreferences + +class ExpiryManager( + private val context: Context, + private val interval: Int = 12 * 60 * 60 * 1000 +) { + fun isExpired() = currentTime() > time + interval + + fun markTime() { + time = currentTime() + } + + fun clearTime() { + time = 0 + } + + private var time + get() = TextSecurePreferences.getLastFCMUploadTime(context) + set(value) = TextSecurePreferences.setLastFCMUploadTime(context, value) + + private fun currentTime() = System.currentTimeMillis() +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt index de3d14d430..af3f49a26a 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.notifications import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.InstanceIdResult import kotlinx.coroutines.* @@ -9,9 +10,7 @@ import kotlinx.coroutines.* fun getFcmInstanceId(body: (Task)->Unit): Job = MainScope().launch(Dispatchers.IO) { val task = FirebaseInstanceId.getInstance().instanceId - while (!task.isComplete && isActive) { - // wait for task to complete while we are active - } + Tasks.await(task) if (!isActive) return@launch // don't 'complete' task if we were canceled body(task) -} \ No newline at end of file +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 108b10f147..4a0d8307e5 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -1,8 +1,6 @@ package org.thoughtcrime.securesms.notifications import android.content.Context -import com.google.android.gms.tasks.Task -import com.google.firebase.iid.InstanceIdResult import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.interfaces.AEAD @@ -15,12 +13,14 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.combine.and import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata +import org.session.libsession.messaging.sending_receiving.notifications.Response import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse @@ -29,7 +29,6 @@ import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.bencode.Bencode import org.session.libsession.utilities.bencode.BencodeList @@ -37,19 +36,20 @@ import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace +import org.session.libsignal.utilities.emptyPromise import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities private const val TAG = "FirebasePushManager" -class FirebasePushManager(private val context: Context, private val prefs: TextSecurePreferences): PushManager { +class FirebasePushManager (private val context: Context): PushManager { companion object { private const val maxRetryCount = 4 - private const val tokenExpirationInterval = 12 * 60 * 60 * 1000 } + private val tokenManager = FcmTokenManager(context, ExpiryManager(context)) private var firebaseInstanceIdJob: Job? = null private val sodium = LazySodiumAndroid(SodiumAndroid()) @@ -59,12 +59,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) } - return Key.fromHexString( - IdentityKeyUtil.retrieve( - context, - IdentityKeyUtil.NOTIFICATION_KEY - ) - ) + return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) } fun decrypt(encPayload: ByteArray): ByteArray? { @@ -78,8 +73,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val expectedList = (bencoded.decode() as? BencodeList)?.values ?: error("Failed to decode bencoded list from payload") - val metadataJson = (expectedList[0] as? BencodeString)?.value - ?: error("no metadata") + val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null @@ -94,78 +88,81 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS return content } + @Synchronized override fun refresh(force: Boolean) { firebaseInstanceIdJob?.apply { if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = getFcmInstanceId { refresh(it, force) } - } - - private fun refresh(task: Task, force: Boolean) { - Log.d(TAG, "refresh") - - // context in here is Dispatchers.IO - if (!task.isSuccessful) { - Log.w(TAG, "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception - ) - return - } - - val token: String = task.result?.token ?: return - val userPublicKey = getLocalNumber(context) ?: return - val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return - - when { - prefs.isUsingFCM() -> register(token, userPublicKey, userEdKey, force) - prefs.getFCMToken() != null -> unregister(token, userPublicKey, userEdKey) - } - } - - private fun unregister(token: String, userPublicKey: String, userEdKey: KeyPair) { - val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s - // if we want to support passing namespace list, here is the place to do it - val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() - val signature = ByteArray(Sign.BYTES) - sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) - - val requestParameters = UnsubscriptionRequest( - pubkey = userPublicKey, - session_ed25519 = userEdKey.publicKey.asHexString, - service = "firebase", - sig_ts = timestamp, - signature = Base64.encodeBytes(signature), - service_info = mapOf("token" to token), - ) - - val url = "${PushNotificationAPI.server}/unsubscribe" - val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { response -> - if (response.success == true) { - Log.d(TAG, "Unsubscribe FCM success") - TextSecurePreferences.setFCMToken(context, null) - PushNotificationAPI.unregister() - } else { - Log.e(TAG, "Couldn't unregister for FCM due to error: ${response.message}") - } - }.fail { exception -> - Log.e(TAG, "Couldn't unregister for FCM due to error: ${exception}.", exception) + firebaseInstanceIdJob = getFcmInstanceId { task -> + when { + task.isSuccessful -> task.result?.token?.let { refresh(it, force).get() } + else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) } } } - private fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { - Log.d(TAG, "register token: $token") + private fun refresh(token: String, force: Boolean): Promise<*, Exception> { + val userPublicKey = getLocalNumber(context) ?: return emptyPromise() + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() - val oldToken = TextSecurePreferences.getFCMToken(context) - val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) -// if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { -// Log.d(TAG, "not registering now... not forced or expired") -// return -// } + return when { + tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey) + tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey) + else -> emptyPromise() + } + } + /** + * Register for push notifications if: + * force is true + * there is no FCM Token + * FCM Token has expired + */ + private fun register( + force: Boolean, + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List = listOf(Namespace.DEFAULT) + ): Promise<*, Exception> = if (force || tokenManager.isInvalid()) { + register(token, publicKey, userEd25519Key, namespaces) + } else emptyPromise() + + /** + * Register for push notifications. + */ + private fun register( + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List = listOf(Namespace.DEFAULT) + ): Promise<*, Exception> = PushNotificationAPI.register(token) and getSubscription( + token, publicKey, userEd25519Key, namespaces + ) fail { + Log.e(TAG, "Couldn't register for FCM due to error: $it.", it) + } success { + tokenManager.fcmToken = token + } + + private fun unregister( + token: String, + userPublicKey: String, + userEdKey: KeyPair + ): Promise<*, Exception> = PushNotificationAPI.unregister() and getUnsubscription( + token, userPublicKey, userEdKey + ) fail { + Log.e(TAG, "Couldn't unregister for FCM due to error: ${it}.", it) + } success { + tokenManager.fcmToken = null + } + + private fun getSubscription( + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List + ): Promise { val pnKey = getOrCreateNotificationKey() val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s @@ -183,33 +180,51 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS signature = Base64.encodeBytes(signature), service_info = mapOf("token" to token), enc_key = pnKey.asHexString, - ) + ).let(Json::encodeToString) - val url = "${PushNotificationAPI.server}/subscribe" - val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { response -> - if (response.isSuccess()) { - Log.d(TAG, "Success $token") - TextSecurePreferences.setFCMToken(context, token) - TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) - PushNotificationAPI.register(token) - } else { - val (_, message) = response.errorInfo() - Log.e(TAG, "Couldn't register for FCM due to error: $message.") - } - }.fail { exception -> - Log.e(TAG, "Couldn't register for FCM due to error: ${exception}.", exception) - } - } + return retryResponseBody("subscribe", requestParameters) } - private inline fun getResponseBody(request: Request): Promise = - OnionRequestAPI.sendOnionRequest( + private fun getUnsubscription( + token: String, + userPublicKey: String, + userEdKey: KeyPair + ): Promise { + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) + + val requestParameters = UnsubscriptionRequest( + pubkey = userPublicKey, + session_ed25519 = userEdKey.publicKey.asHexString, + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + ).let(Json::encodeToString) + + return retryResponseBody("unsubscribe", requestParameters) + } + + private inline fun retryResponseBody(path: String, requestParameters: String): Promise = + retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) } + + private inline fun getResponseBody(path: String, requestParameters: String): Promise { + val url = "${PushNotificationAPI.server}/$path" + val body = RequestBody.create(MediaType.get("application/json"), requestParameters) + val request = Request.Builder().url(url).post(body).build() + + return OnionRequestAPI.sendOnionRequest( request, PushNotificationAPI.server, PushNotificationAPI.serverPublicKey, Version.V4 - ).map { response -> Json.decodeFromStream(response.body!!.inputStream()) } + ).map { response -> + response.body!!.inputStream() + .let { Json.decodeFromStream(it) } + .also { if (it.isFailure()) throw Exception("error: ${it.message}.") } + } + } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt index 30045d6c25..3f33016a02 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -17,8 +17,7 @@ object FirebasePushModule { @Singleton fun provideFirebasePushManager( @ApplicationContext context: Context, - prefs: TextSecurePreferences, - ) = FirebasePushManager(context, prefs) + ) = FirebasePushManager(context) } @Module diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 25fb2194c8..7bb2707a80 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -10,6 +10,7 @@ import okhttp3.RequestBody import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI.server import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.OnionRequestAPI @@ -33,18 +34,21 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { } override fun execute(dispatcherName: String) { - val server = PushNotificationAPI.server val parameters = mapOf( "data" to message.data, "send_to" to message.recipient ) val url = "${server}/notify" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + val request = Request.Builder().url(url).post(body).build() retryIfNeeded(4) { - OnionRequestAPI.sendOnionRequest(request.build(), server, PushNotificationAPI.serverPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code == null || code == 0) { - Log.d("Loki", "Couldn't notify PN server due to error: ${response.info["message"] as? String ?: "null"}.") + OnionRequestAPI.sendOnionRequest( + request, + server, + PushNotificationAPI.serverPublicKey, + Version.V2 + ) success { response -> + when (response.info["code"]) { + null, 0 -> Log.d("Loki", "Couldn't notify PN server due to error: ${response.info["message"]}.") } - }.fail { exception -> + } fail { exception -> Log.d("Loki", "Couldn't notify PN server due to error: $exception.") } }.success { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index 141202ef66..ea2492d5af 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -54,37 +54,39 @@ data class UnsubscriptionRequest( val service_info: Map, ) +/** invalid values, missing reuqired arguments etc, details in message */ +private const val UNPARSEABLE_ERROR = 1 +/** the "service" value is not active / valid */ +private const val SERVICE_NOT_AVAILABLE = 2 +/** something getting wrong internally talking to the backend */ +private const val SERVICE_TIMEOUT = 3 +/** other error processing the subscription (details in the message) */ +private const val GENERIC_ERROR = 4 + @Serializable data class SubscriptionResponse( - val error: Int? = null, - val message: String? = null, - val success: Boolean? = null, + override val error: Int? = null, + override val message: String? = null, + override val success: Boolean? = null, val added: Boolean? = null, val updated: Boolean? = null, -) { - companion object { - /** invalid values, missing reuqired arguments etc, details in message */ - const val UNPARSEABLE_ERROR = 1 - /** the "service" value is not active / valid */ - const val SERVICE_NOT_AVAILABLE = 2 - /** something getting wrong internally talking to the backend */ - const val SERVICE_TIMEOUT = 3 - /** other error processing the subscription (details in the message) */ - const val GENERIC_ERROR = 4 - } - fun isSuccess() = success == true && error == null - fun errorInfo() = if (success != true && error != null) { - error to message - } else null to null -} +): Response @Serializable data class UnsubscribeResponse( - val error: Int? = null, - val message: String? = null, - val success: Boolean? = null, + override val error: Int? = null, + override val message: String? = null, + override val success: Boolean? = null, val removed: Boolean? = null, -) +): Response + +interface Response { + val error: Int? + val message: String? + val success: Boolean? + fun isSuccess() = success == true && error == null + fun isFailure() = !isSuccess() +} @Serializable data class PushNotificationMetadata( diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index a2eaf353fd..b39b30f090 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -1,7 +1,10 @@ package org.session.libsession.messaging.sending_receiving.notifications import android.annotation.SuppressLint +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.task import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody @@ -11,6 +14,7 @@ import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.emptyPromise import org.session.libsignal.utilities.retryIfNeeded @SuppressLint("StaticFieldLeak") @@ -29,62 +33,57 @@ object PushNotificationAPI { Unsubscribe("unsubscribe_closed_group"); } - fun register(token: String? = TextSecurePreferences.getLegacyFCMToken(context)) { - Log.d(TAG, "register: $token") - - register(token, TextSecurePreferences.getLocalNumber(context)) + fun register( + token: String? = TextSecurePreferences.getLegacyFCMToken(context) + ): Promise<*, Exception> = all( + register(token, TextSecurePreferences.getLocalNumber(context)), subscribeGroups() - } + ) - fun register(token: String?, publicKey: String?) { - Log.d(TAG, "register($token)") - - token ?: return - publicKey ?: return - val parameters = mapOf("token" to token, "pubKey" to publicKey) - val url = "$legacyServer/register" - val body = - RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + fun register(token: String?, publicKey: String?): Promise = retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2) - .map { response -> - val code = response.info["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setLegacyFCMToken(context, token) - } else { - Log.d(TAG, "Couldn't register for FCM due to error: ${response.info["message"] as? String ?: "null"}.") - } - }.fail { exception -> + val parameters = mapOf("token" to token!!, "pubKey" to publicKey!!) + val url = "$legacyServer/register" + val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body).build() + + OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> + when (response.info["code"]) { + null, 0 -> throw Exception("error: ${response.info["message"]}.") + else -> TextSecurePreferences.setLegacyFCMToken(context, token) + } + } fail { exception -> Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") } } - } /** * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ - @JvmStatic - fun unregister() { - val token = TextSecurePreferences.getLegacyFCMToken(context) ?: return + fun unregister(): Promise<*, Exception> { + val token = TextSecurePreferences.getLegacyFCMToken(context) ?: emptyPromise() - val parameters = mapOf( "token" to token ) - val url = "$legacyServer/unregister" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body).build() - retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> - TextSecurePreferences.clearLegacyFCMToken(context) - when (response.info["code"]) { - null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") + return retryIfNeeded(maxRetryCount) { + val parameters = mapOf( "token" to token ) + val url = "$legacyServer/unregister" + val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body).build() + + OnionRequestAPI.sendOnionRequest( + request, + legacyServer, + legacyServerPublicKey, + Version.V2 + ) success { + when (it.info["code"]) { + null, 0 -> throw Exception("error: ${it.info["message"]}.") else -> Log.d(TAG, "unregisterV1 success token: $token") } - }.fail { exception -> - Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") + TextSecurePreferences.clearLegacyFCMToken(context) + } fail { + exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") } } - - unsubscribeGroups() } // Legacy Closed Groups @@ -97,7 +96,7 @@ object PushNotificationAPI { private fun subscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) } + ) = closedGroupPublicKeys.map { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) }.let(::all) fun unsubscribeGroup( closedGroupPublicKey: String, @@ -107,27 +106,30 @@ object PushNotificationAPI { private fun unsubscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) } + ) = closedGroupPublicKeys.map { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) }.let(::all) private fun performGroupOperation( operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String - ) { - if (!TextSecurePreferences.isUsingFCM(context)) return - + ): Promise<*, Exception> { val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() - retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> - when (response.info["code"]) { - null, 0 -> Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${response.info["message"]}.") + return retryIfNeeded(maxRetryCount) { + OnionRequestAPI.sendOnionRequest( + request, + legacyServer, + legacyServerPublicKey, + Version.V2 + ) success { + when (it.info["code"]) { + null, 0 -> throw Exception("${it.info["message"]}") else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") } - }.fail { exception -> + } fail { exception -> Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") } } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt b/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt index d6361289c5..8095b44597 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt @@ -3,8 +3,12 @@ package org.session.libsignal.utilities import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred +import nl.komponents.kovenant.task import java.util.concurrent.TimeoutException +fun emptyPromise() = EMPTY_PROMISE +private val EMPTY_PROMISE: Promise<*, java.lang.Exception> = task {} + fun Promise.get(defaultValue: V): V { return try { get() diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt index 9f1b1a3e82..28ed93b5a5 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt @@ -28,3 +28,4 @@ fun > retryIfNeeded(maxRetryCount: Int, retryInterv retryIfNeeded() return deferred.promise } + From 0e0ab9151ea1968e75814d7ad7cc01004a70e32c Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 20 Jun 2023 22:34:10 +0930 Subject: [PATCH 15/53] cleanup --- .../securesms/ApplicationContext.java | 1 - .../securesms/util/MockDataGenerator.kt | 3 - .../notifications/FirebasePushManager.kt | 21 ++++-- .../notifications/PushNotificationService.kt | 25 +++---- .../messaging/jobs/NotifyPNServerJob.kt | 7 +- .../MessageSenderClosedGroupHandler.kt | 4 +- .../ReceivedMessageHandler.kt | 6 +- ...ationAPI.kt => LegacyGroupsPushManager.kt} | 70 ++++++++----------- .../utilities/TextSecurePreferences.kt | 16 ----- 9 files changed, 65 insertions(+), 88 deletions(-) rename libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/{PushNotificationAPI.kt => LegacyGroupsPushManager.kt} (61%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 8f561f6314..8a2b0c178b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -36,7 +36,6 @@ import org.session.libsession.avatars.AvatarHelper; import org.session.libsession.database.MessageDataProvider; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI; import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2; import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.snode.SnodeModule; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt index 10d507a538..b87f49da18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -7,8 +7,6 @@ import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI -import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.recipients.Recipient @@ -21,7 +19,6 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.GroupManager import java.security.SecureRandom -import java.util.* import kotlin.random.asKotlinRandom object MockDataGenerator { diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 4a0d8307e5..65314d76d4 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -18,7 +18,7 @@ import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.Response import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest @@ -63,6 +63,8 @@ class FirebasePushManager (private val context: Context): PushManager { } fun decrypt(encPayload: ByteArray): ByteArray? { + Log.d(TAG, "decrypt() called") + val encKey = getOrCreateNotificationKey() val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() @@ -90,19 +92,23 @@ class FirebasePushManager (private val context: Context): PushManager { @Synchronized override fun refresh(force: Boolean) { + Log.d(TAG, "refresh() called with: force = $force") + firebaseInstanceIdJob?.apply { if (force) cancel() else if (isActive) return } firebaseInstanceIdJob = getFcmInstanceId { task -> when { - task.isSuccessful -> task.result?.token?.let { refresh(it, force).get() } + task.isSuccessful -> try { task.result?.token?.let { refresh(it, force).get() } } catch(e: Exception) { Log.d(TAG, "refresh() failed", e) } else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) } } } private fun refresh(token: String, force: Boolean): Promise<*, Exception> { + Log.d(TAG, "refresh() called with: token = $token, force = $force") + val userPublicKey = getLocalNumber(context) ?: return emptyPromise() val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() @@ -137,11 +143,12 @@ class FirebasePushManager (private val context: Context): PushManager { publicKey: String, userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = PushNotificationAPI.register(token) and getSubscription( + ): Promise<*, Exception> = LegacyGroupsPushManager.register(token, publicKey) and getSubscription( token, publicKey, userEd25519Key, namespaces ) fail { Log.e(TAG, "Couldn't register for FCM due to error: $it.", it) } success { + Log.d(TAG, "register() success!!") tokenManager.fcmToken = token } @@ -149,7 +156,7 @@ class FirebasePushManager (private val context: Context): PushManager { token: String, userPublicKey: String, userEdKey: KeyPair - ): Promise<*, Exception> = PushNotificationAPI.unregister() and getUnsubscription( + ): Promise<*, Exception> = LegacyGroupsPushManager.unregister() and getUnsubscription( token, userPublicKey, userEdKey ) fail { Log.e(TAG, "Couldn't unregister for FCM due to error: ${it}.", it) @@ -212,14 +219,14 @@ class FirebasePushManager (private val context: Context): PushManager { retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) } private inline fun getResponseBody(path: String, requestParameters: String): Promise { - val url = "${PushNotificationAPI.server}/$path" + val url = "${LegacyGroupsPushManager.server}/$path" val body = RequestBody.create(MediaType.get("application/json"), requestParameters) val request = Request.Builder().url(url).post(body).build() return OnionRequestAPI.sendOnionRequest( request, - PushNotificationAPI.server, - PushNotificationAPI.serverPublicKey, + LegacyGroupsPushManager.server, + LegacyGroupsPushManager.serverPublicKey, Version.V4 ).map { response -> response.body!!.inputStream() diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 3e669dbf8c..d09fd79913 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -8,13 +8,15 @@ import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import javax.inject.Inject +private const val TAG = "PushNotificationService" + @AndroidEntryPoint class PushNotificationService : FirebaseMessagingService() { @@ -22,27 +24,26 @@ class PushNotificationService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) - Log.d("Loki", "New FCM token: $token.") + Log.d(TAG, "New FCM token: $token.") TextSecurePreferences.getLocalNumber(this) ?: return - if (TextSecurePreferences.getLocalNumber(this) != token) { + if (TextSecurePreferences.getFCMToken(this) != token) { pushManager.refresh(true) } } override fun onMessageReceived(message: RemoteMessage) { - Log.d("Loki", "Received a push notification.") + Log.d(TAG, "Received a push notification.") val data: ByteArray? = if (message.data.containsKey("spns")) { // this is a v2 push notification try { pushManager.decrypt(Base64.decode(message.data["enc_payload"])) } catch(e: Exception) { - Log.e("Loki", "Invalid push notification: ${e.message}") + Log.e(TAG, "Invalid push notification: ${e.message}") return } } else { // old v1 push notification; we still need this for receiving legacy closed group notifications - val base64EncodedData = message.data?.get("ENCRYPTED_DATA") - base64EncodedData?.let { Base64.decode(it) } + message.data?.get("ENCRYPTED_DATA")?.let(Base64::decode) } if (data != null) { try { @@ -50,10 +51,10 @@ class PushNotificationService : FirebaseMessagingService() { val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) JobQueue.shared.add(job) } catch (e: Exception) { - Log.d("Loki", "Failed to unwrap data for message due to error: $e.") + Log.d(TAG, "Failed to unwrap data for message due to error: $e.") } } else { - Log.d("Loki", "Failed to decode data for message.") + Log.d(TAG, "Failed to decode data for message.") val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER) .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) .setColor(resources.getColor(network.loki.messenger.R.color.textsecure_primary)) @@ -66,8 +67,8 @@ class PushNotificationService : FirebaseMessagingService() { } override fun onDeletedMessages() { - Log.d("Loki", "Called onDeletedMessages.") + Log.d(TAG, "Called onDeletedMessages.") super.onDeletedMessages() - PushNotificationAPI.register() + LegacyGroupsPushManager.register() } -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 7bb2707a80..3ab92210d2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -3,14 +3,13 @@ package org.session.libsession.messaging.jobs import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output -import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI.server +import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager +import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager.server import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.OnionRequestAPI @@ -42,7 +41,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { OnionRequestAPI.sendOnionRequest( request, server, - PushNotificationAPI.serverPublicKey, + LegacyGroupsPushManager.serverPublicKey, Version.V2 ) success { response -> when (response.info["code"]) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index e8cb79188e..926554d3e2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -8,7 +8,7 @@ import nl.komponents.kovenant.deferred import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.sending_receiving.MessageSender.Error -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address @@ -83,7 +83,7 @@ fun MessageSender.create(name: String, members: Collection): Promise = all( - register(token, TextSecurePreferences.getLocalNumber(context)), - subscribeGroups() - ) - - fun register(token: String?, publicKey: String?): Promise = + token: String? = TextSecurePreferences.getFCMToken(context), + publicKey: String? = TextSecurePreferences.getLocalNumber(context), + legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() + ): Promise = retryIfNeeded(maxRetryCount) { - val parameters = mapOf("token" to token!!, "pubKey" to publicKey!!) - val url = "$legacyServer/register" + Log.d(TAG, "register() called") + + val parameters = mapOf( + "token" to token!!, + "pubKey" to publicKey!!, + "legacyGroupPublicKeys" to legacyGroupPublicKeys.takeIf { it.isNotEmpty() }!! + ) + val url = "$legacyServer/register_legacy_groups_only" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { null, 0 -> throw Exception("error: ${response.info["message"]}.") - else -> TextSecurePreferences.setLegacyFCMToken(context, token) + else -> Log.d(TAG, "register() success!!") } - } fail { exception -> - Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") } + } fail { exception -> + Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") } /** * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ fun unregister(): Promise<*, Exception> { - val token = TextSecurePreferences.getLegacyFCMToken(context) ?: emptyPromise() + Log.d(TAG, "unregister() called") + + val token = TextSecurePreferences.getFCMToken(context) ?: emptyPromise() return retryIfNeeded(maxRetryCount) { val parameters = mapOf( "token" to token ) @@ -75,13 +74,14 @@ object PushNotificationAPI { legacyServerPublicKey, Version.V2 ) success { + android.util.Log.d(TAG, "unregister() success!!") + when (it.info["code"]) { null, 0 -> throw Exception("error: ${it.info["message"]}.") else -> Log.d(TAG, "unregisterV1 success token: $token") } - TextSecurePreferences.clearLegacyFCMToken(context) } fail { - exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") + Log.d(TAG, "Couldn't disable FCM with token: $token due to error: $it.") } } } @@ -91,30 +91,20 @@ object PushNotificationAPI { fun subscribeGroup( closedGroupPublicKey: String, publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) - - private fun subscribeGroups( - closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), - publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = closedGroupPublicKeys.map { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) }.let(::all) + ) = performGroupOperation("subscribe_closed_group", closedGroupPublicKey, publicKey) fun unsubscribeGroup( closedGroupPublicKey: String, publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) - - private fun unsubscribeGroups( - closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), - publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = closedGroupPublicKeys.map { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) }.let(::all) + ) = performGroupOperation("unsubscribe_closed_group", closedGroupPublicKey, publicKey) private fun performGroupOperation( - operation: ClosedGroupOperation, + operation: String, closedGroupPublicKey: String, publicKey: String ): Promise<*, Exception> { val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) - val url = "$legacyServer/${operation.rawValue}" + val url = "$legacyServer/$operation" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -127,10 +117,10 @@ object PushNotificationAPI { ) success { when (it.info["code"]) { null, 0 -> throw Exception("${it.info["message"]}") - else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") + else -> Log.d(TAG, "performGroupOperation success: $operation") } } fail { exception -> - Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") + Log.d(TAG, "performGroupOperation fail: $operation: $closedGroupPublicKey due to error: ${exception}.") } } } diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 97fe9d3b49..946c73565c 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -250,7 +250,6 @@ interface TextSecurePreferences { const val GIF_METADATA_WARNING = "has_seen_gif_metadata_warning" const val GIF_GRID_LAYOUT = "pref_gif_grid_layout" const val IS_USING_FCM = "pref_is_using_fcm" - const val FCM_TOKEN_LEGACY = "pref_fcm_token" const val FCM_TOKEN = "pref_fcm_token_2" const val LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time_2" const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" @@ -313,21 +312,6 @@ interface TextSecurePreferences { setBooleanPreference(context, IS_USING_FCM, value) } - @JvmStatic - fun getLegacyFCMToken(context: Context): String? { - return getStringPreference(context, FCM_TOKEN_LEGACY, "") - } - - @JvmStatic - fun setLegacyFCMToken(context: Context, token: String?) { - setStringPreference(context, FCM_TOKEN_LEGACY, token) - } - - @JvmStatic - fun clearLegacyFCMToken(context: Context) { - removePreference(context, FCM_TOKEN_LEGACY) - } - @JvmStatic fun getFCMToken(context: Context): String? { return getStringPreference(context, FCM_TOKEN, "") From 42cfce0c3e073855b515a616e00a6605b3f6c149 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Jun 2023 10:01:35 +0930 Subject: [PATCH 16/53] Refactor v1 and v2 --- .../notifications/FirebasePushManager.kt | 127 ++------------ .../securesms/notifications/PushManagerV2.kt | 155 ++++++++++++++++++ .../notifications/PushNotificationService.kt | 4 +- .../messaging/jobs/NotifyPNServerJob.kt | 6 +- .../MessageSenderClosedGroupHandler.kt | 4 +- .../ReceivedMessageHandler.kt | 6 +- ...yGroupsPushManager.kt => PushManagerV1.kt} | 60 ++++--- .../session/libsignal/utilities/Retrying.kt | 1 - 8 files changed, 215 insertions(+), 148 deletions(-) create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt rename libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/{LegacyGroupsPushManager.kt => PushManagerV1.kt} (74%) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 65314d76d4..d6b738d8c4 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -18,7 +18,7 @@ import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.Response import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest @@ -43,52 +43,18 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities private const val TAG = "FirebasePushManager" -class FirebasePushManager (private val context: Context): PushManager { +class FirebasePushManager( + private val context: Context + ): PushManager { + + private val pushManagerV2 = PushManagerV2(context) companion object { - private const val maxRetryCount = 4 + const val maxRetryCount = 4 } private val tokenManager = FcmTokenManager(context, ExpiryManager(context)) private var firebaseInstanceIdJob: Job? = null - private val sodium = LazySodiumAndroid(SodiumAndroid()) - - private fun getOrCreateNotificationKey(): Key { - if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) { - // generate the key and store it - val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) - IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) - } - return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) - } - - fun decrypt(encPayload: ByteArray): ByteArray? { - Log.d(TAG, "decrypt() called") - - val encKey = getOrCreateNotificationKey() - val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() - val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() - val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) - ?: error("Failed to decrypt push notification") - val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray() - val bencoded = Bencode.Decoder(decrypted) - val expectedList = (bencoded.decode() as? BencodeList)?.values - ?: error("Failed to decode bencoded list from payload") - - val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") - val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) - - val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null - // null content is valid only if we got a "data_too_long" flag - if (content == null) - check(metadata.data_too_long) { "missing message data, but no too-long flag" } - else - check(metadata.data_len == content.size) { "wrong message data size" } - - Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") - - return content - } @Synchronized override fun refresh(force: Boolean) { @@ -143,12 +109,12 @@ class FirebasePushManager (private val context: Context): PushManager { publicKey: String, userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = LegacyGroupsPushManager.register(token, publicKey) and getSubscription( + ): Promise<*, Exception> = PushManagerV1.register(token, publicKey) and pushManagerV2.register( token, publicKey, userEd25519Key, namespaces ) fail { Log.e(TAG, "Couldn't register for FCM due to error: $it.", it) } success { - Log.d(TAG, "register() success!!") + Log.d(TAG, "register() success... saving token!!") tokenManager.fcmToken = token } @@ -156,82 +122,13 @@ class FirebasePushManager (private val context: Context): PushManager { token: String, userPublicKey: String, userEdKey: KeyPair - ): Promise<*, Exception> = LegacyGroupsPushManager.unregister() and getUnsubscription( + ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( token, userPublicKey, userEdKey ) fail { - Log.e(TAG, "Couldn't unregister for FCM due to error: ${it}.", it) + Log.e(TAG, "Couldn't unregister for FCM due to error: $it.", it) } success { tokenManager.fcmToken = null } - private fun getSubscription( - token: String, - publicKey: String, - userEd25519Key: KeyPair, - namespaces: List - ): Promise { - val pnKey = getOrCreateNotificationKey() - - val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s - // if we want to support passing namespace list, here is the place to do it - val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray() - val signature = ByteArray(Sign.BYTES) - sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes) - val requestParameters = SubscriptionRequest( - pubkey = publicKey, - session_ed25519 = userEd25519Key.publicKey.asHexString, - namespaces = listOf(Namespace.DEFAULT), - data = true, // only permit data subscription for now (?) - service = "firebase", - sig_ts = timestamp, - signature = Base64.encodeBytes(signature), - service_info = mapOf("token" to token), - enc_key = pnKey.asHexString, - ).let(Json::encodeToString) - - return retryResponseBody("subscribe", requestParameters) - } - - private fun getUnsubscription( - token: String, - userPublicKey: String, - userEdKey: KeyPair - ): Promise { - val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s - // if we want to support passing namespace list, here is the place to do it - val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() - val signature = ByteArray(Sign.BYTES) - sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) - - val requestParameters = UnsubscriptionRequest( - pubkey = userPublicKey, - session_ed25519 = userEdKey.publicKey.asHexString, - service = "firebase", - sig_ts = timestamp, - signature = Base64.encodeBytes(signature), - service_info = mapOf("token" to token), - ).let(Json::encodeToString) - - return retryResponseBody("unsubscribe", requestParameters) - } - - private inline fun retryResponseBody(path: String, requestParameters: String): Promise = - retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) } - - private inline fun getResponseBody(path: String, requestParameters: String): Promise { - val url = "${LegacyGroupsPushManager.server}/$path" - val body = RequestBody.create(MediaType.get("application/json"), requestParameters) - val request = Request.Builder().url(url).post(body).build() - - return OnionRequestAPI.sendOnionRequest( - request, - LegacyGroupsPushManager.server, - LegacyGroupsPushManager.serverPublicKey, - Version.V4 - ).map { response -> - response.body!!.inputStream() - .let { Json.decodeFromStream(it) } - .also { if (it.isFailure()) throw Exception("error: ${it.message}.") } - } - } + fun decrypt(decode: ByteArray) = pushManagerV2.decrypt(decode) } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt new file mode 100644 index 0000000000..b63d6be5f0 --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt @@ -0,0 +1,155 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import com.goterl.lazysodium.LazySodiumAndroid +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.interfaces.Sign +import com.goterl.lazysodium.utils.Key +import com.goterl.lazysodium.utils.KeyPair +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.map +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata +import org.session.libsession.messaging.sending_receiving.notifications.Response +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest +import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.Version +import org.session.libsession.utilities.bencode.Bencode +import org.session.libsession.utilities.bencode.BencodeList +import org.session.libsession.utilities.bencode.BencodeString +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.Namespace +import org.session.libsignal.utilities.retryIfNeeded +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil + +private const val TAG = "PushManagerV2" + +class PushManagerV2(private val context: Context) { + private val sodium = LazySodiumAndroid(SodiumAndroid()) + + fun register( + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List + ): Promise { + val pnKey = getOrCreateNotificationKey() + + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes) + val requestParameters = SubscriptionRequest( + pubkey = publicKey, + session_ed25519 = userEd25519Key.publicKey.asHexString, + namespaces = listOf(Namespace.DEFAULT), + data = true, // only permit data subscription for now (?) + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + enc_key = pnKey.asHexString, + ).let(Json::encodeToString) + + return retryResponseBody("subscribe", requestParameters) success { + Log.d(TAG, "register() success!!") + } + } + + fun unregister( + token: String, + userPublicKey: String, + userEdKey: KeyPair + ): Promise { + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) + + val requestParameters = UnsubscriptionRequest( + pubkey = userPublicKey, + session_ed25519 = userEdKey.publicKey.asHexString, + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + ).let(Json::encodeToString) + + return retryResponseBody("unsubscribe", requestParameters) success { + Log.d(TAG, "unregister() success!!") + } + } + + private inline fun retryResponseBody(path: String, requestParameters: String): Promise = + retryIfNeeded(FirebasePushManager.maxRetryCount) { getResponseBody(path, requestParameters) } + + private inline fun getResponseBody(path: String, requestParameters: String): Promise { + val url = "${PushManagerV1.server}/$path" + val body = RequestBody.create(MediaType.get("application/json"), requestParameters) + val request = Request.Builder().url(url).post(body).build() + + return OnionRequestAPI.sendOnionRequest( + request, + PushManagerV1.server, + PushManagerV1.serverPublicKey, + Version.V4 + ).map { response -> + response.body!!.inputStream() + .let { Json.decodeFromStream(it) } + .also { if (it.isFailure()) throw Exception("error: ${it.message}.") } + } + } + + private fun getOrCreateNotificationKey(): Key { + if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) { + // generate the key and store it + val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) + IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) + } + return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) + } + + fun decrypt(encPayload: ByteArray): ByteArray? { + Log.d(TAG, "decrypt() called") + + val encKey = getOrCreateNotificationKey() + val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) + ?: error("Failed to decrypt push notification") + val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray() + val bencoded = Bencode.Decoder(decrypted) + val expectedList = (bencoded.decode() as? BencodeList)?.values + ?: error("Failed to decode bencoded list from payload") + + val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") + val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) + + val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null + // null content is valid only if we got a "data_too_long" flag + if (content == null) + check(metadata.data_too_long) { "missing message data, but no too-long flag" } + else + check(metadata.data_len == content.size) { "wrong message data size" } + + Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") + + return content + } +} \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index d09fd79913..0a73d0e428 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -8,7 +8,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters -import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 @@ -69,6 +69,6 @@ class PushNotificationService : FirebaseMessagingService() { override fun onDeletedMessages() { Log.d(TAG, "Called onDeletedMessages.") super.onDeletedMessages() - LegacyGroupsPushManager.register() + PushManagerV1.register() } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 3ab92210d2..7ade248fa6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -8,8 +8,8 @@ import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE -import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager -import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager.server +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1.server import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.OnionRequestAPI @@ -41,7 +41,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { OnionRequestAPI.sendOnionRequest( request, server, - LegacyGroupsPushManager.serverPublicKey, + PushManagerV1.serverPublicKey, Version.V2 ) success { response -> when (response.info["code"]) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 926554d3e2..ca4aa609d8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -8,7 +8,7 @@ import nl.komponents.kovenant.deferred import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.sending_receiving.MessageSender.Error -import org.session.libsession.messaging.sending_receiving.notifications.LegacyGroupsPushManager +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address @@ -83,7 +83,7 @@ fun MessageSender.create(name: String, members: Collection): Promise = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() - ): Promise = + ): Promise<*, Exception> = retryIfNeeded(maxRetryCount) { - Log.d(TAG, "register() called") - - val parameters = mapOf( - "token" to token!!, - "pubKey" to publicKey!!, - "legacyGroupPublicKeys" to legacyGroupPublicKeys.takeIf { it.isNotEmpty() }!! - ) - val url = "$legacyServer/register_legacy_groups_only" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body).build() - - OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> - when (response.info["code"]) { - null, 0 -> throw Exception("error: ${response.info["message"]}.") - else -> Log.d(TAG, "register() success!!") - } - } + doRegister(token, publicKey, legacyGroupPublicKeys) } fail { exception -> Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") + } success { + Log.d(TAG, "register success!!") } + private fun doRegister(token: String?, publicKey: String?, legacyGroupPublicKeys: Collection): Promise<*, Exception> { + Log.d(TAG, "doRegister() called $token, $publicKey, $legacyGroupPublicKeys") + + token ?: return emptyPromise() + publicKey ?: return emptyPromise() + legacyGroupPublicKeys.takeIf { it.isNotEmpty() } ?: return emptyPromise() + + Log.d(TAG, "actually registering...") + + val parameters = mapOf( + "token" to token, + "pubKey" to publicKey, + "legacyGroupPublicKeys" to legacyGroupPublicKeys + ) + + val url = "$legacyServer/register_legacy_groups_only" + val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body).build() + + return OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> + when (response.info["code"]) { + null, 0 -> throw Exception("error: ${response.info["message"]}.") + else -> Log.d(TAG, "register() success!!") + } + } success { + Log.d(TAG, "onion request success") + } fail { + Log.d(TAG, "onion fail: $it") + } + } + /** * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ @@ -74,7 +90,7 @@ object LegacyGroupsPushManager { legacyServerPublicKey, Version.V2 ) success { - android.util.Log.d(TAG, "unregister() success!!") + Log.d(TAG, "unregister() success!!") when (it.info["code"]) { null, 0 -> throw Exception("error: ${it.info["message"]}.") diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt index 28ed93b5a5..9f1b1a3e82 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt @@ -28,4 +28,3 @@ fun > retryIfNeeded(maxRetryCount: Int, retryInterv retryIfNeeded() return deferred.promise } - From e3f60eb5f2b263ec284788073d75e9f950ee366d Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Jun 2023 10:32:29 +0930 Subject: [PATCH 17/53] Fix individual group subs --- .../notifications/FirebasePushManager.kt | 7 +++-- .../notifications/PushNotificationService.kt | 3 +- .../ReceivedMessageHandler.kt | 2 +- .../notifications/PushManagerV1.kt | 28 ++++++------------- .../libsignal/utilities/PromiseUtilities.kt | 10 ++++++- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index d6b738d8c4..25fed8373b 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -73,7 +73,7 @@ class FirebasePushManager( } private fun refresh(token: String, force: Boolean): Promise<*, Exception> { - Log.d(TAG, "refresh() called with: token = $token, force = $force") + Log.d(TAG, "refresh() called") val userPublicKey = getLocalNumber(context) ?: return emptyPromise() val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() @@ -109,7 +109,10 @@ class FirebasePushManager( publicKey: String, userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = PushManagerV1.register(token, publicKey) and pushManagerV2.register( + ): Promise<*, Exception> = PushManagerV1.register( + token = token, + publicKey = publicKey + ) and pushManagerV2.register( token, publicKey, userEd25519Key, namespaces ) fail { Log.e(TAG, "Couldn't register for FCM due to error: $it.", it) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 0a73d0e428..4c6ce9f4ea 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -24,7 +24,6 @@ class PushNotificationService : FirebaseMessagingService() { override fun onNewToken(token: String) { super.onNewToken(token) - Log.d(TAG, "New FCM token: $token.") TextSecurePreferences.getLocalNumber(this) ?: return if (TextSecurePreferences.getFCMToken(this) != token) { pushManager.refresh(true) @@ -69,6 +68,6 @@ class PushNotificationService : FirebaseMessagingService() { override fun onDeletedMessages() { Log.d(TAG, "Called onDeletedMessages.") super.onDeletedMessages() - PushManagerV1.register() + pushManager.refresh(true) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 2f8fdd3629..dec50f0206 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -772,7 +772,7 @@ fun MessageReceiver.disableLocalGroupAndUnsubscribe(groupPublicKey: String, grou storage.setActive(groupID, false) storage.removeMember(groupID, Address.fromSerialized(userPublicKey)) // Notify the PN server - PushManagerV1.register(publicKey = userPublicKey) + PushManagerV1.unsubscribeGroup(groupPublicKey, publicKey = userPublicKey) // Stop polling ClosedGroupPollerV2.shared.stopPolling(groupPublicKey) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index 3e2bb3a844..6e4c61fee8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -14,6 +14,7 @@ import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.emptyPromise import org.session.libsignal.utilities.retryIfNeeded +import org.session.libsignal.utilities.sideEffect @SuppressLint("StaticFieldLeak") object PushManagerV1 { @@ -27,12 +28,14 @@ object PushManagerV1 { private const val maxRetryCount = 4 fun register( + isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), token: String? = TextSecurePreferences.getFCMToken(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context), legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = retryIfNeeded(maxRetryCount) { - doRegister(token, publicKey, legacyGroupPublicKeys) + if (isUsingFCM) doRegister(token, publicKey, legacyGroupPublicKeys) + else emptyPromise() } fail { exception -> Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") } success { @@ -40,13 +43,11 @@ object PushManagerV1 { } private fun doRegister(token: String?, publicKey: String?, legacyGroupPublicKeys: Collection): Promise<*, Exception> { - Log.d(TAG, "doRegister() called $token, $publicKey, $legacyGroupPublicKeys") + Log.d(TAG, "doRegister() called") token ?: return emptyPromise() publicKey ?: return emptyPromise() - legacyGroupPublicKeys.takeIf { it.isNotEmpty() } ?: return emptyPromise() - - Log.d(TAG, "actually registering...") + legacyGroupPublicKeys.takeIf { it.isNotEmpty() } ?: return unregister() val parameters = mapOf( "token" to token, @@ -63,10 +64,6 @@ object PushManagerV1 { null, 0 -> throw Exception("error: ${response.info["message"]}.") else -> Log.d(TAG, "register() success!!") } - } success { - Log.d(TAG, "onion request success") - } fail { - Log.d(TAG, "onion fail: $it") } } @@ -90,14 +87,10 @@ object PushManagerV1 { legacyServerPublicKey, Version.V2 ) success { - Log.d(TAG, "unregister() success!!") - when (it.info["code"]) { null, 0 -> throw Exception("error: ${it.info["message"]}.") - else -> Log.d(TAG, "unregisterV1 success token: $token") + else -> Log.d(TAG, "unregisterV1 success") } - } fail { - Log.d(TAG, "Couldn't disable FCM with token: $token due to error: $it.") } } } @@ -130,13 +123,10 @@ object PushManagerV1 { legacyServer, legacyServerPublicKey, Version.V2 - ) success { + ) sideEffect { when (it.info["code"]) { - null, 0 -> throw Exception("${it.info["message"]}") - else -> Log.d(TAG, "performGroupOperation success: $operation") + 0, null -> throw Exception("${it.info["message"]}") } - } fail { exception -> - Log.d(TAG, "performGroupOperation fail: $operation: $closedGroupPublicKey due to error: ${exception}.") } } } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt b/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt index 8095b44597..fdf8f107b9 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt @@ -3,6 +3,7 @@ package org.session.libsignal.utilities import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred +import nl.komponents.kovenant.functional.map import nl.komponents.kovenant.task import java.util.concurrent.TimeoutException @@ -58,4 +59,11 @@ fun Promise.timeout(millis: Long): Promise { if (!deferred.promise.isDone()) { deferred.reject(it) } } return deferred.promise -} \ No newline at end of file +} + +infix fun Promise.sideEffect( + callback: (value: V) -> Unit +) = map { + callback(it) + it +} From dc7602a1d3449aece0f8df4fa9a105847d7f34b8 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 21 Jun 2023 12:37:10 +0930 Subject: [PATCH 18/53] Check fcm is enabled before modifying group sub --- .../notifications/PushManagerV1.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index 6e4c61fee8..09827ef8fa 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -33,13 +33,14 @@ object PushManagerV1 { publicKey: String? = TextSecurePreferences.getLocalNumber(context), legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = - retryIfNeeded(maxRetryCount) { - if (isUsingFCM) doRegister(token, publicKey, legacyGroupPublicKeys) - else emptyPromise() + if (!isUsingFCM) { + emptyPromise() + } else retryIfNeeded(maxRetryCount) { + doRegister(token, publicKey, legacyGroupPublicKeys) } fail { exception -> Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") } success { - Log.d(TAG, "register success!!") + Log.d(TAG, "register success") } private fun doRegister(token: String?, publicKey: String?, legacyGroupPublicKeys: Collection): Promise<*, Exception> { @@ -62,7 +63,7 @@ object PushManagerV1 { return OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { null, 0 -> throw Exception("error: ${response.info["message"]}.") - else -> Log.d(TAG, "register() success!!") + else -> Log.d(TAG, "doRegister success") } } } @@ -99,13 +100,19 @@ object PushManagerV1 { fun subscribeGroup( closedGroupPublicKey: String, + isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = performGroupOperation("subscribe_closed_group", closedGroupPublicKey, publicKey) + ) = if (isUsingFCM) { + performGroupOperation("subscribe_closed_group", closedGroupPublicKey, publicKey) + } else emptyPromise() fun unsubscribeGroup( closedGroupPublicKey: String, + isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = performGroupOperation("unsubscribe_closed_group", closedGroupPublicKey, publicKey) + ) = if (isUsingFCM) { + performGroupOperation("unsubscribe_closed_group", closedGroupPublicKey, publicKey) + } else emptyPromise() private fun performGroupOperation( operation: String, From 8be088ad56dbf4283e5e1ef3d47b02229e5aa85d Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 23 Jun 2023 10:29:49 +0930 Subject: [PATCH 19/53] Reinstate v1 PNServerJob --- .../notifications/FirebasePushManager.kt | 8 +-- .../securesms/notifications/PushManagerV2.kt | 14 +++-- .../messaging/jobs/NotifyPNServerJob.kt | 20 +++--- .../notifications/PushManagerV1.kt | 61 ++++++++----------- .../sending_receiving/notifications/Server.kt | 6 ++ .../libsession/snode/OnionRequestAPI.kt | 5 +- 6 files changed, 59 insertions(+), 55 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 25fed8373b..89712c2e85 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -66,7 +66,7 @@ class FirebasePushManager( firebaseInstanceIdJob = getFcmInstanceId { task -> when { - task.isSuccessful -> try { task.result?.token?.let { refresh(it, force).get() } } catch(e: Exception) { Log.d(TAG, "refresh() failed", e) } + task.isSuccessful -> try { task.result?.token?.let { refresh(it, force).get() } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) } } @@ -115,9 +115,9 @@ class FirebasePushManager( ) and pushManagerV2.register( token, publicKey, userEd25519Key, namespaces ) fail { - Log.e(TAG, "Couldn't register for FCM due to error: $it.", it) + Log.e(TAG, "registerBoth failed", it) } success { - Log.d(TAG, "register() success... saving token!!") + Log.d(TAG, "registerBoth success... saving token!!") tokenManager.fcmToken = token } @@ -128,7 +128,7 @@ class FirebasePushManager( ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( token, userPublicKey, userEdKey ) fail { - Log.e(TAG, "Couldn't unregister for FCM due to error: $it.", it) + Log.e(TAG, "unregisterBoth failed", it) } success { tokenManager.fcmToken = null } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt index b63d6be5f0..66c7590e5f 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt @@ -19,6 +19,7 @@ import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.Response +import org.session.libsession.messaging.sending_receiving.notifications.Server import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse @@ -67,7 +68,7 @@ class PushManagerV2(private val context: Context) { ).let(Json::encodeToString) return retryResponseBody("subscribe", requestParameters) success { - Log.d(TAG, "register() success!!") + Log.d(TAG, "registerV2 success") } } @@ -92,7 +93,7 @@ class PushManagerV2(private val context: Context) { ).let(Json::encodeToString) return retryResponseBody("unsubscribe", requestParameters) success { - Log.d(TAG, "unregister() success!!") + Log.d(TAG, "unregisterV2 success") } } @@ -100,14 +101,15 @@ class PushManagerV2(private val context: Context) { retryIfNeeded(FirebasePushManager.maxRetryCount) { getResponseBody(path, requestParameters) } private inline fun getResponseBody(path: String, requestParameters: String): Promise { - val url = "${PushManagerV1.server}/$path" + val server = Server.LATEST + val url = "${server.url}/$path" val body = RequestBody.create(MediaType.get("application/json"), requestParameters) val request = Request.Builder().url(url).post(body).build() return OnionRequestAPI.sendOnionRequest( request, - PushManagerV1.server, - PushManagerV1.serverPublicKey, + server.url, + server.publicKey, Version.V4 ).map { response -> response.body!!.inputStream() @@ -152,4 +154,4 @@ class PushManagerV2(private val context: Context) { return content } -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 7ade248fa6..6a0d049ce3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -8,8 +8,7 @@ import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1.server +import org.session.libsession.messaging.sending_receiving.notifications.Server import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.OnionRequestAPI @@ -33,26 +32,27 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { } override fun execute(dispatcherName: String) { + val server = Server.LEGACY val parameters = mapOf( "data" to message.data, "send_to" to message.recipient ) - val url = "${server}/notify" + val url = "${server.url}/notify" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() retryIfNeeded(4) { OnionRequestAPI.sendOnionRequest( request, - server, - PushManagerV1.serverPublicKey, + server.url, + server.publicKey, Version.V2 ) success { response -> - when (response.info["code"]) { - null, 0 -> Log.d("Loki", "Couldn't notify PN server due to error: ${response.info["message"]}.") + when (response.code) { + null, 0 -> Log.d("NotifyPNServerJob", "Couldn't notify PN server due to error: ${response.message}.") } } fail { exception -> - Log.d("Loki", "Couldn't notify PN server due to error: $exception.") + Log.d("NotifyPNServerJob", "Couldn't notify PN server due to error: $exception.") } - }.success { + } success { handleSuccess(dispatcherName) - }. fail { + } fail { handleFailure(dispatcherName, it) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index 09827ef8fa..e112cd2682 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -2,12 +2,12 @@ package org.session.libsession.messaging.sending_receiving.notifications import android.annotation.SuppressLint import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.OnionResponse import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.JsonUtil @@ -21,12 +21,10 @@ object PushManagerV1 { private const val TAG = "PushManagerV1" val context = MessagingModuleConfiguration.shared.context - const val server = "https://push.getsession.org" - const val serverPublicKey: String = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" - private const val legacyServer = "https://dev.apns.getsession.org" - private const val legacyServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private const val maxRetryCount = 4 + private val server = Server.LEGACY + fun register( isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), token: String? = TextSecurePreferences.getFCMToken(context), @@ -38,17 +36,14 @@ object PushManagerV1 { } else retryIfNeeded(maxRetryCount) { doRegister(token, publicKey, legacyGroupPublicKeys) } fail { exception -> - Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") - } success { - Log.d(TAG, "register success") + Log.d(TAG, "Couldn't register for FCM due to error: $exception.") } private fun doRegister(token: String?, publicKey: String?, legacyGroupPublicKeys: Collection): Promise<*, Exception> { - Log.d(TAG, "doRegister() called") + Log.d(TAG, "registerV1 requested") token ?: return emptyPromise() publicKey ?: return emptyPromise() - legacyGroupPublicKeys.takeIf { it.isNotEmpty() } ?: return unregister() val parameters = mapOf( "token" to token, @@ -56,15 +51,16 @@ object PushManagerV1 { "legacyGroupPublicKeys" to legacyGroupPublicKeys ) - val url = "$legacyServer/register_legacy_groups_only" + val url = "${server.url}/register_legacy_groups_only" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() - return OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> - when (response.info["code"]) { - null, 0 -> throw Exception("error: ${response.info["message"]}.") - else -> Log.d(TAG, "doRegister success") + return sendOnionRequest(request) sideEffect { response -> + when (response.code) { + null, 0 -> throw Exception("error: ${response.message}.") } + } success { + Log.d(TAG, "registerV1 success") } } @@ -72,24 +68,19 @@ object PushManagerV1 { * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ fun unregister(): Promise<*, Exception> { - Log.d(TAG, "unregister() called") + Log.d(TAG, "unregisterV1 requested") val token = TextSecurePreferences.getFCMToken(context) ?: emptyPromise() return retryIfNeeded(maxRetryCount) { val parameters = mapOf( "token" to token ) - val url = "$legacyServer/unregister" + val url = "${server.url}/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() - OnionRequestAPI.sendOnionRequest( - request, - legacyServer, - legacyServerPublicKey, - Version.V2 - ) success { - when (it.info["code"]) { - null, 0 -> throw Exception("error: ${it.info["message"]}.") + sendOnionRequest(request) success { + when (it.code) { + null, 0 -> throw Exception("error: ${it.message}.") else -> Log.d(TAG, "unregisterV1 success") } } @@ -120,21 +111,23 @@ object PushManagerV1 { publicKey: String ): Promise<*, Exception> { val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) - val url = "$legacyServer/$operation" + val url = "${server.url}/$operation" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() return retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest( - request, - legacyServer, - legacyServerPublicKey, - Version.V2 - ) sideEffect { - when (it.info["code"]) { - 0, null -> throw Exception("${it.info["message"]}") + sendOnionRequest(request) sideEffect { + when (it.code) { + 0, null -> throw Exception(it.message) } } } } + + private fun sendOnionRequest(request: Request): Promise = OnionRequestAPI.sendOnionRequest( + request, + server.url, + server.publicKey, + Version.V2 + ) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt new file mode 100644 index 0000000000..6fefd694e0 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt @@ -0,0 +1,6 @@ +package org.session.libsession.messaging.sending_receiving.notifications + +enum class Server(val url: String, val publicKey: String) { + LATEST("https://push.getsession.org", "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b"), + LEGACY("https://dev.apns.getsession.org", "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049") +} diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index 087c8e29d3..0ed3a1afe7 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -684,4 +684,7 @@ enum class Version(val value: String) { data class OnionResponse( val info: Map<*, *>, val body: ByteArray? = null -) +) { + val code: Int? get() = info["code"] as? Int + val message: String? get() = info["message"] as? String +} From 58cda9ba4a6c4a6bc54181ddef5fa0b28c35c56f Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 25 Jul 2023 10:32:41 +0930 Subject: [PATCH 20/53] Fix website --- .../org/thoughtcrime/securesms/notifications/FcmTokenManager.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/{main/java => play/kotlin}/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt (100%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt From 01e9d15872481d446fc62c13fdb9c7ecb67f67d4 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 25 Jul 2023 11:38:06 +0930 Subject: [PATCH 21/53] Add Huawei flavor --- app/build.gradle | 8 ++++++ .../notifications/HuaweiPushManager.kt | 9 +++++++ .../notifications/HuaweiPushModule.kt | 27 +++++++++++++++++++ build.gradle | 3 +++ libsession/build.gradle | 1 + 5 files changed, 48 insertions(+) create mode 100644 app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt create mode 100644 app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt diff --git a/app/build.gradle b/app/build.gradle index 9e0bd76e97..9192d48c56 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,6 +136,13 @@ android { buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" } + huawei { + dimension "distribution" + ext.websiteUpdateUrl = "null" + buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" + buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" + } + website { dimension "distribution" ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" @@ -203,6 +210,7 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } + huaweiImplementation 'com.huawei.hms:push:6.7.0.300' implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' implementation 'org.conscrypt:conscrypt-android:2.0.0' diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt new file mode 100644 index 0000000000..72a22ecfa4 --- /dev/null +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt @@ -0,0 +1,9 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context + +class HuaweiPushManager(val context: Context): PushManager { + override fun refresh(force: Boolean) { + + } +} diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt new file mode 100644 index 0000000000..17c40ea826 --- /dev/null +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt @@ -0,0 +1,27 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object HuaweiPushModule { + @Provides + @Singleton + fun provideFirebasePushManager( + @ApplicationContext context: Context, + ) = HuaweiPushManager(context) +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class FirebaseBindingModule { + @Binds + abstract fun bindPushManager(firebasePushManager: HuaweiPushManager): PushManager +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 70da7183d8..8e344c5e14 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ buildscript { repositories { google() mavenCentral() + maven {url 'https://developer.huawei.com/repo/'} } dependencies { classpath "com.android.tools.build:gradle:$gradlePluginVersion" @@ -12,6 +13,7 @@ buildscript { classpath files('libs/gradle-witness.jar') classpath "com.squareup:javapoet:1.13.0" classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" + classpath 'com.huawei.agconnect:agcp:1.6.0.300' } } @@ -55,6 +57,7 @@ allprojects { } jcenter() maven { url "https://jitpack.io" } + maven { url 'https://developer.huawei.com/repo/' } } project.ext { diff --git a/libsession/build.gradle b/libsession/build.gradle index 3cfb66cd32..5dc67f525f 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'kotlinx-serialization' + id 'com.huawei.agconnect' } android { From 34990b13d3e08f689d9cfeed2cd62ea83a73c9ac Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 25 Jul 2023 14:49:41 +0930 Subject: [PATCH 22/53] ... --- app/build.gradle | 3 + app/src/huawei/AndroidManifest.xml | 16 +++ .../notifications/HuaweiPushModule.kt | 6 +- .../HuaweiPushNotificationService.kt | 59 ++++++++++ .../securesms/notifications/PushHandler.kt | 107 ++++++++++++++++++ .../securesms/notifications/ExpiryManager.kt | 0 .../notifications/FcmTokenManager.kt | 0 .../notifications/FirebasePushManager.kt | 5 +- .../securesms/notifications/PushManagerV2.kt | 41 +------ .../notifications/PushNotificationService.kt | 34 +----- .../notifications/PushManagerV1.kt | 13 ++- 11 files changed, 202 insertions(+), 82 deletions(-) create mode 100644 app/src/huawei/AndroidManifest.xml create mode 100644 app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt rename app/src/{play => main}/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt (100%) rename app/src/{play => main}/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt (100%) diff --git a/app/build.gradle b/app/build.gradle index 9192d48c56..db9c42d43a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,6 +133,7 @@ android { apply plugin: 'com.google.gms.google-services' ext.websiteUpdateUrl = "null" buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" + buildConfigField "String", "DEVICE", "\"android\"" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" } @@ -140,6 +141,7 @@ android { dimension "distribution" ext.websiteUpdateUrl = "null" buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" + buildConfigField "String", "DEVICE", "\"huawei\"" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" } @@ -147,6 +149,7 @@ android { dimension "distribution" ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" + buildConfigField "String", "DEVICE", "\"android\"" buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" } } diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml new file mode 100644 index 0000000000..4745c454b2 --- /dev/null +++ b/app/src/huawei/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt index 17c40ea826..e10ed77abf 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt @@ -21,7 +21,7 @@ object HuaweiPushModule { @Module @InstallIn(SingletonComponent::class) -abstract class FirebaseBindingModule { +abstract class HuaweiBindingModule { @Binds - abstract fun bindPushManager(firebasePushManager: HuaweiPushManager): PushManager -} \ No newline at end of file + abstract fun bindPushManager(pushManager: HuaweiPushManager): PushManager +} diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt new file mode 100644 index 0000000000..dd475c8344 --- /dev/null +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -0,0 +1,59 @@ +package org.thoughtcrime.securesms.notifications + +import android.os.Bundle +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.huawei.hms.push.HmsMessageService +import com.huawei.hms.push.RemoteMessage +import dagger.hilt.android.AndroidEntryPoint +import org.session.libsession.messaging.jobs.BatchMessageReceiveJob +import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.jobs.MessageReceiveParameters +import org.session.libsession.messaging.utilities.MessageWrapper +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.Log +import javax.inject.Inject + +@AndroidEntryPoint +class HuaweiPushNotificationService: HmsMessageService() { + + @Inject token + + override fun onNewToken(token: String?, bundle: Bundle?) { + Log.d("Loki", "New HCM token: $token.") + + if (!token.isNullOrEmpty()) { + val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return + PushManager.register(token, userPublicKey, this, false) + } + } + + override fun onMessageReceived(message: RemoteMessage?) { + Log.d("Loki", "Received a push notification.") + val base64EncodedData = message?.data + val data = base64EncodedData?.let { Base64.decode(it) } + if (data != null) { + try { + val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() + val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) + JobQueue.shared.add(job) + } catch (e: Exception) { + Log.d("Loki", "Failed to unwrap data for message due to error: $e.") + } + } else { + Log.d("Loki", "Failed to decode data for message.") + val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER) + .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) + .setColor(this.getResources().getColor(network.loki.messenger.R.color.textsecure_primary)) + .setContentTitle("Session") + .setContentText("You've got a new message.") + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + with(NotificationManagerCompat.from(this)) { + notify(11111, builder.build()) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt new file mode 100644 index 0000000000..be4832104d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt @@ -0,0 +1,107 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.goterl.lazysodium.LazySodiumAndroid +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.utils.Key +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import org.session.libsession.messaging.jobs.BatchMessageReceiveJob +import org.session.libsession.messaging.jobs.JobQueue +import org.session.libsession.messaging.jobs.MessageReceiveParameters +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata +import org.session.libsession.messaging.utilities.MessageWrapper +import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.utilities.bencode.Bencode +import org.session.libsession.utilities.bencode.BencodeList +import org.session.libsession.utilities.bencode.BencodeString +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import javax.inject.Inject + +private const val TAG = "PushHandler" + +class PushHandler @Inject constructor(@ApplicationContext val context: Context) { + private val sodium = LazySodiumAndroid(SodiumAndroid()) + + fun onPush(dataMap: Map) { + val data: ByteArray? = if (dataMap.containsKey("spns")) { + // this is a v2 push notification + try { + decrypt(Base64.decode(dataMap["enc_payload"])) + } catch(e: Exception) { + Log.e(TAG, "Invalid push notification: ${e.message}") + return + } + } else { + // old v1 push notification; we still need this for receiving legacy closed group notifications + dataMap.get("ENCRYPTED_DATA")?.let(Base64::decode) + } + data?.let { onPush(data) } ?: onPush() + + } + + fun onPush() { + Log.d(TAG, "Failed to decode data for message.") + val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER) + .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) + .setColor(context.getColor(network.loki.messenger.R.color.textsecure_primary)) + .setContentTitle("Session") + .setContentText("You've got a new message.") + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + NotificationManagerCompat.from(context).notify(11111, builder.build()) + } + + fun onPush(data: ByteArray) { + try { + val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() + val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) + JobQueue.shared.add(job) + } catch (e: Exception) { + Log.d(TAG, "Failed to unwrap data for message due to error: $e.") + } + } + + fun decrypt(encPayload: ByteArray): ByteArray? { + Log.d(TAG, "decrypt() called") + + val encKey = getOrCreateNotificationKey() + val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) + ?: error("Failed to decrypt push notification") + val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray() + val bencoded = Bencode.Decoder(decrypted) + val expectedList = (bencoded.decode() as? BencodeList)?.values + ?: error("Failed to decode bencoded list from payload") + + val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") + val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) + + val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null + // null content is valid only if we got a "data_too_long" flag + if (content == null) + check(metadata.data_too_long) { "missing message data, but no too-long flag" } + else + check(metadata.data_len == content.size) { "wrong message data size" } + + Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") + + return content + } + + fun getOrCreateNotificationKey(): Key { + if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) { + // generate the key and store it + val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) + IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) + } + return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) + } +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt similarity index 100% rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt similarity index 100% rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 89712c2e85..df15efe80c 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -40,6 +40,7 @@ import org.session.libsignal.utilities.emptyPromise import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities +import javax.inject.Inject private const val TAG = "FirebasePushManager" @@ -47,7 +48,7 @@ class FirebasePushManager( private val context: Context ): PushManager { - private val pushManagerV2 = PushManagerV2(context) + @Inject lateinit var pushManagerV2: PushManagerV2 companion object { const val maxRetryCount = 4 @@ -132,6 +133,4 @@ class FirebasePushManager( } success { tokenManager.fcmToken = null } - - fun decrypt(decode: ByteArray) = pushManagerV2.decrypt(decode) } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt index 66c7590e5f..4fe885758f 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt @@ -39,7 +39,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil private const val TAG = "PushManagerV2" -class PushManagerV2(private val context: Context) { +class PushManagerV2(private val pushHandler: PushHandler) { private val sodium = LazySodiumAndroid(SodiumAndroid()) fun register( @@ -48,7 +48,7 @@ class PushManagerV2(private val context: Context) { userEd25519Key: KeyPair, namespaces: List ): Promise { - val pnKey = getOrCreateNotificationKey() + val pnKey = pushHandler.getOrCreateNotificationKey() val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s // if we want to support passing namespace list, here is the place to do it @@ -117,41 +117,4 @@ class PushManagerV2(private val context: Context) { .also { if (it.isFailure()) throw Exception("error: ${it.message}.") } } } - - private fun getOrCreateNotificationKey(): Key { - if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) { - // generate the key and store it - val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) - IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) - } - return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) - } - - fun decrypt(encPayload: ByteArray): ByteArray? { - Log.d(TAG, "decrypt() called") - - val encKey = getOrCreateNotificationKey() - val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() - val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() - val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) - ?: error("Failed to decrypt push notification") - val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray() - val bencoded = Bencode.Decoder(decrypted) - val expectedList = (bencoded.decode() as? BencodeList)?.values - ?: error("Failed to decode bencoded list from payload") - - val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") - val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) - - val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null - // null content is valid only if we got a "data_too_long" flag - if (content == null) - check(metadata.data_too_long) { "missing message data, but no too-long flag" } - else - check(metadata.data_len == content.size) { "wrong message data size" } - - Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") - - return content - } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 4c6ce9f4ea..e546053fce 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -8,7 +8,6 @@ import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 @@ -21,6 +20,7 @@ private const val TAG = "PushNotificationService" class PushNotificationService : FirebaseMessagingService() { @Inject lateinit var pushManager: FirebasePushManager + @Inject lateinit var pushHandler: PushHandler override fun onNewToken(token: String) { super.onNewToken(token) @@ -32,37 +32,7 @@ class PushNotificationService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { Log.d(TAG, "Received a push notification.") - val data: ByteArray? = if (message.data.containsKey("spns")) { - // this is a v2 push notification - try { - pushManager.decrypt(Base64.decode(message.data["enc_payload"])) - } catch(e: Exception) { - Log.e(TAG, "Invalid push notification: ${e.message}") - return - } - } else { - // old v1 push notification; we still need this for receiving legacy closed group notifications - message.data?.get("ENCRYPTED_DATA")?.let(Base64::decode) - } - if (data != null) { - try { - val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() - val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) - JobQueue.shared.add(job) - } catch (e: Exception) { - Log.d(TAG, "Failed to unwrap data for message due to error: $e.") - } - } else { - Log.d(TAG, "Failed to decode data for message.") - val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER) - .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) - .setColor(resources.getColor(network.loki.messenger.R.color.textsecure_primary)) - .setContentTitle("Session") - .setContentText("You've got a new message.") - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setAutoCancel(true) - NotificationManagerCompat.from(this).notify(11111, builder.build()) - } + pushHandler.onPush(message.data) } override fun onDeletedMessages() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index e112cd2682..1533002d6c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -52,10 +52,13 @@ object PushManagerV1 { ) val url = "${server.url}/register_legacy_groups_only" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val body = RequestBody.create( + MediaType.get("application/json"), + JsonUtil.toJson(parameters) + ) val request = Request.Builder().url(url).post(body).build() - return sendOnionRequest(request) sideEffect { response -> + return sendOnionRequest(request) sideEffect { response -> when (response.code) { null, 0 -> throw Exception("error: ${response.message}.") } @@ -73,7 +76,7 @@ object PushManagerV1 { val token = TextSecurePreferences.getFCMToken(context) ?: emptyPromise() return retryIfNeeded(maxRetryCount) { - val parameters = mapOf( "token" to token ) + val parameters = mapOf("token" to token) val url = "${server.url}/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -110,7 +113,7 @@ object PushManagerV1 { closedGroupPublicKey: String, publicKey: String ): Promise<*, Exception> { - val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) + val parameters = mapOf("closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey) val url = "${server.url}/$operation" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() @@ -118,7 +121,7 @@ object PushManagerV1 { return retryIfNeeded(maxRetryCount) { sendOnionRequest(request) sideEffect { when (it.code) { - 0, null -> throw Exception(it.message) + 0, null -> throw Exception(it.message) } } } From 55216875ac14b7aee08e6561f0a0027136e9f781 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 26 Jul 2023 10:48:20 +0930 Subject: [PATCH 23/53] Connect Huawei push notifications --- app/build.gradle | 6 +- .../notifications/HuaweiPushManager.kt | 22 +++- .../HuaweiPushNotificationService.kt | 39 ++---- .../securesms/ApplicationContext.java | 6 +- .../thoughtcrime/securesms/DeviceModule.kt | 16 +++ .../securesms/groups/CreateGroupFragment.kt | 7 +- .../securesms/notifications/ExpiryManager.kt | 0 .../notifications/FcmTokenManager.kt | 0 .../securesms/notifications/PushHandler.kt | 70 +++++++---- .../notifications/FirebasePushManager.kt | 117 ++---------------- .../notifications/FirebasePushModule.kt | 17 +-- .../notifications/GenericPushManager.kt | 87 +++++++++++++ .../securesms/notifications/PushManagerV2.kt | 26 ++-- .../notifications/PushNotificationService.kt | 2 +- .../messaging/MessagingModuleConfiguration.kt | 2 + .../sending_receiving/MessageSender.kt | 5 +- .../MessageSenderClosedGroupHandler.kt | 9 +- .../ReceivedMessageHandler.kt | 2 +- .../notifications/PushManagerV1.kt | 7 +- .../session/libsession/utilities/Device.kt | 6 + 20 files changed, 231 insertions(+), 215 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt rename app/src/main/{kotlin => java}/org/thoughtcrime/securesms/notifications/ExpiryManager.kt (100%) rename app/src/main/{kotlin => java}/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt (100%) create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt create mode 100644 libsession/src/main/java/org/session/libsession/utilities/Device.kt diff --git a/app/build.gradle b/app/build.gradle index db9c42d43a..91ee23fbb4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -133,7 +133,7 @@ android { apply plugin: 'com.google.gms.google-services' ext.websiteUpdateUrl = "null" buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" - buildConfigField "String", "DEVICE", "\"android\"" + buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" } @@ -141,7 +141,7 @@ android { dimension "distribution" ext.websiteUpdateUrl = "null" buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" - buildConfigField "String", "DEVICE", "\"huawei\"" + buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.HUAWEI" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" } @@ -149,7 +149,7 @@ android { dimension "distribution" ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" - buildConfigField "String", "DEVICE", "\"android\"" + buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID" buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" } } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt index 72a22ecfa4..f17b35f292 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt @@ -1,9 +1,29 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import com.huawei.hms.aaid.HmsInstanceId +import kotlinx.coroutines.Job class HuaweiPushManager(val context: Context): PushManager { - override fun refresh(force: Boolean) { + private var huaweiPushInstanceIdJob: Job? = null + @Synchronized + override fun refresh(force: Boolean) { + val huaweiPushInstanceIdJob = huaweiPushInstanceIdJob + + huaweiPushInstanceIdJob?.apply { + if (force) cancel() else if (isActive) return + } + + val hmsInstanceId = HmsInstanceId.getInstance(context) + + val task = hmsInstanceId.aaid + +// HuaweiPushNotificationService().start() +// +// huaweiPushInstanceIdJob = HmsInstanceId.getInstance(this) { hmsInstanceId -> +// RegisterHuaweiPushService(hmsInstanceId, this, force).start() +// Unit.INSTANCE +// } } } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index dd475c8344..e6941a563d 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -18,42 +18,19 @@ import javax.inject.Inject @AndroidEntryPoint class HuaweiPushNotificationService: HmsMessageService() { - @Inject token + @Inject lateinit var pushManager: PushManager + @Inject lateinit var pushHandler: PushHandler override fun onNewToken(token: String?, bundle: Bundle?) { Log.d("Loki", "New HCM token: $token.") - - if (!token.isNullOrEmpty()) { - val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - PushManager.register(token, userPublicKey, this, false) - } + pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage?) { - Log.d("Loki", "Received a push notification.") - val base64EncodedData = message?.data - val data = base64EncodedData?.let { Base64.decode(it) } - if (data != null) { - try { - val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() - val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) - JobQueue.shared.add(job) - } catch (e: Exception) { - Log.d("Loki", "Failed to unwrap data for message due to error: $e.") - } - } else { - Log.d("Loki", "Failed to decode data for message.") - val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER) - .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) - .setColor(this.getResources().getColor(network.loki.messenger.R.color.textsecure_primary)) - .setContentTitle("Session") - .setContentText("You've got a new message.") - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setAutoCancel(true) - with(NotificationManagerCompat.from(this)) { - notify(11111, builder.build()) - } - } + pushHandler.onPush(message?.dataOfMap) } -} \ No newline at end of file + override fun onDeletedMessages() { + pushManager.refresh(true) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 0ba8a77879..2fe1b1e1c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -41,6 +41,7 @@ import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.snode.SnodeModule; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.ConfigFactoryUpdateListener; +import org.session.libsession.utilities.Device; import org.session.libsession.utilities.ProfilePictureUtilities; import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.TextSecurePreferences; @@ -142,6 +143,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO @Inject LokiAPIDatabase lokiAPIDatabase; @Inject public Storage storage; + @Inject Device device; @Inject MessageDataProvider messageDataProvider; @Inject TextSecurePreferences textSecurePreferences; @Inject PushManager pushManager; @@ -207,8 +209,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO DatabaseModule.init(this); MessagingModuleConfiguration.configure(this); super.onCreate(); - messagingModuleConfiguration = new MessagingModuleConfiguration(this, + messagingModuleConfiguration = new MessagingModuleConfiguration( + this, storage, + device, messageDataProvider, ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this), configFactory diff --git a/app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt b/app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt new file mode 100644 index 0000000000..bdfa9b6088 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/DeviceModule.kt @@ -0,0 +1,16 @@ +package org.thoughtcrime.securesms + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import network.loki.messenger.BuildConfig +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DeviceModule { + @Provides + @Singleton + fun provides() = BuildConfig.DEVICE +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt index ead979b773..ecd40938a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -21,6 +21,7 @@ import nl.komponents.kovenant.ui.successUi import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.groupSizeLimit import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Device import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.contacts.SelectContactsAdapter @@ -31,10 +32,14 @@ import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.util.fadeIn import org.thoughtcrime.securesms.util.fadeOut +import javax.inject.Inject @AndroidEntryPoint class CreateGroupFragment : Fragment() { + @Inject + lateinit var device: Device + private lateinit var binding: FragmentCreateGroupBinding private val viewModel: CreateGroupViewModel by viewModels() @@ -86,7 +91,7 @@ class CreateGroupFragment : Fragment() { val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!! isLoading = true binding.loaderContainer.fadeIn() - MessageSender.createClosedGroup(name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> + MessageSender.createClosedGroup(device, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> binding.loaderContainer.fadeOut() isLoading = false val threadID = DatabaseComponent.get(requireContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(requireContext(), Address.fromSerialized(groupID), false)) diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt similarity index 100% rename from app/src/main/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt similarity index 100% rename from app/src/main/kotlin/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt index be4832104d..bba89d2e96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt @@ -29,24 +29,26 @@ private const val TAG = "PushHandler" class PushHandler @Inject constructor(@ApplicationContext val context: Context) { private val sodium = LazySodiumAndroid(SodiumAndroid()) - fun onPush(dataMap: Map) { - val data: ByteArray? = if (dataMap.containsKey("spns")) { - // this is a v2 push notification - try { - decrypt(Base64.decode(dataMap["enc_payload"])) - } catch(e: Exception) { - Log.e(TAG, "Invalid push notification: ${e.message}") - return - } - } else { - // old v1 push notification; we still need this for receiving legacy closed group notifications - dataMap.get("ENCRYPTED_DATA")?.let(Base64::decode) - } - data?.let { onPush(data) } ?: onPush() - + fun onPush(dataMap: Map?) { + onPush(dataMap?.asByteArray()) } - fun onPush() { + private fun onPush(data: ByteArray?) { + if (data == null) { + onPush() + return + } + + try { + val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() + val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) + JobQueue.shared.add(job) + } catch (e: Exception) { + Log.d(TAG, "Failed to unwrap data for message due to error: $e.") + } + } + + private fun onPush() { Log.d(TAG, "Failed to decode data for message.") val builder = NotificationCompat.Builder(context, NotificationChannels.OTHER) .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) @@ -58,15 +60,20 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context) NotificationManagerCompat.from(context).notify(11111, builder.build()) } - fun onPush(data: ByteArray) { - try { - val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() - val job = BatchMessageReceiveJob(listOf(MessageReceiveParameters(envelopeAsData)), null) - JobQueue.shared.add(job) - } catch (e: Exception) { - Log.d(TAG, "Failed to unwrap data for message due to error: $e.") + private fun Map.asByteArray() = + when { + // this is a v2 push notification + containsKey("spns") -> { + try { + decrypt(Base64.decode(this["enc_payload"])) + } catch (e: Exception) { + Log.e(TAG, "Invalid push notification: ${e.message}") + null + } + } + // old v1 push notification; we still need this for receiving legacy closed group notifications + else -> this["ENCRYPTED_DATA"]?.let(Base64::decode) } - } fun decrypt(encPayload: ByteArray): ByteArray? { Log.d(TAG, "decrypt() called") @@ -84,14 +91,18 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context) val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) - val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null + val content: ByteArray? = + if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null // null content is valid only if we got a "data_too_long" flag if (content == null) check(metadata.data_too_long) { "missing message data, but no too-long flag" } else check(metadata.data_len == content.size) { "wrong message data size" } - Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") + Log.d( + TAG, + "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B" + ) return content } @@ -102,6 +113,11 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context) val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) } - return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) + return Key.fromHexString( + IdentityKeyUtil.retrieve( + context, + IdentityKeyUtil.NOTIFICATION_KEY + ) + ) } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index df15efe80c..3df54fc119 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -1,60 +1,22 @@ package org.thoughtcrime.securesms.notifications import android.content.Context -import com.goterl.lazysodium.LazySodiumAndroid -import com.goterl.lazysodium.SodiumAndroid -import com.goterl.lazysodium.interfaces.AEAD -import com.goterl.lazysodium.interfaces.Sign -import com.goterl.lazysodium.utils.Key -import com.goterl.lazysodium.utils.KeyPair +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Job -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.combine.and -import nl.komponents.kovenant.functional.map -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata -import org.session.libsession.messaging.sending_receiving.notifications.Response -import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest -import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse -import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse -import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest -import org.session.libsession.messaging.utilities.SodiumUtilities -import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.snode.SnodeAPI -import org.session.libsession.snode.Version -import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber -import org.session.libsession.utilities.bencode.Bencode -import org.session.libsession.utilities.bencode.BencodeList -import org.session.libsession.utilities.bencode.BencodeString -import org.session.libsignal.utilities.Base64 +import org.session.libsession.utilities.Device import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.Namespace -import org.session.libsignal.utilities.emptyPromise -import org.session.libsignal.utilities.retryIfNeeded -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil -import org.thoughtcrime.securesms.crypto.KeyPairUtilities import javax.inject.Inject +import javax.inject.Singleton private const val TAG = "FirebasePushManager" -class FirebasePushManager( - private val context: Context - ): PushManager { +@Singleton +class FirebasePushManager @Inject constructor( + @ApplicationContext private val context: Context +): PushManager { - @Inject lateinit var pushManagerV2: PushManagerV2 + @Inject lateinit var genericPushManager: GenericPushManager - companion object { - const val maxRetryCount = 4 - } - - private val tokenManager = FcmTokenManager(context, ExpiryManager(context)) private var firebaseInstanceIdJob: Job? = null @Synchronized @@ -67,70 +29,9 @@ class FirebasePushManager( firebaseInstanceIdJob = getFcmInstanceId { task -> when { - task.isSuccessful -> try { task.result?.token?.let { refresh(it, force).get() } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } + task.isSuccessful -> try { task.result?.token?.let { genericPushManager.refresh(it, force).get() } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) } } } - - private fun refresh(token: String, force: Boolean): Promise<*, Exception> { - Log.d(TAG, "refresh() called") - - val userPublicKey = getLocalNumber(context) ?: return emptyPromise() - val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() - - return when { - tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey) - tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey) - else -> emptyPromise() - } - } - - /** - * Register for push notifications if: - * force is true - * there is no FCM Token - * FCM Token has expired - */ - private fun register( - force: Boolean, - token: String, - publicKey: String, - userEd25519Key: KeyPair, - namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = if (force || tokenManager.isInvalid()) { - register(token, publicKey, userEd25519Key, namespaces) - } else emptyPromise() - - /** - * Register for push notifications. - */ - private fun register( - token: String, - publicKey: String, - userEd25519Key: KeyPair, - namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = PushManagerV1.register( - token = token, - publicKey = publicKey - ) and pushManagerV2.register( - token, publicKey, userEd25519Key, namespaces - ) fail { - Log.e(TAG, "registerBoth failed", it) - } success { - Log.d(TAG, "registerBoth success... saving token!!") - tokenManager.fcmToken = token - } - - private fun unregister( - token: String, - userPublicKey: String, - userEdKey: KeyPair - ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( - token, userPublicKey, userEdKey - ) fail { - Log.e(TAG, "unregisterBoth failed", it) - } success { - tokenManager.fcmToken = null - } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt index 3f33016a02..925ad9d4d7 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -1,28 +1,13 @@ package org.thoughtcrime.securesms.notifications -import android.content.Context import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import org.session.libsession.utilities.TextSecurePreferences -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object FirebasePushModule { - @Provides - @Singleton - fun provideFirebasePushManager( - @ApplicationContext context: Context, - ) = FirebasePushManager(context) -} @Module @InstallIn(SingletonComponent::class) abstract class FirebaseBindingModule { @Binds abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager -} \ No newline at end of file +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt new file mode 100644 index 0000000000..692826225d --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt @@ -0,0 +1,87 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import com.goterl.lazysodium.utils.KeyPair +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.combine.and +import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.utilities.Device +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.Namespace +import org.session.libsignal.utilities.emptyPromise +import org.thoughtcrime.securesms.crypto.KeyPairUtilities +import javax.inject.Inject +import javax.inject.Singleton + +private const val TAG = "GenericPushManager" + +@Singleton +class GenericPushManager @Inject constructor( + private val context: Context, + private val device: Device, + private val tokenManager: FcmTokenManager, + private val pushManagerV2: PushManagerV2, +) { + fun refresh(token: String, force: Boolean): Promise<*, Exception> { + Log.d(TAG, "refresh() called") + + val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return emptyPromise() + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() + + return when { + tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey) + tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey) + else -> emptyPromise() + } + } + + /** + * Register for push notifications if: + * force is true + * there is no FCM Token + * FCM Token has expired + */ + private fun register( + force: Boolean, + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List = listOf(Namespace.DEFAULT) + ): Promise<*, Exception> = if (force || tokenManager.isInvalid()) { + register(token, publicKey, userEd25519Key, namespaces) + } else emptyPromise() + + /** + * Register for push notifications. + */ + private fun register( + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List = listOf(Namespace.DEFAULT) + ): Promise<*, Exception> = PushManagerV1.register( + token = token, + device = device, + publicKey = publicKey + ) and pushManagerV2.register( + token, publicKey, userEd25519Key, namespaces + ) fail { + Log.e(TAG, "registerBoth failed", it) + } success { + Log.d(TAG, "registerBoth success... saving token!!") + tokenManager.fcmToken = token + } + + private fun unregister( + token: String, + userPublicKey: String, + userEdKey: KeyPair + ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( + token, userPublicKey, userEdKey + ) fail { + Log.e(TAG, "unregisterBoth failed", it) + } success { + tokenManager.fcmToken = null + } +} \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt index 4fe885758f..9f9a132aba 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt @@ -1,13 +1,9 @@ package org.thoughtcrime.securesms.notifications -import android.content.Context import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid -import com.goterl.lazysodium.interfaces.AEAD import com.goterl.lazysodium.interfaces.Sign -import com.goterl.lazysodium.utils.Key import com.goterl.lazysodium.utils.KeyPair -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream @@ -16,30 +12,22 @@ import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata -import org.session.libsession.messaging.sending_receiving.notifications.Response -import org.session.libsession.messaging.sending_receiving.notifications.Server -import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest -import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse -import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse -import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest -import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.messaging.sending_receiving.notifications.* import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version -import org.session.libsession.utilities.bencode.Bencode -import org.session.libsession.utilities.bencode.BencodeList -import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.retryIfNeeded -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import javax.inject.Inject +import javax.inject.Singleton private const val TAG = "PushManagerV2" +private const val maxRetryCount = 4 -class PushManagerV2(private val pushHandler: PushHandler) { +@Singleton +class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { private val sodium = LazySodiumAndroid(SodiumAndroid()) fun register( @@ -98,7 +86,7 @@ class PushManagerV2(private val pushHandler: PushHandler) { } private inline fun retryResponseBody(path: String, requestParameters: String): Promise = - retryIfNeeded(FirebasePushManager.maxRetryCount) { getResponseBody(path, requestParameters) } + retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) } private inline fun getResponseBody(path: String, requestParameters: String): Promise { val server = Server.LATEST diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index e546053fce..bc641f82cd 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -19,7 +19,7 @@ private const val TAG = "PushNotificationService" @AndroidEntryPoint class PushNotificationService : FirebaseMessagingService() { - @Inject lateinit var pushManager: FirebasePushManager + @Inject lateinit var pushManager: PushManager @Inject lateinit var pushHandler: PushHandler override fun onNewToken(token: String) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt b/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt index 0437196772..3d48325bf7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/MessagingModuleConfiguration.kt @@ -5,10 +5,12 @@ import com.goterl.lazysodium.utils.KeyPair import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.session.libsession.utilities.ConfigFactoryProtocol +import org.session.libsession.utilities.Device class MessagingModuleConfiguration( val context: Context, val storage: StorageProtocol, + val device: Device, val messageDataProvider: MessageDataProvider, val getUserED25519KeyPair: () -> KeyPair?, val configFactory: ConfigFactoryProtocol diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 804b2f15be..be7075dc54 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -29,6 +29,7 @@ import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Device import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsignal.crypto.PushTransportDetails @@ -454,8 +455,8 @@ object MessageSender { } // Closed groups - fun createClosedGroup(name: String, members: Collection): Promise { - return create(name, members) + fun createClosedGroup(device: Device, name: String, members: Collection): Promise { + return create(device, name, members) } fun explicitNameChange(groupPublicKey: String, newName: String) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 3b4b4b583c..9a068f68c3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -13,6 +13,7 @@ import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPol import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized +import org.session.libsession.utilities.Device import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient @@ -33,7 +34,11 @@ const val groupSizeLimit = 100 val pendingKeyPairs = ConcurrentHashMap>() -fun MessageSender.create(name: String, members: Collection): Promise { +fun MessageSender.create( + device: Device, + name: String, + members: Collection +): Promise { val deferred = deferred() ThreadUtils.queue { // Prepare @@ -89,7 +94,7 @@ fun MessageSender.create(name: String, members: Collection): Promise = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = if (!isUsingFCM) { emptyPromise() } else retryIfNeeded(maxRetryCount) { - doRegister(token, publicKey, legacyGroupPublicKeys) + doRegister(token, publicKey, device, legacyGroupPublicKeys) } fail { exception -> Log.d(TAG, "Couldn't register for FCM due to error: $exception.") } - private fun doRegister(token: String?, publicKey: String?, legacyGroupPublicKeys: Collection): Promise<*, Exception> { + private fun doRegister(token: String?, publicKey: String?, device: Device, legacyGroupPublicKeys: Collection): Promise<*, Exception> { Log.d(TAG, "registerV1 requested") token ?: return emptyPromise() @@ -48,6 +50,7 @@ object PushManagerV1 { val parameters = mapOf( "token" to token, "pubKey" to publicKey, + "device" to device, "legacyGroupPublicKeys" to legacyGroupPublicKeys ) diff --git a/libsession/src/main/java/org/session/libsession/utilities/Device.kt b/libsession/src/main/java/org/session/libsession/utilities/Device.kt new file mode 100644 index 0000000000..3f8d0d6d5a --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/utilities/Device.kt @@ -0,0 +1,6 @@ +package org.session.libsession.utilities + +enum class Device(val value: String) { + ANDROID("android"), + HUAWEI("huawei"); +} From d3ea4e2e30de981ca2131242c1192a638324f9de Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 26 Jul 2023 14:36:06 +0930 Subject: [PATCH 24/53] Fix --- .../notifications/HuaweiPushManager.kt | 28 +++++++++----- .../notifications/HuaweiPushModule.kt | 11 ------ .../HuaweiPushNotificationService.kt | 11 ------ .../securesms/home/HomeActivity.kt | 5 ++- .../securesms/notifications/ExpiryManager.kt | 3 ++ .../notifications/FcmTokenManager.kt | 11 ++++-- .../securesms/onboarding/PNModeActivity.kt | 7 +++- .../NotificationSettingsActivity.kt | 2 + .../NotificationsPreferenceFragment.java | 10 ++++- .../notifications/GenericPushManager.kt | 37 ++++++++++++------- .../securesms/notifications/PushManagerV2.kt | 7 +++- app/src/play/AndroidManifest.xml | 2 +- .../notifications/FirebasePushManager.kt | 12 +++--- ....kt => FirebasePushNotificationService.kt} | 13 +------ .../MessageSenderClosedGroupHandler.kt | 2 +- .../ReceivedMessageHandler.kt | 2 +- .../notifications/PushManagerV1.kt | 17 +++++---- .../session/libsession/utilities/Device.kt | 4 +- 18 files changed, 99 insertions(+), 85 deletions(-) rename app/src/{play => main}/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt (76%) rename app/src/{play => main}/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt (96%) rename app/src/play/kotlin/org/thoughtcrime/securesms/notifications/{PushNotificationService.kt => FirebasePushNotificationService.kt} (64%) diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt index f17b35f292..6e495357f3 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt @@ -1,10 +1,22 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import com.huawei.hmf.tasks.Tasks import com.huawei.hms.aaid.HmsInstanceId +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton -class HuaweiPushManager(val context: Context): PushManager { +@Singleton +class HuaweiPushManager @Inject constructor( + @ApplicationContext private val context: Context, + private val genericPushManager: GenericPushManager +): PushManager { private var huaweiPushInstanceIdJob: Job? = null @Synchronized @@ -17,13 +29,11 @@ class HuaweiPushManager(val context: Context): PushManager { val hmsInstanceId = HmsInstanceId.getInstance(context) - val task = hmsInstanceId.aaid - -// HuaweiPushNotificationService().start() -// -// huaweiPushInstanceIdJob = HmsInstanceId.getInstance(this) { hmsInstanceId -> -// RegisterHuaweiPushService(hmsInstanceId, this, force).start() -// Unit.INSTANCE -// } + MainScope().launch(Dispatchers.IO) { + val task = hmsInstanceId.aaid + Tasks.await(task) + if (!isActive) return@launch // don't 'complete' task if we were canceled + task.result?.id?.let { genericPushManager.refresh(it, force) } + } } } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt index e10ed77abf..c1be786afc 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt @@ -8,17 +8,6 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object HuaweiPushModule { - @Provides - @Singleton - fun provideFirebasePushManager( - @ApplicationContext context: Context, - ) = HuaweiPushManager(context) -} - @Module @InstallIn(SingletonComponent::class) abstract class HuaweiBindingModule { diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index e6941a563d..507787123c 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -1,17 +1,9 @@ package org.thoughtcrime.securesms.notifications import android.os.Bundle -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import com.huawei.hms.push.HmsMessageService import com.huawei.hms.push.RemoteMessage import dagger.hilt.android.AndroidEntryPoint -import org.session.libsession.messaging.jobs.BatchMessageReceiveJob -import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.jobs.MessageReceiveParameters -import org.session.libsession.messaging.utilities.MessageWrapper -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import javax.inject.Inject @@ -20,16 +12,13 @@ class HuaweiPushNotificationService: HmsMessageService() { @Inject lateinit var pushManager: PushManager @Inject lateinit var pushHandler: PushHandler - override fun onNewToken(token: String?, bundle: Bundle?) { Log.d("Loki", "New HCM token: $token.") pushManager.refresh(true) } - override fun onMessageReceived(message: RemoteMessage?) { pushHandler.onPush(message?.dataOfMap) } - override fun onDeletedMessages() { pushManager.refresh(true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 6570c0ca18..c64d6ef071 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -67,6 +67,7 @@ import org.thoughtcrime.securesms.home.search.GlobalSearchViewModel import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests +import org.thoughtcrime.securesms.notifications.PushManager import org.thoughtcrime.securesms.onboarding.SeedActivity import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.permissions.Permissions @@ -106,6 +107,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), @Inject lateinit var groupDatabase: GroupDatabase @Inject lateinit var textSecurePreferences: TextSecurePreferences @Inject lateinit var configFactory: ConfigFactory + @Inject lateinit var pushManager: PushManager private val globalSearchViewModel by viewModels() private val homeViewModel by viewModels() @@ -230,8 +232,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), (applicationContext as ApplicationContext).startPollingIfNeeded() // update things based on TextSecurePrefs (profile info etc) // Set up remaining components if needed - val application = ApplicationContext.getInstance(this@HomeActivity) - application.registerForPnIfNeeded(false) + pushManager.refresh(false) if (textSecurePreferences.getLocalNumber() != null) { OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt index 36c986f0ea..45318d43ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt @@ -1,7 +1,10 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import org.session.libsession.utilities.TextSecurePreferences +import javax.inject.Inject +import javax.inject.Singleton class ExpiryManager( private val context: Context, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt index e1182a480b..295c8296fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt @@ -1,12 +1,17 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import org.session.libsession.utilities.TextSecurePreferences +import javax.inject.Inject +import javax.inject.Singleton -class FcmTokenManager( - private val context: Context, - private val expiryManager: ExpiryManager +@Singleton +class FcmTokenManager @Inject constructor( + @ApplicationContext private val context: Context, ) { + private val expiryManager = ExpiryManager(context) + val isUsingFCM get() = TextSecurePreferences.isUsingFCM(context) var fcmToken diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index 8210937830..c8f036f15f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -19,6 +19,7 @@ import org.session.libsession.utilities.ThemeUtil import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.home.HomeActivity +import org.thoughtcrime.securesms.notifications.PushManager import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.GlowViewUtilities import org.thoughtcrime.securesms.util.PNModeView @@ -27,8 +28,12 @@ import org.thoughtcrime.securesms.util.getAccentColor import org.thoughtcrime.securesms.util.getColorWithID import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo import org.thoughtcrime.securesms.util.show +import javax.inject.Inject class PNModeActivity : BaseActionBarActivity() { + + @Inject lateinit var pushManager: PushManager + private lateinit var binding: ActivityPnModeBinding private var selectedOptionView: PNModeView? = null @@ -161,7 +166,7 @@ class PNModeActivity : BaseActionBarActivity() { TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == binding.fcmOptionView)) val application = ApplicationContext.getInstance(this) application.startPollingIfNeeded() - application.registerForPnIfNeeded(true) + pushManager.refresh(true) val intent = Intent(this, HomeActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.putExtra(HomeActivity.FROM_ONBOARDING, true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt index af039a4fdb..b18859ea07 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationSettingsActivity.kt @@ -1,10 +1,12 @@ package org.thoughtcrime.securesms.preferences import android.os.Bundle +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment +@AndroidEntryPoint class NotificationSettingsActivity : PassphraseRequiredActionBarActivity() { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 8afc0ff41b..228392ad76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -23,14 +23,22 @@ import org.session.libsession.utilities.TextSecurePreferences; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; import org.thoughtcrime.securesms.notifications.NotificationChannels; +import org.thoughtcrime.securesms.notifications.PushManager; +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; import network.loki.messenger.R; +@AndroidEntryPoint public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment { @SuppressWarnings("unused") private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName(); + @Inject + PushManager pushManager; + @Override public void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); @@ -41,7 +49,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme this.findPreference(fcmKey) .setOnPreferenceChangeListener((preference, newValue) -> { TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue); - ApplicationContext.getInstance(getContext()).registerForPnIfNeeded(true); + pushManager.refresh(true); return true; }); diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt similarity index 76% rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt index 692826225d..ce4210fcfd 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt +++ b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import com.goterl.lazysodium.utils.KeyPair +import dagger.hilt.android.qualifiers.ApplicationContext import nl.komponents.kovenant.Promise import nl.komponents.kovenant.combine.and import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 @@ -18,7 +19,7 @@ private const val TAG = "GenericPushManager" @Singleton class GenericPushManager @Inject constructor( - private val context: Context, + @ApplicationContext private val context: Context, private val device: Device, private val tokenManager: FcmTokenManager, private val pushManagerV2: PushManagerV2, @@ -60,17 +61,25 @@ class GenericPushManager @Inject constructor( publicKey: String, userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = PushManagerV1.register( - token = token, - device = device, - publicKey = publicKey - ) and pushManagerV2.register( - token, publicKey, userEd25519Key, namespaces - ) fail { - Log.e(TAG, "registerBoth failed", it) - } success { - Log.d(TAG, "registerBoth success... saving token!!") - tokenManager.fcmToken = token + ): Promise<*, Exception> { + val v1 = PushManagerV1.register( + device = device, + token = token, + publicKey = publicKey + ) fail { + Log.e(TAG, "register v1 failed", it) + } + + val v2 = pushManagerV2.register( + Device.ANDROID, token, publicKey, userEd25519Key, namespaces + ) fail { + Log.e(TAG, "register v2 failed", it) + } + + return v1 and v2 success { + Log.d(TAG, "registerBoth success... saving token!!") + tokenManager.fcmToken = token + } } private fun unregister( @@ -78,10 +87,10 @@ class GenericPushManager @Inject constructor( userPublicKey: String, userEdKey: KeyPair ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( - token, userPublicKey, userEdKey + Device.ANDROID, token, userPublicKey, userEdKey ) fail { Log.e(TAG, "unregisterBoth failed", it) } success { tokenManager.fcmToken = null } -} \ No newline at end of file +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt similarity index 96% rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt index 9f9a132aba..93e6fc6819 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt +++ b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt @@ -16,6 +16,7 @@ import org.session.libsession.messaging.sending_receiving.notifications.* import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version +import org.session.libsession.utilities.Device import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace @@ -31,6 +32,7 @@ class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { private val sodium = LazySodiumAndroid(SodiumAndroid()) fun register( + device: Device, token: String, publicKey: String, userEd25519Key: KeyPair, @@ -48,7 +50,7 @@ class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { session_ed25519 = userEd25519Key.publicKey.asHexString, namespaces = listOf(Namespace.DEFAULT), data = true, // only permit data subscription for now (?) - service = "firebase", + service = device.service, sig_ts = timestamp, signature = Base64.encodeBytes(signature), service_info = mapOf("token" to token), @@ -61,6 +63,7 @@ class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { } fun unregister( + device: Device, token: String, userPublicKey: String, userEdKey: KeyPair @@ -74,7 +77,7 @@ class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { val requestParameters = UnsubscriptionRequest( pubkey = userPublicKey, session_ed25519 = userEdKey.publicKey.asHexString, - service = "firebase", + service = device.service, sig_ts = timestamp, signature = Base64.encodeBytes(signature), service_info = mapOf("token" to token), diff --git a/app/src/play/AndroidManifest.xml b/app/src/play/AndroidManifest.xml index f5b54fd47e..8b975b0634 100644 --- a/app/src/play/AndroidManifest.xml +++ b/app/src/play/AndroidManifest.xml @@ -4,7 +4,7 @@ diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 3df54fc119..074ef379f8 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -1,9 +1,6 @@ package org.thoughtcrime.securesms.notifications -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Job -import org.session.libsession.utilities.Device import org.session.libsignal.utilities.Log import javax.inject.Inject import javax.inject.Singleton @@ -12,11 +9,9 @@ private const val TAG = "FirebasePushManager" @Singleton class FirebasePushManager @Inject constructor( - @ApplicationContext private val context: Context + private val genericPushManager: GenericPushManager ): PushManager { - @Inject lateinit var genericPushManager: GenericPushManager - private var firebaseInstanceIdJob: Job? = null @Synchronized @@ -24,7 +19,10 @@ class FirebasePushManager @Inject constructor( Log.d(TAG, "refresh() called with: force = $force") firebaseInstanceIdJob?.apply { - if (force) cancel() else if (isActive) return + when { + force -> cancel() + isActive -> return + } } firebaseInstanceIdJob = getFcmInstanceId { task -> diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushNotificationService.kt similarity index 64% rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushNotificationService.kt index bc641f82cd..b8640296fa 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushNotificationService.kt @@ -1,23 +1,15 @@ package org.thoughtcrime.securesms.notifications -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import dagger.hilt.android.AndroidEntryPoint -import org.session.libsession.messaging.jobs.BatchMessageReceiveJob -import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.jobs.MessageReceiveParameters -import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import javax.inject.Inject -private const val TAG = "PushNotificationService" - +private const val TAG = "FirebasePushNotificationService" @AndroidEntryPoint -class PushNotificationService : FirebaseMessagingService() { +class FirebasePushNotificationService : FirebaseMessagingService() { @Inject lateinit var pushManager: PushManager @Inject lateinit var pushHandler: PushHandler @@ -37,7 +29,6 @@ class PushNotificationService : FirebaseMessagingService() { override fun onDeletedMessages() { Log.d(TAG, "Called onDeletedMessages.") - super.onDeletedMessages() pushManager.refresh(true) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 9a068f68c3..022d32f170 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -94,7 +94,7 @@ fun MessageSender.create( // Add the group to the config now that it was successfully created storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair) // Notify the PN server - PushManagerV1.register(publicKey = userPublicKey, device = device) + PushManagerV1.register(device = device, publicKey = userPublicKey) // Start polling ClosedGroupPollerV2.shared.startPolling(groupPublicKey) // Fulfill the promise diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 91197649c7..7c5092e5ea 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -556,7 +556,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli // Set expiration timer storage.setExpirationTimer(groupID, expireTimer) // Notify the PN server - PushManagerV1.register(publicKey = userPublicKey, device = MessagingModuleConfiguration.shared.device) + PushManagerV1.register(device = MessagingModuleConfiguration.shared.device, publicKey = userPublicKey) // Notify the user if (userPublicKey == sender && !groupExists) { val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index 32e5f6b2dc..b8894162d5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -27,18 +27,19 @@ object PushManagerV1 { private val server = Server.LEGACY fun register( + device: Device, isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), token: String? = TextSecurePreferences.getFCMToken(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context), - device: Device, legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = - if (!isUsingFCM) { - emptyPromise() - } else retryIfNeeded(maxRetryCount) { - doRegister(token, publicKey, device, legacyGroupPublicKeys) - } fail { exception -> - Log.d(TAG, "Couldn't register for FCM due to error: $exception.") + when { + isUsingFCM -> retryIfNeeded(maxRetryCount) { + doRegister(token, publicKey, device, legacyGroupPublicKeys) + } fail { exception -> + Log.d(TAG, "Couldn't register for FCM due to error: $exception.") + } + else -> emptyPromise() } private fun doRegister(token: String?, publicKey: String?, device: Device, legacyGroupPublicKeys: Collection): Promise<*, Exception> { @@ -50,7 +51,7 @@ object PushManagerV1 { val parameters = mapOf( "token" to token, "pubKey" to publicKey, - "device" to device, + "device" to device.value, "legacyGroupPublicKeys" to legacyGroupPublicKeys ) diff --git a/libsession/src/main/java/org/session/libsession/utilities/Device.kt b/libsession/src/main/java/org/session/libsession/utilities/Device.kt index 3f8d0d6d5a..c00e0a69dd 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/Device.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/Device.kt @@ -1,6 +1,6 @@ package org.session.libsession.utilities -enum class Device(val value: String) { - ANDROID("android"), +enum class Device(val value: String, val service: String = value) { + ANDROID("android", "firebase"), HUAWEI("huawei"); } From 4738c9b4f909c3ddcd76d7eae7cc01f8238b1b33 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 26 Jul 2023 14:48:15 +0930 Subject: [PATCH 25/53] Fix v2 --- .../securesms/notifications/GenericPushManager.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt index ce4210fcfd..471affcd24 100644 --- a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt +++ b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt @@ -71,7 +71,7 @@ class GenericPushManager @Inject constructor( } val v2 = pushManagerV2.register( - Device.ANDROID, token, publicKey, userEd25519Key, namespaces + device, token, publicKey, userEd25519Key, namespaces ) fail { Log.e(TAG, "register v2 failed", it) } @@ -87,7 +87,7 @@ class GenericPushManager @Inject constructor( userPublicKey: String, userEdKey: KeyPair ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( - Device.ANDROID, token, userPublicKey, userEdKey + device, token, userPublicKey, userEdKey ) fail { Log.e(TAG, "unregisterBoth failed", it) } success { From 7be1f092f90ce8be4069ccc6625d5aa457a35be5 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 26 Jul 2023 22:09:24 +0930 Subject: [PATCH 26/53] More Huawei stuff --- app/agconnect-services.json | 92 +++++++++++++++++++ app/src/huawei/AndroidManifest.xml | 4 +- .../HuaweiPushNotificationService.kt | 3 +- 3 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 app/agconnect-services.json diff --git a/app/agconnect-services.json b/app/agconnect-services.json new file mode 100644 index 0000000000..99e95057e4 --- /dev/null +++ b/app/agconnect-services.json @@ -0,0 +1,92 @@ +{ + "agcgw":{ + "backurl":"connect-dre.hispace.hicloud.com", + "url":"connect-dre.dbankcloud.cn", + "websocketbackurl":"connect-ws-dre.hispace.dbankcloud.com", + "websocketurl":"connect-ws-dre.hispace.dbankcloud.cn" + }, + "agcgw_all":{ + "CN":"connect-drcn.dbankcloud.cn", + "CN_back":"connect-drcn.hispace.hicloud.com", + "DE":"connect-dre.dbankcloud.cn", + "DE_back":"connect-dre.hispace.hicloud.com", + "RU":"connect-drru.hispace.dbankcloud.ru", + "RU_back":"connect-drru.hispace.dbankcloud.cn", + "SG":"connect-dra.dbankcloud.cn", + "SG_back":"connect-dra.hispace.hicloud.com" + }, + "websocketgw_all":{ + "CN":"connect-ws-drcn.hispace.dbankcloud.cn", + "CN_back":"connect-ws-drcn.hispace.dbankcloud.com", + "DE":"connect-ws-dre.hispace.dbankcloud.cn", + "DE_back":"connect-ws-dre.hispace.dbankcloud.com", + "RU":"connect-ws-drru.hispace.dbankcloud.ru", + "RU_back":"connect-ws-drru.hispace.dbankcloud.cn", + "SG":"connect-ws-dra.hispace.dbankcloud.cn", + "SG_back":"connect-ws-dra.hispace.dbankcloud.com" + }, + "client":{ + "cp_id":"30061000024605000", + "product_id":"99536292102650699", + "client_id":"993355872258247936", + "client_secret":"0A83DC166FBF0696B884D38830E0B90AE8BBFF2D9CA8C26C9D9DA26816C2A9E3", + "project_id":"99536292102650699", + "app_id":"107146885", + "api_key":"DAEDAPld5VAZXpjfD+g0PwlXfya5ykPMYTMRYkySUjGfJg055ijAFNZBUtcykDPWZiaUcc4Mo9oJqmooL6HT8n4toO5COlWZjkqGXA==", + "package_name":"network.loki.messenger" + }, + "oauth_client":{ + "client_id":"107146885", + "client_type":1 + }, + "app_info":{ + "app_id":"107146885", + "package_name":"network.loki.messenger" + }, + "service":{ + "analytics":{ + "collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com", + "collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", + "collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", + "resource_id":"p1", + "channel_id":"" + }, + "search":{ + "url":"https://search-dre.cloud.huawei.com" + }, + "cloudstorage":{ + "storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia", + "storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru", + "storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru", + "storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu", + "storage_url_de":"https://ops-dre.agcstorage.link", + "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn", + "storage_url_sg":"https://ops-dra.agcstorage.link", + "storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn", + "storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn" + }, + "ml":{ + "mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn" + } + }, + "region":"DE", + "configuration_version":"3.0", + "appInfos":[ + { + "package_name":"network.loki.messenger", + "client":{ + "app_id":"107146885" + }, + "app_info":{ + "package_name":"network.loki.messenger", + "app_id":"107146885" + }, + "oauth_client":{ + "client_type":1, + "client_id":"107146885" + } + } + ] +} diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml index 4745c454b2..57279978c1 100644 --- a/app/src/huawei/AndroidManifest.xml +++ b/app/src/huawei/AndroidManifest.xml @@ -8,9 +8,9 @@ android:enabled="true" android:exported="false"> - + - \ No newline at end of file + diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index 507787123c..640caf3cf4 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -17,7 +17,8 @@ class HuaweiPushNotificationService: HmsMessageService() { pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage?) { - pushHandler.onPush(message?.dataOfMap) + TODO("Huawei works!") +// pushHandler.onPush(message?.dataOfMap) } override fun onDeletedMessages() { pushManager.refresh(true) From d1e22ca36934129c2bfa366e3bd4d5c6de51e1a6 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 27 Jul 2023 13:53:33 +0930 Subject: [PATCH 27/53] Fix --- .../securesms/notifications/HuaweiPushNotificationService.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index 640caf3cf4..507787123c 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -17,8 +17,7 @@ class HuaweiPushNotificationService: HmsMessageService() { pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage?) { - TODO("Huawei works!") -// pushHandler.onPush(message?.dataOfMap) + pushHandler.onPush(message?.dataOfMap) } override fun onDeletedMessages() { pushManager.refresh(true) From 7ee9b14247c7c277a5ecaca7df9d20b2ad57bb9e Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 2 Aug 2023 10:41:50 +0930 Subject: [PATCH 28/53] Fix PNModeActivity DI --- .../org/thoughtcrime/securesms/onboarding/PNModeActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index c8f036f15f..edaf646aaa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -12,6 +12,7 @@ import android.view.View import android.widget.Toast import androidx.annotation.ColorInt import androidx.annotation.DrawableRes +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ActivityPnModeBinding import org.session.libsession.utilities.TextSecurePreferences @@ -30,6 +31,7 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo import org.thoughtcrime.securesms.util.show import javax.inject.Inject +@AndroidEntryPoint class PNModeActivity : BaseActionBarActivity() { @Inject lateinit var pushManager: PushManager From 41d24ef2c36f78b858b790c22367ea010c5b2399 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 2 Aug 2023 14:55:31 +0930 Subject: [PATCH 29/53] ... --- .../notifications/HuaweiPushManager.kt | 15 +++++++--- .../HuaweiPushNotificationService.kt | 29 ++++++++++++++++++- app/src/main/AndroidManifest.xml | 10 +++++++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt index 6e495357f3..87a78ef035 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import android.util.Log import com.huawei.hmf.tasks.Tasks import com.huawei.hms.aaid.HmsInstanceId import dagger.hilt.android.qualifiers.ApplicationContext @@ -9,9 +10,12 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import org.session.libsession.utilities.TextSecurePreferences import javax.inject.Inject import javax.inject.Singleton +private val TAG = HuaweiPushManager::class.java.name + @Singleton class HuaweiPushManager @Inject constructor( @ApplicationContext private val context: Context, @@ -27,13 +31,16 @@ class HuaweiPushManager @Inject constructor( if (force) cancel() else if (isActive) return } + val appId = "107146885" + val tokenScope = "HCM" val hmsInstanceId = HmsInstanceId.getInstance(context) MainScope().launch(Dispatchers.IO) { - val task = hmsInstanceId.aaid - Tasks.await(task) - if (!isActive) return@launch // don't 'complete' task if we were canceled - task.result?.id?.let { genericPushManager.refresh(it, force) } + val token = hmsInstanceId.getToken(appId, tokenScope) + + Log.d(TAG, "refresh() with huawei token: $token") + + genericPushManager.refresh(token, force) } } } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index 507787123c..7dae82dd30 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -5,18 +5,45 @@ import com.huawei.hms.push.HmsMessageService import com.huawei.hms.push.RemoteMessage import dagger.hilt.android.AndroidEntryPoint import org.session.libsignal.utilities.Log +import java.lang.Exception import javax.inject.Inject @AndroidEntryPoint class HuaweiPushNotificationService: HmsMessageService() { + init { + Log.d("pnh", "init Huawei Service") + } + @Inject lateinit var pushManager: PushManager @Inject lateinit var pushHandler: PushHandler + + override fun onCreate() { + Log.d("pnh", "onCreate Huawei Service") + super.onCreate() + } + + override fun onMessageDelivered(p0: String?, p1: Exception?) { + Log.d("pnh", "onMessageDelivered") + super.onMessageDelivered(p0, p1) + } + + override fun onMessageSent(p0: String?) { + Log.d("pnh", "onMessageSent") + super.onMessageSent(p0) + } + + override fun onNewToken(p0: String?) { + Log.d("pnh", "onNewToken") + super.onNewToken(p0) + } + override fun onNewToken(token: String?, bundle: Bundle?) { - Log.d("Loki", "New HCM token: $token.") + Log.d("pnh", "New HCM token: $token.") pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage?) { + Log.d("pnh", "onMessageReceived: $message.") pushHandler.onPush(message?.dataOfMap) } override fun onDeletedMessages() { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc63ab6a87..d6bed361e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,6 +78,16 @@ android:theme="@style/Theme.Session.DayNight" tools:replace="android:allowBackup"> + + + + + + Date: Thu, 3 Aug 2023 14:14:55 +0930 Subject: [PATCH 30/53] ... --- app/agconnect-services.json | 186 +++++++++--------- .../notifications/HuaweiPushManager.kt | 11 +- .../HuaweiPushNotificationService.kt | 5 + app/src/main/AndroidManifest.xml | 2 +- .../notifications/GenericPushManager.kt | 10 +- .../notifications/PushManagerV1.kt | 25 ++- 6 files changed, 131 insertions(+), 108 deletions(-) diff --git a/app/agconnect-services.json b/app/agconnect-services.json index 99e95057e4..0c81d0477a 100644 --- a/app/agconnect-services.json +++ b/app/agconnect-services.json @@ -1,92 +1,96 @@ { - "agcgw":{ - "backurl":"connect-dre.hispace.hicloud.com", - "url":"connect-dre.dbankcloud.cn", - "websocketbackurl":"connect-ws-dre.hispace.dbankcloud.com", - "websocketurl":"connect-ws-dre.hispace.dbankcloud.cn" - }, - "agcgw_all":{ - "CN":"connect-drcn.dbankcloud.cn", - "CN_back":"connect-drcn.hispace.hicloud.com", - "DE":"connect-dre.dbankcloud.cn", - "DE_back":"connect-dre.hispace.hicloud.com", - "RU":"connect-drru.hispace.dbankcloud.ru", - "RU_back":"connect-drru.hispace.dbankcloud.cn", - "SG":"connect-dra.dbankcloud.cn", - "SG_back":"connect-dra.hispace.hicloud.com" - }, - "websocketgw_all":{ - "CN":"connect-ws-drcn.hispace.dbankcloud.cn", - "CN_back":"connect-ws-drcn.hispace.dbankcloud.com", - "DE":"connect-ws-dre.hispace.dbankcloud.cn", - "DE_back":"connect-ws-dre.hispace.dbankcloud.com", - "RU":"connect-ws-drru.hispace.dbankcloud.ru", - "RU_back":"connect-ws-drru.hispace.dbankcloud.cn", - "SG":"connect-ws-dra.hispace.dbankcloud.cn", - "SG_back":"connect-ws-dra.hispace.dbankcloud.com" - }, - "client":{ - "cp_id":"30061000024605000", - "product_id":"99536292102650699", - "client_id":"993355872258247936", - "client_secret":"0A83DC166FBF0696B884D38830E0B90AE8BBFF2D9CA8C26C9D9DA26816C2A9E3", - "project_id":"99536292102650699", - "app_id":"107146885", - "api_key":"DAEDAPld5VAZXpjfD+g0PwlXfya5ykPMYTMRYkySUjGfJg055ijAFNZBUtcykDPWZiaUcc4Mo9oJqmooL6HT8n4toO5COlWZjkqGXA==", - "package_name":"network.loki.messenger" - }, - "oauth_client":{ - "client_id":"107146885", - "client_type":1 - }, - "app_info":{ - "app_id":"107146885", - "package_name":"network.loki.messenger" - }, - "service":{ - "analytics":{ - "collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", - "collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com", - "collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", - "collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", - "collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", - "resource_id":"p1", - "channel_id":"" - }, - "search":{ - "url":"https://search-dre.cloud.huawei.com" - }, - "cloudstorage":{ - "storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia", - "storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru", - "storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru", - "storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu", - "storage_url_de":"https://ops-dre.agcstorage.link", - "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn", - "storage_url_sg":"https://ops-dra.agcstorage.link", - "storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn", - "storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn" - }, - "ml":{ - "mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn" - } - }, - "region":"DE", - "configuration_version":"3.0", - "appInfos":[ - { - "package_name":"network.loki.messenger", - "client":{ - "app_id":"107146885" - }, - "app_info":{ - "package_name":"network.loki.messenger", - "app_id":"107146885" - }, - "oauth_client":{ - "client_type":1, - "client_id":"107146885" - } - } - ] -} + "agcgw":{ + "backurl":"connect-dre.hispace.hicloud.com", + "url":"connect-dre.dbankcloud.cn", + "websocketbackurl":"connect-ws-dre.hispace.dbankcloud.com", + "websocketurl":"connect-ws-dre.hispace.dbankcloud.cn" + }, + "agcgw_all":{ + "CN":"connect-drcn.dbankcloud.cn", + "CN_back":"connect-drcn.hispace.hicloud.com", + "DE":"connect-dre.dbankcloud.cn", + "DE_back":"connect-dre.hispace.hicloud.com", + "RU":"connect-drru.hispace.dbankcloud.ru", + "RU_back":"connect-drru.hispace.dbankcloud.cn", + "SG":"connect-dra.dbankcloud.cn", + "SG_back":"connect-dra.hispace.hicloud.com" + }, + "websocketgw_all":{ + "CN":"connect-ws-drcn.hispace.dbankcloud.cn", + "CN_back":"connect-ws-drcn.hispace.dbankcloud.com", + "DE":"connect-ws-dre.hispace.dbankcloud.cn", + "DE_back":"connect-ws-dre.hispace.dbankcloud.com", + "RU":"connect-ws-drru.hispace.dbankcloud.ru", + "RU_back":"connect-ws-drru.hispace.dbankcloud.cn", + "SG":"connect-ws-dra.hispace.dbankcloud.cn", + "SG_back":"connect-ws-dra.hispace.dbankcloud.com" + }, + "client":{ + "cp_id":"890061000023000573", + "product_id":"99536292102532562", + "client_id":"954244311350791232", + "client_secret":"555999202D718B6744DAD2E923B386DC17F3F4E29F5105CE0D061EED328DADEE", + "project_id":"99536292102532562", + "app_id":"107205081", + "api_key":"DAEDABeddLEqUy0QRwa1THLwRA0OqrSuyci/HjNvVSmsdWsXRM2U2hRaCyqfvGYH1IFOKrauArssz/WPMLRHCYxliWf+DTj9bDwlWA==", + "package_name":"network.loki.messenger" + }, + "oauth_client":{ + "client_id":"107205081", + "client_type":1 + }, + "app_info":{ + "app_id":"107205081", + "package_name":"network.loki.messenger" + }, + "service":{ + "analytics":{ + "collector_url":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com", + "collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", + "collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", + "collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", + "resource_id":"p1", + "channel_id":"" + }, + "edukit":{ + "edu_url":"edukit.edu.cloud.huawei.com.cn", + "dh_url":"edukit.edu.cloud.huawei.com.cn" + }, + "search":{ + "url":"https://search-dre.cloud.huawei.com" + }, + "cloudstorage":{ + "storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia", + "storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru", + "storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru", + "storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu", + "storage_url_de":"https://ops-dre.agcstorage.link", + "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn", + "storage_url_sg":"https://ops-dra.agcstorage.link", + "storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn", + "storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn" + }, + "ml":{ + "mlservice_url":"ml-api-dre.ai.dbankcloud.com,ml-api-dre.ai.dbankcloud.cn" + } + }, + "region":"DE", + "configuration_version":"3.0", + "appInfos":[ + { + "package_name":"network.loki.messenger", + "client":{ + "app_id":"107205081" + }, + "app_info":{ + "package_name":"network.loki.messenger", + "app_id":"107205081" + }, + "oauth_client":{ + "client_type":1, + "client_id":"107205081" + } + } + ] +} \ No newline at end of file diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt index 87a78ef035..26da350372 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt @@ -31,16 +31,17 @@ class HuaweiPushManager @Inject constructor( if (force) cancel() else if (isActive) return } - val appId = "107146885" + val appId = "107205081" val tokenScope = "HCM" val hmsInstanceId = HmsInstanceId.getInstance(context) + genericPushManager.refresh(TextSecurePreferences.getFCMToken(context), force) + MainScope().launch(Dispatchers.IO) { val token = hmsInstanceId.getToken(appId, tokenScope) - - Log.d(TAG, "refresh() with huawei token: $token") - - genericPushManager.refresh(token, force) + Log.d(TAG, "refresh() hmsInstanceId => huawei token: $token") +// +//// genericPushManager.refresh(token, force) } } } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index 7dae82dd30..16ac969d2c 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -4,6 +4,7 @@ import android.os.Bundle import com.huawei.hms.push.HmsMessageService import com.huawei.hms.push.RemoteMessage import dagger.hilt.android.AndroidEntryPoint +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log import java.lang.Exception import javax.inject.Inject @@ -40,6 +41,10 @@ class HuaweiPushNotificationService: HmsMessageService() { override fun onNewToken(token: String?, bundle: Bundle?) { Log.d("pnh", "New HCM token: $token.") + + if (token == TextSecurePreferences.getFCMToken(this)) return + + TextSecurePreferences.setFCMToken(this, token) pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage?) { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d6bed361e5..ed51a7ea8d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,7 +80,7 @@ + android:value="appid=107205081"> { - Log.d(TAG, "refresh() called") + fun refresh(token: String?, force: Boolean): Promise<*, Exception> { + Log.d(TAG, "refresh($token, $force) called") + token ?: return emptyPromise() val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return emptyPromise() val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() @@ -62,6 +63,11 @@ class GenericPushManager @Inject constructor( userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) ): Promise<*, Exception> { + android.util.Log.d( + TAG, + "register() called with: token = $token, publicKey = $publicKey, userEd25519Key = $userEd25519Key, namespaces = $namespaces" + ) + val v1 = PushManagerV1.register( device = device, token = token, diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index b8894162d5..3e82bda4a3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -32,18 +32,25 @@ object PushManagerV1 { token: String? = TextSecurePreferences.getFCMToken(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context), legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() - ): Promise<*, Exception> = - when { - isUsingFCM -> retryIfNeeded(maxRetryCount) { - doRegister(token, publicKey, device, legacyGroupPublicKeys) - } fail { exception -> - Log.d(TAG, "Couldn't register for FCM due to error: $exception.") - } - else -> emptyPromise() + ): Promise<*, Exception> = when { + isUsingFCM -> retryIfNeeded(maxRetryCount) { + android.util.Log.d( + TAG, + "register() called with: device = $device, isUsingFCM = $isUsingFCM, token = $token, publicKey = $publicKey, legacyGroupPublicKeys = $legacyGroupPublicKeys" + ) + doRegister(token, publicKey, device, legacyGroupPublicKeys) + } fail { exception -> + Log.d(TAG, "Couldn't register for FCM due to error: $exception... $device $token $publicKey $legacyGroupPublicKeys") } + else -> emptyPromise() + } + private fun doRegister(token: String?, publicKey: String?, device: Device, legacyGroupPublicKeys: Collection): Promise<*, Exception> { - Log.d(TAG, "registerV1 requested") + android.util.Log.d( + TAG, + "doRegister() called with: token = $token, publicKey = $publicKey, device = $device, legacyGroupPublicKeys = $legacyGroupPublicKeys" + ) token ?: return emptyPromise() publicKey ?: return emptyPromise() From a27f81db30a58961ef4d3fd629b5f007e87f59db Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 4 Aug 2023 13:36:09 +0930 Subject: [PATCH 31/53] Fix Huawei push notifications --- .../notifications/HuaweiPushManager.kt | 7 ++- .../HuaweiPushNotificationService.kt | 41 ++++++++++------ .../securesms/ApplicationContext.java | 4 +- .../notifications/FcmTokenManager.kt | 2 +- .../securesms/notifications/PushHandler.kt | 2 +- .../securesms/onboarding/PNModeActivity.kt | 2 +- .../NotificationsPreferenceFragment.java | 4 +- .../notifications/FirebasePushManager.kt | 4 +- .../notifications/PushManagerV1.kt | 8 ++-- .../utilities/TextSecurePreferences.kt | 48 +++++++++---------- 10 files changed, 70 insertions(+), 52 deletions(-) diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt index 26da350372..c6f0161a43 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt @@ -25,6 +25,8 @@ class HuaweiPushManager @Inject constructor( @Synchronized override fun refresh(force: Boolean) { + Log.d(TAG, "refresh() called with: force = $force") + val huaweiPushInstanceIdJob = huaweiPushInstanceIdJob huaweiPushInstanceIdJob?.apply { @@ -35,9 +37,12 @@ class HuaweiPushManager @Inject constructor( val tokenScope = "HCM" val hmsInstanceId = HmsInstanceId.getInstance(context) - genericPushManager.refresh(TextSecurePreferences.getFCMToken(context), force) + Log.d(TAG, "hmsInstanceId: $hmsInstanceId") + +// genericPushManager.refresh(TextSecurePreferences.getFCMToken(context), force) MainScope().launch(Dispatchers.IO) { + Log.d(TAG, "hmInstanceId getting token...") val token = hmsInstanceId.getToken(appId, tokenScope) Log.d(TAG, "refresh() hmsInstanceId => huawei token: $token") // diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt index 16ac969d2c..a0ce0283d4 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt @@ -5,52 +5,63 @@ import com.huawei.hms.push.HmsMessageService import com.huawei.hms.push.RemoteMessage import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import java.lang.Exception import javax.inject.Inject +private val TAG = HuaweiPushNotificationService::class.java.simpleName + @AndroidEntryPoint class HuaweiPushNotificationService: HmsMessageService() { init { - Log.d("pnh", "init Huawei Service") + Log.d(TAG, "init Huawei Service") } @Inject lateinit var pushManager: PushManager + @Inject lateinit var genericPushManager: GenericPushManager @Inject lateinit var pushHandler: PushHandler override fun onCreate() { - Log.d("pnh", "onCreate Huawei Service") + Log.d(TAG, "onCreate Huawei Service") super.onCreate() } - override fun onMessageDelivered(p0: String?, p1: Exception?) { - Log.d("pnh", "onMessageDelivered") - super.onMessageDelivered(p0, p1) + override fun onMessageReceived(message: RemoteMessage?) { + Log.d(TAG, "onMessageReceived: $message.") + pushHandler.onPush(message?.data?.let(Base64::decode)) } override fun onMessageSent(p0: String?) { - Log.d("pnh", "onMessageSent") + Log.d(TAG, "onMessageSent() called with: p0 = $p0") super.onMessageSent(p0) } + override fun onSendError(p0: String?, p1: Exception?) { + Log.d(TAG, "onSendError() called with: p0 = $p0, p1 = $p1") + super.onSendError(p0, p1) + } + + override fun onMessageDelivered(p0: String?, p1: Exception?) { + Log.d(TAG, "onMessageDelivered") + super.onMessageDelivered(p0, p1) + } + + override fun onNewToken(p0: String?) { - Log.d("pnh", "onNewToken") + Log.d(TAG, "onNewToken") super.onNewToken(p0) } override fun onNewToken(token: String?, bundle: Bundle?) { - Log.d("pnh", "New HCM token: $token.") - - if (token == TextSecurePreferences.getFCMToken(this)) return + Log.d(TAG, "New HCM token: $token.") TextSecurePreferences.setFCMToken(this, token) - pushManager.refresh(true) - } - override fun onMessageReceived(message: RemoteMessage?) { - Log.d("pnh", "onMessageReceived: $message.") - pushHandler.onPush(message?.dataOfMap) + + genericPushManager.refresh(token, true) } + override fun onDeletedMessages() { pushManager.refresh(true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 2fe1b1e1c7..f3cf59255f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -506,10 +506,10 @@ public class ApplicationContext extends Application implements DefaultLifecycleO firebaseInstanceIdJob.cancel(null); } String displayName = TextSecurePreferences.getProfileName(this); - boolean isUsingFCM = TextSecurePreferences.isUsingFCM(this); + boolean isUsingFCM = TextSecurePreferences.isPushEnabled(this); TextSecurePreferences.clearAll(this); if (isMigratingToV2KeyPair) { - TextSecurePreferences.setIsUsingFCM(this, isUsingFCM); + TextSecurePreferences.setPushEnabled(this, isUsingFCM); TextSecurePreferences.setProfileName(this, displayName); } getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt index 295c8296fc..cfccb7f721 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt @@ -12,7 +12,7 @@ class FcmTokenManager @Inject constructor( ) { private val expiryManager = ExpiryManager(context) - val isUsingFCM get() = TextSecurePreferences.isUsingFCM(context) + val isUsingFCM get() = TextSecurePreferences.isPushEnabled(context) var fcmToken get() = TextSecurePreferences.getFCMToken(context) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt index bba89d2e96..087d1ee982 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt @@ -33,7 +33,7 @@ class PushHandler @Inject constructor(@ApplicationContext val context: Context) onPush(dataMap?.asByteArray()) } - private fun onPush(data: ByteArray?) { + fun onPush(data: ByteArray?) { if (data == null) { onPush() return diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index edaf646aaa..c6de3cd676 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -165,7 +165,7 @@ class PNModeActivity : BaseActionBarActivity() { return } - TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == binding.fcmOptionView)) + TextSecurePreferences.setPushEnabled(this, (selectedOptionView == binding.fcmOptionView)) val application = ApplicationContext.getInstance(this) application.startPollingIfNeeded() pushManager.refresh(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 228392ad76..ea0510d579 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -45,10 +45,10 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme // Set up FCM toggle String fcmKey = "pref_key_use_fcm"; - ((SwitchPreferenceCompat)findPreference(fcmKey)).setChecked(TextSecurePreferences.isUsingFCM(getContext())); + ((SwitchPreferenceCompat)findPreference(fcmKey)).setChecked(TextSecurePreferences.isPushEnabled(getContext())); this.findPreference(fcmKey) .setOnPreferenceChangeListener((preference, newValue) -> { - TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue); + TextSecurePreferences.setPushEnabled(getContext(), (boolean) newValue); pushManager.refresh(true); return true; }); diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 074ef379f8..8bd19de61d 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -27,7 +27,9 @@ class FirebasePushManager @Inject constructor( firebaseInstanceIdJob = getFcmInstanceId { task -> when { - task.isSuccessful -> try { task.result?.token?.let { genericPushManager.refresh(it, force).get() } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } + task.isSuccessful -> try { task.result?.token?.let { + genericPushManager.refresh(it, force).get() + } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt index 3e82bda4a3..d1a822ae3d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt @@ -28,7 +28,7 @@ object PushManagerV1 { fun register( device: Device, - isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), + isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), token: String? = TextSecurePreferences.getFCMToken(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context), legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() @@ -94,7 +94,7 @@ object PushManagerV1 { sendOnionRequest(request) success { when (it.code) { - null, 0 -> throw Exception("error: ${it.message}.") + null, 0 -> Log.d(TAG, "error: ${it.message}.") else -> Log.d(TAG, "unregisterV1 success") } } @@ -105,7 +105,7 @@ object PushManagerV1 { fun subscribeGroup( closedGroupPublicKey: String, - isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), + isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! ) = if (isUsingFCM) { performGroupOperation("subscribe_closed_group", closedGroupPublicKey, publicKey) @@ -113,7 +113,7 @@ object PushManagerV1 { fun unsubscribeGroup( closedGroupPublicKey: String, - isUsingFCM: Boolean = TextSecurePreferences.isUsingFCM(context), + isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! ) = if (isUsingFCM) { performGroupOperation("unsubscribe_closed_group", closedGroupPublicKey, publicKey) diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 349546838d..ee17fb2c26 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -37,12 +37,12 @@ interface TextSecurePreferences { fun setLastConfigurationSyncTime(value: Long) fun getConfigurationMessageSynced(): Boolean fun setConfigurationMessageSynced(value: Boolean) - fun isUsingFCM(): Boolean - fun setIsUsingFCM(value: Boolean) - fun getFCMToken(): String? - fun setFCMToken(value: String) - fun getLastFCMUploadTime(): Long - fun setLastFCMUploadTime(value: Long) + fun isPushEnabled(): Boolean + fun setPushEnabled(value: Boolean) + fun getPushToken(): String? + fun setPushToken(value: String) + fun getPushRegisterTime(): Long + fun setPushRegisterTime(value: Long) fun isScreenLockEnabled(): Boolean fun setScreenLockEnabled(value: Boolean) fun getScreenLockTimeout(): Long @@ -251,9 +251,9 @@ interface TextSecurePreferences { const val LINK_PREVIEWS = "pref_link_previews" const val GIF_METADATA_WARNING = "has_seen_gif_metadata_warning" const val GIF_GRID_LAYOUT = "pref_gif_grid_layout" - const val IS_USING_FCM = "pref_is_using_fcm" + const val IS_PUSH_ENABLED = "pref_is_using_fcm" const val FCM_TOKEN = "pref_fcm_token_2" - const val LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time_2" + const val PUSH_REGISTER_TIME = "pref_last_fcm_token_upload_time_2" const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" const val CONFIGURATION_SYNCED = "pref_configuration_synced" const val LAST_PROFILE_UPDATE_TIME = "pref_last_profile_update_time" @@ -309,13 +309,13 @@ interface TextSecurePreferences { } @JvmStatic - fun isUsingFCM(context: Context): Boolean { - return getBooleanPreference(context, IS_USING_FCM, false) + fun isPushEnabled(context: Context): Boolean { + return getBooleanPreference(context, IS_PUSH_ENABLED, false) } @JvmStatic - fun setIsUsingFCM(context: Context, value: Boolean) { - setBooleanPreference(context, IS_USING_FCM, value) + fun setPushEnabled(context: Context, value: Boolean) { + setBooleanPreference(context, IS_PUSH_ENABLED, value) } @JvmStatic @@ -329,11 +329,11 @@ interface TextSecurePreferences { } fun getLastFCMUploadTime(context: Context): Long { - return getLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, 0) + return getLongPreference(context, PUSH_REGISTER_TIME, 0) } fun setLastFCMUploadTime(context: Context, value: Long) { - setLongPreference(context, LAST_FCM_TOKEN_UPLOAD_TIME, value) + setLongPreference(context, PUSH_REGISTER_TIME, value) } // endregion @@ -1032,28 +1032,28 @@ class AppTextSecurePreferences @Inject constructor( TextSecurePreferences._events.tryEmit(TextSecurePreferences.CONFIGURATION_SYNCED) } - override fun isUsingFCM(): Boolean { - return getBooleanPreference(TextSecurePreferences.IS_USING_FCM, false) + override fun isPushEnabled(): Boolean { + return getBooleanPreference(TextSecurePreferences.IS_PUSH_ENABLED, false) } - override fun setIsUsingFCM(value: Boolean) { - setBooleanPreference(TextSecurePreferences.IS_USING_FCM, value) + override fun setPushEnabled(value: Boolean) { + setBooleanPreference(TextSecurePreferences.IS_PUSH_ENABLED, value) } - override fun getFCMToken(): String? { + override fun getPushToken(): String? { return getStringPreference(TextSecurePreferences.FCM_TOKEN, "") } - override fun setFCMToken(value: String) { + override fun setPushToken(value: String) { setStringPreference(TextSecurePreferences.FCM_TOKEN, value) } - override fun getLastFCMUploadTime(): Long { - return getLongPreference(TextSecurePreferences.LAST_FCM_TOKEN_UPLOAD_TIME, 0) + override fun getPushRegisterTime(): Long { + return getLongPreference(TextSecurePreferences.PUSH_REGISTER_TIME, 0) } - override fun setLastFCMUploadTime(value: Long) { - setLongPreference(TextSecurePreferences.LAST_FCM_TOKEN_UPLOAD_TIME, value) + override fun setPushRegisterTime(value: Long) { + setLongPreference(TextSecurePreferences.PUSH_REGISTER_TIME, value) } override fun isScreenLockEnabled(): Boolean { From bcf925c132fcbded7b225f25536c47afb4504acb Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 6 Aug 2023 22:22:39 +0930 Subject: [PATCH 32/53] Cleanup --- app/src/huawei/AndroidManifest.xml | 2 +- .../notifications/HuaweiPushManager.kt | 52 ------------------- .../notifications/HuaweiPushModule.kt | 7 +-- ...icationService.kt => HuaweiPushService.kt} | 17 +++--- .../notifications/HuaweiTokenFetcher.kt | 27 ++++++++++ .../securesms/ApplicationContext.java | 6 +-- .../securesms/database/Storage.kt | 4 +- .../securesms/dependencies/PushComponent.kt | 2 +- .../securesms/groups/ClosedGroupManager.kt | 4 +- .../securesms/home/HomeActivity.kt | 6 +-- .../securesms/notifications/ExpiryManager.kt | 4 +- .../{PushHandler.kt => PushReceiver.kt} | 2 +- ...FcmTokenManager.kt => PushTokenManager.kt} | 11 ++-- .../securesms/notifications/TokenFetcher.kt | 7 +++ .../securesms/onboarding/PNModeActivity.kt | 5 +- .../NotificationsPreferenceFragment.java | 6 +-- ...{GenericPushManager.kt => PushRegistry.kt} | 30 ++++++++--- .../{PushManagerV2.kt => PushRegistryV2.kt} | 4 +- app/src/play/AndroidManifest.xml | 2 +- .../securesms/notifications/FcmUtils.kt | 16 ------ .../notifications/FirebasePushManager.kt | 37 ------------- .../notifications/FirebasePushModule.kt | 2 +- ...ationService.kt => FirebasePushService.kt} | 19 ++++--- .../notifications/FirebaseTokenFetcher.kt | 45 ++++++++++++++++ .../notifications/NoOpPushManager.kt | 10 ---- .../securesms/notifications/NoOpPushModule.kt | 4 +- .../MessageSenderClosedGroupHandler.kt | 4 +- .../ReceivedMessageHandler.kt | 6 +-- .../{PushManagerV1.kt => PushRegistryV1.kt} | 8 +-- .../utilities/TextSecurePreferences.kt | 18 +++---- .../utilities/ExternalStorageUtil.kt | 13 ++--- 31 files changed, 176 insertions(+), 204 deletions(-) delete mode 100644 app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt rename app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/{HuaweiPushNotificationService.kt => HuaweiPushService.kt} (74%) create mode 100644 app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt rename app/src/main/java/org/thoughtcrime/securesms/notifications/{PushHandler.kt => PushReceiver.kt} (98%) rename app/src/main/java/org/thoughtcrime/securesms/notifications/{FcmTokenManager.kt => PushTokenManager.kt} (68%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt rename app/src/main/kotlin/org/thoughtcrime/securesms/notifications/{GenericPushManager.kt => PushRegistry.kt} (79%) rename app/src/main/kotlin/org/thoughtcrime/securesms/notifications/{PushManagerV2.kt => PushRegistryV2.kt} (96%) delete mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt delete mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt rename app/src/play/kotlin/org/thoughtcrime/securesms/notifications/{FirebasePushNotificationService.kt => FirebasePushService.kt} (60%) create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt delete mode 100644 app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt rename libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/{PushManagerV1.kt => PushRegistryV1.kt} (96%) diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml index 57279978c1..0483deac1c 100644 --- a/app/src/huawei/AndroidManifest.xml +++ b/app/src/huawei/AndroidManifest.xml @@ -4,7 +4,7 @@ diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt deleted file mode 100644 index c6f0161a43..0000000000 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushManager.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import android.content.Context -import android.util.Log -import com.huawei.hmf.tasks.Tasks -import com.huawei.hms.aaid.HmsInstanceId -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import org.session.libsession.utilities.TextSecurePreferences -import javax.inject.Inject -import javax.inject.Singleton - -private val TAG = HuaweiPushManager::class.java.name - -@Singleton -class HuaweiPushManager @Inject constructor( - @ApplicationContext private val context: Context, - private val genericPushManager: GenericPushManager -): PushManager { - private var huaweiPushInstanceIdJob: Job? = null - - @Synchronized - override fun refresh(force: Boolean) { - Log.d(TAG, "refresh() called with: force = $force") - - val huaweiPushInstanceIdJob = huaweiPushInstanceIdJob - - huaweiPushInstanceIdJob?.apply { - if (force) cancel() else if (isActive) return - } - - val appId = "107205081" - val tokenScope = "HCM" - val hmsInstanceId = HmsInstanceId.getInstance(context) - - Log.d(TAG, "hmsInstanceId: $hmsInstanceId") - -// genericPushManager.refresh(TextSecurePreferences.getFCMToken(context), force) - - MainScope().launch(Dispatchers.IO) { - Log.d(TAG, "hmInstanceId getting token...") - val token = hmsInstanceId.getToken(appId, tokenScope) - Log.d(TAG, "refresh() hmsInstanceId => huawei token: $token") -// -//// genericPushManager.refresh(token, force) - } - } -} diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt index c1be786afc..26a484df16 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt @@ -1,16 +1,13 @@ package org.thoughtcrime.securesms.notifications -import android.content.Context import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton + @Module @InstallIn(SingletonComponent::class) abstract class HuaweiBindingModule { @Binds - abstract fun bindPushManager(pushManager: HuaweiPushManager): PushManager + abstract fun bindTokenFetcher(tokenFetcher: HuaweiTokenFetcher): TokenFetcher } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt similarity index 74% rename from app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt rename to app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt index a0ce0283d4..2c3512acc7 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushNotificationService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt @@ -10,18 +10,17 @@ import org.session.libsignal.utilities.Log import java.lang.Exception import javax.inject.Inject -private val TAG = HuaweiPushNotificationService::class.java.simpleName +private val TAG = HuaweiPushService::class.java.simpleName @AndroidEntryPoint -class HuaweiPushNotificationService: HmsMessageService() { +class HuaweiPushService: HmsMessageService() { init { Log.d(TAG, "init Huawei Service") } - @Inject lateinit var pushManager: PushManager - @Inject lateinit var genericPushManager: GenericPushManager - @Inject lateinit var pushHandler: PushHandler + @Inject lateinit var pushRegistry: PushRegistry + @Inject lateinit var pushReceiver: PushReceiver override fun onCreate() { Log.d(TAG, "onCreate Huawei Service") @@ -30,7 +29,7 @@ class HuaweiPushNotificationService: HmsMessageService() { override fun onMessageReceived(message: RemoteMessage?) { Log.d(TAG, "onMessageReceived: $message.") - pushHandler.onPush(message?.data?.let(Base64::decode)) + pushReceiver.onPush(message?.data?.let(Base64::decode)) } override fun onMessageSent(p0: String?) { @@ -57,12 +56,12 @@ class HuaweiPushNotificationService: HmsMessageService() { override fun onNewToken(token: String?, bundle: Bundle?) { Log.d(TAG, "New HCM token: $token.") - TextSecurePreferences.setFCMToken(this, token) + TextSecurePreferences.setPushToken(this, token) - genericPushManager.refresh(token, true) + pushRegistry.refresh(token, true) } override fun onDeletedMessages() { - pushManager.refresh(true) + pushRegistry.refresh(true) } } diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt new file mode 100644 index 0000000000..e3b6afe4cc --- /dev/null +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt @@ -0,0 +1,27 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import com.huawei.hms.aaid.HmsInstanceId +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class HuaweiTokenFetcher @Inject constructor( + @ApplicationContext private val context: Context +): TokenFetcher { + override fun fetch(): Job { + val hmsInstanceId = HmsInstanceId.getInstance(context) + + return MainScope().launch(Dispatchers.IO) { + val appId = "107205081" + val tokenScope = "HCM" + // getToken returns an empty string, but triggers the service to initialize. + hmsInstanceId.getToken(appId, tokenScope) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index f3cf59255f..73b0af55f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -76,7 +76,7 @@ import org.thoughtcrime.securesms.notifications.BackgroundPollWorker; import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; -import org.thoughtcrime.securesms.notifications.PushManager; +import org.thoughtcrime.securesms.notifications.PushRegistry; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.KeyCachingService; @@ -146,7 +146,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO @Inject Device device; @Inject MessageDataProvider messageDataProvider; @Inject TextSecurePreferences textSecurePreferences; - @Inject PushManager pushManager; + @Inject PushRegistry pushRegistry; @Inject ConfigFactory configFactory; CallMessageProcessor callMessageProcessor; MessagingModuleConfiguration messagingModuleConfiguration; @@ -429,7 +429,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private static class ProviderInitializationException extends RuntimeException { } public void registerForPnIfNeeded(final Boolean force) { - pushManager.refresh(force); + pushRegistry.refresh(force); } private void setUpPollingIfNeeded() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 5a95ed64f6..9b038b9955 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -50,7 +50,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.utilities.SessionId @@ -591,7 +591,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co val expireTimer = group.disappearingTimer setExpirationTimer(groupId, expireTimer.toInt()) // Notify the PN server - PushManagerV1.subscribeGroup(group.sessionId, publicKey = localUserPublicKey) + PushRegistryV1.subscribeGroup(group.sessionId, publicKey = localUserPublicKey) // Notify the user val threadID = getOrCreateThreadIdFor(Address.fromSerialized(groupId)) threadDb.setDate(threadID, formationTimestamp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt index cd4e00416c..69baf1f208 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt @@ -8,5 +8,5 @@ import org.thoughtcrime.securesms.notifications.PushManager @EntryPoint @InstallIn(SingletonComponent::class) interface PushComponent { - fun providePushManager(): PushManager + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt index 46142a4c58..f8e64dd381 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.groups import android.content.Context import network.loki.messenger.libsession_util.ConfigBase import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupRecord @@ -24,7 +24,7 @@ object ClosedGroupManager { storage.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) storage.removeMember(groupID, Address.fromSerialized(userPublicKey)) // Notify the PN server - PushManagerV1.unsubscribeGroup(closedGroupPublicKey = groupPublicKey, publicKey = userPublicKey) + PushRegistryV1.unsubscribeGroup(closedGroupPublicKey = groupPublicKey, publicKey = userPublicKey) // Stop polling ClosedGroupPollerV2.shared.stopPolling(groupPublicKey) storage.cancelPendingMessageSendJobs(threadId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index c64d6ef071..59f324b525 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -67,7 +67,7 @@ import org.thoughtcrime.securesms.home.search.GlobalSearchViewModel import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests -import org.thoughtcrime.securesms.notifications.PushManager +import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.onboarding.SeedActivity import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.permissions.Permissions @@ -107,7 +107,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), @Inject lateinit var groupDatabase: GroupDatabase @Inject lateinit var textSecurePreferences: TextSecurePreferences @Inject lateinit var configFactory: ConfigFactory - @Inject lateinit var pushManager: PushManager + @Inject lateinit var pushRegistry: PushRegistry private val globalSearchViewModel by viewModels() private val homeViewModel by viewModels() @@ -232,7 +232,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), (applicationContext as ApplicationContext).startPollingIfNeeded() // update things based on TextSecurePrefs (profile info etc) // Set up remaining components if needed - pushManager.refresh(false) + pushRegistry.refresh(false) if (textSecurePreferences.getLocalNumber() != null) { OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt index 45318d43ad..2b3f0aa860 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt @@ -21,8 +21,8 @@ class ExpiryManager( } private var time - get() = TextSecurePreferences.getLastFCMUploadTime(context) - set(value) = TextSecurePreferences.setLastFCMUploadTime(context, value) + get() = TextSecurePreferences.getPushRegisterTime(context) + set(value) = TextSecurePreferences.setPushRegisterTime(context, value) private fun currentTime() = System.currentTimeMillis() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt similarity index 98% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt index 087d1ee982..56871ba64e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt @@ -26,7 +26,7 @@ import javax.inject.Inject private const val TAG = "PushHandler" -class PushHandler @Inject constructor(@ApplicationContext val context: Context) { +class PushReceiver @Inject constructor(@ApplicationContext val context: Context) { private val sodium = LazySodiumAndroid(SodiumAndroid()) fun onPush(dataMap: Map?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt similarity index 68% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt index cfccb7f721..45469a3dd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt @@ -2,22 +2,24 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Job import org.session.libsession.utilities.TextSecurePreferences import javax.inject.Inject import javax.inject.Singleton @Singleton -class FcmTokenManager @Inject constructor( +class PushTokenManager @Inject constructor( @ApplicationContext private val context: Context, + private val tokenFetcher: TokenFetcher ) { private val expiryManager = ExpiryManager(context) - val isUsingFCM get() = TextSecurePreferences.isPushEnabled(context) + val isPushEnabled get() = TextSecurePreferences.isPushEnabled(context) var fcmToken - get() = TextSecurePreferences.getFCMToken(context) + get() = TextSecurePreferences.getPushToken(context) set(value) { - TextSecurePreferences.setFCMToken(context, value) + TextSecurePreferences.setPushToken(context, value) if (value != null) markTime() else clearTime() } @@ -28,4 +30,5 @@ class FcmTokenManager @Inject constructor( private fun isExpired() = expiryManager.isExpired() fun isInvalid(): Boolean = fcmToken == null || isExpired() + fun fetchToken(): Job = tokenFetcher.fetch() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt new file mode 100644 index 0000000000..d8657e95d3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt @@ -0,0 +1,7 @@ +package org.thoughtcrime.securesms.notifications + +import kotlinx.coroutines.Job + +interface TokenFetcher { + fun fetch(): Job +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index c6de3cd676..e4e8e6a9a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.home.HomeActivity import org.thoughtcrime.securesms.notifications.PushManager +import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.util.GlowViewUtilities import org.thoughtcrime.securesms.util.PNModeView @@ -34,7 +35,7 @@ import javax.inject.Inject @AndroidEntryPoint class PNModeActivity : BaseActionBarActivity() { - @Inject lateinit var pushManager: PushManager + @Inject lateinit var pushRegistry: PushRegistry private lateinit var binding: ActivityPnModeBinding private var selectedOptionView: PNModeView? = null @@ -168,7 +169,7 @@ class PNModeActivity : BaseActionBarActivity() { TextSecurePreferences.setPushEnabled(this, (selectedOptionView == binding.fcmOptionView)) val application = ApplicationContext.getInstance(this) application.startPollingIfNeeded() - pushManager.refresh(true) + pushRegistry.refresh(true) val intent = Intent(this, HomeActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.putExtra(HomeActivity.FROM_ONBOARDING, true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index ea0510d579..4cb316f40f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -23,7 +23,7 @@ import org.session.libsession.utilities.TextSecurePreferences; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.notifications.PushManager; +import org.thoughtcrime.securesms.notifications.PushRegistry; import javax.inject.Inject; @@ -37,7 +37,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName(); @Inject - PushManager pushManager; + PushRegistry pushRegistry; @Override public void onCreate(Bundle paramBundle) { @@ -49,7 +49,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme this.findPreference(fcmKey) .setOnPreferenceChangeListener((preference, newValue) -> { TextSecurePreferences.setPushEnabled(getContext(), (boolean) newValue); - pushManager.refresh(true); + pushRegistry.refresh(true); return true; }); diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistry.kt similarity index 79% rename from app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistry.kt index f3e0443eed..872b2956a3 100644 --- a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/GenericPushManager.kt +++ b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistry.kt @@ -3,9 +3,10 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import com.goterl.lazysodium.utils.KeyPair import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Job import nl.komponents.kovenant.Promise import nl.komponents.kovenant.combine.and -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.utilities.Device import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log @@ -18,12 +19,25 @@ import javax.inject.Singleton private const val TAG = "GenericPushManager" @Singleton -class GenericPushManager @Inject constructor( +class PushRegistry @Inject constructor( @ApplicationContext private val context: Context, private val device: Device, - private val tokenManager: FcmTokenManager, - private val pushManagerV2: PushManagerV2, + private val tokenManager: PushTokenManager, + private val pushRegistryV2: PushRegistryV2, ) { + + private var firebaseInstanceIdJob: Job? = null + + fun refresh(force: Boolean) { + Log.d(TAG, "refresh() called with: force = $force") + + firebaseInstanceIdJob?.apply { + if (force) cancel() else if (isActive) return + } + + firebaseInstanceIdJob = tokenManager.fetchToken() + } + fun refresh(token: String?, force: Boolean): Promise<*, Exception> { Log.d(TAG, "refresh($token, $force) called") @@ -32,7 +46,7 @@ class GenericPushManager @Inject constructor( val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() return when { - tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey) + tokenManager.isPushEnabled -> register(force, token, userPublicKey, userEdKey) tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey) else -> emptyPromise() } @@ -68,7 +82,7 @@ class GenericPushManager @Inject constructor( "register() called with: token = $token, publicKey = $publicKey, userEd25519Key = $userEd25519Key, namespaces = $namespaces" ) - val v1 = PushManagerV1.register( + val v1 = PushRegistryV1.register( device = device, token = token, publicKey = publicKey @@ -76,7 +90,7 @@ class GenericPushManager @Inject constructor( Log.e(TAG, "register v1 failed", it) } - val v2 = pushManagerV2.register( + val v2 = pushRegistryV2.register( device, token, publicKey, userEd25519Key, namespaces ) fail { Log.e(TAG, "register v2 failed", it) @@ -92,7 +106,7 @@ class GenericPushManager @Inject constructor( token: String, userPublicKey: String, userEdKey: KeyPair - ): Promise<*, Exception> = PushManagerV1.unregister() and pushManagerV2.unregister( + ): Promise<*, Exception> = PushRegistryV1.unregister() and pushRegistryV2.unregister( device, token, userPublicKey, userEdKey ) fail { Log.e(TAG, "unregisterBoth failed", it) diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt similarity index 96% rename from app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt rename to app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt index 93e6fc6819..d03af92e6d 100644 --- a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushManagerV2.kt +++ b/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt @@ -28,7 +28,7 @@ private const val TAG = "PushManagerV2" private const val maxRetryCount = 4 @Singleton -class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { +class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver) { private val sodium = LazySodiumAndroid(SodiumAndroid()) fun register( @@ -38,7 +38,7 @@ class PushManagerV2 @Inject constructor(private val pushHandler: PushHandler) { userEd25519Key: KeyPair, namespaces: List ): Promise { - val pnKey = pushHandler.getOrCreateNotificationKey() + val pnKey = pushReceiver.getOrCreateNotificationKey() val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s // if we want to support passing namespace list, here is the place to do it diff --git a/app/src/play/AndroidManifest.xml b/app/src/play/AndroidManifest.xml index 8b975b0634..b3e5ead475 100644 --- a/app/src/play/AndroidManifest.xml +++ b/app/src/play/AndroidManifest.xml @@ -4,7 +4,7 @@ diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt deleted file mode 100644 index af3f49a26a..0000000000 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt +++ /dev/null @@ -1,16 +0,0 @@ -@file:JvmName("FcmUtils") -package org.thoughtcrime.securesms.notifications - -import com.google.android.gms.tasks.Task -import com.google.android.gms.tasks.Tasks -import com.google.firebase.iid.FirebaseInstanceId -import com.google.firebase.iid.InstanceIdResult -import kotlinx.coroutines.* - - -fun getFcmInstanceId(body: (Task)->Unit): Job = MainScope().launch(Dispatchers.IO) { - val task = FirebaseInstanceId.getInstance().instanceId - Tasks.await(task) - if (!isActive) return@launch // don't 'complete' task if we were canceled - body(task) -} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt deleted file mode 100644 index 8bd19de61d..0000000000 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import kotlinx.coroutines.Job -import org.session.libsignal.utilities.Log -import javax.inject.Inject -import javax.inject.Singleton - -private const val TAG = "FirebasePushManager" - -@Singleton -class FirebasePushManager @Inject constructor( - private val genericPushManager: GenericPushManager -): PushManager { - - private var firebaseInstanceIdJob: Job? = null - - @Synchronized - override fun refresh(force: Boolean) { - Log.d(TAG, "refresh() called with: force = $force") - - firebaseInstanceIdJob?.apply { - when { - force -> cancel() - isActive -> return - } - } - - firebaseInstanceIdJob = getFcmInstanceId { task -> - when { - task.isSuccessful -> try { task.result?.token?.let { - genericPushManager.refresh(it, force).get() - } } catch(e: Exception) { Log.e(TAG, "refresh() failed", e) } - else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) - } - } - } -} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt index 925ad9d4d7..2dac25072e 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -9,5 +9,5 @@ import dagger.hilt.components.SingletonComponent @InstallIn(SingletonComponent::class) abstract class FirebaseBindingModule { @Binds - abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager + abstract fun bindTokenFetcher(tokenFetcher: FirebaseTokenFetcher): TokenFetcher } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt similarity index 60% rename from app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushNotificationService.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt index b8640296fa..1ebf405a28 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt @@ -9,26 +9,25 @@ import javax.inject.Inject private const val TAG = "FirebasePushNotificationService" @AndroidEntryPoint -class FirebasePushNotificationService : FirebaseMessagingService() { +class FirebasePushService : FirebaseMessagingService() { - @Inject lateinit var pushManager: PushManager - @Inject lateinit var pushHandler: PushHandler + @Inject lateinit var prefs: TextSecurePreferences + @Inject lateinit var pushReceiver: PushReceiver + @Inject lateinit var pushRegistry: PushRegistry override fun onNewToken(token: String) { - super.onNewToken(token) - TextSecurePreferences.getLocalNumber(this) ?: return - if (TextSecurePreferences.getFCMToken(this) != token) { - pushManager.refresh(true) - } + if (token == prefs.getPushToken()) return + + pushRegistry.refresh(token, true) } override fun onMessageReceived(message: RemoteMessage) { Log.d(TAG, "Received a push notification.") - pushHandler.onPush(message.data) + pushReceiver.onPush(message.data) } override fun onDeletedMessages() { Log.d(TAG, "Called onDeletedMessages.") - pushManager.refresh(true) + pushRegistry.refresh(true) } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt new file mode 100644 index 0000000000..e0aa14f6d2 --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt @@ -0,0 +1,45 @@ +package org.thoughtcrime.securesms.notifications + +import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks +import com.google.firebase.iid.FirebaseInstanceId +import com.google.firebase.iid.InstanceIdResult +import dagger.Lazy +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import org.session.libsignal.utilities.Log +import javax.inject.Inject +import javax.inject.Singleton + +private val TAG = FirebaseTokenFetcher::class.java.name + +@Singleton +class FirebaseTokenFetcher @Inject constructor( + private val pushRegistry: Lazy, +): TokenFetcher { + override fun fetch(): Job = MainScope().launch(Dispatchers.IO) { + FirebaseInstanceId.getInstance().instanceId + .also(Tasks::await) + .also { if (!isActive) return@launch } // don't 'complete' task if we were canceled + .process() + } + + private fun Task.process() { + when { + isSuccessful -> try { + result?.token?.let { + pushRegistry.get().refresh(it, force = true).get() + } + } catch (e: Exception) { + onFail(e) + } + else -> exception?.let(::onFail) + } + } + + private fun onFail(e: Exception) = Log.e(TAG, "fetch failed", e) +} + diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt deleted file mode 100644 index a817e59d5d..0000000000 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import org.session.libsignal.utilities.Log - -class NoOpPushManager: PushManager { - - override fun refresh(force: Boolean) { - Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") - } -} diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt index 1c8f2f936c..af907b51ca 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt @@ -9,7 +9,5 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) class NoOpPushModule { - @Provides - @Singleton - fun provideNoOpManager(): PushManager = NoOpPushManager() + } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 022d32f170..1a7e3d8eaa 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -8,7 +8,7 @@ import nl.komponents.kovenant.deferred import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.sending_receiving.MessageSender.Error -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address @@ -94,7 +94,7 @@ fun MessageSender.create( // Add the group to the config now that it was successfully created storage.createInitialConfigGroup(groupPublicKey, name, GroupUtil.createConfigMemberMap(members, admins), sentTime, encryptionKeyPair) // Notify the PN server - PushManagerV1.register(device = device, publicKey = userPublicKey) + PushRegistryV1.register(device = device, publicKey = userPublicKey) // Start polling ClosedGroupPollerV2.shared.startPolling(groupPublicKey) // Fulfill the promise diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 7c5092e5ea..b101e9db0b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -23,7 +23,7 @@ import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview -import org.session.libsession.messaging.sending_receiving.notifications.PushManagerV1 +import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.utilities.SessionId @@ -556,7 +556,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli // Set expiration timer storage.setExpirationTimer(groupID, expireTimer) // Notify the PN server - PushManagerV1.register(device = MessagingModuleConfiguration.shared.device, publicKey = userPublicKey) + PushRegistryV1.register(device = MessagingModuleConfiguration.shared.device, publicKey = userPublicKey) // Notify the user if (userPublicKey == sender && !groupExists) { val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) @@ -869,7 +869,7 @@ fun MessageReceiver.disableLocalGroupAndUnsubscribe(groupPublicKey: String, grou storage.setActive(groupID, false) storage.removeMember(groupID, Address.fromSerialized(userPublicKey)) // Notify the PN server - PushManagerV1.unsubscribeGroup(groupPublicKey, publicKey = userPublicKey) + PushRegistryV1.unsubscribeGroup(groupPublicKey, publicKey = userPublicKey) // Stop polling ClosedGroupPollerV2.shared.stopPolling(groupPublicKey) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt similarity index 96% rename from libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt rename to libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt index d1a822ae3d..d7d84b5c61 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushManagerV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt @@ -18,8 +18,8 @@ import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.sideEffect @SuppressLint("StaticFieldLeak") -object PushManagerV1 { - private const val TAG = "PushManagerV1" +object PushRegistryV1 { + private val TAG = PushRegistryV1::class.java.name val context = MessagingModuleConfiguration.shared.context private const val maxRetryCount = 4 @@ -29,7 +29,7 @@ object PushManagerV1 { fun register( device: Device, isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), - token: String? = TextSecurePreferences.getFCMToken(context), + token: String? = TextSecurePreferences.getPushToken(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context), legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = when { @@ -84,7 +84,7 @@ object PushManagerV1 { fun unregister(): Promise<*, Exception> { Log.d(TAG, "unregisterV1 requested") - val token = TextSecurePreferences.getFCMToken(context) ?: emptyPromise() + val token = TextSecurePreferences.getPushToken(context) ?: emptyPromise() return retryIfNeeded(maxRetryCount) { val parameters = mapOf("token" to token) diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index ee17fb2c26..2b92b9836e 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -252,7 +252,7 @@ interface TextSecurePreferences { const val GIF_METADATA_WARNING = "has_seen_gif_metadata_warning" const val GIF_GRID_LAYOUT = "pref_gif_grid_layout" const val IS_PUSH_ENABLED = "pref_is_using_fcm" - const val FCM_TOKEN = "pref_fcm_token_2" + const val PUSH_TOKEN = "pref_fcm_token_2" const val PUSH_REGISTER_TIME = "pref_last_fcm_token_upload_time_2" const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" const val CONFIGURATION_SYNCED = "pref_configuration_synced" @@ -319,20 +319,20 @@ interface TextSecurePreferences { } @JvmStatic - fun getFCMToken(context: Context): String? { - return getStringPreference(context, FCM_TOKEN, "") + fun getPushToken(context: Context): String? { + return getStringPreference(context, PUSH_TOKEN, "") } @JvmStatic - fun setFCMToken(context: Context, value: String?) { - setStringPreference(context, FCM_TOKEN, value) + fun setPushToken(context: Context, value: String?) { + setStringPreference(context, PUSH_TOKEN, value) } - fun getLastFCMUploadTime(context: Context): Long { + fun getPushRegisterTime(context: Context): Long { return getLongPreference(context, PUSH_REGISTER_TIME, 0) } - fun setLastFCMUploadTime(context: Context, value: Long) { + fun setPushRegisterTime(context: Context, value: Long) { setLongPreference(context, PUSH_REGISTER_TIME, value) } @@ -1041,11 +1041,11 @@ class AppTextSecurePreferences @Inject constructor( } override fun getPushToken(): String? { - return getStringPreference(TextSecurePreferences.FCM_TOKEN, "") + return getStringPreference(TextSecurePreferences.PUSH_TOKEN, "") } override fun setPushToken(value: String) { - setStringPreference(TextSecurePreferences.FCM_TOKEN, value) + setStringPreference(TextSecurePreferences.PUSH_TOKEN, value) } override fun getPushRegisterTime(): Long { diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/ExternalStorageUtil.kt b/libsignal/src/main/java/org/session/libsignal/utilities/ExternalStorageUtil.kt index a8a8fd662f..f9ccb315cb 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/ExternalStorageUtil.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/ExternalStorageUtil.kt @@ -15,7 +15,7 @@ object ExternalStorageUtil { @Throws(NoExternalStorageException::class) fun getDir(context: Context, type: String?): File { return context.getExternalFilesDir(type) - ?: throw NoExternalStorageException("External storage dir is currently unavailable: $type") + ?: throw NoExternalStorageException("External storage dir is currently unavailable: $type") } @Throws(NoExternalStorageException::class) @@ -73,10 +73,7 @@ object ExternalStorageUtil { } @JvmStatic - fun getCleanFileName(fileName: String?): String? { - var fileName = fileName ?: return null - fileName = fileName.replace('\u202D', '\uFFFD') - fileName = fileName.replace('\u202E', '\uFFFD') - return fileName - } -} \ No newline at end of file + fun getCleanFileName(fileName: String?): String? = + fileName?.replace('\u202D', '\uFFFD') + ?.replace('\u202E', '\uFFFD') +} From 24f7bb2b45ff90add1ccb6697b73b1ff2e42e4a0 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 6 Aug 2023 22:39:49 +0930 Subject: [PATCH 33/53] Move files --- .../securesms/notifications/PushRegistry.kt | 10 +++++----- .../securesms/notifications/PushRegistryV2.kt | 11 ++++++++--- .../notifications/FirebaseTokenFetcher.kt | 16 +++++++--------- 3 files changed, 20 insertions(+), 17 deletions(-) rename app/src/main/{kotlin => java}/org/thoughtcrime/securesms/notifications/PushRegistry.kt (94%) rename app/src/main/{kotlin => java}/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt (90%) diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistry.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt similarity index 94% rename from app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistry.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt index 872b2956a3..9a625f133c 100644 --- a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistry.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt @@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities import javax.inject.Inject import javax.inject.Singleton -private const val TAG = "GenericPushManager" +private val TAG = PushRegistry::class.java.name @Singleton class PushRegistry @Inject constructor( @@ -26,16 +26,16 @@ class PushRegistry @Inject constructor( private val pushRegistryV2: PushRegistryV2, ) { - private var firebaseInstanceIdJob: Job? = null + private var pushRegistrationJob: Job? = null fun refresh(force: Boolean) { Log.d(TAG, "refresh() called with: force = $force") - firebaseInstanceIdJob?.apply { + pushRegistrationJob?.apply { if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = tokenManager.fetchToken() + pushRegistrationJob = tokenManager.fetchToken() } fun refresh(token: String?, force: Boolean): Promise<*, Exception> { @@ -113,4 +113,4 @@ class PushRegistry @Inject constructor( } success { tokenManager.fcmToken = null } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt similarity index 90% rename from app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt index d03af92e6d..4bef45ff97 100644 --- a/app/src/main/kotlin/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistryV2.kt @@ -12,7 +12,12 @@ import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.* +import org.session.libsession.messaging.sending_receiving.notifications.Response +import org.session.libsession.messaging.sending_receiving.notifications.Server +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version @@ -24,7 +29,7 @@ import org.session.libsignal.utilities.retryIfNeeded import javax.inject.Inject import javax.inject.Singleton -private const val TAG = "PushManagerV2" +private val TAG = PushRegistryV2::class.java.name private const val maxRetryCount = 4 @Singleton @@ -108,4 +113,4 @@ class PushRegistryV2 @Inject constructor(private val pushReceiver: PushReceiver) .also { if (it.isFailure()) throw Exception("error: ${it.message}.") } } } -} +} \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt index e0aa14f6d2..70f3fb4522 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt @@ -27,17 +27,15 @@ class FirebaseTokenFetcher @Inject constructor( .process() } - private fun Task.process() { - when { - isSuccessful -> try { - result?.token?.let { - pushRegistry.get().refresh(it, force = true).get() - } - } catch (e: Exception) { - onFail(e) + private fun Task.process() = when { + isSuccessful -> try { + result?.token?.let { + pushRegistry.get().refresh(it, force = true).get() } - else -> exception?.let(::onFail) + } catch (e: Exception) { + onFail(e) } + else -> exception?.let(::onFail) } private fun onFail(e: Exception) = Log.e(TAG, "fetch failed", e) From d6380c5e6366a4f2f20d992d852834f6ff3fc1dc Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 7 Aug 2023 09:50:51 +0930 Subject: [PATCH 34/53] Fix website flavor --- .../securesms/notifications/NoOpPushModule.kt | 8 +++++--- .../securesms/notifications/NoOpTokenFetcher.kt | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt index af907b51ca..59873c82be 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.notifications +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -8,6 +9,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -class NoOpPushModule { - -} \ No newline at end of file +abstract class NoOpPushModule { + @Binds + abstract fun bindTokenFetcher(tokenFetcher: NoOpTokenFetcher): TokenFetcher +} diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt new file mode 100644 index 0000000000..1705eefe09 --- /dev/null +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt @@ -0,0 +1,12 @@ +package org.thoughtcrime.securesms.notifications + +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NoOpTokenFetcher @Inject constructor() : TokenFetcher { + override fun fetch(): Job = MainScope().launch { } +} From 5a5b2f593f846faf0b1ca2900a02821b194e6cae Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 9 Aug 2023 10:08:42 +0930 Subject: [PATCH 35/53] Refactor to accept Huawei token from getToken() and/or onNewToken() --- .../notifications/HuaweiPushService.kt | 31 +---------- .../notifications/HuaweiTokenFetcher.kt | 22 ++++---- .../securesms/ApplicationContext.java | 5 -- .../securesms/notifications/PushReceiver.kt | 2 +- .../securesms/notifications/PushRegistry.kt | 55 +++++++++---------- .../notifications/PushTokenManager.kt | 34 ------------ .../securesms/notifications/TokenFetcher.kt | 4 +- .../{ExpiryManager.kt => TokenManager.kt} | 18 +++--- .../notifications/FirebasePushService.kt | 2 +- .../notifications/FirebaseTokenFetcher.kt | 34 ++---------- 10 files changed, 59 insertions(+), 148 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt rename app/src/main/java/org/thoughtcrime/securesms/notifications/{ExpiryManager.kt => TokenManager.kt} (59%) diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt index 2c3512acc7..fb4e262560 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt @@ -14,11 +14,6 @@ private val TAG = HuaweiPushService::class.java.simpleName @AndroidEntryPoint class HuaweiPushService: HmsMessageService() { - - init { - Log.d(TAG, "init Huawei Service") - } - @Inject lateinit var pushRegistry: PushRegistry @Inject lateinit var pushReceiver: PushReceiver @@ -32,33 +27,13 @@ class HuaweiPushService: HmsMessageService() { pushReceiver.onPush(message?.data?.let(Base64::decode)) } - override fun onMessageSent(p0: String?) { - Log.d(TAG, "onMessageSent() called with: p0 = $p0") - super.onMessageSent(p0) - } - - override fun onSendError(p0: String?, p1: Exception?) { - Log.d(TAG, "onSendError() called with: p0 = $p0, p1 = $p1") - super.onSendError(p0, p1) - } - - override fun onMessageDelivered(p0: String?, p1: Exception?) { - Log.d(TAG, "onMessageDelivered") - super.onMessageDelivered(p0, p1) - } - - - override fun onNewToken(p0: String?) { - Log.d(TAG, "onNewToken") - super.onNewToken(p0) + override fun onNewToken(token: String?) { + pushRegistry.register(token) } override fun onNewToken(token: String?, bundle: Bundle?) { Log.d(TAG, "New HCM token: $token.") - - TextSecurePreferences.setPushToken(this, token) - - pushRegistry.refresh(token, true) + onNewToken(token) } override fun onDeletedMessages() { diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt index e3b6afe4cc..9d9b61ce9a 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt @@ -2,26 +2,28 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import com.huawei.hms.aaid.HmsInstanceId +import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.session.libsignal.utilities.Log import javax.inject.Inject import javax.inject.Singleton +private const val APP_ID = "107205081" +private const val TOKEN_SCOPE = "HCM" + @Singleton class HuaweiTokenFetcher @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext private val context: Context, + private val pushRegistry: Lazy, ): TokenFetcher { - override fun fetch(): Job { - val hmsInstanceId = HmsInstanceId.getInstance(context) - - return MainScope().launch(Dispatchers.IO) { - val appId = "107205081" - val tokenScope = "HCM" - // getToken returns an empty string, but triggers the service to initialize. - hmsInstanceId.getToken(appId, tokenScope) - } + override suspend fun fetch(): String? = HmsInstanceId.getInstance(context).run { + // https://developer.huawei.com/consumer/en/doc/development/HMS-Guides/push-basic-capability#h2-1576218800370 + // getToken may return an empty string, if so HuaweiPushService#onNewToken will be called. + withContext(Dispatchers.IO) { getToken(APP_ID, TOKEN_SCOPE) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 73b0af55f7..75622c7bb6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -427,11 +427,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO } private static class ProviderInitializationException extends RuntimeException { } - - public void registerForPnIfNeeded(final Boolean force) { - pushRegistry.refresh(force); - } - private void setUpPollingIfNeeded() { String userPublicKey = TextSecurePreferences.getLocalNumber(this); if (userPublicKey == null) return; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt index 56871ba64e..1d097c7059 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushReceiver.kt @@ -75,7 +75,7 @@ class PushReceiver @Inject constructor(@ApplicationContext val context: Context) else -> this["ENCRYPTED_DATA"]?.let(Base64::decode) } - fun decrypt(encPayload: ByteArray): ByteArray? { + private fun decrypt(encPayload: ByteArray): ByteArray? { Log.d(TAG, "decrypt() called") val encKey = getOrCreateNotificationKey() diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt index 9a625f133c..dc50ad8bdd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt @@ -4,6 +4,8 @@ import android.content.Context import com.goterl.lazysodium.utils.KeyPair import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Job +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch import nl.komponents.kovenant.Promise import nl.komponents.kovenant.combine.and import org.session.libsession.messaging.sending_receiving.notifications.PushRegistryV1 @@ -22,8 +24,10 @@ private val TAG = PushRegistry::class.java.name class PushRegistry @Inject constructor( @ApplicationContext private val context: Context, private val device: Device, - private val tokenManager: PushTokenManager, + private val tokenManager: TokenManager, private val pushRegistryV2: PushRegistryV2, + private val prefs: TextSecurePreferences, + private val tokenFetcher: TokenFetcher, ) { private var pushRegistrationJob: Job? = null @@ -32,42 +36,33 @@ class PushRegistry @Inject constructor( Log.d(TAG, "refresh() called with: force = $force") pushRegistrationJob?.apply { - if (force) cancel() else if (isActive) return + if (force) cancel() else if (isActive || !tokenManager.hasValidRegistration) return } - pushRegistrationJob = tokenManager.fetchToken() + pushRegistrationJob = MainScope().launch { + register(tokenFetcher.fetch()) fail { + Log.e(TAG, "register failed", it) + } + } } - fun refresh(token: String?, force: Boolean): Promise<*, Exception> { - Log.d(TAG, "refresh($token, $force) called") + fun register(token: String?): Promise<*, Exception> { + Log.d(TAG, "refresh($token) called") - token ?: return emptyPromise() - val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return emptyPromise() + if (token?.isNotEmpty() != true) return emptyPromise() + + prefs.setPushToken(token) + + val userPublicKey = prefs.getLocalNumber() ?: return emptyPromise() val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() return when { - tokenManager.isPushEnabled -> register(force, token, userPublicKey, userEdKey) - tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey) + prefs.isPushEnabled() -> register(token, userPublicKey, userEdKey) + tokenManager.isRegistered -> unregister(token, userPublicKey, userEdKey) else -> emptyPromise() } } - /** - * Register for push notifications if: - * force is true - * there is no FCM Token - * FCM Token has expired - */ - private fun register( - force: Boolean, - token: String, - publicKey: String, - userEd25519Key: KeyPair, - namespaces: List = listOf(Namespace.DEFAULT) - ): Promise<*, Exception> = if (force || tokenManager.isInvalid()) { - register(token, publicKey, userEd25519Key, namespaces) - } else emptyPromise() - /** * Register for push notifications. */ @@ -77,7 +72,7 @@ class PushRegistry @Inject constructor( userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) ): Promise<*, Exception> { - android.util.Log.d( + Log.d( TAG, "register() called with: token = $token, publicKey = $publicKey, userEd25519Key = $userEd25519Key, namespaces = $namespaces" ) @@ -97,8 +92,8 @@ class PushRegistry @Inject constructor( } return v1 and v2 success { - Log.d(TAG, "registerBoth success... saving token!!") - tokenManager.fcmToken = token + Log.d(TAG, "register v1 & v2 success") + tokenManager.register() } } @@ -111,6 +106,6 @@ class PushRegistry @Inject constructor( ) fail { Log.e(TAG, "unregisterBoth failed", it) } success { - tokenManager.fcmToken = null + tokenManager.unregister() } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt deleted file mode 100644 index 45469a3dd9..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushTokenManager.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import kotlinx.coroutines.Job -import org.session.libsession.utilities.TextSecurePreferences -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class PushTokenManager @Inject constructor( - @ApplicationContext private val context: Context, - private val tokenFetcher: TokenFetcher -) { - private val expiryManager = ExpiryManager(context) - - val isPushEnabled get() = TextSecurePreferences.isPushEnabled(context) - - var fcmToken - get() = TextSecurePreferences.getPushToken(context) - set(value) { - TextSecurePreferences.setPushToken(context, value) - if (value != null) markTime() else clearTime() - } - - val requiresUnregister get() = fcmToken != null - - private fun clearTime() = expiryManager.clearTime() - private fun markTime() = expiryManager.markTime() - private fun isExpired() = expiryManager.isExpired() - - fun isInvalid(): Boolean = fcmToken == null || isExpired() - fun fetchToken(): Job = tokenFetcher.fetch() -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt index d8657e95d3..5bd9ce0d8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenFetcher.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.notifications -import kotlinx.coroutines.Job - interface TokenFetcher { - fun fetch(): Job + suspend fun fetch(): String? } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenManager.kt similarity index 59% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/TokenManager.kt index 2b3f0aa860..b3db642b81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/ExpiryManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/TokenManager.kt @@ -6,17 +6,21 @@ import org.session.libsession.utilities.TextSecurePreferences import javax.inject.Inject import javax.inject.Singleton -class ExpiryManager( - private val context: Context, - private val interval: Int = 12 * 60 * 60 * 1000 -) { - fun isExpired() = currentTime() > time + interval +private const val INTERVAL: Int = 12 * 60 * 60 * 1000 - fun markTime() { +@Singleton +class TokenManager @Inject constructor( + @ApplicationContext private val context: Context, +) { + val hasValidRegistration get() = isRegistered && !isExpired + val isRegistered get() = time > 0 + private val isExpired get() = currentTime() > time + INTERVAL + + fun register() { time = currentTime() } - fun clearTime() { + fun unregister() { time = 0 } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt index 1ebf405a28..2a3b054a58 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushService.kt @@ -18,7 +18,7 @@ class FirebasePushService : FirebaseMessagingService() { override fun onNewToken(token: String) { if (token == prefs.getPushToken()) return - pushRegistry.refresh(token, true) + pushRegistry.register(token) } override fun onMessageReceived(message: RemoteMessage) { diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt index 70f3fb4522..d40f160d0b 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebaseTokenFetcher.kt @@ -1,43 +1,19 @@ package org.thoughtcrime.securesms.notifications -import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Tasks import com.google.firebase.iid.FirebaseInstanceId -import com.google.firebase.iid.InstanceIdResult -import dagger.Lazy import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.MainScope import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import org.session.libsignal.utilities.Log +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton -private val TAG = FirebaseTokenFetcher::class.java.name - @Singleton -class FirebaseTokenFetcher @Inject constructor( - private val pushRegistry: Lazy, -): TokenFetcher { - override fun fetch(): Job = MainScope().launch(Dispatchers.IO) { +class FirebaseTokenFetcher @Inject constructor(): TokenFetcher { + override suspend fun fetch() = withContext(Dispatchers.IO) { FirebaseInstanceId.getInstance().instanceId .also(Tasks::await) - .also { if (!isActive) return@launch } // don't 'complete' task if we were canceled - .process() + .takeIf { isActive } // don't 'complete' task if we were canceled + ?.run { result?.token ?: throw exception!! } } - - private fun Task.process() = when { - isSuccessful -> try { - result?.token?.let { - pushRegistry.get().refresh(it, force = true).get() - } - } catch (e: Exception) { - onFail(e) - } - else -> exception?.let(::onFail) - } - - private fun onFail(e: Exception) = Log.e(TAG, "fetch failed", e) } - From e60c05cee0827e05e111215af0a38e765906084f Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 9 Aug 2023 12:18:22 +0930 Subject: [PATCH 36/53] Fix Huawei message parsing --- .../securesms/notifications/HuaweiPushService.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt index fb4e262560..dc7bf893d7 100644 --- a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt +++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt @@ -4,6 +4,7 @@ import android.os.Bundle import com.huawei.hms.push.HmsMessageService import com.huawei.hms.push.RemoteMessage import dagger.hilt.android.AndroidEntryPoint +import org.json.JSONException import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log @@ -17,13 +18,9 @@ class HuaweiPushService: HmsMessageService() { @Inject lateinit var pushRegistry: PushRegistry @Inject lateinit var pushReceiver: PushReceiver - override fun onCreate() { - Log.d(TAG, "onCreate Huawei Service") - super.onCreate() - } - override fun onMessageReceived(message: RemoteMessage?) { - Log.d(TAG, "onMessageReceived: $message.") + Log.d(TAG, "onMessageReceived") + message?.dataOfMap?.takeIf { it.isNotEmpty() }?.let(pushReceiver::onPush) ?: pushReceiver.onPush(message?.data?.let(Base64::decode)) } @@ -33,10 +30,11 @@ class HuaweiPushService: HmsMessageService() { override fun onNewToken(token: String?, bundle: Bundle?) { Log.d(TAG, "New HCM token: $token.") - onNewToken(token) + pushRegistry.register(token) } override fun onDeletedMessages() { - pushRegistry.refresh(true) + Log.d(TAG, "onDeletedMessages") + pushRegistry.refresh(false) } } From e1e5c5937b60d934c92eade1b03e850ec56712ca Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 9 Aug 2023 12:42:53 +0930 Subject: [PATCH 37/53] Use live endpoint for legacy groups --- .../messaging/sending_receiving/notifications/Server.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt index 6fefd694e0..0497fe2220 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Server.kt @@ -2,5 +2,5 @@ package org.session.libsession.messaging.sending_receiving.notifications enum class Server(val url: String, val publicKey: String) { LATEST("https://push.getsession.org", "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b"), - LEGACY("https://dev.apns.getsession.org", "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049") + LEGACY("https://live.apns.getsession.org", "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049") } From 309293df63763c2bd5e42fa8409315795dd2ab45 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 10 Aug 2023 10:36:52 +0930 Subject: [PATCH 38/53] Improve logs in SingleRecipientNotificationBuilder --- .../notifications/SingleRecipientNotificationBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index da0896d05a..891d0bb2df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -99,7 +99,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil .get(); setLargeIcon(iconBitmap); } catch (InterruptedException | ExecutionException e) { - Log.w(TAG, e); + Log.w(TAG, "get iconBitmap in getThread failed", e); setLargeIcon(getPlaceholderDrawable(context, recipient)); } } else { @@ -298,7 +298,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil .submit(64, 64) .get(); } catch (InterruptedException | ExecutionException e) { - Log.w(TAG, e); + Log.w(TAG, "getBigPicture failed", e); return Bitmap.createBitmap(64, 64, Bitmap.Config.RGB_565); } } From 16177d5cb12275d65a3c1fbcd393e4d9e91f7858 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 11 Aug 2023 17:36:55 +0930 Subject: [PATCH 39/53] Fix logs --- .../securesms/dependencies/PushComponent.kt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt deleted file mode 100644 index 69baf1f208..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.thoughtcrime.securesms.dependencies - -import dagger.hilt.EntryPoint -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import org.thoughtcrime.securesms.notifications.PushManager - -@EntryPoint -@InstallIn(SingletonComponent::class) -interface PushComponent { - -} \ No newline at end of file From 77100231d2c3b2bf26d7e005db634aa3adec9550 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 11 Aug 2023 17:37:35 +0930 Subject: [PATCH 40/53] Fix logs --- app/src/main/AndroidManifest.xml | 10 -------- .../securesms/notifications/PushReceiver.kt | 23 ++++++------------- .../securesms/notifications/PushRegistry.kt | 11 ++++----- .../notifications/PushRegistryV1.kt | 12 +++------- 4 files changed, 14 insertions(+), 42 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed51a7ea8d..cc63ab6a87 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,16 +78,6 @@ android:theme="@style/Theme.Session.DayNight" tools:replace="android:allowBackup"> - - - - - - = 2) (expectedList[1] as? BencodeString)?.value else null - // null content is valid only if we got a "data_too_long" flag - if (content == null) - check(metadata.data_too_long) { "missing message data, but no too-long flag" } - else - check(metadata.data_len == content.size) { "wrong message data size" } - - Log.d( - TAG, - "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B" - ) - - return content + return (expectedList.getOrNull(1) as? BencodeString)?.value.also { + // null content is valid only if we got a "data_too_long" flag + it?.let { check(metadata.data_len == it.size) { "wrong message data size" } } + ?: check(metadata.data_too_long) { "missing message data, but no too-long flag" } + } } fun getOrCreateNotificationKey(): Key { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt index dc50ad8bdd..718b2b29ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt @@ -40,14 +40,14 @@ class PushRegistry @Inject constructor( } pushRegistrationJob = MainScope().launch { - register(tokenFetcher.fetch()) fail { - Log.e(TAG, "register failed", it) + register(tokenFetcher.fetch()) fail { e -> + Log.e(TAG, "register failed", e) } } } fun register(token: String?): Promise<*, Exception> { - Log.d(TAG, "refresh($token) called") + Log.d(TAG, "refresh() called") if (token?.isNotEmpty() != true) return emptyPromise() @@ -72,10 +72,7 @@ class PushRegistry @Inject constructor( userEd25519Key: KeyPair, namespaces: List = listOf(Namespace.DEFAULT) ): Promise<*, Exception> { - Log.d( - TAG, - "register() called with: token = $token, publicKey = $publicKey, userEd25519Key = $userEd25519Key, namespaces = $namespaces" - ) + Log.d(TAG, "register() called") val v1 = PushRegistryV1.register( device = device, diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt index d7d84b5c61..ac5fce6d50 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt @@ -34,23 +34,17 @@ object PushRegistryV1 { legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = when { isUsingFCM -> retryIfNeeded(maxRetryCount) { - android.util.Log.d( - TAG, - "register() called with: device = $device, isUsingFCM = $isUsingFCM, token = $token, publicKey = $publicKey, legacyGroupPublicKeys = $legacyGroupPublicKeys" - ) + Log.d(TAG, "register() called") doRegister(token, publicKey, device, legacyGroupPublicKeys) } fail { exception -> - Log.d(TAG, "Couldn't register for FCM due to error: $exception... $device $token $publicKey $legacyGroupPublicKeys") + Log.d(TAG, "Couldn't register for FCM due to error", exception) } else -> emptyPromise() } private fun doRegister(token: String?, publicKey: String?, device: Device, legacyGroupPublicKeys: Collection): Promise<*, Exception> { - android.util.Log.d( - TAG, - "doRegister() called with: token = $token, publicKey = $publicKey, device = $device, legacyGroupPublicKeys = $legacyGroupPublicKeys" - ) + Log.d(TAG, "doRegister() called") token ?: return emptyPromise() publicKey ?: return emptyPromise() From 9899b37f43072c5fcc80921c33cbafb7a1e305a3 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 11 Aug 2023 17:45:56 +0930 Subject: [PATCH 41/53] Move huawei manifest metadata to huawei flavor --- app/src/huawei/AndroidManifest.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml index 0483deac1c..dad7ab3ac6 100644 --- a/app/src/huawei/AndroidManifest.xml +++ b/app/src/huawei/AndroidManifest.xml @@ -3,6 +3,16 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + + Date: Fri, 11 Aug 2023 19:26:53 +0930 Subject: [PATCH 42/53] Move plugin to huawei flavor --- app/build.gradle | 4 ++++ app/{ => src/huawei}/agconnect-services.json | 0 libsession/build.gradle | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) rename app/{ => src/huawei}/agconnect-services.json (100%) diff --git a/app/build.gradle b/app/build.gradle index 91ee23fbb4..6d6f0c9b0c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,10 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' apply plugin: 'dagger.hilt.android.plugin' +if (getGradle().getStartParameter().getTaskRequests().toString().contains("Huawei")){ + apply plugin: 'com.huawei.agconnect' +} + configurations.all { exclude module: "commons-logging" } diff --git a/app/agconnect-services.json b/app/src/huawei/agconnect-services.json similarity index 100% rename from app/agconnect-services.json rename to app/src/huawei/agconnect-services.json diff --git a/libsession/build.gradle b/libsession/build.gradle index 5dc67f525f..3cfb66cd32 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -2,7 +2,6 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'kotlinx-serialization' - id 'com.huawei.agconnect' } android { From ed7ce364021ff97b303e6af8e18d6f5ecf040c60 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 14 Aug 2023 10:31:22 +0930 Subject: [PATCH 43/53] Cleanup gradle --- app/build.gradle | 4 ---- build.gradle | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6d6f0c9b0c..91ee23fbb4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,10 +27,6 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' apply plugin: 'dagger.hilt.android.plugin' -if (getGradle().getStartParameter().getTaskRequests().toString().contains("Huawei")){ - apply plugin: 'com.huawei.agconnect' -} - configurations.all { exclude module: "commons-logging" } diff --git a/build.gradle b/build.gradle index 8e344c5e14..8e26563ee3 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { repositories { google() mavenCentral() - maven {url 'https://developer.huawei.com/repo/'} + maven { url 'https://developer.huawei.com/repo/' } } dependencies { classpath "com.android.tools.build:gradle:$gradlePluginVersion" From 4c8f38df729f97d8b733d687e6f2e93a820bc97d Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 15 Aug 2023 13:51:21 +0930 Subject: [PATCH 44/53] Require command line arg to include huawei dependencies --- app/build.gradle | 19 ++++++++++++++++++- build.gradle | 5 +++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 91ee23fbb4..d176cccdf1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,16 @@ def abiPostFix = ['armeabi-v7a' : 1, 'x86_64' : 4, 'universal' : 5] +tasks.register('checkHuaweiEnabled') { + doFirst { + if (!project.hasProperty('huawei')) { + def message = 'Huawei is not enabled. Please add -Phuawei command line arg. See README.' + logger.error(message) + throw new GradleException(message) + } + } +} + android { compileSdkVersion androidCompileSdkVersion namespace 'network.loki.messenger' @@ -143,6 +153,7 @@ android { buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.HUAWEI" buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" + } website { @@ -180,6 +191,12 @@ android { dataBinding true viewBinding true } + + applicationVariants.all { variant -> + if (variant.flavorName == 'huawei') { + variant.preBuild.dependsOn checkHuaweiEnabled + } + } } dependencies { @@ -213,7 +230,7 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } - huaweiImplementation 'com.huawei.hms:push:6.7.0.300' + if (project.hasProperty('huawei')) huaweiImplementation 'com.huawei.hms:push:6.7.0.300' implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1' implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1' implementation 'org.conscrypt:conscrypt-android:2.0.0' diff --git a/build.gradle b/build.gradle index 8e26563ee3..ebb3f5f250 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,7 @@ buildscript { mavenCentral() maven { url 'https://developer.huawei.com/repo/' } } + dependencies { classpath "com.android.tools.build:gradle:$gradlePluginVersion" classpath files('libs/gradle-witness.jar') @@ -13,7 +14,7 @@ buildscript { classpath files('libs/gradle-witness.jar') classpath "com.squareup:javapoet:1.13.0" classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" - classpath 'com.huawei.agconnect:agcp:1.6.0.300' + if (project.hasProperty('huawei')) classpath 'com.huawei.agconnect:agcp:1.6.0.300' } } @@ -57,7 +58,7 @@ allprojects { } jcenter() maven { url "https://jitpack.io" } - maven { url 'https://developer.huawei.com/repo/' } + if (project.hasProperty('huawei')) maven { url 'https://developer.huawei.com/repo/' } } project.ext { From b09b6836d49a568663473d6ba6c45c535992e0b5 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 15 Aug 2023 14:52:40 +0930 Subject: [PATCH 45/53] Add huawei info to BUILDING.md --- BUILDING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BUILDING.md b/BUILDING.md index f88509c680..48b4412ddd 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -34,6 +34,12 @@ Setting up a development environment and building from Android Studio 6. Project initialization and building should proceed. 7. Clone submodules with `git submodule update --init --recursive` +If you would like to build the Huawei Flavor with Huawei HMS push notifications you will need to pass 'huawei' as a command line arg to include the required dependencies. + +e.g. `./gradlew assembleHuaweiDebug -Phuawei` + +If you are building in Android Studio then add `-Phuawei` to `Preferences > Build, Execution, Deployment > Gradle-Android Compiler > Command-line Options` + Contributing code ----------------- From 7c8882e1f395cba648adfd68fa887c6852b54fe8 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 15 Aug 2023 18:42:49 +0930 Subject: [PATCH 46/53] Fix project reference in task --- app/build.gradle | 5 +++-- build.gradle | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d176cccdf1..d15bca89da 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,8 +42,9 @@ def abiPostFix = ['armeabi-v7a' : 1, 'universal' : 5] tasks.register('checkHuaweiEnabled') { + ext.huaweiEnabled = project.hasProperty('huawei') doFirst { - if (!project.hasProperty('huawei')) { + if (!huaweiEnabled) { def message = 'Huawei is not enabled. Please add -Phuawei command line arg. See README.' logger.error(message) throw new GradleException(message) @@ -194,7 +195,7 @@ android { applicationVariants.all { variant -> if (variant.flavorName == 'huawei') { - variant.preBuild.dependsOn checkHuaweiEnabled + variant.getPreBuildProvider().get().dependsOn checkHuaweiEnabled } } } diff --git a/build.gradle b/build.gradle index ebb3f5f250..90266d56cb 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ buildscript { classpath files('libs/gradle-witness.jar') classpath "com.squareup:javapoet:1.13.0" classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" - if (project.hasProperty('huawei')) classpath 'com.huawei.agconnect:agcp:1.6.0.300' + if (project.hasProperty('huawei')) classpath 'com.huawei.agconnect:agcp:1.9.1.300' } } From 0aa5dc79693f4ade52212755185b727d7f469357 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Aug 2023 13:25:45 +0930 Subject: [PATCH 47/53] Convert NotificationPreferenceFragment to Kotlin --- .../NotificationsPreferenceFragment.java | 190 ------------------ .../NotificationsPreferenceFragment.kt | 172 ++++++++++++++++ 2 files changed, 172 insertions(+), 190 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java deleted file mode 100644 index 4cb316f40f..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.thoughtcrime.securesms.preferences; - -import static android.app.Activity.RESULT_OK; - -import static org.thoughtcrime.securesms.preferences.ListPreferenceDialogKt.listPreferenceDialog; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.provider.Settings; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.preference.ListPreference; -import androidx.preference.Preference; - -import org.session.libsession.utilities.TextSecurePreferences; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.notifications.PushRegistry; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; -import network.loki.messenger.R; - -@AndroidEntryPoint -public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragment { - - @SuppressWarnings("unused") - private static final String TAG = NotificationsPreferenceFragment.class.getSimpleName(); - - @Inject - PushRegistry pushRegistry; - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - - // Set up FCM toggle - String fcmKey = "pref_key_use_fcm"; - ((SwitchPreferenceCompat)findPreference(fcmKey)).setChecked(TextSecurePreferences.isPushEnabled(getContext())); - this.findPreference(fcmKey) - .setOnPreferenceChangeListener((preference, newValue) -> { - TextSecurePreferences.setPushEnabled(getContext(), (boolean) newValue); - pushRegistry.refresh(true); - return true; - }); - - if (NotificationChannels.supported()) { - TextSecurePreferences.setNotificationRingtone(getContext(), NotificationChannels.getMessageRingtone(getContext()).toString()); - TextSecurePreferences.setNotificationVibrateEnabled(getContext(), NotificationChannels.getMessageVibrate(getContext())); - } - this.findPreference(TextSecurePreferences.RINGTONE_PREF) - .setOnPreferenceChangeListener(new RingtoneSummaryListener()); - this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF) - .setOnPreferenceChangeListener(new NotificationPrivacyListener()); - this.findPreference(TextSecurePreferences.VIBRATE_PREF) - .setOnPreferenceChangeListener((preference, newValue) -> { - NotificationChannels.updateMessageVibrate(getContext(), (boolean) newValue); - return true; - }); - - this.findPreference(TextSecurePreferences.RINGTONE_PREF) - .setOnPreferenceClickListener(preference -> { - Uri current = TextSecurePreferences.getNotificationRingtone(getContext()); - - Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, Settings.System.DEFAULT_NOTIFICATION_URI); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current); - - startActivityForResult(intent, 1); - - return true; - }); - - this.findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF) - .setOnPreferenceClickListener(preference -> { - ListPreference listPreference = (ListPreference) preference; - listPreference.setDialogMessage(R.string.preferences_notifications__content_message); - listPreferenceDialog(getContext(), listPreference, () -> { - initializeListSummary(findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)); - return null; - }); - return true; - }); - - initializeListSummary((ListPreference) findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)); - - if (NotificationChannels.supported()) { - this.findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF) - .setOnPreferenceClickListener(preference -> { - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(getContext())); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName()); - startActivity(intent); - return true; - }); - } - - initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF)); - initializeMessageVibrateSummary((SwitchPreferenceCompat)findPreference(TextSecurePreferences.VIBRATE_PREF)); - } - - @Override - public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_notifications); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 1 && resultCode == RESULT_OK && data != null) { - Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - - if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)) { - NotificationChannels.updateMessageRingtone(getContext(), uri); - TextSecurePreferences.removeNotificationRingtone(getContext()); - } else { - uri = uri == null ? Uri.EMPTY : uri; - NotificationChannels.updateMessageRingtone(getContext(), uri); - TextSecurePreferences.setNotificationRingtone(getContext(), uri.toString()); - } - - initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF)); - } - } - - private class RingtoneSummaryListener implements Preference.OnPreferenceChangeListener { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - Uri value = (Uri) newValue; - - if (value == null || TextUtils.isEmpty(value.toString())) { - preference.setSummary(R.string.preferences__silent); - } else { - Ringtone tone = RingtoneManager.getRingtone(getActivity(), value); - - if (tone != null) { - preference.setSummary(tone.getTitle(getActivity())); - } - } - - return true; - } - } - - private void initializeRingtoneSummary(Preference pref) { - RingtoneSummaryListener listener = (RingtoneSummaryListener) pref.getOnPreferenceChangeListener(); - Uri uri = TextSecurePreferences.getNotificationRingtone(getContext()); - - listener.onPreferenceChange(pref, uri); - } - - private void initializeMessageVibrateSummary(SwitchPreferenceCompat pref) { - pref.setChecked(TextSecurePreferences.isNotificationVibrateEnabled(getContext())); - } - - public static CharSequence getSummary(Context context) { - final int onCapsResId = R.string.ApplicationPreferencesActivity_On; - final int offCapsResId = R.string.ApplicationPreferencesActivity_Off; - - return context.getString(TextSecurePreferences.isNotificationsEnabled(context) ? onCapsResId : offCapsResId); - } - - private class NotificationPrivacyListener extends ListSummaryListener { - @SuppressLint("StaticFieldLeak") - @Override - public boolean onPreferenceChange(Preference preference, Object value) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - ApplicationContext.getInstance(getActivity()).messageNotifier.updateNotification(getActivity()); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - return super.onPreferenceChange(preference, value); - } - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt new file mode 100644 index 0000000000..933f13b9ec --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt @@ -0,0 +1,172 @@ +package org.thoughtcrime.securesms.preferences + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.RingtoneManager +import android.net.Uri +import android.os.AsyncTask +import android.os.Bundle +import android.provider.Settings +import android.text.TextUtils +import androidx.lifecycle.lifecycleScope +import androidx.preference.ListPreference +import androidx.preference.Preference +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import network.loki.messenger.R +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.TextSecurePreferences.Companion.isNotificationsEnabled +import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.components.SwitchPreferenceCompat +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.notifications.PushRegistry +import javax.inject.Inject + +@AndroidEntryPoint +class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() { + @Inject + lateinit var pushRegistry: PushRegistry + @Inject + lateinit var prefs: TextSecurePreferences + + override fun onCreate(paramBundle: Bundle?) { + super.onCreate(paramBundle) + + // Set up FCM toggle + val fcmKey = "pref_key_use_fcm" + val fcmPreference: SwitchPreferenceCompat = findPreference(fcmKey)!! + fcmPreference.isChecked = prefs.isPushEnabled() + fcmPreference.setOnPreferenceChangeListener { _: Preference, newValue: Any -> + prefs.setPushEnabled(newValue as Boolean) + pushRegistry.refresh(true) + true + } + if (NotificationChannels.supported()) { + prefs.setNotificationRingtone( + NotificationChannels.getMessageRingtone(requireContext()).toString() + ) + prefs.setNotificationVibrateEnabled( + NotificationChannels.getMessageVibrate(requireContext()) + ) + } + findPreference(TextSecurePreferences.RINGTONE_PREF)!!.onPreferenceChangeListener = RingtoneSummaryListener() + findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)!!.onPreferenceChangeListener = NotificationPrivacyListener() + findPreference(TextSecurePreferences.VIBRATE_PREF)!!.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any -> + NotificationChannels.updateMessageVibrate(requireContext(), newValue as Boolean) + true + } + findPreference(TextSecurePreferences.RINGTONE_PREF)!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val current = prefs.getNotificationRingtone() + val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER) + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true) + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true) + intent.putExtra( + RingtoneManager.EXTRA_RINGTONE_TYPE, + RingtoneManager.TYPE_NOTIFICATION + ) + intent.putExtra( + RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, + Settings.System.DEFAULT_NOTIFICATION_URI + ) + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current) + startActivityForResult(intent, 1) + true + } + findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { preference: Preference -> + val listPreference = preference as ListPreference + listPreference.setDialogMessage(R.string.preferences_notifications__content_message) + listPreferenceDialog(requireContext(), listPreference) { + initializeListSummary(findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF)) + } + true + } + initializeListSummary(findPreference(TextSecurePreferences.NOTIFICATION_PRIVACY_PREF) as ListPreference?) + if (NotificationChannels.supported()) { + findPreference(TextSecurePreferences.NOTIFICATION_PRIORITY_PREF)!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) + intent.putExtra( + Settings.EXTRA_CHANNEL_ID, NotificationChannels.getMessagesChannel(requireContext()) + ) + intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) + startActivity(intent) + true + } + } + initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF)) + initializeMessageVibrateSummary(findPreference(TextSecurePreferences.VIBRATE_PREF) as SwitchPreferenceCompat?) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_notifications) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == 1 && resultCode == Activity.RESULT_OK && data != null) { + var uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) + if (Settings.System.DEFAULT_NOTIFICATION_URI == uri) { + NotificationChannels.updateMessageRingtone(requireContext(), uri) + prefs.removeNotificationRingtone() + } else { + uri = uri ?: Uri.EMPTY + NotificationChannels.updateMessageRingtone(requireContext(), uri) + prefs.setNotificationRingtone(uri.toString()) + } + initializeRingtoneSummary(findPreference(TextSecurePreferences.RINGTONE_PREF)) + } + } + + private inner class RingtoneSummaryListener : Preference.OnPreferenceChangeListener { + override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { + val value = newValue as? Uri + if (value == null || TextUtils.isEmpty(value.toString())) { + preference.setSummary(R.string.preferences__silent) + } else { + RingtoneManager.getRingtone(activity, value) + ?.getTitle(activity) + ?.let { preference.summary = it } + + } + return true + } + } + + private fun initializeRingtoneSummary(pref: Preference?) { + val listener = pref!!.onPreferenceChangeListener as RingtoneSummaryListener? + val uri = prefs.getNotificationRingtone() + listener!!.onPreferenceChange(pref, uri) + } + + private fun initializeMessageVibrateSummary(pref: SwitchPreferenceCompat?) { + pref!!.isChecked = prefs.isNotificationVibrateEnabled() + } + + private inner class NotificationPrivacyListener : ListSummaryListener() { + @SuppressLint("StaticFieldLeak") + override fun onPreferenceChange(preference: Preference, value: Any): Boolean { + object : AsyncTask() { + override fun doInBackground(vararg params: Void?): Void? { + ApplicationContext.getInstance(activity).messageNotifier.updateNotification(activity!!) + return null + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + return super.onPreferenceChange(preference, value) + } + } + + companion object { + @Suppress("unused") + private val TAG = NotificationsPreferenceFragment::class.java.simpleName + fun getSummary(context: Context): CharSequence = when (isNotificationsEnabled(context)) { + true -> R.string.ApplicationPreferencesActivity_On + false -> R.string.ApplicationPreferencesActivity_Off + }.let(context::getString) + } +} From d308f381d9e9554b1cc9d6948c9483677e0bbdf9 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Aug 2023 13:27:28 +0930 Subject: [PATCH 48/53] Disable fcm preference while register request is in flight --- .../securesms/notifications/PushRegistry.kt | 13 ++++++++----- .../preferences/NotificationsPreferenceFragment.kt | 13 ++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt index 718b2b29ca..b0954f2327 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushRegistry.kt @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.notifications import android.content.Context import com.goterl.lazysodium.utils.KeyPair import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch @@ -32,18 +33,20 @@ class PushRegistry @Inject constructor( private var pushRegistrationJob: Job? = null - fun refresh(force: Boolean) { + fun refresh(force: Boolean): Job { Log.d(TAG, "refresh() called with: force = $force") pushRegistrationJob?.apply { - if (force) cancel() else if (isActive || !tokenManager.hasValidRegistration) return + if (force) cancel() else if (isActive) return MainScope().launch {} } - pushRegistrationJob = MainScope().launch { - register(tokenFetcher.fetch()) fail { e -> + return MainScope().launch(Dispatchers.IO) { + try { + register(tokenFetcher.fetch()).get() + } catch (e: Exception) { Log.e(TAG, "register failed", e) } - } + }.also { pushRegistrationJob = it } } fun register(token: String?): Promise<*, Exception> { diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt index 933f13b9ec..fa6461acc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.kt @@ -42,7 +42,18 @@ class NotificationsPreferenceFragment : ListSummaryPreferenceFragment() { fcmPreference.isChecked = prefs.isPushEnabled() fcmPreference.setOnPreferenceChangeListener { _: Preference, newValue: Any -> prefs.setPushEnabled(newValue as Boolean) - pushRegistry.refresh(true) + val job = pushRegistry.refresh(true) + + fcmPreference.isEnabled = false + + lifecycleScope.launch(Dispatchers.IO) { + job.join() + + withContext(Dispatchers.Main) { + fcmPreference.isEnabled = true + } + } + true } if (NotificationChannels.supported()) { From 62cd0f68f01b64da3683ec95539f5d53aa32d7a6 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Aug 2023 19:23:13 +0930 Subject: [PATCH 49/53] Improve huawei guarding --- app/build.gradle | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d15bca89da..61f5601891 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,17 +41,6 @@ def abiPostFix = ['armeabi-v7a' : 1, 'x86_64' : 4, 'universal' : 5] -tasks.register('checkHuaweiEnabled') { - ext.huaweiEnabled = project.hasProperty('huawei') - doFirst { - if (!huaweiEnabled) { - def message = 'Huawei is not enabled. Please add -Phuawei command line arg. See README.' - logger.error(message) - throw new GradleException(message) - } - } -} - android { compileSdkVersion androidCompileSdkVersion namespace 'network.loki.messenger' @@ -193,9 +182,19 @@ android { viewBinding true } - applicationVariants.all { variant -> + def huaweiEnabled = project.properties['huawei'] != null + + applicationVariants.configureEach { variant -> if (variant.flavorName == 'huawei') { - variant.getPreBuildProvider().get().dependsOn checkHuaweiEnabled + variant.getPreBuildProvider().configure { task -> + task.doFirst { + if (!huaweiEnabled) { + def message = 'Huawei is not enabled. Please add -Phuawei command line arg. See BUILDING.md' + logger.error(message) + throw new GradleException(message) + } + } + } } } } From 550955f5307ecede4240d263013c0f30046bba3a Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 16 Aug 2023 20:54:07 +0930 Subject: [PATCH 50/53] Add huawei strings --- app/src/huawei/res/values/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/src/huawei/res/values/strings.xml diff --git a/app/src/huawei/res/values/strings.xml b/app/src/huawei/res/values/strings.xml new file mode 100644 index 0000000000..78d42b3e30 --- /dev/null +++ b/app/src/huawei/res/values/strings.xml @@ -0,0 +1,5 @@ + + + You\'ll be notified of new messages reliably and immediately using Huawei’s notification servers. + You\'ll be notified of new messages reliably and immediately using Huawei’s notification servers. + \ No newline at end of file From ae9d3810e156ce20c1522212369e506cefc0f089 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Aug 2023 14:02:22 +0930 Subject: [PATCH 51/53] Fix website flavor TokenFetcher --- .../thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt index 1705eefe09..875518354b 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpTokenFetcher.kt @@ -1,12 +1,9 @@ package org.thoughtcrime.securesms.notifications -import kotlinx.coroutines.Job -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Singleton @Singleton class NoOpTokenFetcher @Inject constructor() : TokenFetcher { - override fun fetch(): Job = MainScope().launch { } + override suspend fun fetch(): String? = null } From 296c5d743f825122cb6ec003a442be35f9895aeb Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Aug 2023 22:34:13 +0930 Subject: [PATCH 52/53] Limit scope of huawei dependencies --- build.gradle | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 90266d56cb..2cb531a7c1 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,12 @@ buildscript { repositories { google() mavenCentral() - maven { url 'https://developer.huawei.com/repo/' } + if (project.hasProperty('huawei')) maven { + url 'https://developer.huawei.com/repo/' + content { + includeGroup 'com.huawei.agconnect' + } + } } dependencies { @@ -58,7 +63,15 @@ allprojects { } jcenter() maven { url "https://jitpack.io" } - if (project.hasProperty('huawei')) maven { url 'https://developer.huawei.com/repo/' } + if (project.hasProperty('huawei')) maven { + url 'https://developer.huawei.com/repo/' + content { + includeGroup 'com.huawei.android.hms' + includeGroup 'com.huawei.agconnect' + includeGroup 'com.huawei.hmf' + includeGroup 'com.huawei.hms' + } + } } project.ext { From 7861eb25c2cf45c9da68460a6b73a5aefa11c487 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 17 Aug 2023 22:36:05 +0930 Subject: [PATCH 53/53] Improve param naming for isPushEnabled --- .../notifications/PushRegistryV1.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt index ac5fce6d50..1599dd93d5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushRegistryV1.kt @@ -28,12 +28,12 @@ object PushRegistryV1 { fun register( device: Device, - isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), + isPushEnabled: Boolean = TextSecurePreferences.isPushEnabled(context), token: String? = TextSecurePreferences.getPushToken(context), publicKey: String? = TextSecurePreferences.getLocalNumber(context), legacyGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() ): Promise<*, Exception> = when { - isUsingFCM -> retryIfNeeded(maxRetryCount) { + isPushEnabled -> retryIfNeeded(maxRetryCount) { Log.d(TAG, "register() called") doRegister(token, publicKey, device, legacyGroupPublicKeys) } fail { exception -> @@ -99,17 +99,17 @@ object PushRegistryV1 { fun subscribeGroup( closedGroupPublicKey: String, - isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), + isPushEnabled: Boolean = TextSecurePreferences.isPushEnabled(context), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = if (isUsingFCM) { + ) = if (isPushEnabled) { performGroupOperation("subscribe_closed_group", closedGroupPublicKey, publicKey) } else emptyPromise() fun unsubscribeGroup( closedGroupPublicKey: String, - isUsingFCM: Boolean = TextSecurePreferences.isPushEnabled(context), + isPushEnabled: Boolean = TextSecurePreferences.isPushEnabled(context), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = if (isUsingFCM) { + ) = if (isPushEnabled) { performGroupOperation("unsubscribe_closed_group", closedGroupPublicKey, publicKey) } else emptyPromise()