diff --git a/.gitignore b/.gitignore
index 01ec4c41c..1fe35a0e7 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 a2bb94092..a8a869fc5 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 6755addc0..7912ab973 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 a5333ef5d..62aaf58f1 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 033b3ef45..000000000
--- 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 000000000..cd4e00416
--- /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 9b1e01a54..72df05798 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 ef73325f3..e660a774b 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 5b4ce8d13..000000000
--- 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 000000000..36aca1c91
--- /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 b7a92e3b7..6e4f8d104 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 9cf9c3d04..e440ba21d 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 9ae78fc5c..efc20aca4 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 187713df9..000000000
--- 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 000000000..f5b54fd47
--- /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 87a9efc0d..de3d14d43 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 000000000..0865c25c9
--- /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 000000000..983973d96
--- /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 e37b5b0b6..78b6cd4a1 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 e49375bab..cf037b019 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"}.")