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/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
-----------------
diff --git a/app/build.gradle b/app/build.gradle
index 74b9f84f07..61f5601891 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -24,15 +24,181 @@ 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'
-
configurations.all {
exclude module: "commons-logging"
}
+def canonicalVersionCode = 354
+def canonicalVersionName = "1.17.0"
+
+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
+ }
+ }
+
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.4.7'
+ }
+
+ 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 "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID"
+ buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
+ }
+
+ huawei {
+ dimension "distribution"
+ ext.websiteUpdateUrl = "null"
+ 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 {
+ dimension "distribution"
+ ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases"
+ buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
+ buildConfigField "org.session.libsession.utilities.Device", "DEVICE", "org.session.libsession.utilities.Device.ANDROID"
+ 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
+ }
+
+ def huaweiEnabled = project.properties['huawei'] != null
+
+ applicationVariants.configureEach { variant ->
+ if (variant.flavorName == 'huawei') {
+ 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)
+ }
+ }
+ }
+ }
+ }
+}
+
dependencies {
implementation("com.google.dagger:hilt-android:2.46.1")
@@ -59,11 +225,12 @@ 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'
}
+ 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'
@@ -176,144 +343,6 @@ dependencies {
implementation 'androidx.compose.material:material:1.5.0-alpha02'
}
-def canonicalVersionCode = 354
-def canonicalVersionName = "1.17.0"
-
-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
- }
- }
-
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion '1.4.7'
- }
-
- 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/huawei/AndroidManifest.xml b/app/src/huawei/AndroidManifest.xml
new file mode 100644
index 0000000000..dad7ab3ac6
--- /dev/null
+++ b/app/src/huawei/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/huawei/agconnect-services.json b/app/src/huawei/agconnect-services.json
new file mode 100644
index 0000000000..0c81d0477a
--- /dev/null
+++ b/app/src/huawei/agconnect-services.json
@@ -0,0 +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":"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/HuaweiPushModule.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
new file mode 100644
index 0000000000..26a484df16
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushModule.kt
@@ -0,0 +1,13 @@
+package org.thoughtcrime.securesms.notifications
+
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class HuaweiBindingModule {
+ @Binds
+ abstract fun bindTokenFetcher(tokenFetcher: HuaweiTokenFetcher): TokenFetcher
+}
diff --git a/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt
new file mode 100644
index 0000000000..dc7bf893d7
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiPushService.kt
@@ -0,0 +1,40 @@
+package org.thoughtcrime.securesms.notifications
+
+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
+import java.lang.Exception
+import javax.inject.Inject
+
+private val TAG = HuaweiPushService::class.java.simpleName
+
+@AndroidEntryPoint
+class HuaweiPushService: HmsMessageService() {
+ @Inject lateinit var pushRegistry: PushRegistry
+ @Inject lateinit var pushReceiver: PushReceiver
+
+ override fun onMessageReceived(message: RemoteMessage?) {
+ Log.d(TAG, "onMessageReceived")
+ message?.dataOfMap?.takeIf { it.isNotEmpty() }?.let(pushReceiver::onPush) ?:
+ pushReceiver.onPush(message?.data?.let(Base64::decode))
+ }
+
+ override fun onNewToken(token: String?) {
+ pushRegistry.register(token)
+ }
+
+ override fun onNewToken(token: String?, bundle: Bundle?) {
+ Log.d(TAG, "New HCM token: $token.")
+ pushRegistry.register(token)
+ }
+
+ override fun onDeletedMessages() {
+ Log.d(TAG, "onDeletedMessages")
+ pushRegistry.refresh(false)
+ }
+}
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..9d9b61ce9a
--- /dev/null
+++ b/app/src/huawei/kotlin/org/thoughtcrime/securesms/notifications/HuaweiTokenFetcher.kt
@@ -0,0 +1,29 @@
+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,
+ private val pushRegistry: Lazy,
+): TokenFetcher {
+ 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/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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aa81fafc2b..cc63ab6a87 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -310,14 +310,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.home.HomeActivity" />
-
-
-
-
-
KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this),
configFactory
@@ -226,10 +230,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) {
- registerForFCMIfNeeded(false);
- }
initializeExpiringMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
@@ -427,33 +427,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
private static class ProviderInitializationException extends RuntimeException { }
-
- public void registerForFCMIfNeeded(final Boolean force) {
- if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive() && !force) return;
- if (force && firebaseInstanceIdJob != null) {
- firebaseInstanceIdJob.cancel(null);
- }
- firebaseInstanceIdJob = FcmUtils.getFcmInstanceId(task->{
- 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)) {
- LokiPushNotificationManager.register(token, userPublicKey, this, force);
- } else {
- LokiPushNotificationManager.unregister(token, this);
- }
- });
-
- return Unit.INSTANCE;
- });
- }
-
private void setUpPollingIfNeeded() {
String userPublicKey = TextSecurePreferences.getLocalNumber(this);
if (userPublicKey == null) return;
@@ -524,18 +497,14 @@ 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);
- }
if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) {
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/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/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/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
index c77ad1c638..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.PushNotificationAPI
+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
- PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, group.sessionId, 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/groups/ClosedGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ClosedGroupManager.kt
index 8b362d70d1..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.PushNotificationAPI
+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
- PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Unsubscribe, groupPublicKey, 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/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/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt
index 72bd098f4f..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,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.PushRegistry
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 pushRegistry: PushRegistry
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.registerForFCMIfNeeded(false)
+ pushRegistry.refresh(false)
if (textSecurePreferences.getLocalNumber() != null) {
OpenGroupManager.startPolling()
JobQueue.shared.resumePendingJobs()
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/FcmUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt
deleted file mode 100644
index 87a9efc0de..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-@file:JvmName("FcmUtils")
-package org.thoughtcrime.securesms.notifications
-
-import com.google.android.gms.tasks.Task
-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
- while (!task.isComplete && isActive) {
- // 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)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt
deleted file mode 100644
index adaec0e17a..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.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 LokiPushNotificationManager {
- 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