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:
0x330a 2022-12-19 11:29:05 +11:00 committed by GitHub
parent bda50d263c
commit cdd2559839
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 820 additions and 930 deletions

View File

@ -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

View File

@ -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>

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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
}
}

View File

@ -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
} }

View File

@ -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()
} }

View File

@ -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) }
)
}
}

View File

@ -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)
} }
} }
} }

View File

@ -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
) )

View File

@ -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())
if (errorMessage != null) {
binding.errorMessage.text = errorMessage 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

View File

@ -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()
} }

View File

@ -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

View File

@ -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 {

View File

@ -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)) {

View File

@ -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) {

View File

@ -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)
} }

View File

@ -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);
if (timestamp == -1) {
contentValues.put(LAST_SEEN, System.currentTimeMillis()); 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);

View File

@ -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() {

View File

@ -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;

View File

@ -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);
}
} }

View File

@ -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)

View File

@ -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) {

View File

@ -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.");
} }

View File

@ -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()
} }

View File

@ -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);
}
} }

View File

@ -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);
} }

View File

@ -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);

View File

@ -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);

View File

@ -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() {

View File

@ -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);
} }

View File

@ -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);

View File

@ -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
} }

View File

@ -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()

View File

@ -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);
} }
} }

View File

@ -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;
} }
} }

View File

@ -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,9 +37,8 @@ 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;
@ -84,8 +80,6 @@ public class DirectShareService extends ChooserTargetService {
} }
return results; return results;
} finally {
if (cursor != null) cursor.close();
} }
} }

View File

@ -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);

View File

@ -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;
@ -90,7 +91,7 @@ public class GenericForegroundService extends Service {
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());
} }

View File

@ -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

View File

@ -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);

View File

@ -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)

View File

@ -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
@ -108,7 +94,8 @@ 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)
@ -118,15 +105,23 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
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) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_SET_MUTE_AUDIO) .setAction(ACTION_SET_MUTE_AUDIO)
.putExtra(EXTRA_MUTE, !enabled) .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(
context: Context,
address: Address,
sdp: String,
callId: UUID,
callTime: Long
) =
Intent(context, WebRtcCallService::class.java) Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_INCOMING_RING) .setAction(ACTION_INCOMING_RING)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address) .putExtra(EXTRA_RECIPIENT_ADDRESS, address)
@ -148,22 +143,33 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
.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(
context: Context,
address: Address,
iceCandidates: List<IceCandidate>,
callId: UUID
) =
Intent(context, WebRtcCallService::class.java) Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_ICE_MESSAGE) .setAction(ACTION_ICE_MESSAGE)
.putExtra(EXTRA_CALL_ID, callId) .putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray()) .putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray())
.putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()) .putExtra(
EXTRA_ICE_SDP_LINE_INDEX,
iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()
)
.putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray()) .putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray())
.putExtra(EXTRA_RECIPIENT_ADDRESS, address) .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)
@ -188,7 +194,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
} }
} }
@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,9 +206,18 @@ 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 {
private val hangupOnCallAnswered by lazy {
HangUpRtcOnPstnCallAnsweredListener {
ContextCompat.startForegroundService(this, hangupIntent(this)) 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
private var callReceiver: IncomingPstnCallReceiver? = null private var callReceiver: IncomingPstnCallReceiver? = 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()
if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
getSystemService(TelephonyManager::class.java) getSystemService(TelephonyManager::class.java)
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE) .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,7 +468,13 @@ 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(
expectedState,
expectedCallId,
callManager.currentConnectionState,
callManager.callId
)
) {
Log.e(TAG, e) Log.e(TAG, e)
callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE) callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE)
callManager.postConnectionError() callManager.postConnectionError()
@ -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()
@ -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()
} }
@ -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)
} }
@ -680,7 +758,10 @@ 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()")
@ -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,7 +820,8 @@ 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)
@ -739,14 +833,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
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)
@ -756,7 +852,8 @@ 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)
@ -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
)
} }
} }
} }

View File

@ -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;

View File

@ -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()

View File

@ -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();
}
} }

View File

@ -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.")
}
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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
} }

View File

@ -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);

View File

@ -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"

View File

@ -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
android:id="@+id/mainContentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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>

View File

@ -18,35 +18,13 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/mainContentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" 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:clipToPadding="false" android:clipToPadding="false"
android:scrollbars="vertical" android:scrollbars="vertical"
tools:listitem="@layout/view_user"/> 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>

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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
} }
} }

View File

@ -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'
} }

View File

@ -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
appcompatVersion=1.5.1
materialVersion=1.7.0
preferenceVersion=1.2.0
coreVersion=1.8.0
junitVersion=4.13.2
mockitoKotlinVersion=4.0.0 mockitoKotlinVersion=4.0.0
testCoreVersion=1.4.0
pagingVersion=3.0.0

View File

@ -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

View File

@ -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"

View File

@ -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);

View File

@ -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

View File

@ -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
} }
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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);
}
} }

View File

@ -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"
} }