mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 10:05:15 +00:00
Paged conversation recycler, update compile sdk version 31 (#1049)
* Update build tools
* Update appcompat version
* Update dependencies
* feat: add paging into conversation recycler and queries to fetch data off-thread
* refactor: wip for updating paged results and bucketing messages / fetching enough to display
* fix: currently works for scrolling and possibly refreshing? need scroll to message and auto scroll down on insert (at bottom)
* fix: search and scrolling to X message works now
* build: increase version code and name
* fix: re-add refresh, remove the outdated comment
* refactor: lets see if 25 size pages increases performance 👀
* feat: add in some equals overrides for mms records to refresh if media has finished DLing
* feat: add scroll to bottom for new messages if we are at the end of the chat
* build: update build numbers
* fix: update AGP and fix compile errors for sdk version 31
* feat: add log for loki-avatar and loki-fs on upload types and responses
* feat: increase build number to match latest installed version
* feat: changing props and permission checks for call service
* fix: possible service exception when no call ID remote foreground service not terminated
* revert: google services version
* fix: re-add paging dependency
* feat: adding new last seen function and figuring out the last seen for recycler adapter
* build: update version names and codes for deploy
* refactor: undo the new adapter and query changes to use previous cursor logic. revert this commit to enable new paged adapter
* fix: use author's address in typist equality and hashcode for set inclusion
* refactor: refactor the select contacts activity
* refactor: refactor the select contacts activity
* build: update version code
* fix: hide all other bound views if deleted
* refactor: change voice message tint, upgrade build number
* fix: message detail showing up properly
* revert: realise copy public key is actually not allowed if open group participant
* fix: copy session ID, message detail activity support re-enabled
* build: update build version code
* build: remove version name
* build: update build code
* feat: google services version minimum compatible
* fix: selection for re-created objects not properly highlighting
* fix: foreground CENTER_INSIDE instead of just CENTER for scaletype
* build: update version code
* fix: don't show error if no error
* build: update version code
* fix: clear error messages if any on successful send
Co-authored-by: charles <charles@oxen.io>
This commit is contained in:
parent
bda50d263c
commit
cdd2559839
@ -4,11 +4,11 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
classpath "com.android.tools.build:gradle:$gradlePluginVersion"
|
||||||
classpath files('libs/gradle-witness.jar')
|
classpath files('libs/gradle-witness.jar')
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
|
||||||
classpath "com.google.gms:google-services:4.3.10"
|
classpath "com.google.gms:google-services:$googleServicesVersion"
|
||||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion"
|
classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,26 +27,27 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation "com.google.android.material:material:$materialVersion"
|
||||||
implementation 'com.google.android:flexbox:2.0.1'
|
implementation 'com.google.android:flexbox:2.0.1'
|
||||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.3'
|
implementation 'androidx.exifinterface:exifinterface:1.3.4'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
|
||||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
|
||||||
implementation 'androidx.activity:activity-ktx:1.2.2'
|
implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.3.2'
|
implementation 'androidx.activity:activity-ktx:1.5.1'
|
||||||
implementation "androidx.core:core-ktx:1.3.2"
|
implementation 'androidx.fragment:fragment-ktx:1.5.3'
|
||||||
implementation "androidx.work:work-runtime-ktx:2.4.0"
|
implementation "androidx.core:core-ktx:$coreVersion"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
||||||
implementation ("com.google.firebase:firebase-messaging:18.0.0") {
|
implementation ("com.google.firebase:firebase-messaging:18.0.0") {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
@ -119,7 +120,7 @@ dependencies {
|
|||||||
implementation "com.github.tbruyelle:rxpermissions:0.10.2"
|
implementation "com.github.tbruyelle:rxpermissions:0.10.2"
|
||||||
implementation "com.github.ybq:Android-SpinKit:1.4.0"
|
implementation "com.github.ybq:Android-SpinKit:1.4.0"
|
||||||
implementation "com.opencsv:opencsv:4.6"
|
implementation "com.opencsv:opencsv:4.6"
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation "junit:junit:$junitVersion"
|
||||||
testImplementation 'org.assertj:assertj-core:3.11.1'
|
testImplementation 'org.assertj:assertj-core:3.11.1'
|
||||||
testImplementation "org.mockito:mockito-inline:4.0.0"
|
testImplementation "org.mockito:mockito-inline:4.0.0"
|
||||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||||
@ -127,7 +128,7 @@ dependencies {
|
|||||||
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
||||||
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
||||||
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
||||||
testImplementation 'androidx.test:core:1.3.0'
|
testImplementation "androidx.test:core:$testCoreVersion"
|
||||||
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
|
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
|
||||||
@ -141,7 +142,7 @@ dependencies {
|
|||||||
// Assertions
|
// Assertions
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.ext:truth:1.4.0'
|
androidTestImplementation 'androidx.test.ext:truth:1.4.0'
|
||||||
androidTestImplementation 'com.google.truth:truth:1.0'
|
androidTestImplementation 'com.google.truth:truth:1.1.3'
|
||||||
|
|
||||||
// Espresso dependencies
|
// Espresso dependencies
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
@ -151,14 +152,14 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0'
|
||||||
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0'
|
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
|
||||||
androidTestUtil 'androidx.test:orchestrator:1.4.0'
|
androidTestUtil 'androidx.test:orchestrator:1.4.1'
|
||||||
|
|
||||||
testImplementation 'org.robolectric:robolectric:4.4'
|
testImplementation 'org.robolectric:robolectric:4.4'
|
||||||
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 310
|
def canonicalVersionCode = 321
|
||||||
def canonicalVersionName = "1.16.1"
|
def canonicalVersionName = "1.16.3"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
@ -169,13 +170,9 @@ def abiPostFix = ['armeabi-v7a' : 1,
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion androidCompileSdkVersion
|
compileSdkVersion androidCompileSdkVersion
|
||||||
buildToolsVersion '29.0.3'
|
namespace 'network.loki.messenger'
|
||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
|
|
||||||
dexOptions {
|
|
||||||
javaMaxHeapSize "4g"
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
@ -209,7 +206,7 @@ android {
|
|||||||
versionName canonicalVersionName
|
versionName canonicalVersionName
|
||||||
|
|
||||||
minSdkVersion androidMinimumSdkVersion
|
minSdkVersion androidMinimumSdkVersion
|
||||||
targetSdkVersion androidCompileSdkVersion
|
targetSdkVersion androidTargetSdkVersion
|
||||||
|
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest
|
<manifest
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
package="network.loki.messenger">
|
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference" />
|
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference" />
|
||||||
|
|
||||||
@ -31,6 +30,7 @@
|
|||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="network.loki.messenger.ACCESS_SESSION_SECRETS" />
|
<uses-permission android:name="network.loki.messenger.ACCESS_SESSION_SECRETS" />
|
||||||
@ -174,6 +174,7 @@
|
|||||||
android:screenOrientation="portrait"/>
|
android:screenOrientation="portrait"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="true"
|
||||||
android:name="org.thoughtcrime.securesms.ShareActivity"
|
android:name="org.thoughtcrime.securesms.ShareActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
@ -321,6 +322,7 @@
|
|||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name="org.thoughtcrime.securesms.service.DirectShareService"
|
android:name="org.thoughtcrime.securesms.service.DirectShareService"
|
||||||
|
android:exported="true"
|
||||||
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.chooser.ChooserTargetService" />
|
<action android:name="android.service.chooser.ChooserTargetService" />
|
||||||
@ -398,42 +400,48 @@
|
|||||||
android:authorities="network.loki.securesms.database.recipient"
|
android:authorities="network.loki.securesms.database.recipient"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver android:name="org.thoughtcrime.securesms.service.BootReceiver">
|
<receiver android:name="org.thoughtcrime.securesms.service.BootReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="network.loki.securesms.RESTART" />
|
<action android:name="network.loki.securesms.RESTART" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name="org.thoughtcrime.securesms.service.LocalBackupListener">
|
<receiver android:name="org.thoughtcrime.securesms.service.LocalBackupListener"
|
||||||
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name="org.thoughtcrime.securesms.service.PersistentConnectionBootListener">
|
<receiver android:name="org.thoughtcrime.securesms.service.PersistentConnectionBootListener"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name="org.thoughtcrime.securesms.notifications.LocaleChangedReceiver">
|
<receiver android:name="org.thoughtcrime.securesms.notifications.LocaleChangedReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name="org.thoughtcrime.securesms.notifications.DeleteNotificationReceiver">
|
<receiver android:name="org.thoughtcrime.securesms.notifications.DeleteNotificationReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="network.loki.securesms.DELETE_NOTIFICATION" />
|
<action android:name="network.loki.securesms.DELETE_NOTIFICATION" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name="org.thoughtcrime.securesms.service.PanicResponderListener"
|
android:name="org.thoughtcrime.securesms.service.PanicResponderListener"
|
||||||
android:exported="true">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name="org.thoughtcrime.securesms.notifications.BackgroundPollWorker$BootBroadcastReceiver"
|
android:name="org.thoughtcrime.securesms.notifications.BackgroundPollWorker$BootBroadcastReceiver"
|
||||||
android:enabled="true">
|
android:enabled="true"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
@ -481,6 +481,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return;
|
if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return;
|
||||||
ThreadUtils.queue(() -> {
|
ThreadUtils.queue(() -> {
|
||||||
// Don't generate a new profile key here; we do that when the user changes their profile picture
|
// Don't generate a new profile key here; we do that when the user changes their profile picture
|
||||||
|
Log.d("Loki-Avatar", "Uploading Avatar Started");
|
||||||
String encodedProfileKey = TextSecurePreferences.getProfileKey(ApplicationContext.this);
|
String encodedProfileKey = TextSecurePreferences.getProfileKey(ApplicationContext.this);
|
||||||
try {
|
try {
|
||||||
// Read the file into a byte array
|
// Read the file into a byte array
|
||||||
@ -497,6 +498,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
ProfilePictureUtilities.INSTANCE.upload(profilePicture, encodedProfileKey, ApplicationContext.this).success(unit -> {
|
ProfilePictureUtilities.INSTANCE.upload(profilePicture, encodedProfileKey, ApplicationContext.this).success(unit -> {
|
||||||
// Update the last profile picture upload date
|
// Update the last profile picture upload date
|
||||||
TextSecurePreferences.setLastProfilePictureUpload(ApplicationContext.this, new Date().getTime());
|
TextSecurePreferences.setLastProfilePictureUpload(ApplicationContext.this, new Date().getTime());
|
||||||
|
Log.d("Loki-Avatar", "Uploading Avatar Finished");
|
||||||
return Unit.INSTANCE;
|
return Unit.INSTANCE;
|
||||||
});
|
});
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.backup;
|
|
||||||
|
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.thoughtcrime.securesms.util.BackupDirSelector;
|
|
||||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
|
||||||
|
|
||||||
import org.session.libsession.utilities.Util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public class BackupDialog {
|
|
||||||
private static final String TAG = "BackupDialog";
|
|
||||||
|
|
||||||
public static void showEnableBackupDialog(
|
|
||||||
@NonNull Context context,
|
|
||||||
@NonNull SwitchPreferenceCompat preference,
|
|
||||||
@NonNull BackupDirSelector backupDirSelector) {
|
|
||||||
|
|
||||||
String[] password = BackupUtil.generateBackupPassphrase();
|
|
||||||
String passwordSt = Util.join(password, "");
|
|
||||||
|
|
||||||
AlertDialog dialog = new AlertDialog.Builder(context)
|
|
||||||
.setTitle(R.string.BackupDialog_enable_local_backups)
|
|
||||||
.setView(R.layout.backup_enable_dialog)
|
|
||||||
.setPositiveButton(R.string.BackupDialog_enable_backups, null)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
dialog.setOnShowListener(created -> {
|
|
||||||
Button button = ((AlertDialog) created).getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
button.setOnClickListener(v -> {
|
|
||||||
CheckBox confirmationCheckBox = dialog.findViewById(R.id.confirmation_check);
|
|
||||||
if (confirmationCheckBox.isChecked()) {
|
|
||||||
backupDirSelector.selectBackupDir(true, uri -> {
|
|
||||||
try {
|
|
||||||
BackupUtil.enableBackups(context, passwordSt);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Failed to activate backups.", e);
|
|
||||||
Toast.makeText(context,
|
|
||||||
context.getString(R.string.dialog_backup_activation_failed),
|
|
||||||
Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
preference.setChecked(true);
|
|
||||||
created.dismiss();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, R.string.BackupDialog_please_acknowledge_your_understanding_by_marking_the_confirmation_check_box, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
CheckBox checkBox = dialog.findViewById(R.id.confirmation_check);
|
|
||||||
TextView textView = dialog.findViewById(R.id.confirmation_text);
|
|
||||||
|
|
||||||
((TextView)dialog.findViewById(R.id.code_first)).setText(password[0]);
|
|
||||||
((TextView)dialog.findViewById(R.id.code_second)).setText(password[1]);
|
|
||||||
((TextView)dialog.findViewById(R.id.code_third)).setText(password[2]);
|
|
||||||
|
|
||||||
((TextView)dialog.findViewById(R.id.code_fourth)).setText(password[3]);
|
|
||||||
((TextView)dialog.findViewById(R.id.code_fifth)).setText(password[4]);
|
|
||||||
((TextView)dialog.findViewById(R.id.code_sixth)).setText(password[5]);
|
|
||||||
|
|
||||||
textView.setOnClickListener(v -> checkBox.toggle());
|
|
||||||
|
|
||||||
dialog.findViewById(R.id.number_table).setOnClickListener(v -> {
|
|
||||||
((ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", passwordSt));
|
|
||||||
Toast.makeText(context, R.string.BackupDialog_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void showDisableBackupDialog(@NonNull Context context, @NonNull SwitchPreferenceCompat preference) {
|
|
||||||
new AlertDialog.Builder(context)
|
|
||||||
.setTitle(R.string.BackupDialog_delete_backups)
|
|
||||||
.setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.BackupDialog_delete_backups_statement, (dialog, which) -> {
|
|
||||||
BackupUtil.disableBackups(context, true);
|
|
||||||
preference.setChecked(false);
|
|
||||||
})
|
|
||||||
.create()
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,206 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.backup
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.provider.OpenableColumns
|
|
||||||
import android.text.Spannable
|
|
||||||
import android.text.SpannableStringBuilder
|
|
||||||
import android.text.style.ClickableSpan
|
|
||||||
import android.text.style.StyleSpan
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.google.android.gms.common.util.Strings
|
|
||||||
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.libsignal.utilities.Log
|
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
|
||||||
import org.thoughtcrime.securesms.backup.FullBackupImporter.DatabaseDowngradeException
|
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
|
||||||
import org.thoughtcrime.securesms.home.HomeActivity
|
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
|
||||||
import org.thoughtcrime.securesms.util.BackupUtil
|
|
||||||
import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo
|
|
||||||
import org.thoughtcrime.securesms.util.show
|
|
||||||
|
|
||||||
class BackupRestoreActivity : BaseActionBarActivity() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "BackupRestoreActivity"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val viewModel by viewModels<BackupRestoreViewModel>()
|
|
||||||
|
|
||||||
private val fileSelectionResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()
|
|
||||||
) { result: ActivityResult ->
|
|
||||||
if (result.resultCode == Activity.RESULT_OK && result.data != null && result.data!!.data != null) {
|
|
||||||
viewModel.backupFile.value = result.data!!.data!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setUpActionBarSessionLogo()
|
|
||||||
|
|
||||||
// val viewBinding = DataBindingUtil.setContentView<ActivityBackupRestoreBinding>(this, R.layout.activity_backup_restore)
|
|
||||||
// viewBinding.lifecycleOwner = this
|
|
||||||
// viewBinding.viewModel = viewModel
|
|
||||||
|
|
||||||
// viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() }
|
|
||||||
|
|
||||||
// viewBinding.buttonSelectFile.setOnClickListener {
|
|
||||||
// fileSelectionResultLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
|
||||||
// //FIXME On some old APIs (tested on 21 & 23) the mime type doesn't filter properly
|
|
||||||
// // and the backup files are unavailable for selection.
|
|
||||||
//// type = BackupUtil.BACKUP_FILE_MIME_TYPE
|
|
||||||
// type = "*/*"
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// viewBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() }
|
|
||||||
|
|
||||||
// Focus passphrase text edit when backup file is selected.
|
|
||||||
// viewModel.backupFile.observe(this, { backupFile ->
|
|
||||||
// if (backupFile != null) viewBinding.backupCode.post {
|
|
||||||
// viewBinding.backupCode.requestFocus()
|
|
||||||
// (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager)
|
|
||||||
// .showSoftInput(viewBinding.backupCode, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// React to backup import result.
|
|
||||||
viewModel.backupImportResult.observe(this) { result ->
|
|
||||||
if (result != null) when (result) {
|
|
||||||
BackupRestoreViewModel.BackupRestoreResult.SUCCESS -> {
|
|
||||||
val intent = Intent(this, HomeActivity::class.java)
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
this.show(intent)
|
|
||||||
}
|
|
||||||
BackupRestoreViewModel.BackupRestoreResult.FAILURE_VERSION_DOWNGRADE ->
|
|
||||||
Toast.makeText(this, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show()
|
|
||||||
BackupRestoreViewModel.BackupRestoreResult.FAILURE_UNKNOWN ->
|
|
||||||
Toast.makeText(this, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//region Legal info views
|
|
||||||
val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy")
|
|
||||||
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
termsExplanation.setSpan(object : ClickableSpan() {
|
|
||||||
override fun onClick(widget: View) {
|
|
||||||
openURL("https://getsession.org/terms-of-service/")
|
|
||||||
}
|
|
||||||
}, 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
termsExplanation.setSpan(object : ClickableSpan() {
|
|
||||||
override fun onClick(widget: View) {
|
|
||||||
openURL("https://getsession.org/privacy-policy/")
|
|
||||||
}
|
|
||||||
}, 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
// viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance()
|
|
||||||
// viewBinding.termsTextView.text = termsExplanation
|
|
||||||
//endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openURL(url: String) {
|
|
||||||
try {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
||||||
startActivity(intent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BackupRestoreViewModel(application: Application): AndroidViewModel(application) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "BackupRestoreViewModel"
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun uriToFileName(view: View, fileUri: Uri?): String? {
|
|
||||||
fileUri ?: return null
|
|
||||||
|
|
||||||
view.context.contentResolver.query(fileUri, null, null, null, null).use {
|
|
||||||
val nameIndex = it!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
||||||
it.moveToFirst()
|
|
||||||
return it.getString(nameIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun validateData(fileUri: Uri?, passphrase: String?): Boolean {
|
|
||||||
return fileUri != null &&
|
|
||||||
!Strings.isEmptyOrWhitespace(passphrase) &&
|
|
||||||
passphrase!!.length == BackupUtil.BACKUP_PASSPHRASE_LENGTH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val backupFile = MutableLiveData<Uri>(null)
|
|
||||||
val backupPassphrase = MutableLiveData<String>(null)
|
|
||||||
|
|
||||||
val processingBackupFile = MutableLiveData<Boolean>(false)
|
|
||||||
val backupImportResult = MutableLiveData<BackupRestoreResult>(null)
|
|
||||||
|
|
||||||
fun tryRestoreBackup() = viewModelScope.launch {
|
|
||||||
if (processingBackupFile.value == true) return@launch
|
|
||||||
if (backupImportResult.value == BackupRestoreResult.SUCCESS) return@launch
|
|
||||||
if (!validateData(backupFile.value, backupPassphrase.value)) return@launch
|
|
||||||
|
|
||||||
val context = getApplication<Application>()
|
|
||||||
val backupFile = backupFile.value!!
|
|
||||||
val passphrase = backupPassphrase.value!!
|
|
||||||
|
|
||||||
val result: BackupRestoreResult
|
|
||||||
|
|
||||||
processingBackupFile.value = true
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
result = try {
|
|
||||||
val database = DatabaseComponent.get(context).openHelper().readableDatabase
|
|
||||||
FullBackupImporter.importFromUri(
|
|
||||||
context,
|
|
||||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
|
||||||
database,
|
|
||||||
backupFile,
|
|
||||||
passphrase
|
|
||||||
)
|
|
||||||
DatabaseFactory.upgradeRestored(context, database)
|
|
||||||
NotificationChannels.restoreContactNotificationChannels(context)
|
|
||||||
TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis())
|
|
||||||
TextSecurePreferences.setHasViewedSeed(context, true)
|
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(context, true)
|
|
||||||
|
|
||||||
BackupRestoreResult.SUCCESS
|
|
||||||
} catch (e: DatabaseDowngradeException) {
|
|
||||||
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e)
|
|
||||||
BackupRestoreResult.FAILURE_VERSION_DOWNGRADE
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, e)
|
|
||||||
BackupRestoreResult.FAILURE_UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processingBackupFile.value = false
|
|
||||||
|
|
||||||
backupImportResult.value = result
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class BackupRestoreResult {
|
|
||||||
SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,6 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.loader.app.LoaderManager
|
import androidx.loader.app.LoaderManager
|
||||||
import androidx.loader.content.Loader
|
import androidx.loader.content.Loader
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
|
||||||
import network.loki.messenger.databinding.ContactSelectionListFragmentBinding
|
import network.loki.messenger.databinding.ContactSelectionListFragmentBinding
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
@ -58,7 +57,6 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
binding.recyclerView.layoutManager = LinearLayoutManager(activity)
|
binding.recyclerView.layoutManager = LinearLayoutManager(activity)
|
||||||
binding.recyclerView.adapter = listAdapter
|
binding.recyclerView.adapter = listAdapter
|
||||||
binding.swipeRefreshLayout.isEnabled = requireActivity().intent.getBooleanExtra(REFRESHABLE, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
@ -73,15 +71,6 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
|
|||||||
|
|
||||||
fun resetQueryFilter() {
|
fun resetQueryFilter() {
|
||||||
setQueryFilter(null)
|
setQueryFilter(null)
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setRefreshing(refreshing: Boolean) {
|
|
||||||
binding.swipeRefreshLayout.isRefreshing = refreshing
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setOnRefreshListener(onRefreshListener: OnRefreshListener?) {
|
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener(onRefreshListener)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ContactSelectionListItem>> {
|
override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ContactSelectionListItem>> {
|
||||||
@ -106,7 +95,7 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
listAdapter.items = items
|
listAdapter.items = items
|
||||||
binding.mainContentContainer.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE
|
binding.recyclerView.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE
|
||||||
binding.emptyStateContainer.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE
|
binding.emptyStateContainer.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,12 @@ package org.thoughtcrime.securesms.contacts
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.loader.app.LoaderManager
|
|
||||||
import androidx.loader.content.Loader
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.loader.app.LoaderManager
|
||||||
|
import androidx.loader.content.Loader
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ActivitySelectContactsBinding
|
import network.loki.messenger.databinding.ActivitySelectContactsBinding
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
@ -49,7 +49,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana
|
|||||||
LoaderManager.getInstance(this).initLoader(0, null, this)
|
LoaderManager.getInstance(this).initLoader(0, null, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_done, menu)
|
menuInflater.inflate(R.menu.menu_done, menu)
|
||||||
return members.isNotEmpty()
|
return members.isNotEmpty()
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana
|
|||||||
|
|
||||||
private fun update(members: List<String>) {
|
private fun update(members: List<String>) {
|
||||||
this.members = members
|
this.members = members
|
||||||
binding.mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
|
binding.recyclerView.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
|
||||||
binding.emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
|
binding.emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.paging
|
||||||
|
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.paging.Pager
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.SessionContactDatabase
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
|
|
||||||
|
private const val TIME_BUCKET = 600000L // bucket into 10 minute increments
|
||||||
|
|
||||||
|
private fun config() = PagingConfig(
|
||||||
|
pageSize = 25,
|
||||||
|
maxSize = 100,
|
||||||
|
enablePlaceholders = false
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Long.bucketed(): Long = (TIME_BUCKET - this % TIME_BUCKET) + this
|
||||||
|
|
||||||
|
fun conversationPager(threadId: Long, initialKey: PageLoad? = null, db: MmsSmsDatabase, contactDb: SessionContactDatabase) = Pager(config(), initialKey = initialKey) {
|
||||||
|
ConversationPagingSource(threadId, db, contactDb)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConversationPagerDiffCallback: DiffUtil.ItemCallback<MessageAndContact>() {
|
||||||
|
override fun areItemsTheSame(oldItem: MessageAndContact, newItem: MessageAndContact): Boolean =
|
||||||
|
oldItem.message.id == newItem.message.id && oldItem.message.isMms == newItem.message.isMms
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: MessageAndContact, newItem: MessageAndContact): Boolean =
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MessageAndContact(val message: MessageRecord,
|
||||||
|
val contact: Contact?)
|
||||||
|
|
||||||
|
data class PageLoad(val fromTime: Long, val toTime: Long? = null)
|
||||||
|
|
||||||
|
class ConversationPagingSource(
|
||||||
|
private val threadId: Long,
|
||||||
|
private val messageDb: MmsSmsDatabase,
|
||||||
|
private val contactDb: SessionContactDatabase
|
||||||
|
): PagingSource<PageLoad, MessageAndContact>() {
|
||||||
|
|
||||||
|
override fun getRefreshKey(state: PagingState<PageLoad, MessageAndContact>): PageLoad? {
|
||||||
|
val anchorPosition = state.anchorPosition ?: return null
|
||||||
|
val anchorPage = state.closestPageToPosition(anchorPosition) ?: return null
|
||||||
|
val next = anchorPage.nextKey?.fromTime
|
||||||
|
val previous = anchorPage.prevKey?.fromTime ?: anchorPage.data.firstOrNull()?.message?.dateSent ?: return null
|
||||||
|
return PageLoad(previous, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val contactCache = mutableMapOf<String, Contact>()
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private fun getContact(sessionId: String): Contact? {
|
||||||
|
contactCache[sessionId]?.let { contact ->
|
||||||
|
return contact
|
||||||
|
} ?: run {
|
||||||
|
contactDb.getContactWithSessionID(sessionId)?.let { contact ->
|
||||||
|
contactCache[sessionId] = contact
|
||||||
|
return contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun load(params: LoadParams<PageLoad>): LoadResult<PageLoad, MessageAndContact> {
|
||||||
|
val pageLoad = params.key ?: withContext(Dispatchers.IO) {
|
||||||
|
messageDb.getConversationSnippet(threadId).use {
|
||||||
|
val reader = messageDb.readerFor(it)
|
||||||
|
var record: MessageRecord? = null
|
||||||
|
if (reader != null) {
|
||||||
|
record = reader.next
|
||||||
|
while (record != null && record.isDeleted) {
|
||||||
|
record = reader.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record?.dateSent?.let { fromTime ->
|
||||||
|
PageLoad(fromTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: return LoadResult.Page(emptyList(), null, null)
|
||||||
|
|
||||||
|
val result = withContext(Dispatchers.IO) {
|
||||||
|
val cursor = messageDb.getConversationPage(
|
||||||
|
threadId,
|
||||||
|
pageLoad.fromTime,
|
||||||
|
pageLoad.toTime ?: -1L,
|
||||||
|
params.loadSize
|
||||||
|
)
|
||||||
|
val processedList = mutableListOf<MessageAndContact>()
|
||||||
|
val reader = messageDb.readerFor(cursor)
|
||||||
|
while (reader.next != null && !invalid) {
|
||||||
|
reader.current?.let { item ->
|
||||||
|
val contact = getContact(item.individualRecipient.address.serialize())
|
||||||
|
processedList += MessageAndContact(item, contact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.close()
|
||||||
|
processedList.toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasNext = withContext(Dispatchers.IO) {
|
||||||
|
if (result.isEmpty()) return@withContext false
|
||||||
|
val lastTime = result.last().message.dateSent
|
||||||
|
messageDb.hasNextPage(threadId, lastTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextCheckTime = if (hasNext) {
|
||||||
|
val lastSent = result.last().message.dateSent
|
||||||
|
if (lastSent == pageLoad.fromTime) null else lastSent
|
||||||
|
} else null
|
||||||
|
|
||||||
|
val hasPrevious = withContext(Dispatchers.IO) { messageDb.hasPreviousPage(threadId, pageLoad.fromTime) }
|
||||||
|
val nextKey = if (!hasNext) null else nextCheckTime
|
||||||
|
val prevKey = if (!hasPrevious) null else messageDb.getPreviousPage(threadId, pageLoad.fromTime, params.loadSize)
|
||||||
|
|
||||||
|
return LoadResult.Page(
|
||||||
|
data = result, // next check time is not null if drop is true
|
||||||
|
prevKey = prevKey?.let { PageLoad(it, pageLoad.fromTime) },
|
||||||
|
nextKey = nextKey?.let { PageLoad(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -3,31 +3,18 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.animation.FloatEvaluator
|
import android.animation.FloatEvaluator
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.content.ClipData
|
import android.content.*
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.AsyncTask
|
import android.os.*
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Pair
|
import android.util.Pair
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.ActionMode
|
import android.view.*
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@ -68,12 +55,8 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
|||||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||||
import org.session.libsession.messaging.utilities.SessionId
|
import org.session.libsession.messaging.utilities.SessionId
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.*
|
||||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.MediaTypes
|
|
||||||
import org.session.libsession.utilities.Stub
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.concurrent.SimpleTask
|
import org.session.libsession.utilities.concurrent.SimpleTask
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsession.utilities.recipients.RecipientModifiedListener
|
import org.session.libsession.utilities.recipients.RecipientModifiedListener
|
||||||
@ -106,25 +89,10 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
|
|||||||
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
|
||||||
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
|
||||||
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
|
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
|
import org.thoughtcrime.securesms.conversation.v2.utilities.*
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
import org.thoughtcrime.securesms.database.*
|
||||||
import org.thoughtcrime.securesms.database.LokiAPIDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.LokiMessageDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.ReactionDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.SessionContactDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.Storage
|
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId
|
import org.thoughtcrime.securesms.database.model.MessageId
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||||
@ -137,25 +105,12 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
||||||
import org.thoughtcrime.securesms.mediasend.Media
|
import org.thoughtcrime.securesms.mediasend.Media
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
|
||||||
import org.thoughtcrime.securesms.mms.AudioSlide
|
import org.thoughtcrime.securesms.mms.*
|
||||||
import org.thoughtcrime.securesms.mms.GifSlide
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
|
||||||
import org.thoughtcrime.securesms.mms.MediaConstraints
|
|
||||||
import org.thoughtcrime.securesms.mms.Slide
|
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
|
||||||
import org.thoughtcrime.securesms.mms.VideoSlide
|
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
||||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
||||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
import org.thoughtcrime.securesms.util.*
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
import java.util.*
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil
|
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
|
||||||
import org.thoughtcrime.securesms.util.push
|
|
||||||
import org.thoughtcrime.securesms.util.toPx
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
@ -635,7 +590,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
this
|
this
|
||||||
) { onOptionsItemSelected(it) }
|
) { onOptionsItemSelected(it) }
|
||||||
}
|
}
|
||||||
super.onPrepareOptionsMenu(menu)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1789,6 +1743,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
endActionMode()
|
endActionMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun destroyActionMode() {
|
||||||
|
this.actionMode = null
|
||||||
|
}
|
||||||
|
|
||||||
private fun sendScreenshotNotification() {
|
private fun sendScreenshotNotification() {
|
||||||
val recipient = viewModel.recipient ?: return
|
val recipient = viewModel.recipient ?: return
|
||||||
if (recipient.isGroupRecipient) return
|
if (recipient.isGroupRecipient) return
|
||||||
@ -1834,7 +1792,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
if (result == null) return@Observer
|
if (result == null) return@Observer
|
||||||
if (result.getResults().isNotEmpty()) {
|
if (result.getResults().isNotEmpty()) {
|
||||||
result.getResults()[result.position]?.let {
|
result.getResults()[result.position]?.let {
|
||||||
jumpToMessage(it.messageRecipient.address, it.receivedTimestampMs) {
|
jumpToMessage(it.messageRecipient.address, it.sentTimestampMs) {
|
||||||
searchViewModel.onMissingResult() }
|
searchViewModel.onMissingResult() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1900,7 +1858,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
ConversationReactionOverlay.Action.DELETE -> deleteMessages(selectedItems)
|
ConversationReactionOverlay.Action.DELETE -> deleteMessages(selectedItems)
|
||||||
ConversationReactionOverlay.Action.BAN_AND_DELETE_ALL -> banAndDeleteAll(selectedItems)
|
ConversationReactionOverlay.Action.BAN_AND_DELETE_ALL -> banAndDeleteAll(selectedItems)
|
||||||
ConversationReactionOverlay.Action.BAN_USER -> banUser(selectedItems)
|
ConversationReactionOverlay.Action.BAN_USER -> banUser(selectedItems)
|
||||||
ConversationReactionOverlay.Action.COPY_SESSION_ID -> TODO()
|
ConversationReactionOverlay.Action.COPY_SESSION_ID -> copySessionID(selectedItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,6 @@ class ConversationViewModel(
|
|||||||
data class UiMessage(val id: Long, val message: String)
|
data class UiMessage(val id: Long, val message: String)
|
||||||
|
|
||||||
data class ConversationUiState(
|
data class ConversationUiState(
|
||||||
val isOxenHostedOpenGroup: Boolean = false,
|
|
||||||
val uiMessages: List<UiMessage> = emptyList(),
|
val uiMessages: List<UiMessage> = emptyList(),
|
||||||
val isMessageRequestAccepted: Boolean? = null
|
val isMessageRequestAccepted: Boolean? = null
|
||||||
)
|
)
|
||||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ActivityMessageDetailBinding
|
import network.loki.messenger.databinding.ActivityMessageDetailBinding
|
||||||
@ -20,8 +21,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
|
|||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.*
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@ -48,7 +48,10 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
|
|||||||
// We only show this screen for messages fail to send,
|
// We only show this screen for messages fail to send,
|
||||||
// so the author of the messages must be the current user.
|
// so the author of the messages must be the current user.
|
||||||
val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!)
|
val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!)
|
||||||
messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author)
|
messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
val threadId = messageRecord!!.threadId
|
val threadId = messageRecord!!.threadId
|
||||||
val openGroup = storage.getOpenGroup(threadId)
|
val openGroup = storage.getOpenGroup(threadId)
|
||||||
val blindedKey = openGroup?.let { group ->
|
val blindedKey = openGroup?.let { group ->
|
||||||
@ -71,8 +74,15 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
|
|||||||
val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale)
|
val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale)
|
||||||
binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent))
|
binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent))
|
||||||
|
|
||||||
val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) ?: "Message failed to send."
|
val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId())
|
||||||
binding.errorMessage.text = errorMessage
|
if (errorMessage != null) {
|
||||||
|
binding.errorMessage.text = errorMessage
|
||||||
|
binding.resendContainer.isVisible = true
|
||||||
|
binding.errorContainer.isVisible = true
|
||||||
|
} else {
|
||||||
|
binding.errorContainer.isVisible = false
|
||||||
|
binding.resendContainer.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) {
|
if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) {
|
||||||
binding.expiresContainer.visibility = View.GONE
|
binding.expiresContainer.visibility = View.GONE
|
||||||
|
@ -65,9 +65,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
|||||||
menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText
|
menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText
|
||||||
// Copy Session ID
|
// Copy Session ID
|
||||||
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
|
menu.findItem(R.id.menu_context_copy_public_key).isVisible =
|
||||||
(thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey)
|
(thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey)
|
||||||
// Message detail
|
// Message detail
|
||||||
menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
|
menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing)
|
||||||
// Resend
|
// Resend
|
||||||
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
|
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
|
||||||
// Save media
|
// Save media
|
||||||
@ -101,6 +101,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
|
|||||||
override fun onDestroyActionMode(mode: ActionMode) {
|
override fun onDestroyActionMode(mode: ActionMode) {
|
||||||
adapter.selectedItems.clear()
|
adapter.selectedItems.clear()
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
delegate?.destroyActionMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,4 +117,5 @@ interface ConversationActionModeCallbackDelegate {
|
|||||||
fun showMessageDetail(messages: Set<MessageRecord>)
|
fun showMessageDetail(messages: Set<MessageRecord>)
|
||||||
fun saveAttachment(messages: Set<MessageRecord>)
|
fun saveAttachment(messages: Set<MessageRecord>)
|
||||||
fun reply(messages: Set<MessageRecord>)
|
fun reply(messages: Set<MessageRecord>)
|
||||||
|
fun destroyActionMode()
|
||||||
}
|
}
|
@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord
|
|||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.util.SearchUtil
|
import org.thoughtcrime.securesms.util.SearchUtil
|
||||||
import org.thoughtcrime.securesms.util.getAccentColor
|
import org.thoughtcrime.securesms.util.getAccentColor
|
||||||
import java.util.Locale
|
import java.util.*
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class VisibleMessageContentView : LinearLayout {
|
class VisibleMessageContentView : LinearLayout {
|
||||||
@ -86,6 +86,14 @@ class VisibleMessageContentView : LinearLayout {
|
|||||||
if (message.isDeleted) {
|
if (message.isDeleted) {
|
||||||
binding.deletedMessageView.root.isVisible = true
|
binding.deletedMessageView.root.isVisible = true
|
||||||
binding.deletedMessageView.root.bind(message, getTextColor(context, message))
|
binding.deletedMessageView.root.bind(message, getTextColor(context, message))
|
||||||
|
binding.bodyTextView.isVisible = false
|
||||||
|
binding.quoteView.root.isVisible = false
|
||||||
|
binding.linkPreviewView.isVisible = false
|
||||||
|
binding.untrustedView.root.isVisible = false
|
||||||
|
binding.voiceMessageView.root.isVisible = false
|
||||||
|
binding.documentView.root.isVisible = false
|
||||||
|
binding.albumThumbnailView.isVisible = false
|
||||||
|
binding.openGroupInvitationView.root.isVisible = false
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
binding.deletedMessageView.root.isVisible = false
|
binding.deletedMessageView.root.isVisible = false
|
||||||
|
@ -136,6 +136,11 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab
|
|||||||
database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
|
database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearErrorMessage(messageID: Long) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
database.delete(errorMessageTable, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteThread(threadId: Long) {
|
fun deleteThread(threadId: Long) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
try {
|
try {
|
||||||
|
@ -112,6 +112,64 @@ public class MmsSmsDatabase extends Database {
|
|||||||
return getMessageFor(timestamp, author.serialize());
|
return getMessageFor(timestamp, author.serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getPreviousPage(long threadId, long fromTime, int limit) {
|
||||||
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" ASC";
|
||||||
|
String selection = MmsSmsColumns.THREAD_ID+" = "+threadId
|
||||||
|
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > "+fromTime;
|
||||||
|
String limitStr = ""+limit;
|
||||||
|
long sent = -1;
|
||||||
|
Cursor cursor = queryTables(PROJECTION, selection, order, limitStr);
|
||||||
|
if (cursor == null) return sent;
|
||||||
|
Reader reader = readerFor(cursor);
|
||||||
|
if (!cursor.move(limit)) {
|
||||||
|
cursor.moveToLast();
|
||||||
|
}
|
||||||
|
MessageRecord record = reader.getCurrent();
|
||||||
|
sent = record.getDateSent();
|
||||||
|
reader.close();
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cursor getConversationPage(long threadId, long fromTime, long toTime, int limit) {
|
||||||
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC";
|
||||||
|
String selection = MmsSmsColumns.THREAD_ID + " = "+threadId
|
||||||
|
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" <= " + fromTime;
|
||||||
|
String limitStr = null;
|
||||||
|
if (toTime != -1L) {
|
||||||
|
selection += " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > "+toTime;
|
||||||
|
} else {
|
||||||
|
limitStr = ""+limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryTables(PROJECTION, selection, order, limitStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNextPage(long threadId, long toTime) {
|
||||||
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC";
|
||||||
|
String selection = MmsSmsColumns.THREAD_ID + " = "+threadId
|
||||||
|
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" < " + toTime; // check if there's at least one message before the `toTime`
|
||||||
|
Cursor cursor = queryTables(PROJECTION, selection, order, null);
|
||||||
|
boolean hasNext = false;
|
||||||
|
if (cursor != null) {
|
||||||
|
hasNext = cursor.getCount() > 0;
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return hasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPreviousPage(long threadId, long fromTime) {
|
||||||
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC";
|
||||||
|
String selection = MmsSmsColumns.THREAD_ID + " = "+threadId
|
||||||
|
+ " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > " + fromTime; // check if there's at least one message after the `fromTime`
|
||||||
|
Cursor cursor = queryTables(PROJECTION, selection, order, null);
|
||||||
|
boolean hasNext = false;
|
||||||
|
if (cursor != null) {
|
||||||
|
hasNext = cursor.getCount() > 0;
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return hasNext;
|
||||||
|
}
|
||||||
|
|
||||||
public Cursor getConversation(long threadId, boolean reverse, long offset, long limit) {
|
public Cursor getConversation(long threadId, boolean reverse, long offset, long limit) {
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + (reverse ? " DESC" : " ASC");
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + (reverse ? " DESC" : " ASC");
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||||
@ -199,16 +257,16 @@ public class MmsSmsDatabase extends Database {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull Address address) {
|
public int getMessagePositionInConversation(long threadId, long sentTimestamp, @NonNull Address address) {
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||||
|
|
||||||
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.ADDRESS }, selection, order, null)) {
|
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) {
|
||||||
String serializedAddress = address.serialize();
|
String serializedAddress = address.serialize();
|
||||||
boolean isOwnNumber = Util.isOwnNumber(context, address.serialize());
|
boolean isOwnNumber = Util.isOwnNumber(context, address.serialize());
|
||||||
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
boolean timestampMatches = cursor.getLong(0) == receivedTimestamp;
|
boolean timestampMatches = cursor.getLong(0) == sentTimestamp;
|
||||||
boolean addressMatches = serializedAddress.equals(cursor.getString(1));
|
boolean addressMatches = serializedAddress.equals(cursor.getString(1));
|
||||||
|
|
||||||
if (timestampMatches && (addressMatches || isOwnNumber)) {
|
if (timestampMatches && (addressMatches || isOwnNumber)) {
|
||||||
|
@ -63,7 +63,7 @@ public class SearchDatabase extends Database {
|
|||||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
||||||
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
||||||
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||||
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
|
||||||
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||||
"FROM " + SmsDatabase.TABLE_NAME + " " +
|
"FROM " + SmsDatabase.TABLE_NAME + " " +
|
||||||
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
|
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
|
||||||
@ -74,13 +74,13 @@ public class SearchDatabase extends Database {
|
|||||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
||||||
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
||||||
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||||
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
|
||||||
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||||
"FROM " + MmsDatabase.TABLE_NAME + " " +
|
"FROM " + MmsDatabase.TABLE_NAME + " " +
|
||||||
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
|
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
|
||||||
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||||
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? " +
|
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? " +
|
||||||
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
|
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " +
|
||||||
"LIMIT ?";
|
"LIMIT ?";
|
||||||
|
|
||||||
private static final String MESSAGES_FOR_THREAD_QUERY =
|
private static final String MESSAGES_FOR_THREAD_QUERY =
|
||||||
@ -88,7 +88,7 @@ public class SearchDatabase extends Database {
|
|||||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
||||||
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
||||||
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
"snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||||
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
|
||||||
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||||
"FROM " + SmsDatabase.TABLE_NAME + " " +
|
"FROM " + SmsDatabase.TABLE_NAME + " " +
|
||||||
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
|
"INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " +
|
||||||
@ -99,13 +99,13 @@ public class SearchDatabase extends Database {
|
|||||||
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
|
||||||
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
|
||||||
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
"snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " +
|
||||||
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " +
|
MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " +
|
||||||
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " +
|
||||||
"FROM " + MmsDatabase.TABLE_NAME + " " +
|
"FROM " + MmsDatabase.TABLE_NAME + " " +
|
||||||
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
|
"INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " +
|
||||||
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
"INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " +
|
||||||
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? AND " + MmsDatabase.TABLE_NAME + "." + MmsSmsColumns.THREAD_ID + " = ? " +
|
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? AND " + MmsDatabase.TABLE_NAME + "." + MmsSmsColumns.THREAD_ID + " = ? " +
|
||||||
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
|
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " +
|
||||||
"LIMIT 500";
|
"LIMIT 500";
|
||||||
|
|
||||||
public SearchDatabase(@NonNull Context context, @NonNull SQLCipherOpenHelper databaseHelper) {
|
public SearchDatabase(@NonNull Context context, @NonNull SQLCipherOpenHelper databaseHelper) {
|
||||||
|
@ -6,22 +6,11 @@ import org.session.libsession.database.StorageProtocol
|
|||||||
import org.session.libsession.messaging.BlindedIdMapping
|
import org.session.libsession.messaging.BlindedIdMapping
|
||||||
import org.session.libsession.messaging.calls.CallMessageType
|
import org.session.libsession.messaging.calls.CallMessageType
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.messaging.jobs.AttachmentUploadJob
|
import org.session.libsession.messaging.jobs.*
|
||||||
import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
|
|
||||||
import org.session.libsession.messaging.jobs.Job
|
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
|
||||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||||
import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
|
import org.session.libsession.messaging.messages.signal.*
|
||||||
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
|
|
||||||
import org.session.libsession.messaging.messages.signal.IncomingMediaMessage
|
|
||||||
import org.session.libsession.messaging.messages.signal.IncomingTextMessage
|
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage
|
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage
|
|
||||||
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
|
||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.messages.visible.Reaction
|
import org.session.libsession.messaging.messages.visible.Reaction
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
@ -36,12 +25,8 @@ import org.session.libsession.messaging.utilities.SessionId
|
|||||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.*
|
||||||
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
import org.session.libsession.utilities.Address.Companion.fromSerialized
|
||||||
import org.session.libsession.utilities.GroupRecord
|
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.ProfileKeyUtil
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||||
import org.session.libsignal.messages.SignalServiceAttachmentPointer
|
import org.session.libsignal.messages.SignalServiceAttachmentPointer
|
||||||
@ -428,6 +413,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearErrorMessage(messageID: Long) {
|
||||||
|
val db = DatabaseComponent.get(context).lokiMessageDatabase()
|
||||||
|
db.clearErrorMessage(messageID)
|
||||||
|
}
|
||||||
|
|
||||||
override fun setMessageServerHash(messageID: Long, serverHash: String) {
|
override fun setMessageServerHash(messageID: Long, serverHash: String) {
|
||||||
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash)
|
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash)
|
||||||
}
|
}
|
||||||
|
@ -502,15 +502,23 @@ public class ThreadDatabase extends Database {
|
|||||||
return db.rawQuery(query, null);
|
return db.rawQuery(query, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastSeen(long threadId) {
|
public void setLastSeen(long threadId, long timestamp) {
|
||||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||||
ContentValues contentValues = new ContentValues(1);
|
ContentValues contentValues = new ContentValues(1);
|
||||||
contentValues.put(LAST_SEEN, System.currentTimeMillis());
|
if (timestamp == -1) {
|
||||||
|
contentValues.put(LAST_SEEN, System.currentTimeMillis());
|
||||||
|
} else {
|
||||||
|
contentValues.put(LAST_SEEN, timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
|
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLastSeen(long threadId) {
|
||||||
|
setLastSeen(threadId, -1);
|
||||||
|
}
|
||||||
|
|
||||||
public Pair<Long, Boolean> getLastSeenAndHasSent(long threadId) {
|
public Pair<Long, Boolean> getLastSeenAndHasSent(long threadId) {
|
||||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||||
Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
|
Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null);
|
||||||
|
@ -33,6 +33,7 @@ import org.session.libsession.utilities.NetworkFailure;
|
|||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for message record models that are displayed in
|
* The base class for message record models that are displayed in
|
||||||
@ -140,14 +141,16 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
return spannable;
|
return spannable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
return other instanceof MessageRecord
|
return other instanceof MessageRecord
|
||||||
&& ((MessageRecord) other).getId() == getId()
|
&& ((MessageRecord) other).getId() == getId()
|
||||||
&& ((MessageRecord) other).isMms() == isMms();
|
&& ((MessageRecord) other).isMms() == isMms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return (int)getId();
|
return Objects.hash(id, isMms());
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull List<ReactionRecord> getReactions() {
|
public @NonNull List<ReactionRecord> getReactions() {
|
||||||
|
@ -2,13 +2,15 @@ package org.thoughtcrime.securesms.database.model;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import org.session.libsession.utilities.Contact;
|
|
||||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
|
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.Contact;
|
||||||
import org.session.libsession.utilities.IdentityKeyMismatch;
|
import org.session.libsession.utilities.IdentityKeyMismatch;
|
||||||
import org.session.libsession.utilities.NetworkFailure;
|
import org.session.libsession.utilities.NetworkFailure;
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.mms.Slide;
|
import org.thoughtcrime.securesms.mms.Slide;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
|
|||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class Quote {
|
public class Quote {
|
||||||
|
|
||||||
private final long id;
|
private final long id;
|
||||||
@ -47,4 +49,17 @@ public class Quote {
|
|||||||
public QuoteModel getQuoteModel() {
|
public QuoteModel getQuoteModel() {
|
||||||
return new QuoteModel(id, author, text, missing, attachment.asAttachments());
|
return new QuoteModel(id, author, text, missing, attachment.asAttachments());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Quote quote = (Quote) o;
|
||||||
|
return id == quote.id && missing == quote.missing && Objects.equals(author, quote.author) && Objects.equals(text, quote.text) && Objects.equals(attachment, quote.attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id, author, text, missing, attachment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
when (model) {
|
when (model) {
|
||||||
is GlobalSearchAdapter.Model.Message -> {
|
is GlobalSearchAdapter.Model.Message -> {
|
||||||
val threadId = model.messageResult.threadId
|
val threadId = model.messageResult.threadId
|
||||||
val timestamp = model.messageResult.receivedTimestampMs
|
val timestamp = model.messageResult.sentTimestampMs
|
||||||
val author = model.messageResult.messageRecipient.address
|
val author = model.messageResult.messageRecipient.address
|
||||||
|
|
||||||
val intent = Intent(this, ConversationActivityV2::class.java)
|
val intent = Intent(this, ConversationActivityV2::class.java)
|
||||||
|
@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) {
|
|||||||
// if (hasUnreads) {
|
// if (hasUnreads) {
|
||||||
// binding.unreadCountTextView.text = model.unread.toString()
|
// binding.unreadCountTextView.text = model.unread.toString()
|
||||||
// }
|
// }
|
||||||
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.receivedTimestampMs)
|
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
||||||
binding.searchResultProfilePicture.root.update(model.messageResult.conversationRecipient)
|
binding.searchResultProfilePicture.root.update(model.messageResult.conversationRecipient)
|
||||||
val textSpannable = SpannableStringBuilder()
|
val textSpannable = SpannableStringBuilder()
|
||||||
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
||||||
|
@ -6,17 +6,19 @@ import android.app.PendingIntent;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import network.loki.messenger.BuildConfig;
|
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import network.loki.messenger.BuildConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules tasks using the {@link AlarmManager}.
|
* Schedules tasks using the {@link AlarmManager}.
|
||||||
*
|
*
|
||||||
@ -51,7 +53,7 @@ public class AlarmManagerScheduler implements Scheduler {
|
|||||||
Intent intent = new Intent(context, RetryReceiver.class);
|
Intent intent = new Intent(context, RetryReceiver.class);
|
||||||
|
|
||||||
intent.setAction(BuildConfig.APPLICATION_ID + UUID.randomUUID().toString());
|
intent.setAction(BuildConfig.APPLICATION_ID + UUID.randomUUID().toString());
|
||||||
alarmManager.set(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(context, 0, intent, 0));
|
alarmManager.set(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE));
|
||||||
|
|
||||||
Log.i(TAG, "Set an alarm to retry a job in " + (time - System.currentTimeMillis()) + " ms.");
|
Log.i(TAG, "Set an alarm to retry a job in " + (time - System.currentTimeMillis()) + " ms.");
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
|||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.util.forceShowIcon
|
|
||||||
|
|
||||||
class MessageRequestsAdapter(
|
class MessageRequestsAdapter(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -64,7 +63,7 @@ class MessageRequestsAdapter(
|
|||||||
item.iconTintList = ColorStateList.valueOf(context.getColor(R.color.destructive))
|
item.iconTintList = ColorStateList.valueOf(context.getColor(R.color.destructive))
|
||||||
item.title = s
|
item.title = s
|
||||||
}
|
}
|
||||||
popupMenu.forceShowIcon()
|
popupMenu.setForceShowIcon(true)
|
||||||
popupMenu.show()
|
popupMenu.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,17 +17,19 @@
|
|||||||
package org.thoughtcrime.securesms.mms;
|
package org.thoughtcrime.securesms.mms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
|
||||||
import org.session.libsignal.utilities.guava.Optional;
|
import org.session.libsignal.utilities.guava.Optional;
|
||||||
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class SlideDeck {
|
public class SlideDeck {
|
||||||
|
|
||||||
@ -138,4 +140,17 @@ public class SlideDeck {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SlideDeck slideDeck = (SlideDeck) o;
|
||||||
|
return Objects.equals(slides, slideDeck.slides);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(slides);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ import com.annimon.stream.Optional;
|
|||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
import com.goterl.lazysodium.utils.KeyPair;
|
import com.goterl.lazysodium.utils.KeyPair;
|
||||||
|
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration;
|
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroup;
|
import org.session.libsession.messaging.open_groups.OpenGroup;
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
|
||||||
import org.session.libsession.messaging.utilities.SessionId;
|
import org.session.libsession.messaging.utilities.SessionId;
|
||||||
@ -453,8 +452,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
NotificationState notificationState = new NotificationState();
|
NotificationState notificationState = new NotificationState();
|
||||||
MmsSmsDatabase.Reader reader = DatabaseComponent.get(context).mmsSmsDatabase().readerFor(cursor);
|
MmsSmsDatabase.Reader reader = DatabaseComponent.get(context).mmsSmsDatabase().readerFor(cursor);
|
||||||
ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase();
|
ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase();
|
||||||
LokiThreadDatabase lokiThreadDatabase= DatabaseComponent.get(context).lokiThreadDatabase();
|
|
||||||
KeyPair edKeyPair = MessagingModuleConfiguration.getShared().getGetUserED25519KeyPair().invoke();
|
|
||||||
MessageRecord record;
|
MessageRecord record;
|
||||||
Map<Long, String> cache = new HashMap<Long, String>();
|
Map<Long, String> cache = new HashMap<Long, String>();
|
||||||
|
|
||||||
@ -575,7 +573,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
|
Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
|
||||||
alarmIntent.putExtra("reminder_count", count);
|
alarmIntent.putExtra("reminder_count", count);
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
long timeout = TimeUnit.MINUTES.toMillis(2);
|
long timeout = TimeUnit.MINUTES.toMillis(2);
|
||||||
|
|
||||||
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent);
|
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent);
|
||||||
@ -584,7 +582,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
|||||||
@Override
|
@Override
|
||||||
public void clearReminder(Context context) {
|
public void clearReminder(Context context) {
|
||||||
Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
|
Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
alarmManager.cancel(pendingIntent);
|
alarmManager.cancel(pendingIntent);
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,10 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.session.libsession.utilities.NotificationPrivacyPreference;
|
import org.session.libsession.utilities.NotificationPrivacyPreference;
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public class FailedNotificationBuilder extends AbstractNotificationBuilder {
|
public class FailedNotificationBuilder extends AbstractNotificationBuilder {
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ public class FailedNotificationBuilder extends AbstractNotificationBuilder {
|
|||||||
setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed));
|
setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed));
|
||||||
setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_message));
|
setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_message));
|
||||||
setTicker(context.getString(R.string.MessageNotifier_error_delivering_message));
|
setTicker(context.getString(R.string.MessageNotifier_error_delivering_message));
|
||||||
setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
|
setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE));
|
||||||
setAutoCancel(true);
|
setAutoCancel(true);
|
||||||
setAlarms(null, Recipient.VibrateState.DEFAULT);
|
setAlarms(null, Recipient.VibrateState.DEFAULT);
|
||||||
setChannelId(NotificationChannels.FAILURES);
|
setChannelId(NotificationChannels.FAILURES);
|
||||||
|
@ -34,7 +34,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu
|
|||||||
setColor(context.getResources().getColor(R.color.textsecure_primary));
|
setColor(context.getResources().getColor(R.color.textsecure_primary));
|
||||||
setSmallIcon(R.drawable.ic_notification);
|
setSmallIcon(R.drawable.ic_notification);
|
||||||
setContentTitle(context.getString(R.string.app_name));
|
setContentTitle(context.getString(R.string.app_name));
|
||||||
setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, HomeActivity.class), 0));
|
setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, HomeActivity.class), PendingIntent.FLAG_IMMUTABLE));
|
||||||
setCategory(NotificationCompat.CATEGORY_MESSAGE);
|
setCategory(NotificationCompat.CATEGORY_MESSAGE);
|
||||||
setGroupSummary(true);
|
setGroupSummary(true);
|
||||||
|
|
||||||
|
@ -4,14 +4,15 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
|
|
||||||
public class NotificationItem {
|
public class NotificationItem {
|
||||||
|
|
||||||
@ -75,9 +76,14 @@ public class NotificationItem {
|
|||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
||||||
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||||
|
|
||||||
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
return TaskStackBuilder.create(context)
|
return TaskStackBuilder.create(context)
|
||||||
.addNextIntentWithParentStack(intent)
|
.addNextIntentWithParentStack(intent)
|
||||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
.getPendingIntent(0, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
|
@ -4,12 +4,14 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
import org.session.libsession.utilities.recipients.Recipient.*;
|
import org.session.libsession.utilities.recipients.Recipient.VibrateState;
|
||||||
|
import org.session.libsignal.utilities.Log;
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
@ -114,7 +116,12 @@ public class NotificationState {
|
|||||||
intent.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray);
|
intent.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray);
|
||||||
intent.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId);
|
intent.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId);
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(context, 0, intent, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, ReplyMethod replyMethod) {
|
public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, ReplyMethod replyMethod) {
|
||||||
@ -127,7 +134,12 @@ public class NotificationState {
|
|||||||
intent.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod);
|
intent.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod);
|
||||||
intent.setPackage(context.getPackageName());
|
intent.setPackage(context.getPackageName());
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(context, 0, intent, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getAndroidAutoReplyIntent(Context context, Recipient recipient) {
|
public PendingIntent getAndroidAutoReplyIntent(Context context, Recipient recipient) {
|
||||||
@ -141,7 +153,12 @@ public class NotificationState {
|
|||||||
intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]);
|
intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]);
|
||||||
intent.setPackage(context.getPackageName());
|
intent.setPackage(context.getPackageName());
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(context, 0, intent, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getAndroidAutoHeardIntent(Context context, int notificationId) {
|
public PendingIntent getAndroidAutoHeardIntent(Context context, int notificationId) {
|
||||||
@ -160,7 +177,12 @@ public class NotificationState {
|
|||||||
intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId);
|
intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId);
|
||||||
intent.setPackage(context.getPackageName());
|
intent.setPackage(context.getPackageName());
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(context, 0, intent, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) {
|
public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) {
|
||||||
@ -171,7 +193,12 @@ public class NotificationState {
|
|||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]);
|
||||||
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||||
|
|
||||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getActivity(context, 0, intent, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingIntent getDeleteIntent(Context context) {
|
public PendingIntent getDeleteIntent(Context context) {
|
||||||
@ -190,7 +217,12 @@ public class NotificationState {
|
|||||||
intent.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms);
|
intent.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms);
|
||||||
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
intentFlags |= PendingIntent.FLAG_MUTABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingIntent.getBroadcast(context, 0, intent, intentFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,12 +4,13 @@ package org.thoughtcrime.securesms.notifications;
|
|||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
|
||||||
import org.session.libsession.utilities.NotificationPrivacyPreference;
|
import org.session.libsession.utilities.NotificationPrivacyPreference;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ public class PendingMessageNotificationBuilder extends AbstractNotificationBuild
|
|||||||
setContentText(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages));
|
setContentText(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages));
|
||||||
setTicker(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages));
|
setTicker(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages));
|
||||||
|
|
||||||
setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
|
setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE));
|
||||||
setAutoCancel(true);
|
setAutoCancel(true);
|
||||||
setAlarms(null, Recipient.VibrateState.DEFAULT);
|
setAlarms(null, Recipient.VibrateState.DEFAULT);
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class PNModeActivity : BaseActionBarActivity() {
|
|||||||
toggleFCM()
|
toggleFCM()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_pn_mode, menu)
|
menuInflater.inflate(R.menu.menu_pn_mode, menu)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,7 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() {
|
|||||||
addPreferencesFromResource(R.xml.preferences_help)
|
addPreferencesFromResource(R.xml.preferences_help)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
preference ?: return false
|
|
||||||
return when (preference.key) {
|
return when (preference.key) {
|
||||||
EXPORT_LOGS -> {
|
EXPORT_LOGS -> {
|
||||||
shareLogs()
|
shareLogs()
|
||||||
|
@ -301,10 +301,10 @@ public class SearchRepository {
|
|||||||
Recipient conversationRecipient = Recipient.from(context, conversationAddress, false);
|
Recipient conversationRecipient = Recipient.from(context, conversationAddress, false);
|
||||||
Recipient messageRecipient = Recipient.from(context, messageAddress, false);
|
Recipient messageRecipient = Recipient.from(context, messageAddress, false);
|
||||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET));
|
String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET));
|
||||||
long receivedMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED));
|
long sentMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT));
|
||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID));
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID));
|
||||||
|
|
||||||
return new MessageResult(conversationRecipient, messageRecipient, body, threadId, receivedMs);
|
return new MessageResult(conversationRecipient, messageRecipient, body, threadId, sentMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,18 +13,18 @@ public class MessageResult {
|
|||||||
public final Recipient messageRecipient;
|
public final Recipient messageRecipient;
|
||||||
public final String bodySnippet;
|
public final String bodySnippet;
|
||||||
public final long threadId;
|
public final long threadId;
|
||||||
public final long receivedTimestampMs;
|
public final long sentTimestampMs;
|
||||||
|
|
||||||
public MessageResult(@NonNull Recipient conversationRecipient,
|
public MessageResult(@NonNull Recipient conversationRecipient,
|
||||||
@NonNull Recipient messageRecipient,
|
@NonNull Recipient messageRecipient,
|
||||||
@NonNull String bodySnippet,
|
@NonNull String bodySnippet,
|
||||||
long threadId,
|
long threadId,
|
||||||
long receivedTimestampMs)
|
long sentTimestampMs)
|
||||||
{
|
{
|
||||||
this.conversationRecipient = conversationRecipient;
|
this.conversationRecipient = conversationRecipient;
|
||||||
this.messageRecipient = messageRecipient;
|
this.messageRecipient = messageRecipient;
|
||||||
this.bodySnippet = bodySnippet;
|
this.bodySnippet = bodySnippet;
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
this.receivedTimestampMs = receivedTimestampMs;
|
this.sentTimestampMs = sentTimestampMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,12 @@ import android.content.IntentFilter;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Icon;
|
import android.graphics.drawable.Icon;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.service.chooser.ChooserTarget;
|
import android.service.chooser.ChooserTarget;
|
||||||
import android.service.chooser.ChooserTargetService;
|
import android.service.chooser.ChooserTargetService;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
@ -28,7 +26,6 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
|
||||||
public class DirectShareService extends ChooserTargetService {
|
public class DirectShareService extends ChooserTargetService {
|
||||||
|
|
||||||
private static final String TAG = DirectShareService.class.getSimpleName();
|
private static final String TAG = DirectShareService.class.getSimpleName();
|
||||||
@ -40,53 +37,50 @@ public class DirectShareService extends ChooserTargetService {
|
|||||||
List<ChooserTarget> results = new LinkedList<>();
|
List<ChooserTarget> results = new LinkedList<>();
|
||||||
ComponentName componentName = new ComponentName(this, ShareActivity.class);
|
ComponentName componentName = new ComponentName(this, ShareActivity.class);
|
||||||
ThreadDatabase threadDatabase = DatabaseComponent.get(this).threadDatabase();
|
ThreadDatabase threadDatabase = DatabaseComponent.get(this).threadDatabase();
|
||||||
Cursor cursor = threadDatabase.getDirectShareList();
|
|
||||||
|
|
||||||
try {
|
try (Cursor cursor = threadDatabase.getDirectShareList()) {
|
||||||
ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor);
|
ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor);
|
||||||
ThreadRecord record;
|
ThreadRecord record;
|
||||||
|
|
||||||
while ((record = reader.getNext()) != null && results.size() < 10) {
|
while ((record = reader.getNext()) != null && results.size() < 10) {
|
||||||
Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false);
|
Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false);
|
||||||
String name = recipient.toShortString();
|
String name = recipient.toShortString();
|
||||||
|
|
||||||
Bitmap avatar;
|
Bitmap avatar;
|
||||||
|
|
||||||
|
if (recipient.getContactPhoto() != null) {
|
||||||
|
try {
|
||||||
|
avatar = GlideApp.with(this)
|
||||||
|
.asBitmap()
|
||||||
|
.load(recipient.getContactPhoto())
|
||||||
|
.circleCrop()
|
||||||
|
.submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
|
||||||
|
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width))
|
||||||
|
.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
avatar = getFallbackDrawable(recipient);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
avatar = getFallbackDrawable(recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
parcel.writeParcelable(recipient.getAddress(), 0);
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId());
|
||||||
|
bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall());
|
||||||
|
bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType());
|
||||||
|
bundle.setClassLoader(getClassLoader());
|
||||||
|
|
||||||
|
results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle));
|
||||||
|
parcel.recycle();
|
||||||
|
|
||||||
if (recipient.getContactPhoto() != null) {
|
|
||||||
try {
|
|
||||||
avatar = GlideApp.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.load(recipient.getContactPhoto())
|
|
||||||
.circleCrop()
|
|
||||||
.submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
|
|
||||||
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width))
|
|
||||||
.get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
avatar = getFallbackDrawable(recipient);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
avatar = getFallbackDrawable(recipient);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Parcel parcel = Parcel.obtain();
|
return results;
|
||||||
parcel.writeParcelable(recipient.getAddress(), 0);
|
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId());
|
|
||||||
bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall());
|
|
||||||
bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType());
|
|
||||||
bundle.setClassLoader(getClassLoader());
|
|
||||||
|
|
||||||
results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle));
|
|
||||||
parcel.recycle();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) cursor.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap getFallbackDrawable(@NonNull Recipient recipient) {
|
private Bitmap getFallbackDrawable(@NonNull Recipient recipient) {
|
||||||
|
@ -17,7 +17,7 @@ public class ExpirationListener extends BroadcastReceiver {
|
|||||||
|
|
||||||
public static void setAlarm(Context context, long waitTimeMillis) {
|
public static void setAlarm(Context context, long waitTimeMillis) {
|
||||||
Intent intent = new Intent(context, ExpirationListener.class);
|
Intent intent = new Intent(context, ExpirationListener.class);
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
alarmManager.cancel(pendingIntent);
|
alarmManager.cancel(pendingIntent);
|
||||||
|
@ -6,6 +6,7 @@ import android.app.Service;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -13,9 +14,9 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
import org.session.libsignal.utilities.guava.Preconditions;
|
||||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.session.libsignal.utilities.guava.Preconditions;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
@ -87,10 +88,10 @@ public class GenericForegroundService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void postObligatoryForegroundNotification(String title, String channelId, @DrawableRes int iconRes) {
|
private void postObligatoryForegroundNotification(String title, String channelId, @DrawableRes int iconRes) {
|
||||||
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId)
|
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId)
|
||||||
.setSmallIcon(iconRes)
|
.setSmallIcon(iconRes)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, HomeActivity.class), 0))
|
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, HomeActivity.class), PendingIntent.FLAG_IMMUTABLE))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,18 +29,19 @@ import android.os.AsyncTask;
|
|||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import org.session.libsession.utilities.ServiceUtil;
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
import org.session.libsignal.utilities.Log;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
||||||
import org.thoughtcrime.securesms.DummyActivity;
|
import org.thoughtcrime.securesms.DummyActivity;
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
import org.thoughtcrime.securesms.home.HomeActivity;
|
import org.thoughtcrime.securesms.home.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.session.libsession.utilities.ServiceUtil;
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -255,18 +256,18 @@ public class KeyCachingService extends Service {
|
|||||||
private PendingIntent buildLockIntent() {
|
private PendingIntent buildLockIntent() {
|
||||||
Intent intent = new Intent(this, KeyCachingService.class);
|
Intent intent = new Intent(this, KeyCachingService.class);
|
||||||
intent.setAction(PASSPHRASE_EXPIRED_EVENT);
|
intent.setAction(PASSPHRASE_EXPIRED_EVENT);
|
||||||
return PendingIntent.getService(getApplicationContext(), 0, intent, 0);
|
return PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent buildLaunchIntent() {
|
private PendingIntent buildLaunchIntent() {
|
||||||
Intent intent = new Intent(this, HomeActivity.class);
|
Intent intent = new Intent(this, HomeActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
return PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
|
return PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PendingIntent buildExpirationPendingIntent(@NonNull Context context) {
|
private static PendingIntent buildExpirationPendingIntent(@NonNull Context context) {
|
||||||
Intent expirationIntent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class);
|
Intent expirationIntent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class);
|
||||||
return PendingIntent.getService(context, 0, expirationIntent, 0);
|
return PendingIntent.getService(context, 0, expirationIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -6,6 +6,7 @@ import android.app.PendingIntent;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
|
||||||
public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
|
public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
|
||||||
@ -21,7 +22,7 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
|
|||||||
long scheduledTime = getNextScheduledExecutionTime(context);
|
long scheduledTime = getNextScheduledExecutionTime(context);
|
||||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
Intent alarmIntent = new Intent(context, getClass());
|
Intent alarmIntent = new Intent(context, getClass());
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0);
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
if (System.currentTimeMillis() >= scheduledTime) {
|
if (System.currentTimeMillis() >= scheduledTime) {
|
||||||
scheduledTime = onAlarm(context, scheduledTime);
|
scheduledTime = onAlarm(context, scheduledTime);
|
||||||
|
@ -12,21 +12,22 @@ import android.net.Uri;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import org.session.libsignal.utilities.Log;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
||||||
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
|
||||||
import org.session.libsession.utilities.FileUtils;
|
import org.session.libsession.utilities.FileUtils;
|
||||||
import org.session.libsignal.utilities.Hex;
|
|
||||||
import org.session.libsession.utilities.ServiceUtil;
|
import org.session.libsession.utilities.ServiceUtil;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
import org.session.libsignal.utilities.Hex;
|
||||||
|
import org.session.libsignal.utilities.Log;
|
||||||
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
|
import org.thoughtcrime.securesms.util.FileProviderUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public class UpdateApkReadyListener extends BroadcastReceiver {
|
public class UpdateApkReadyListener extends BroadcastReceiver {
|
||||||
|
|
||||||
private static final String TAG = UpdateApkReadyListener.class.getSimpleName();
|
private static final String TAG = UpdateApkReadyListener.class.getSimpleName();
|
||||||
@ -61,7 +62,7 @@ public class UpdateApkReadyListener extends BroadcastReceiver {
|
|||||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
intent.setData(uri);
|
intent.setData(uri);
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.APP_UPDATES)
|
Notification notification = new NotificationCompat.Builder(context, NotificationChannels.APP_UPDATES)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
|
@ -7,10 +7,13 @@ import android.content.Intent
|
|||||||
import android.content.Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
|
import android.content.Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.ResultReceiver
|
import android.os.ResultReceiver
|
||||||
import android.telephony.PhoneStateListener
|
import android.telephony.PhoneStateListener
|
||||||
|
import android.telephony.PhoneStateListener.LISTEN_NONE
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
@ -28,30 +31,13 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_IN
|
|||||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER
|
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER
|
||||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING
|
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING
|
||||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
|
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
|
||||||
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
import org.thoughtcrime.securesms.webrtc.*
|
||||||
import org.thoughtcrime.securesms.webrtc.CallManager
|
|
||||||
import org.thoughtcrime.securesms.webrtc.CallViewModel
|
|
||||||
import org.thoughtcrime.securesms.webrtc.HangUpRtcOnPstnCallAnsweredListener
|
|
||||||
import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver
|
|
||||||
import org.thoughtcrime.securesms.webrtc.NetworkChangeReceiver
|
|
||||||
import org.thoughtcrime.securesms.webrtc.PeerConnectionException
|
|
||||||
import org.thoughtcrime.securesms.webrtc.PowerButtonReceiver
|
|
||||||
import org.thoughtcrime.securesms.webrtc.ProximityLockRelease
|
|
||||||
import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager
|
|
||||||
import org.thoughtcrime.securesms.webrtc.WiredHeadsetStateReceiver
|
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
||||||
import org.thoughtcrime.securesms.webrtc.data.Event
|
import org.thoughtcrime.securesms.webrtc.data.Event
|
||||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
||||||
import org.webrtc.DataChannel
|
import org.webrtc.*
|
||||||
import org.webrtc.IceCandidate
|
import org.webrtc.PeerConnection.IceConnectionState.*
|
||||||
import org.webrtc.MediaStream
|
import java.util.*
|
||||||
import org.webrtc.PeerConnection
|
|
||||||
import org.webrtc.PeerConnection.IceConnectionState.CONNECTED
|
|
||||||
import org.webrtc.PeerConnection.IceConnectionState.DISCONNECTED
|
|
||||||
import org.webrtc.PeerConnection.IceConnectionState.FAILED
|
|
||||||
import org.webrtc.RtpReceiver
|
|
||||||
import org.webrtc.SessionDescription
|
|
||||||
import java.util.UUID
|
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ScheduledFuture
|
import java.util.concurrent.ScheduledFuture
|
||||||
@ -60,7 +46,7 @@ import javax.inject.Inject
|
|||||||
import org.thoughtcrime.securesms.webrtc.data.State as CallState
|
import org.thoughtcrime.securesms.webrtc.data.State as CallState
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
class WebRtcCallService : Service(), CallManager.WebRtcListener {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@ -108,62 +94,82 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
private const val RECONNECT_SECONDS = 5L
|
private const val RECONNECT_SECONDS = 5L
|
||||||
private const val MAX_RECONNECTS = 5
|
private const val MAX_RECONNECTS = 5
|
||||||
|
|
||||||
fun cameraEnabled(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java)
|
fun cameraEnabled(context: Context, enabled: Boolean) =
|
||||||
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_SET_MUTE_VIDEO)
|
.setAction(ACTION_SET_MUTE_VIDEO)
|
||||||
.putExtra(EXTRA_MUTE, !enabled)
|
.putExtra(EXTRA_MUTE, !enabled)
|
||||||
|
|
||||||
fun flipCamera(context: Context) = Intent(context, WebRtcCallService::class.java)
|
fun flipCamera(context: Context) = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_FLIP_CAMERA)
|
.setAction(ACTION_FLIP_CAMERA)
|
||||||
|
|
||||||
fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java)
|
fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_ANSWER_CALL)
|
.setAction(ACTION_ANSWER_CALL)
|
||||||
|
|
||||||
fun microphoneIntent(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java)
|
fun microphoneIntent(context: Context, enabled: Boolean) =
|
||||||
.setAction(ACTION_SET_MUTE_AUDIO)
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.putExtra(EXTRA_MUTE, !enabled)
|
.setAction(ACTION_SET_MUTE_AUDIO)
|
||||||
|
.putExtra(EXTRA_MUTE, !enabled)
|
||||||
|
|
||||||
fun createCall(context: Context, recipient: Recipient) = Intent(context, WebRtcCallService::class.java)
|
fun createCall(context: Context, recipient: Recipient) =
|
||||||
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_OUTGOING_CALL)
|
.setAction(ACTION_OUTGOING_CALL)
|
||||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
|
.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
|
||||||
|
|
||||||
fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID, callTime: Long) =
|
fun incomingCall(
|
||||||
Intent(context, WebRtcCallService::class.java)
|
context: Context,
|
||||||
.setAction(ACTION_INCOMING_RING)
|
address: Address,
|
||||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
sdp: String,
|
||||||
.putExtra(EXTRA_CALL_ID, callId)
|
callId: UUID,
|
||||||
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
|
callTime: Long
|
||||||
.putExtra(EXTRA_TIMESTAMP, callTime)
|
) =
|
||||||
|
Intent(context, WebRtcCallService::class.java)
|
||||||
|
.setAction(ACTION_INCOMING_RING)
|
||||||
|
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
||||||
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
|
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
|
||||||
|
.putExtra(EXTRA_TIMESTAMP, callTime)
|
||||||
|
|
||||||
fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) =
|
fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) =
|
||||||
Intent(context, WebRtcCallService::class.java)
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_RESPONSE_MESSAGE)
|
.setAction(ACTION_RESPONSE_MESSAGE)
|
||||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
||||||
.putExtra(EXTRA_CALL_ID, callId)
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
|
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
|
||||||
|
|
||||||
fun preOffer(context: Context, address: Address, callId: UUID, callTime: Long) =
|
fun preOffer(context: Context, address: Address, callId: UUID, callTime: Long) =
|
||||||
Intent(context, WebRtcCallService::class.java)
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_PRE_OFFER)
|
.setAction(ACTION_PRE_OFFER)
|
||||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
||||||
.putExtra(EXTRA_CALL_ID, callId)
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
.putExtra(EXTRA_TIMESTAMP, callTime)
|
.putExtra(EXTRA_TIMESTAMP, callTime)
|
||||||
|
|
||||||
fun iceCandidates(context: Context, address: Address, iceCandidates: List<IceCandidate>, callId: UUID) =
|
fun iceCandidates(
|
||||||
Intent(context, WebRtcCallService::class.java)
|
context: Context,
|
||||||
.setAction(ACTION_ICE_MESSAGE)
|
address: Address,
|
||||||
.putExtra(EXTRA_CALL_ID, callId)
|
iceCandidates: List<IceCandidate>,
|
||||||
.putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray())
|
callId: UUID
|
||||||
.putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray())
|
) =
|
||||||
.putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray())
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
.setAction(ACTION_ICE_MESSAGE)
|
||||||
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
|
.putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray())
|
||||||
|
.putExtra(
|
||||||
|
EXTRA_ICE_SDP_LINE_INDEX,
|
||||||
|
iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()
|
||||||
|
)
|
||||||
|
.putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray())
|
||||||
|
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
||||||
|
|
||||||
fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
|
fun denyCallIntent(context: Context) =
|
||||||
|
Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
|
||||||
|
|
||||||
fun remoteHangupIntent(context: Context, callId: UUID) = Intent(context, WebRtcCallService::class.java)
|
fun remoteHangupIntent(context: Context, callId: UUID) =
|
||||||
|
Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_REMOTE_HANGUP)
|
.setAction(ACTION_REMOTE_HANGUP)
|
||||||
.putExtra(EXTRA_CALL_ID, callId)
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
|
|
||||||
fun hangupIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP)
|
fun hangupIntent(context: Context) =
|
||||||
|
Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP)
|
||||||
|
|
||||||
fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) {
|
fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) {
|
||||||
val intent = Intent(context, WebRtcCallService::class.java)
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
@ -174,7 +180,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
fun broadcastWantsToAnswer(context: Context, wantsToAnswer: Boolean) {
|
fun broadcastWantsToAnswer(context: Context, wantsToAnswer: Boolean) {
|
||||||
val intent = Intent(ACTION_WANTS_TO_ANSWER)
|
val intent = Intent(ACTION_WANTS_TO_ANSWER)
|
||||||
.putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer)
|
.putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer)
|
||||||
|
|
||||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
|
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
|
||||||
}
|
}
|
||||||
@ -182,13 +188,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isCallActive(context: Context, resultReceiver: ResultReceiver) {
|
fun isCallActive(context: Context, resultReceiver: ResultReceiver) {
|
||||||
val intent = Intent(context, WebRtcCallService::class.java)
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_IS_IN_CALL_QUERY)
|
.setAction(ACTION_IS_IN_CALL_QUERY)
|
||||||
.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
|
.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject lateinit var callManager: CallManager
|
@Inject
|
||||||
|
lateinit var callManager: CallManager
|
||||||
|
|
||||||
private var wantsToAnswer = false
|
private var wantsToAnswer = false
|
||||||
private var currentTimeouts = 0
|
private var currentTimeouts = 0
|
||||||
@ -199,8 +206,17 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
private val lockManager by lazy { LockManager(this) }
|
private val lockManager by lazy { LockManager(this) }
|
||||||
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
||||||
private val timeoutExecutor = Executors.newScheduledThreadPool(1)
|
private val timeoutExecutor = Executors.newScheduledThreadPool(1)
|
||||||
private val hangupOnCallAnswered = HangUpRtcOnPstnCallAnsweredListener {
|
|
||||||
ContextCompat.startForegroundService(this, hangupIntent(this))
|
private val hangupOnCallAnswered by lazy {
|
||||||
|
HangUpRtcOnPstnCallAnsweredListener {
|
||||||
|
ContextCompat.startForegroundService(this, hangupIntent(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val hangupTelephonyCallback by lazy {
|
||||||
|
HangUpRtcTelephonyCallback {
|
||||||
|
ContextCompat.startForegroundService(this, hangupIntent(this))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var networkChangedReceiver: NetworkChangeReceiver? = null
|
private var networkChangedReceiver: NetworkChangeReceiver? = null
|
||||||
@ -258,7 +274,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
val action = intent.action
|
val action = intent.action
|
||||||
Log.i("Loki", "Handling ${intent.action}")
|
Log.i("Loki", "Handling ${intent.action}")
|
||||||
when {
|
when {
|
||||||
action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(intent)
|
action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(
|
||||||
|
intent
|
||||||
|
)
|
||||||
action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent)
|
action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent)
|
||||||
action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent)
|
action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent)
|
||||||
action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent)
|
action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent)
|
||||||
@ -272,7 +290,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent)
|
action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent)
|
||||||
action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
|
action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
|
||||||
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
|
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
|
||||||
action == ACTION_RESPONSE_MESSAGE && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleResponseMessage(intent)
|
action == ACTION_RESPONSE_MESSAGE && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleResponseMessage(
|
||||||
|
intent
|
||||||
|
)
|
||||||
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
|
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
|
||||||
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
|
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
|
||||||
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
|
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
|
||||||
@ -293,8 +313,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
registerIncomingPstnCallReceiver()
|
registerIncomingPstnCallReceiver()
|
||||||
registerWiredHeadsetStateReceiver()
|
registerWiredHeadsetStateReceiver()
|
||||||
registerWantsToAnswerReceiver()
|
registerWantsToAnswerReceiver()
|
||||||
getSystemService(TelephonyManager::class.java)
|
if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
|
||||||
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
getSystemService(TelephonyManager::class.java)
|
||||||
|
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
||||||
|
} else {
|
||||||
|
getSystemService(TelephonyManager::class.java)
|
||||||
|
.registerTelephonyCallback(serviceExecutor, hangupTelephonyCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
registerUncaughtExceptionHandler()
|
registerUncaughtExceptionHandler()
|
||||||
networkChangedReceiver = NetworkChangeReceiver(::networkChange)
|
networkChangedReceiver = NetworkChangeReceiver(::networkChange)
|
||||||
networkChangedReceiver!!.register(this)
|
networkChangedReceiver!!.register(this)
|
||||||
@ -318,7 +345,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
wantsToAnswerReceiver = receiver
|
wantsToAnswerReceiver = receiver
|
||||||
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(ACTION_WANTS_TO_ANSWER))
|
LocalBroadcastManager.getInstance(this)
|
||||||
|
.registerReceiver(receiver, IntentFilter(ACTION_WANTS_TO_ANSWER))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerWiredHeadsetStateReceiver() {
|
private fun registerWiredHeadsetStateReceiver() {
|
||||||
@ -339,7 +367,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
private fun handleUpdateAudio(intent: Intent) {
|
private fun handleUpdateAudio(intent: Intent) {
|
||||||
val audioCommand = intent.getParcelableExtra<AudioManagerCommand>(EXTRA_AUDIO_COMMAND)!!
|
val audioCommand = intent.getParcelableExtra<AudioManagerCommand>(EXTRA_AUDIO_COMMAND)!!
|
||||||
if (callManager.currentConnectionState !in arrayOf(CallState.Connected, *CallState.PENDING_CONNECTION_STATES)) {
|
if (callManager.currentConnectionState !in arrayOf(
|
||||||
|
CallState.Connected,
|
||||||
|
*CallState.PENDING_CONNECTION_STATES
|
||||||
|
)
|
||||||
|
) {
|
||||||
Log.w(TAG, "handling audio command not in call")
|
Log.w(TAG, "handling audio command not in call")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -419,8 +451,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
callManager.initializeAudioForCall()
|
callManager.initializeAudioForCall()
|
||||||
callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING)
|
callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING)
|
||||||
setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient)
|
setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient)
|
||||||
callManager.insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_OUTGOING)
|
callManager.insertCallMessage(
|
||||||
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
recipient.address.serialize(),
|
||||||
|
CallMessageType.CALL_OUTGOING
|
||||||
|
)
|
||||||
|
scheduledTimeout = timeoutExecutor.schedule(
|
||||||
|
TimeoutRunnable(callId, this),
|
||||||
|
TIMEOUT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
)
|
||||||
callManager.setAudioEnabled(true)
|
callManager.setAudioEnabled(true)
|
||||||
|
|
||||||
val expectedState = callManager.currentConnectionState
|
val expectedState = callManager.currentConnectionState
|
||||||
@ -429,15 +468,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
try {
|
try {
|
||||||
val offerFuture = callManager.onOutgoingCall(this)
|
val offerFuture = callManager.onOutgoingCall(this)
|
||||||
offerFuture.fail { e ->
|
offerFuture.fail { e ->
|
||||||
if (isConsistentState(expectedState, expectedCallId, callManager.currentConnectionState, callManager.callId)) {
|
if (isConsistentState(
|
||||||
Log.e(TAG,e)
|
expectedState,
|
||||||
|
expectedCallId,
|
||||||
|
callManager.currentConnectionState,
|
||||||
|
callManager.callId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Log.e(TAG, e)
|
||||||
callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE)
|
callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE)
|
||||||
callManager.postConnectionError()
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG,e)
|
Log.e(TAG, e)
|
||||||
callManager.postConnectionError()
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
@ -476,7 +521,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
callManager.silenceIncomingRinger()
|
callManager.silenceIncomingRinger()
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_INCOMING)
|
callManager.postViewModelState(CallViewModel.State.CALL_INCOMING)
|
||||||
|
|
||||||
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
scheduledTimeout = timeoutExecutor.schedule(
|
||||||
|
TimeoutRunnable(callId, this),
|
||||||
|
TIMEOUT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
)
|
||||||
|
|
||||||
callManager.initializeAudioForCall()
|
callManager.initializeAudioForCall()
|
||||||
callManager.initializeVideo(this)
|
callManager.initializeVideo(this)
|
||||||
@ -487,7 +536,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
try {
|
try {
|
||||||
val answerFuture = callManager.onIncomingCall(this)
|
val answerFuture = callManager.onIncomingCall(this)
|
||||||
answerFuture.fail { e ->
|
answerFuture.fail { e ->
|
||||||
if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) {
|
if (isConsistentState(
|
||||||
|
expectedState,
|
||||||
|
expectedCallId,
|
||||||
|
callManager.currentConnectionState,
|
||||||
|
callManager.callId
|
||||||
|
)
|
||||||
|
) {
|
||||||
Log.e(TAG, e)
|
Log.e(TAG, e)
|
||||||
insertMissedCall(recipient, true)
|
insertMissedCall(recipient, true)
|
||||||
callManager.postConnectionError()
|
callManager.postConnectionError()
|
||||||
@ -497,7 +552,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING)
|
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING)
|
||||||
callManager.setAudioEnabled(true)
|
callManager.setAudioEnabled(true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG,e)
|
Log.e(TAG, e)
|
||||||
callManager.postConnectionError()
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
@ -518,6 +573,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
private fun handleRemoteHangup(intent: Intent) {
|
private fun handleRemoteHangup(intent: Intent) {
|
||||||
if (callManager.callId != getCallId(intent)) {
|
if (callManager.callId != getCallId(intent)) {
|
||||||
Log.e(TAG, "Hangup for non-active call...")
|
Log.e(TAG, "Hangup for non-active call...")
|
||||||
|
stopForeground(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,7 +611,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
val callId = getCallId(intent)
|
val callId = getCallId(intent)
|
||||||
val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION)
|
val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION)
|
||||||
callManager.handleResponseMessage(recipient, callId, SessionDescription(SessionDescription.Type.ANSWER, description))
|
callManager.handleResponseMessage(
|
||||||
|
recipient,
|
||||||
|
callId,
|
||||||
|
SessionDescription(SessionDescription.Type.ANSWER, description)
|
||||||
|
)
|
||||||
} catch (e: PeerConnectionException) {
|
} catch (e: PeerConnectionException) {
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
@ -567,14 +627,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
val sdpLineIndexes = intent.getIntArrayExtra(EXTRA_ICE_SDP_LINE_INDEX) ?: return
|
val sdpLineIndexes = intent.getIntArrayExtra(EXTRA_ICE_SDP_LINE_INDEX) ?: return
|
||||||
val sdps = intent.getStringArrayExtra(EXTRA_ICE_SDP) ?: return
|
val sdps = intent.getStringArrayExtra(EXTRA_ICE_SDP) ?: return
|
||||||
if (sdpMids.size != sdpLineIndexes.size || sdpLineIndexes.size != sdps.size) {
|
if (sdpMids.size != sdpLineIndexes.size || sdpLineIndexes.size != sdps.size) {
|
||||||
Log.w(TAG,"sdp info not of equal length")
|
Log.w(TAG, "sdp info not of equal length")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val iceCandidates = sdpMids.indices.map { index ->
|
val iceCandidates = sdpMids.indices.map { index ->
|
||||||
IceCandidate(
|
IceCandidate(
|
||||||
sdpMids[index],
|
sdpMids[index],
|
||||||
sdpLineIndexes[index],
|
sdpLineIndexes[index],
|
||||||
sdps[index]
|
sdps[index]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
callManager.handleRemoteIceCandidate(iceCandidates, callId)
|
callManager.handleRemoteIceCandidate(iceCandidates, callId)
|
||||||
@ -597,7 +657,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
private fun handleIsInCallQuery(intent: Intent) {
|
private fun handleIsInCallQuery(intent: Intent) {
|
||||||
val listener = intent.getParcelableExtra<ResultReceiver>(EXTRA_RESULT_RECEIVER) ?: return
|
val listener = intent.getParcelableExtra<ResultReceiver>(EXTRA_RESULT_RECEIVER) ?: return
|
||||||
val currentState = callManager.currentConnectionState
|
val currentState = callManager.currentConnectionState
|
||||||
val isInCall = if (currentState in arrayOf(*CallState.PENDING_CONNECTION_STATES, CallState.Connected)) 1 else 0
|
val isInCall = if (currentState in arrayOf(
|
||||||
|
*CallState.PENDING_CONNECTION_STATES,
|
||||||
|
CallState.Connected
|
||||||
|
)
|
||||||
|
) 1 else 0
|
||||||
listener.send(isInCall, bundleOf())
|
listener.send(isInCall, bundleOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,10 +680,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) {
|
if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) {
|
||||||
Log.i("Loki", "Trying to re-connect")
|
Log.i("Loki", "Trying to re-connect")
|
||||||
callManager.networkReestablished()
|
callManager.networkReestablished()
|
||||||
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
scheduledTimeout = timeoutExecutor.schedule(
|
||||||
|
TimeoutRunnable(callId, this),
|
||||||
|
TIMEOUT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
)
|
||||||
} else if (numTimeouts < MAX_RECONNECTS) {
|
} else if (numTimeouts < MAX_RECONNECTS) {
|
||||||
Log.i("Loki", "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS")
|
Log.i(
|
||||||
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
|
"Loki",
|
||||||
|
"Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS"
|
||||||
|
)
|
||||||
|
scheduledReconnect = timeoutExecutor.schedule(
|
||||||
|
CheckReconnectedRunnable(callId, this),
|
||||||
|
RECONNECT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Log.i("Loki", "Network isn't available, timing out")
|
Log.i("Loki", "Network isn't available, timing out")
|
||||||
handleLocalHangup(intent)
|
handleLocalHangup(intent)
|
||||||
@ -627,12 +702,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleCheckTimeout(intent: Intent) {
|
private fun handleCheckTimeout(intent: Intent) {
|
||||||
val callId = callManager.callId ?: return
|
val callId = callManager.callId ?: return
|
||||||
val callState = callManager.currentConnectionState
|
val callState = callManager.currentConnectionState
|
||||||
|
|
||||||
if (callId == getCallId(intent) && (callState !in arrayOf(CallState.Connected, CallState.Connecting))) {
|
if (callId == getCallId(intent) && (callState !in arrayOf(
|
||||||
|
CallState.Connected,
|
||||||
|
CallState.Connecting
|
||||||
|
))
|
||||||
|
) {
|
||||||
Log.w(TAG, "Timing out call: $callId")
|
Log.w(TAG, "Timing out call: $callId")
|
||||||
handleLocalHangup(intent)
|
handleLocalHangup(intent)
|
||||||
}
|
}
|
||||||
@ -640,8 +718,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
private fun setCallInProgressNotification(type: Int, recipient: Recipient?) {
|
private fun setCallInProgressNotification(type: Int, recipient: Recipient?) {
|
||||||
startForeground(
|
startForeground(
|
||||||
CallNotificationBuilder.WEBRTC_NOTIFICATION,
|
CallNotificationBuilder.WEBRTC_NOTIFICATION,
|
||||||
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
|
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
|
||||||
)
|
)
|
||||||
if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) {
|
if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) {
|
||||||
// start an intent for the fullscreen
|
// start an intent for the fullscreen
|
||||||
@ -661,14 +739,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
private fun getRemoteRecipient(intent: Intent): Recipient {
|
private fun getRemoteRecipient(intent: Intent): Recipient {
|
||||||
val remoteAddress = intent.getParcelableExtra<Address>(EXTRA_RECIPIENT_ADDRESS)
|
val remoteAddress = intent.getParcelableExtra<Address>(EXTRA_RECIPIENT_ADDRESS)
|
||||||
?: throw AssertionError("No recipient in intent!")
|
?: throw AssertionError("No recipient in intent!")
|
||||||
|
|
||||||
return Recipient.from(this, remoteAddress, true)
|
return Recipient.from(this, remoteAddress, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCallId(intent: Intent) : UUID {
|
private fun getCallId(intent: Intent): UUID {
|
||||||
return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID
|
return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID
|
||||||
?: throw AssertionError("No callId in intent!")
|
?: throw AssertionError("No callId in intent!")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertMissedCall(recipient: Recipient, signal: Boolean) {
|
private fun insertMissedCall(recipient: Recipient, signal: Boolean) {
|
||||||
@ -680,10 +758,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isIncomingMessageExpired(intent: Intent) =
|
private fun isIncomingMessageExpired(intent: Intent) =
|
||||||
System.currentTimeMillis() - intent.getLongExtra(EXTRA_TIMESTAMP, -1) > TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS)
|
System.currentTimeMillis() - intent.getLongExtra(
|
||||||
|
EXTRA_TIMESTAMP,
|
||||||
|
-1
|
||||||
|
) > TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS)
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG,"onDestroy()")
|
Log.d(TAG, "onDestroy()")
|
||||||
callManager.unregisterListener(this)
|
callManager.unregisterListener(this)
|
||||||
callReceiver?.let { receiver ->
|
callReceiver?.let { receiver ->
|
||||||
unregisterReceiver(receiver)
|
unregisterReceiver(receiver)
|
||||||
@ -698,6 +779,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
wantsToAnswer = false
|
wantsToAnswer = false
|
||||||
currentTimeouts = 0
|
currentTimeouts = 0
|
||||||
isNetworkAvailable = false
|
isNetworkAvailable = false
|
||||||
|
if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
val telephonyManager = getSystemService(TelephonyManager::class.java)
|
||||||
|
with(telephonyManager) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
this.listen(hangupOnCallAnswered, LISTEN_NONE)
|
||||||
|
} else {
|
||||||
|
this.unregisterTelephonyCallback(hangupTelephonyCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,7 +800,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context): Runnable {
|
private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context) :
|
||||||
|
Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val intent = Intent(context, WebRtcCallService::class.java)
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_CHECK_RECONNECT)
|
.setAction(ACTION_CHECK_RECONNECT)
|
||||||
@ -718,7 +810,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context): Runnable {
|
private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context) :
|
||||||
|
Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val intent = Intent(context, WebRtcCallService::class.java)
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_CHECK_RECONNECT_TIMEOUT)
|
.setAction(ACTION_CHECK_RECONNECT_TIMEOUT)
|
||||||
@ -727,26 +820,29 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TimeoutRunnable(private val callId: UUID, private val context: Context): Runnable {
|
private class TimeoutRunnable(private val callId: UUID, private val context: Context) :
|
||||||
|
Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val intent = Intent(context, WebRtcCallService::class.java)
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_CHECK_TIMEOUT)
|
.setAction(ACTION_CHECK_TIMEOUT)
|
||||||
.putExtra(EXTRA_CALL_ID, callId)
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class FailureListener<V>(
|
private abstract class FailureListener<V>(
|
||||||
expectedState: CallState,
|
expectedState: CallState,
|
||||||
expectedCallId: UUID?,
|
expectedCallId: UUID?,
|
||||||
getState: () -> Pair<CallState, UUID?>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
getState: () -> Pair<CallState, UUID?>
|
||||||
|
) : StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
||||||
override fun onSuccessContinue(result: V) {}
|
override fun onSuccessContinue(result: V) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class SuccessOnlyListener<V>(
|
private abstract class SuccessOnlyListener<V>(
|
||||||
expectedState: CallState,
|
expectedState: CallState,
|
||||||
expectedCallId: UUID?,
|
expectedCallId: UUID?,
|
||||||
getState: () -> Pair<CallState, UUID>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
getState: () -> Pair<CallState, UUID>
|
||||||
|
) : StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
||||||
override fun onFailureContinue(throwable: Throwable?) {
|
override fun onFailureContinue(throwable: Throwable?) {
|
||||||
Log.e(TAG, throwable)
|
Log.e(TAG, throwable)
|
||||||
throw AssertionError(throwable)
|
throw AssertionError(throwable)
|
||||||
@ -754,9 +850,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private abstract class StateAwareListener<V>(
|
private abstract class StateAwareListener<V>(
|
||||||
private val expectedState: CallState,
|
private val expectedState: CallState,
|
||||||
private val expectedCallId: UUID?,
|
private val expectedCallId: UUID?,
|
||||||
private val getState: ()->Pair<CallState, UUID?>): FutureTaskListener<V> {
|
private val getState: () -> Pair<CallState, UUID?>
|
||||||
|
) : FutureTaskListener<V> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(StateAwareListener::class.java)
|
private val TAG = Log.tag(StateAwareListener::class.java)
|
||||||
@ -764,7 +861,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
override fun onSuccess(result: V) {
|
override fun onSuccess(result: V) {
|
||||||
if (!isConsistentState()) {
|
if (!isConsistentState()) {
|
||||||
Log.w(TAG,"State has changed since request, aborting success callback...")
|
Log.w(TAG, "State has changed since request, aborting success callback...")
|
||||||
} else {
|
} else {
|
||||||
onSuccessContinue(result)
|
onSuccessContinue(result)
|
||||||
}
|
}
|
||||||
@ -773,7 +870,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
override fun onFailure(exception: ExecutionException?) {
|
override fun onFailure(exception: ExecutionException?) {
|
||||||
if (!isConsistentState()) {
|
if (!isConsistentState()) {
|
||||||
Log.w(TAG, exception)
|
Log.w(TAG, exception)
|
||||||
Log.w(TAG,"State has changed since request, aborting failure callback...")
|
Log.w(TAG, "State has changed since request, aborting failure callback...")
|
||||||
} else {
|
} else {
|
||||||
exception?.let {
|
exception?.let {
|
||||||
onFailureContinue(it.cause)
|
onFailureContinue(it.cause)
|
||||||
@ -792,10 +889,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isConsistentState(
|
private fun isConsistentState(
|
||||||
expectedState: CallState,
|
expectedState: CallState,
|
||||||
expectedCallId: UUID?,
|
expectedCallId: UUID?,
|
||||||
currentState: CallState,
|
currentState: CallState,
|
||||||
currentCallId: UUID?
|
currentCallId: UUID?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return expectedState == currentState && expectedCallId == currentCallId
|
return expectedState == currentState && expectedCallId == currentCallId
|
||||||
}
|
}
|
||||||
@ -817,17 +914,29 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
val intent = Intent(this, WebRtcCallService::class.java)
|
val intent = Intent(this, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_ICE_CONNECTED)
|
.setAction(ACTION_ICE_CONNECTED)
|
||||||
startService(intent)
|
startService(intent)
|
||||||
} else if (newState in arrayOf(FAILED, DISCONNECTED) && (scheduledReconnect == null && scheduledTimeout == null)) {
|
} else if (newState in arrayOf(
|
||||||
|
FAILED,
|
||||||
|
DISCONNECTED
|
||||||
|
) && (scheduledReconnect == null && scheduledTimeout == null)
|
||||||
|
) {
|
||||||
callManager.callId?.let { callId ->
|
callManager.callId?.let { callId ->
|
||||||
callManager.postConnectionEvent(Event.IceDisconnect) {
|
callManager.postConnectionEvent(Event.IceDisconnect) {
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING)
|
callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING)
|
||||||
if (callManager.isInitiator()) {
|
if (callManager.isInitiator()) {
|
||||||
Log.i("Loki", "Starting reconnect timer")
|
Log.i("Loki", "Starting reconnect timer")
|
||||||
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
|
scheduledReconnect = timeoutExecutor.schedule(
|
||||||
|
CheckReconnectedRunnable(callId, this),
|
||||||
|
RECONNECT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Log.i("Loki", "Starting timeout, awaiting new reconnect")
|
Log.i("Loki", "Starting timeout, awaiting new reconnect")
|
||||||
callManager.postConnectionEvent(Event.PrepareForNewOffer) {
|
callManager.postConnectionEvent(Event.PrepareForNewOffer) {
|
||||||
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
scheduledTimeout = timeoutExecutor.schedule(
|
||||||
|
TimeoutRunnable(callId, this),
|
||||||
|
TIMEOUT_SECONDS,
|
||||||
|
TimeUnit.SECONDS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -855,7 +964,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
override fun onDataChannel(p0: DataChannel?) {}
|
override fun onDataChannel(p0: DataChannel?) {}
|
||||||
|
|
||||||
override fun onRenegotiationNeeded() {
|
override fun onRenegotiationNeeded() {
|
||||||
Log.w(TAG,"onRenegotiationNeeded was called!")
|
Log.w(TAG, "onRenegotiationNeeded was called!")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) {}
|
override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) {}
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
package org.thoughtcrime.securesms.sskenvironment;
|
package org.thoughtcrime.securesms.sskenvironment;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.annimon.stream.Collectors;
|
import com.annimon.stream.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.session.libsession.utilities.SSKEnvironment;
|
import org.session.libsession.utilities.SSKEnvironment;
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
import org.session.libsession.utilities.Util;
|
import org.session.libsession.utilities.Util;
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -198,12 +199,12 @@ public class TypingStatusRepository implements SSKEnvironment.TypingIndicatorsPr
|
|||||||
|
|
||||||
if (device != typist.device) return false;
|
if (device != typist.device) return false;
|
||||||
if (threadId != typist.threadId) return false;
|
if (threadId != typist.threadId) return false;
|
||||||
return author.equals(typist.author);
|
return author.getAddress().equals(typist.author.getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = author.hashCode();
|
int result = author.getAddress().hashCode();
|
||||||
result = 31 * result + device;
|
result = 31 * result + device;
|
||||||
result = 31 * result + (int) (threadId ^ (threadId >>> 32));
|
result = 31 * result + (int) (threadId ^ (threadId >>> 32));
|
||||||
return result;
|
return result;
|
||||||
|
@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.database.BackupFileRecord
|
import org.thoughtcrime.securesms.database.BackupFileRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.service.LocalBackupListener
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
@ -74,44 +73,6 @@ object BackupUtil {
|
|||||||
return prefList
|
return prefList
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set app-wide configuration to enable the backups and schedule them.
|
|
||||||
*
|
|
||||||
* Make sure that the backup dir is selected prior activating the backup.
|
|
||||||
* Use [BackupDirSelector] or [setBackupDirUri] manually.
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun enableBackups(context: Context, password: String) {
|
|
||||||
val backupDir = getBackupDirUri(context)
|
|
||||||
if (backupDir == null || !validateDirAccess(context, backupDir)) {
|
|
||||||
throw IOException("Backup dir is not set or invalid.")
|
|
||||||
}
|
|
||||||
|
|
||||||
BackupPassphrase.set(context, password)
|
|
||||||
TextSecurePreferences.setBackupEnabled(context, true)
|
|
||||||
LocalBackupListener.schedule(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set app-wide configuration to disable the backups.
|
|
||||||
*
|
|
||||||
* This call resets the backup dir value.
|
|
||||||
* Make sure to call [setBackupDirUri] prior next call to [enableBackups].
|
|
||||||
*
|
|
||||||
* @param deleteBackupFiles if true, deletes all the previously created backup files
|
|
||||||
* (if the app has access to them)
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
fun disableBackups(context: Context, deleteBackupFiles: Boolean) {
|
|
||||||
BackupPassphrase.set(context, null)
|
|
||||||
TextSecurePreferences.setBackupEnabled(context, false)
|
|
||||||
if (deleteBackupFiles) {
|
|
||||||
deleteAllBackupFiles(context)
|
|
||||||
}
|
|
||||||
setBackupDirUri(context, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getLastBackupTimeString(context: Context, locale: Locale): String {
|
fun getLastBackupTimeString(context: Context, locale: Locale): String {
|
||||||
val timestamp = DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFileTime()
|
val timestamp = DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFileTime()
|
||||||
|
@ -4,8 +4,6 @@ import android.database.Cursor;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
|
|
||||||
public final class CursorUtil {
|
public final class CursorUtil {
|
||||||
|
|
||||||
@ -19,71 +17,8 @@ public final class CursorUtil {
|
|||||||
return cursor.getInt(cursor.getColumnIndexOrThrow(column));
|
return cursor.getInt(cursor.getColumnIndexOrThrow(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float requireFloat(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
return cursor.getFloat(cursor.getColumnIndexOrThrow(column));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long requireLong(@NonNull Cursor cursor, @NonNull String column) {
|
public static long requireLong(@NonNull Cursor cursor, @NonNull String column) {
|
||||||
return cursor.getLong(cursor.getColumnIndexOrThrow(column));
|
return cursor.getLong(cursor.getColumnIndexOrThrow(column));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean requireBoolean(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
return requireInt(cursor, column) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] requireBlob(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
return cursor.getBlob(cursor.getColumnIndexOrThrow(column));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isNull(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
return cursor.isNull(cursor.getColumnIndexOrThrow(column));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<String> getString(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
if (cursor.getColumnIndex(column) < 0) {
|
|
||||||
return Optional.empty();
|
|
||||||
} else {
|
|
||||||
return Optional.ofNullable(requireString(cursor, column));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Integer> getInt(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
if (cursor.getColumnIndex(column) < 0) {
|
|
||||||
return Optional.empty();
|
|
||||||
} else {
|
|
||||||
return Optional.of(requireInt(cursor, column));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<Boolean> getBoolean(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
if (cursor.getColumnIndex(column) < 0) {
|
|
||||||
return Optional.empty();
|
|
||||||
} else {
|
|
||||||
return Optional.of(requireBoolean(cursor, column));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<byte[]> getBlob(@NonNull Cursor cursor, @NonNull String column) {
|
|
||||||
if (cursor.getColumnIndex(column) < 0) {
|
|
||||||
return Optional.empty();
|
|
||||||
} else {
|
|
||||||
return Optional.ofNullable(requireBlob(cursor, column));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads each column as a string, and concatenates them together into a single string separated by |
|
|
||||||
*/
|
|
||||||
public static String readRowAsString(@NonNull Cursor cursor) {
|
|
||||||
StringBuilder row = new StringBuilder();
|
|
||||||
|
|
||||||
for (int i = 0, len = cursor.getColumnCount(); i < len; i++) {
|
|
||||||
row.append(cursor.getString(i));
|
|
||||||
if (i < len - 1) {
|
|
||||||
row.append(" | ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return row.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.util
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
|
||||||
import android.widget.PopupMenu
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
@Deprecated(message = "Not needed when using appcompat 1.4.1+", replaceWith = ReplaceWith("setForceShowIcon(true)"))
|
|
||||||
fun PopupMenu.forceShowIcon() {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
this.setForceShowIcon(true)
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
val popupField = PopupMenu::class.java.getDeclaredField("mPopup")
|
|
||||||
popupField.isAccessible = true
|
|
||||||
val menu = popupField.get(this)
|
|
||||||
menu.javaClass.getDeclaredMethod("setForceShowIcon", Boolean::class.java)
|
|
||||||
.invoke(menu, true)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
Log.d("Loki", "Couldn't show message request popupmenu due to error: $exception.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,11 @@ package org.thoughtcrime.securesms.webrtc
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.telephony.PhoneStateListener
|
import android.telephony.PhoneStateListener
|
||||||
|
import android.telephony.TelephonyCallback
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService
|
import org.thoughtcrime.securesms.service.WebRtcCallService
|
||||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
||||||
@ -25,6 +28,21 @@ class HangUpRtcOnPstnCallAnsweredListener(private val hangupListener: ()->Unit):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
class HangUpRtcTelephonyCallback(private val hangupListener: ()->Unit): TelephonyCallback(), TelephonyCallback.CallStateListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = Log.tag(HangUpRtcTelephonyCallback::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCallStateChanged(state: Int) {
|
||||||
|
if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
|
||||||
|
hangupListener()
|
||||||
|
Log.i(TAG, "Device phone call ended Session call.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PowerButtonReceiver : BroadcastReceiver() {
|
class PowerButtonReceiver : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (Intent.ACTION_SCREEN_OFF == intent.action) {
|
if (Intent.ACTION_SCREEN_OFF == intent.action) {
|
||||||
|
@ -6,7 +6,6 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.SoundPool
|
import android.media.SoundPool
|
||||||
import android.os.Build
|
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
@ -108,7 +107,7 @@ class SignalAudioManager(private val context: Context,
|
|||||||
updateAudioDeviceState()
|
updateAudioDeviceState()
|
||||||
|
|
||||||
wiredHeadsetReceiver = WiredHeadsetReceiver()
|
wiredHeadsetReceiver = WiredHeadsetReceiver()
|
||||||
context.registerReceiver(wiredHeadsetReceiver, IntentFilter(if (Build.VERSION.SDK_INT >= 21) AudioManager.ACTION_HEADSET_PLUG else Intent.ACTION_HEADSET_PLUG))
|
context.registerReceiver(wiredHeadsetReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG))
|
||||||
|
|
||||||
state = State.PREINITIALIZED
|
state = State.PREINITIALIZED
|
||||||
|
|
||||||
|
@ -2,14 +2,15 @@ package org.thoughtcrime.securesms.webrtc.audio
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.bluetooth.BluetoothHeadset
|
import android.bluetooth.BluetoothHeadset
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -80,7 +81,6 @@ class SignalBluetoothManager(
|
|||||||
bluetoothReceiver = BluetoothHeadsetBroadcastReceiver()
|
bluetoothReceiver = BluetoothHeadsetBroadcastReceiver()
|
||||||
context.registerReceiver(bluetoothReceiver, bluetoothHeadsetFilter)
|
context.registerReceiver(bluetoothReceiver, bluetoothHeadsetFilter)
|
||||||
|
|
||||||
Log.i(TAG, "Headset profile state: ${bluetoothAdapter?.getProfileConnectionState(BluetoothProfile.HEADSET)?.toStateString()}")
|
|
||||||
Log.i(TAG, "Bluetooth proxy for headset profile has started")
|
Log.i(TAG, "Bluetooth proxy for headset profile has started")
|
||||||
state = State.UNAVAILABLE
|
state = State.UNAVAILABLE
|
||||||
}
|
}
|
||||||
@ -161,7 +161,8 @@ class SignalBluetoothManager(
|
|||||||
|
|
||||||
Log.d(TAG, "updateDevice(): state: $state")
|
Log.d(TAG, "updateDevice(): state: $state")
|
||||||
|
|
||||||
if (state == State.UNINITIALIZED || bluetoothHeadset == null) {
|
if (state == State.UNINITIALIZED || bluetoothHeadset == null
|
||||||
|
|| ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ public class LockManager {
|
|||||||
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial");
|
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial");
|
||||||
proximityLock = new ProximityLock(pm);
|
proximityLock = new ProximityLock(pm);
|
||||||
|
|
||||||
WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||||
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi");
|
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi");
|
||||||
|
|
||||||
fullLock.setReferenceCounted(false);
|
fullLock.setReferenceCounted(false);
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
<TableRow
|
<TableRow
|
||||||
|
android:id="@+id/error_container"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="@dimen/small_spacing"
|
android:paddingHorizontal="@dimen/small_spacing"
|
||||||
@ -94,6 +95,7 @@
|
|||||||
</TableLayout>
|
</TableLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/resend_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/very_large_spacing"
|
android:layout_marginTop="@dimen/very_large_spacing"
|
||||||
|
@ -1,26 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/mainContentContainer"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent" />
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/emptyStateContainer"
|
android:id="@+id/emptyStateContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_centerInParent="true">
|
android:layout_centerInParent="true">
|
||||||
@ -35,4 +28,4 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -18,35 +18,13 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/mainContentContainer"
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:clipToPadding="false"
|
||||||
|
android:scrollbars="vertical"
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
tools:listitem="@layout/view_user"/>
|
||||||
android:id="@+id/swipeRefreshLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
tools:listitem="@layout/view_user"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/loadingTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/contact_selection_group_activity__finding_contacts"
|
|
||||||
android:textSize="@dimen/large_font_size" />
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:background="@color/transparent_black_6" />
|
android:background="@color/transparent_black_30" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="84dp"
|
android:layout_width="84dp"
|
||||||
|
@ -26,11 +26,6 @@
|
|||||||
android:id="@+id/menu_message_details"
|
android:id="@+id/menu_message_details"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
|
||||||
android:title="@string/conversation_context__menu_message_details"
|
|
||||||
android:id="@+id/menu_context_select_message"
|
|
||||||
app:showAsAction="never" />
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:title="@string/conversation_context__menu_copy_text"
|
android:title="@string/conversation_context__menu_copy_text"
|
||||||
android:id="@+id/menu_context_copy"
|
android:id="@+id/menu_context_copy"
|
||||||
|
@ -37,15 +37,9 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
recipient = mock(Recipient::class.java)
|
recipient = mock(Recipient::class.java)
|
||||||
whenever(repository.isOxenHostedOpenGroup(anyLong())).thenReturn(true)
|
|
||||||
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
|
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `should emit group type on init`() = runBlockingTest {
|
|
||||||
assertTrue(viewModel.uiState.first().isOxenHostedOpenGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should save draft message`() {
|
fun `should save draft message`() {
|
||||||
val draft = "Hi there"
|
val draft = "Hi there"
|
||||||
|
@ -4,9 +4,9 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.2.2'
|
classpath "com.android.tools.build:gradle:$gradlePluginVersion"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
classpath "com.google.gms:google-services:4.3.10"
|
classpath "com.google.gms:google-services:$googleServicesVersion"
|
||||||
classpath files('libs/gradle-witness.jar')
|
classpath files('libs/gradle-witness.jar')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,6 +51,7 @@ allprojects {
|
|||||||
|
|
||||||
project.ext {
|
project.ext {
|
||||||
androidMinimumSdkVersion = 23
|
androidMinimumSdkVersion = 23
|
||||||
androidCompileSdkVersion = 30
|
androidTargetSdkVersion = 31
|
||||||
|
androidCompileSdkVersion = 32
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,5 +6,5 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.android.tools.build:apksig:4.0.1'
|
implementation 'com.android.tools.build:apksig:4.0.2'
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
org.gradle.jvmargs=-Xmx4g
|
org.gradle.jvmargs=-Xmx8g
|
||||||
|
|
||||||
kotlinVersion=1.6.0
|
gradlePluginVersion=7.3.1
|
||||||
coroutinesVersion=1.6.0
|
googleServicesVersion=4.3.12
|
||||||
kotlinxJsonVersion=1.3.0
|
kotlinVersion=1.6.21
|
||||||
lifecycleVersion=2.3.1
|
coroutinesVersion=1.6.4
|
||||||
|
kotlinxJsonVersion=1.3.3
|
||||||
|
lifecycleVersion=2.5.1
|
||||||
daggerVersion=2.40.1
|
daggerVersion=2.40.1
|
||||||
glideVersion=4.11.0
|
glideVersion=4.11.0
|
||||||
kovenantVersion=3.3.0
|
kovenantVersion=3.3.0
|
||||||
@ -13,4 +15,12 @@ curve25519Version=0.6.0
|
|||||||
protobufVersion=2.5.0
|
protobufVersion=2.5.0
|
||||||
okhttpVersion=3.12.1
|
okhttpVersion=3.12.1
|
||||||
jacksonDatabindVersion=2.9.8
|
jacksonDatabindVersion=2.9.8
|
||||||
mockitoKotlinVersion=4.0.0
|
appcompatVersion=1.5.1
|
||||||
|
materialVersion=1.7.0
|
||||||
|
preferenceVersion=1.2.0
|
||||||
|
coreVersion=1.8.0
|
||||||
|
|
||||||
|
junitVersion=4.13.2
|
||||||
|
mockitoKotlinVersion=4.0.0
|
||||||
|
testCoreVersion=1.4.0
|
||||||
|
pagingVersion=3.0.0
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Thu Dec 30 07:09:53 SAST 2021
|
#Thu Dec 30 07:09:53 SAST 2021
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -19,17 +19,16 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":libsignal")
|
implementation project(":libsignal")
|
||||||
implementation project(":liblazysodium")
|
implementation project(":liblazysodium")
|
||||||
// implementation 'com.goterl:lazysodium-android:5.0.2@aar'
|
|
||||||
implementation "net.java.dev.jna:jna:5.8.0@aar"
|
implementation "net.java.dev.jna:jna:5.8.0@aar"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation "androidx.core:core-ktx:$coreVersion"
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation "androidx.appcompat:appcompat:$appcompatVersion"
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation "androidx.preference:preference-ktx:$preferenceVersion"
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation "com.google.android.material:material:$materialVersion"
|
||||||
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
||||||
implementation "com.google.dagger:hilt-android:$daggerVersion"
|
implementation "com.google.dagger:hilt-android:$daggerVersion"
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
implementation "com.github.bumptech.glide:glide:$glideVersion"
|
implementation "com.github.bumptech.glide:glide:$glideVersion"
|
||||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
implementation 'com.annimon:stream:1.1.8'
|
implementation 'com.annimon:stream:1.1.8'
|
||||||
@ -43,7 +42,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
||||||
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
|
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation "junit:junit:$junitVersion"
|
||||||
testImplementation 'org.assertj:assertj-core:3.11.1'
|
testImplementation 'org.assertj:assertj-core:3.11.1'
|
||||||
testImplementation "org.mockito:mockito-inline:4.0.0"
|
testImplementation "org.mockito:mockito-inline:4.0.0"
|
||||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||||
@ -51,7 +50,7 @@ dependencies {
|
|||||||
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
|
||||||
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
|
||||||
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
|
||||||
testImplementation 'androidx.test:core:1.3.0'
|
testImplementation "androidx.test:core:$testCoreVersion"
|
||||||
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
|
||||||
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
|
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
|
||||||
|
@ -12,7 +12,6 @@ import androidx.annotation.DrawableRes;
|
|||||||
import com.amulyakhare.textdrawable.TextDrawable;
|
import com.amulyakhare.textdrawable.TextDrawable;
|
||||||
import com.makeramen.roundedimageview.RoundedDrawable;
|
import com.makeramen.roundedimageview.RoundedDrawable;
|
||||||
|
|
||||||
|
|
||||||
import org.session.libsession.R;
|
import org.session.libsession.R;
|
||||||
import org.session.libsession.utilities.ThemeUtil;
|
import org.session.libsession.utilities.ThemeUtil;
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ public class ResourceContactPhoto implements FallbackContactPhoto {
|
|||||||
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
|
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
|
||||||
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(resourceId));
|
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(resourceId));
|
||||||
|
|
||||||
foreground.setScaleType(ImageView.ScaleType.CENTER);
|
foreground.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
|
||||||
|
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||||
|
@ -108,6 +108,7 @@ interface StorageProtocol {
|
|||||||
fun markAsSent(timestamp: Long, author: String)
|
fun markAsSent(timestamp: Long, author: String)
|
||||||
fun markUnidentified(timestamp: Long, author: String)
|
fun markUnidentified(timestamp: Long, author: String)
|
||||||
fun setErrorMessage(timestamp: Long, author: String, error: Exception)
|
fun setErrorMessage(timestamp: Long, author: String, error: Exception)
|
||||||
|
fun clearErrorMessage(messageID: Long)
|
||||||
fun setMessageServerHash(messageID: Long, serverHash: String)
|
fun setMessageServerHash(messageID: Long, serverHash: String)
|
||||||
|
|
||||||
// Closed Groups
|
// Closed Groups
|
||||||
|
@ -96,7 +96,10 @@ object FileServerApi {
|
|||||||
)
|
)
|
||||||
return send(request).map { response ->
|
return send(request).map { response ->
|
||||||
val json = JsonUtil.fromJson(response, Map::class.java)
|
val json = JsonUtil.fromJson(response, Map::class.java)
|
||||||
(json["id"] as? String)?.toLong() ?: throw Error.ParsingFailed
|
val hasId = json.containsKey("id")
|
||||||
|
val id = json.getOrDefault("id", null)
|
||||||
|
Log.d("Loki-FS", "File Upload Response hasId: $hasId of type: ${id?.javaClass}")
|
||||||
|
(id as? String)?.toLong() ?: throw Error.ParsingFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,11 +8,7 @@ import org.session.libsession.messaging.jobs.MessageSendJob
|
|||||||
import org.session.libsession.messaging.jobs.NotifyPNServerJob
|
import org.session.libsession.messaging.jobs.NotifyPNServerJob
|
||||||
import org.session.libsession.messaging.messages.Destination
|
import org.session.libsession.messaging.messages.Destination
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.CallMessage
|
import org.session.libsession.messaging.messages.control.*
|
||||||
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
|
||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
|
||||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
|
||||||
import org.session.libsession.messaging.messages.visible.LinkPreview
|
import org.session.libsession.messaging.messages.visible.LinkPreview
|
||||||
import org.session.libsession.messaging.messages.visible.Profile
|
import org.session.libsession.messaging.messages.visible.Profile
|
||||||
import org.session.libsession.messaging.messages.visible.Quote
|
import org.session.libsession.messaging.messages.visible.Quote
|
||||||
@ -32,12 +28,7 @@ import org.session.libsession.utilities.GroupUtil
|
|||||||
import org.session.libsession.utilities.SSKEnvironment
|
import org.session.libsession.utilities.SSKEnvironment
|
||||||
import org.session.libsignal.crypto.PushTransportDetails
|
import org.session.libsignal.crypto.PushTransportDetails
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.utilities.IdPrefix
|
|
||||||
import org.session.libsignal.utilities.Namespace
|
|
||||||
import org.session.libsignal.utilities.defaultRequiresAuth
|
|
||||||
import org.session.libsignal.utilities.hasNamespaces
|
|
||||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
|
||||||
@ -337,6 +328,8 @@ object MessageSender {
|
|||||||
message.serverHash?.let {
|
message.serverHash?.let {
|
||||||
storage.setMessageServerHash(messageID, it)
|
storage.setMessageServerHash(messageID, it)
|
||||||
}
|
}
|
||||||
|
// in case any errors from previous sends
|
||||||
|
storage.clearErrorMessage(messageID)
|
||||||
// Track the open group server message ID
|
// Track the open group server message ID
|
||||||
if (message.openGroupServerMessageID != null && (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup)) {
|
if (message.openGroupServerMessageID != null && (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup)) {
|
||||||
val server: String
|
val server: String
|
||||||
|
@ -6,15 +6,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
|||||||
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
|
import org.session.libsession.messaging.jobs.BackgroundGroupAddJob
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.messages.Message
|
import org.session.libsession.messaging.messages.Message
|
||||||
import org.session.libsession.messaging.messages.control.CallMessage
|
import org.session.libsession.messaging.messages.control.*
|
||||||
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
|
||||||
import org.session.libsession.messaging.messages.control.DataExtractionNotification
|
|
||||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
|
||||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
|
||||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
|
||||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
|
||||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
|
||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.messages.visible.Reaction
|
import org.session.libsession.messaging.messages.visible.Reaction
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
@ -29,26 +21,18 @@ import org.session.libsession.messaging.utilities.SessionId
|
|||||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
import org.session.libsession.messaging.utilities.WebRtcUtils
|
import org.session.libsession.messaging.utilities.WebRtcUtils
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.*
|
||||||
import org.session.libsession.utilities.GroupRecord
|
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.ProfileKeyUtil
|
|
||||||
import org.session.libsession.utilities.SSKEnvironment
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||||
import org.session.libsignal.messages.SignalServiceGroup
|
import org.session.libsignal.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.IdPrefix
|
|
||||||
import org.session.libsignal.utilities.Log
|
|
||||||
import org.session.libsignal.utilities.guava.Optional
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
import org.session.libsignal.utilities.removingIdPrefixIfNeeded
|
|
||||||
import org.session.libsignal.utilities.toHexString
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.LinkedList
|
import java.util.*
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
|
internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
|
||||||
@ -307,6 +291,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
|
|||||||
return@mapNotNull attachment
|
return@mapNotNull attachment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Cancel any typing indicators if needed
|
||||||
|
cancelTypingIndicatorsIfNeeded(message.sender!!)
|
||||||
// Parse reaction if needed
|
// Parse reaction if needed
|
||||||
val threadIsGroup = threadRecipient?.isGroupRecipient == true
|
val threadIsGroup = threadRecipient?.isGroupRecipient == true
|
||||||
message.reaction?.let { reaction ->
|
message.reaction?.let { reaction ->
|
||||||
@ -332,8 +318,6 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
|
|||||||
}
|
}
|
||||||
return messageID
|
return messageID
|
||||||
}
|
}
|
||||||
// Cancel any typing indicators if needed
|
|
||||||
cancelTypingIndicatorsIfNeeded(message.sender!!)
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import org.session.libsignal.utilities.JsonUtil;
|
|||||||
import org.session.libsignal.utilities.guava.Optional;
|
import org.session.libsignal.utilities.guava.Optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class LinkPreview {
|
public class LinkPreview {
|
||||||
|
|
||||||
@ -75,4 +76,17 @@ public class LinkPreview {
|
|||||||
public static LinkPreview deserialize(@NonNull String serialized) throws IOException {
|
public static LinkPreview deserialize(@NonNull String serialized) throws IOException {
|
||||||
return JsonUtil.fromJson(serialized, LinkPreview.class);
|
return JsonUtil.fromJson(serialized, LinkPreview.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
LinkPreview that = (LinkPreview) o;
|
||||||
|
return Objects.equals(url, that.url) && Objects.equals(title, that.title) && Objects.equals(attachmentId, that.attachmentId) && Objects.equals(thumbnail, that.thumbnail);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(url, title, attachmentId, thumbnail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,16 +15,16 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.annotation:annotation:1.2.0"
|
implementation "androidx.annotation:annotation:1.5.0"
|
||||||
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
|
||||||
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
|
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
|
||||||
implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
|
implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
|
||||||
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
|
||||||
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
|
implementation "nl.komponents.kovenant:kovenant:$kovenantVersion"
|
||||||
testImplementation "junit:junit:3.8.2"
|
testImplementation "junit:junit:$junitVersion"
|
||||||
testImplementation "org.assertj:assertj-core:1.7.1"
|
testImplementation "org.assertj:assertj-core:1.7.1"
|
||||||
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
|
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user