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()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath "com.android.tools.build:gradle:$gradlePluginVersion"
classpath files('libs/gradle-witness.jar')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
classpath "com.google.gms:google-services:4.3.10"
classpath "com.google.gms:google-services:$googleServicesVersion"
classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion"
}
}
@ -27,26 +27,27 @@ configurations.all {
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.2.1'
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "com.google.android.material:material:$materialVersion"
implementation 'com.google.android:flexbox:2.0.1'
implementation 'androidx.legacy:legacy-support-v13: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.gridlayout:gridlayout:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.exifinterface:exifinterface:1.3.4'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion"
implementation 'androidx.activity:activity-ktx:1.2.2'
implementation 'androidx.fragment:fragment-ktx:1.3.2'
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.work:work-runtime-ktx:2.4.0"
implementation "androidx.paging:paging-runtime-ktx:$pagingVersion"
implementation 'androidx.activity:activity-ktx:1.5.1'
implementation 'androidx.fragment:fragment-ktx:1.5.3'
implementation "androidx.core:core-ktx:$coreVersion"
implementation "androidx.work:work-runtime-ktx:2.7.1"
implementation ("com.google.firebase:firebase-messaging:18.0.0") {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
@ -119,7 +120,7 @@ dependencies {
implementation "com.github.tbruyelle:rxpermissions:0.10.2"
implementation "com.github.ybq:Android-SpinKit:1.4.0"
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.mockito:mockito-inline:4.0.0"
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-rule: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 "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
@ -141,7 +142,7 @@ dependencies {
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
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
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.idling:idling-concurrent: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:shadows-multidex:4.4'
}
def canonicalVersionCode = 310
def canonicalVersionName = "1.16.1"
def canonicalVersionCode = 321
def canonicalVersionName = "1.16.3"
def postFixSize = 10
def abiPostFix = ['armeabi-v7a' : 1,
@ -169,13 +170,9 @@ def abiPostFix = ['armeabi-v7a' : 1,
android {
compileSdkVersion androidCompileSdkVersion
buildToolsVersion '29.0.3'
namespace 'network.loki.messenger'
useLibrary 'org.apache.http.legacy'
dexOptions {
javaMaxHeapSize "4g"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -209,7 +206,7 @@ android {
versionName canonicalVersionName
minSdkVersion androidMinimumSdkVersion
targetSdkVersion androidCompileSdkVersion
targetSdkVersion androidTargetSdkVersion
multiDexEnabled = true

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="network.loki.messenger">
xmlns:tools="http://schemas.android.com/tools">
<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" />
<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.USE_FINGERPRINT" />
<uses-permission android:name="network.loki.messenger.ACCESS_SESSION_SECRETS" />
@ -174,6 +174,7 @@
android:screenOrientation="portrait"/>
<activity
android:exported="true"
android:name="org.thoughtcrime.securesms.ShareActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:excludeFromRecents="true"
@ -321,6 +322,7 @@
android:exported="false" />
<service
android:name="org.thoughtcrime.securesms.service.DirectShareService"
android:exported="true"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
<intent-filter>
<action android:name="android.service.chooser.ChooserTargetService" />
@ -398,42 +400,48 @@
android:authorities="network.loki.securesms.database.recipient"
android:exported="false" />
<receiver android:name="org.thoughtcrime.securesms.service.BootReceiver">
<receiver android:name="org.thoughtcrime.securesms.service.BootReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="network.loki.securesms.RESTART" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.service.LocalBackupListener">
<receiver android:name="org.thoughtcrime.securesms.service.LocalBackupListener"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.service.PersistentConnectionBootListener">
<receiver android:name="org.thoughtcrime.securesms.service.PersistentConnectionBootListener"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.notifications.LocaleChangedReceiver">
<receiver android:name="org.thoughtcrime.securesms.notifications.LocaleChangedReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.notifications.DeleteNotificationReceiver">
<receiver android:name="org.thoughtcrime.securesms.notifications.DeleteNotificationReceiver"
android:exported="true">
<intent-filter>
<action android:name="network.loki.securesms.DELETE_NOTIFICATION" />
</intent-filter>
</receiver>
<receiver
android:name="org.thoughtcrime.securesms.service.PanicResponderListener"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
</intent-filter>
</receiver>
<receiver
android:name="org.thoughtcrime.securesms.notifications.BackgroundPollWorker$BootBroadcastReceiver"
android:enabled="true">
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>

View File

@ -481,6 +481,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return;
ThreadUtils.queue(() -> {
// 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);
try {
// 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 -> {
// Update the last profile picture upload date
TextSecurePreferences.setLastProfilePictureUpload(ApplicationContext.this, new Date().getTime());
Log.d("Loki-Avatar", "Uploading Avatar Finished");
return Unit.INSTANCE;
});
} 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.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import network.loki.messenger.databinding.ContactSelectionListFragmentBinding
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.Log
@ -58,7 +57,6 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
super.onViewCreated(view, savedInstanceState)
binding.recyclerView.layoutManager = LinearLayoutManager(activity)
binding.recyclerView.adapter = listAdapter
binding.swipeRefreshLayout.isEnabled = requireActivity().intent.getBooleanExtra(REFRESHABLE, true)
}
override fun onStop() {
@ -73,15 +71,6 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
fun resetQueryFilter() {
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>> {
@ -106,7 +95,7 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks<L
return
}
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
}

View File

@ -3,12 +3,12 @@ package org.thoughtcrime.securesms.contacts
import android.app.Activity
import android.content.Intent
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.MenuItem
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.databinding.ActivitySelectContactsBinding
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
@ -49,7 +49,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana
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)
return members.isNotEmpty()
}
@ -70,7 +70,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana
private fun update(members: List<String>) {
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
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.animation.FloatEvaluator
import android.animation.ValueAnimator
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.*
import android.content.res.Resources
import android.database.Cursor
import android.graphics.Rect
import android.graphics.Typeface
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.*
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Pair
import android.util.TypedValue
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.*
import android.widget.LinearLayout
import android.widget.RelativeLayout
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.quotes.QuoteModel
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.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.recipients.Recipient
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.search.SearchBottomBar
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
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.conversation.v2.utilities.*
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import org.thoughtcrime.securesms.database.GroupDatabase
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.*
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
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.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivity
import org.thoughtcrime.securesms.mms.AudioSlide
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.mms.*
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
import org.thoughtcrime.securesms.util.ActivityDispatcher
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
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 org.thoughtcrime.securesms.util.*
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference
@ -635,7 +590,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
this
) { onOptionsItemSelected(it) }
}
super.onPrepareOptionsMenu(menu)
return true
}
@ -1789,6 +1743,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
endActionMode()
}
override fun destroyActionMode() {
this.actionMode = null
}
private fun sendScreenshotNotification() {
val recipient = viewModel.recipient ?: return
if (recipient.isGroupRecipient) return
@ -1834,7 +1792,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
if (result == null) return@Observer
if (result.getResults().isNotEmpty()) {
result.getResults()[result.position]?.let {
jumpToMessage(it.messageRecipient.address, it.receivedTimestampMs) {
jumpToMessage(it.messageRecipient.address, it.sentTimestampMs) {
searchViewModel.onMissingResult() }
}
}
@ -1900,7 +1858,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
ConversationReactionOverlay.Action.DELETE -> deleteMessages(selectedItems)
ConversationReactionOverlay.Action.BAN_AND_DELETE_ALL -> banAndDeleteAll(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 ConversationUiState(
val isOxenHostedOpenGroup: Boolean = false,
val uiMessages: List<UiMessage> = emptyList(),
val isMessageRequestAccepted: Boolean? = null
)

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
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.util.DateUtils
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
@ -48,7 +48,10 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
// We only show this screen for messages fail to send,
// so the author of the messages must be the current user.
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 openGroup = storage.getOpenGroup(threadId)
val blindedKey = openGroup?.let { group ->
@ -71,8 +74,15 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale)
binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent))
val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) ?: "Message failed to send."
binding.errorMessage.text = errorMessage
val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId())
if (errorMessage != null) {
binding.errorMessage.text = errorMessage
binding.resendContainer.isVisible = true
binding.errorContainer.isVisible = true
} else {
binding.errorContainer.isVisible = false
binding.resendContainer.isVisible = false
}
if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) {
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
// Copy Session ID
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
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
menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed)
// Save media
@ -101,6 +101,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p
override fun onDestroyActionMode(mode: ActionMode) {
adapter.selectedItems.clear()
adapter.notifyDataSetChanged()
delegate?.destroyActionMode()
}
}
@ -116,4 +117,5 @@ interface ConversationActionModeCallbackDelegate {
fun showMessageDetail(messages: Set<MessageRecord>)
fun saveAttachment(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.util.SearchUtil
import org.thoughtcrime.securesms.util.getAccentColor
import java.util.Locale
import java.util.*
import kotlin.math.roundToInt
class VisibleMessageContentView : LinearLayout {
@ -86,6 +86,14 @@ class VisibleMessageContentView : LinearLayout {
if (message.isDeleted) {
binding.deletedMessageView.root.isVisible = true
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
} else {
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()))
}
fun clearErrorMessage(messageID: Long) {
val database = databaseHelper.writableDatabase
database.delete(errorMessageTable, "${Companion.messageID} = ?", arrayOf(messageID.toString()))
}
fun deleteThread(threadId: Long) {
val database = databaseHelper.writableDatabase
try {

View File

@ -112,6 +112,64 @@ public class MmsSmsDatabase extends Database {
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) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + (reverse ? " DESC" : " ASC");
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@ -199,16 +257,16 @@ public class MmsSmsDatabase extends Database {
return -1;
}
public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull Address address) {
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
public int getMessagePositionInConversation(long threadId, long sentTimestamp, @NonNull Address address) {
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
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();
boolean isOwnNumber = Util.isOwnNumber(context, address.serialize());
while (cursor != null && cursor.moveToNext()) {
boolean timestampMatches = cursor.getLong(0) == receivedTimestamp;
boolean timestampMatches = cursor.getLong(0) == sentTimestamp;
boolean addressMatches = serializedAddress.equals(cursor.getString(1));
if (timestampMatches && (addressMatches || isOwnNumber)) {

View File

@ -63,7 +63,7 @@ public class SearchDatabase extends Database {
ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"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 + " " +
"FROM " + SmsDatabase.TABLE_NAME + " " +
"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 + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"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 + " " +
"FROM " + MmsDatabase.TABLE_NAME + " " +
"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 + " " +
"WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " +
"ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " +
"LIMIT ?";
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 + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"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 + " " +
"FROM " + SmsDatabase.TABLE_NAME + " " +
"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 + ", " +
MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " +
"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 + " " +
"FROM " + MmsDatabase.TABLE_NAME + " " +
"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 + " " +
"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";
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.calls.CallMessageType
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.AttachmentUploadJob
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.jobs.*
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.ConfigurationMessage
import org.session.libsession.messaging.messages.control.MessageRequestResponse
import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
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.signal.*
import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.Reaction
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.UpdateMessageData
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.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.libsignal.crypto.ecc.ECKeyPair
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) {
DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash)
}

View File

@ -502,15 +502,23 @@ public class ThreadDatabase extends Database {
return db.rawQuery(query, null);
}
public void setLastSeen(long threadId) {
public void setLastSeen(long threadId, long timestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1);
contentValues.put(LAST_SEEN, System.currentTimeMillis());
if (timestamp == -1) {
contentValues.put(LAST_SEEN, System.currentTimeMillis());
} else {
contentValues.put(LAST_SEEN, timestamp);
}
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
notifyConversationListListeners();
}
public void setLastSeen(long threadId) {
setLastSeen(threadId, -1);
}
public Pair<Long, Boolean> getLastSeenAndHasSent(long threadId) {
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);

View File

@ -33,6 +33,7 @@ import org.session.libsession.utilities.NetworkFailure;
import org.session.libsession.utilities.recipients.Recipient;
import java.util.List;
import java.util.Objects;
/**
* The base class for message record models that are displayed in
@ -140,14 +141,16 @@ public abstract class MessageRecord extends DisplayRecord {
return spannable;
}
@Override
public boolean equals(Object other) {
return other instanceof MessageRecord
&& ((MessageRecord) other).getId() == getId()
&& ((MessageRecord) other).isMms() == isMms();
&& ((MessageRecord) other).getId() == getId()
&& ((MessageRecord) other).isMms() == isMms();
}
@Override
public int hashCode() {
return (int)getId();
return Objects.hash(id, isMms());
}
public @NonNull List<ReactionRecord> getReactions() {

View File

@ -2,13 +2,15 @@ package org.thoughtcrime.securesms.database.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.session.libsession.utilities.Contact;
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.NetworkFailure;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import java.util.LinkedList;
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.thoughtcrime.securesms.mms.SlideDeck;
import java.util.Objects;
public class Quote {
private final long id;
@ -47,4 +49,17 @@ public class Quote {
public QuoteModel getQuoteModel() {
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) {
is GlobalSearchAdapter.Model.Message -> {
val threadId = model.messageResult.threadId
val timestamp = model.messageResult.receivedTimestampMs
val timestamp = model.messageResult.sentTimestampMs
val author = model.messageResult.messageRecipient.address
val intent = Intent(this, ConversationActivityV2::class.java)

View File

@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) {
// if (hasUnreads) {
// 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)
val textSpannable = SpannableStringBuilder()
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {

View File

@ -6,17 +6,19 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.ApplicationContext;
import network.loki.messenger.BuildConfig;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.ApplicationContext;
import java.util.List;
import java.util.UUID;
import network.loki.messenger.BuildConfig;
/**
* Schedules tasks using the {@link AlarmManager}.
*
@ -51,7 +53,7 @@ public class AlarmManagerScheduler implements Scheduler {
Intent intent = new Intent(context, RetryReceiver.class);
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.");
}

View File

@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.forceShowIcon
class MessageRequestsAdapter(
context: Context,
@ -64,7 +63,7 @@ class MessageRequestsAdapter(
item.iconTintList = ColorStateList.valueOf(context.getColor(R.color.destructive))
item.title = s
}
popupMenu.forceShowIcon()
popupMenu.setForceShowIcon(true)
popupMenu.show()
}

View File

@ -17,17 +17,19 @@
package org.thoughtcrime.securesms.mms;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.util.MediaUtil;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class SlideDeck {
@ -138,4 +140,17 @@ public class SlideDeck {
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.goterl.lazysodium.utils.KeyPair;
import org.session.libsession.messaging.MessagingModuleConfiguration;
import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.utilities.SessionId;
@ -453,8 +452,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
NotificationState notificationState = new NotificationState();
MmsSmsDatabase.Reader reader = DatabaseComponent.get(context).mmsSmsDatabase().readerFor(cursor);
ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase();
LokiThreadDatabase lokiThreadDatabase= DatabaseComponent.get(context).lokiThreadDatabase();
KeyPair edKeyPair = MessagingModuleConfiguration.getShared().getGetUserED25519KeyPair().invoke();
MessageRecord record;
Map<Long, String> cache = new HashMap<Long, String>();
@ -575,7 +573,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION);
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);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent);
@ -584,7 +582,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
@Override
public void clearReminder(Context context) {
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.cancel(pendingIntent);
}

View File

@ -5,9 +5,10 @@ import android.content.Context;
import android.content.Intent;
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.recipients.Recipient;
import network.loki.messenger.R;
public class FailedNotificationBuilder extends AbstractNotificationBuilder {
@ -20,7 +21,7 @@ public class FailedNotificationBuilder extends AbstractNotificationBuilder {
setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed));
setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_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);
setAlarms(null, Recipient.VibrateState.DEFAULT);
setChannelId(NotificationChannels.FAILURES);

View File

@ -34,7 +34,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu
setColor(context.getResources().getColor(R.color.textsecure_primary));
setSmallIcon(R.drawable.ic_notification);
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);
setGroupSummary(true);

View File

@ -4,14 +4,15 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.TaskStackBuilder;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.session.libsession.utilities.recipients.Recipient;
public class NotificationItem {
@ -75,9 +76,14 @@ public class NotificationItem {
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
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)
.addNextIntentWithParentStack(intent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
.getPendingIntent(0, intentFlags);
}
public long getId() {

View File

@ -4,12 +4,14 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
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.VibrateState;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
import java.util.LinkedHashSet;
@ -114,7 +116,12 @@ public class NotificationState {
intent.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray);
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) {
@ -127,7 +134,12 @@ public class NotificationState {
intent.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod);
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) {
@ -141,7 +153,12 @@ public class NotificationState {
intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]);
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) {
@ -160,7 +177,12 @@ public class NotificationState {
intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId);
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) {
@ -171,7 +193,12 @@ public class NotificationState {
intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]);
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) {
@ -190,7 +217,12 @@ public class NotificationState {
intent.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms);
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.content.Context;
import android.content.Intent;
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.TextSecurePreferences;
import org.session.libsession.utilities.recipients.Recipient;
import org.thoughtcrime.securesms.home.HomeActivity;
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));
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);
setAlarms(null, Recipient.VibrateState.DEFAULT);

View File

@ -52,7 +52,7 @@ class PNModeActivity : BaseActionBarActivity() {
toggleFCM()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_pn_mode, menu)
return true
}

View File

@ -41,8 +41,7 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() {
addPreferencesFromResource(R.xml.preferences_help)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
preference ?: return false
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
EXPORT_LOGS -> {
shareLogs()

View File

@ -301,10 +301,10 @@ public class SearchRepository {
Recipient conversationRecipient = Recipient.from(context, conversationAddress, false);
Recipient messageRecipient = Recipient.from(context, messageAddress, false);
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));
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 String bodySnippet;
public final long threadId;
public final long receivedTimestampMs;
public final long sentTimestampMs;
public MessageResult(@NonNull Recipient conversationRecipient,
@NonNull Recipient messageRecipient,
@NonNull String bodySnippet,
long threadId,
long receivedTimestampMs)
long sentTimestampMs)
{
this.conversationRecipient = conversationRecipient;
this.messageRecipient = messageRecipient;
this.bodySnippet = bodySnippet;
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.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.utilities.Log;
@ -28,7 +26,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
@RequiresApi(api = Build.VERSION_CODES.M)
public class DirectShareService extends ChooserTargetService {
private static final String TAG = DirectShareService.class.getSimpleName();
@ -40,53 +37,50 @@ public class DirectShareService extends ChooserTargetService {
List<ChooserTarget> results = new LinkedList<>();
ComponentName componentName = new ComponentName(this, ShareActivity.class);
ThreadDatabase threadDatabase = DatabaseComponent.get(this).threadDatabase();
Cursor cursor = threadDatabase.getDirectShareList();
try {
ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor);
ThreadRecord record;
try (Cursor cursor = threadDatabase.getDirectShareList()) {
ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor);
ThreadRecord record;
while ((record = reader.getNext()) != null && results.size() < 10) {
Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false);
String name = recipient.toShortString();
while ((record = reader.getNext()) != null && results.size() < 10) {
Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false);
String name = recipient.toShortString();
Bitmap avatar;
Bitmap avatar;
if (recipient.getContactPhoto() != null) {
try {
avatar = GlideApp.with(this)
.asBitmap()
.load(recipient.getContactPhoto())
.circleCrop()
.submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width))
.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
avatar = getFallbackDrawable(recipient);
}
} else {
avatar = getFallbackDrawable(recipient);
}
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(recipient.getAddress(), 0);
Bundle bundle = new Bundle();
bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId());
bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall());
bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType());
bundle.setClassLoader(getClassLoader());
results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle));
parcel.recycle();
if (recipient.getContactPhoto() != null) {
try {
avatar = GlideApp.with(this)
.asBitmap()
.load(recipient.getContactPhoto())
.circleCrop()
.submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width))
.get();
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
avatar = getFallbackDrawable(recipient);
}
} else {
avatar = getFallbackDrawable(recipient);
}
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(recipient.getAddress(), 0);
Bundle bundle = new Bundle();
bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId());
bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall());
bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType());
bundle.setClassLoader(getClassLoader());
results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle));
parcel.recycle();
return results;
}
return results;
} finally {
if (cursor != null) cursor.close();
}
}
private Bitmap getFallbackDrawable(@NonNull Recipient recipient) {

View File

@ -17,7 +17,7 @@ public class ExpirationListener extends BroadcastReceiver {
public static void setAlarm(Context context, long waitTimeMillis) {
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.cancel(pendingIntent);

View File

@ -6,6 +6,7 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -13,9 +14,9 @@ import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.guava.Preconditions;
import org.thoughtcrime.securesms.home.HomeActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.session.libsignal.utilities.guava.Preconditions;
import network.loki.messenger.R;
@ -87,10 +88,10 @@ public class GenericForegroundService extends Service {
}
private void postObligatoryForegroundNotification(String title, String channelId, @DrawableRes int iconRes) {
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId)
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId)
.setSmallIcon(iconRes)
.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());
}

View File

@ -29,18 +29,19 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.DatabaseUpgradeActivity;
import org.thoughtcrime.securesms.DummyActivity;
import org.session.libsignal.utilities.Log;
import org.thoughtcrime.securesms.home.HomeActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.session.libsession.utilities.ServiceUtil;
import org.session.libsession.utilities.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
@ -255,18 +256,18 @@ public class KeyCachingService extends Service {
private PendingIntent buildLockIntent() {
Intent intent = new Intent(this, KeyCachingService.class);
intent.setAction(PASSPHRASE_EXPIRED_EVENT);
return PendingIntent.getService(getApplicationContext(), 0, intent, 0);
return PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
}
private PendingIntent buildLaunchIntent() {
Intent intent = new Intent(this, HomeActivity.class);
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) {
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

View File

@ -6,6 +6,7 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.session.libsignal.utilities.Log;
public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
@ -21,7 +22,7 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
long scheduledTime = getNextScheduledExecutionTime(context);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
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) {
scheduledTime = onAlarm(context, scheduledTime);

View File

@ -12,21 +12,22 @@ import android.net.Uri;
import androidx.annotation.Nullable;
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.libsignal.utilities.Hex;
import org.session.libsession.utilities.ServiceUtil;
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.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import network.loki.messenger.R;
public class UpdateApkReadyListener extends BroadcastReceiver {
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.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)
.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_NEW_TASK
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.media.AudioManager
import android.os.Build
import android.os.IBinder
import android.os.ResultReceiver
import android.telephony.PhoneStateListener
import android.telephony.PhoneStateListener.LISTEN_NONE
import android.telephony.TelephonyManager
import androidx.core.content.ContextCompat
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_RINGING
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
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.*
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
import org.thoughtcrime.securesms.webrtc.data.Event
import org.thoughtcrime.securesms.webrtc.locks.LockManager
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
import org.webrtc.MediaStream
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 org.webrtc.*
import org.webrtc.PeerConnection.IceConnectionState.*
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
@ -60,7 +46,7 @@ import javax.inject.Inject
import org.thoughtcrime.securesms.webrtc.data.State as CallState
@AndroidEntryPoint
class WebRtcCallService: Service(), CallManager.WebRtcListener {
class WebRtcCallService : Service(), CallManager.WebRtcListener {
companion object {
@ -108,62 +94,82 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private const val RECONNECT_SECONDS = 5L
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)
.putExtra(EXTRA_MUTE, !enabled)
fun flipCamera(context: Context) = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_FLIP_CAMERA)
.setAction(ACTION_FLIP_CAMERA)
fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_ANSWER_CALL)
.setAction(ACTION_ANSWER_CALL)
fun microphoneIntent(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_SET_MUTE_AUDIO)
.putExtra(EXTRA_MUTE, !enabled)
fun microphoneIntent(context: Context, enabled: Boolean) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_SET_MUTE_AUDIO)
.putExtra(EXTRA_MUTE, !enabled)
fun createCall(context: Context, recipient: Recipient) = Intent(context, WebRtcCallService::class.java)
fun createCall(context: Context, recipient: Recipient) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_OUTGOING_CALL)
.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID, callTime: Long) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_INCOMING_RING)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
.putExtra(EXTRA_TIMESTAMP, callTime)
fun incomingCall(
context: Context,
address: Address,
sdp: String,
callId: UUID,
callTime: Long
) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_INCOMING_RING)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
.putExtra(EXTRA_TIMESTAMP, callTime)
fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_RESPONSE_MESSAGE)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_RESPONSE_MESSAGE)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
fun preOffer(context: Context, address: Address, callId: UUID, callTime: Long) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_PRE_OFFER)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_TIMESTAMP, callTime)
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_PRE_OFFER)
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_TIMESTAMP, callTime)
fun iceCandidates(context: Context, address: Address, iceCandidates: List<IceCandidate>, callId: UUID) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_ICE_MESSAGE)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray())
.putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray())
.putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray())
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
fun iceCandidates(
context: Context,
address: Address,
iceCandidates: List<IceCandidate>,
callId: UUID
) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_ICE_MESSAGE)
.putExtra(EXTRA_CALL_ID, callId)
.putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray())
.putExtra(
EXTRA_ICE_SDP_LINE_INDEX,
iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()
)
.putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray())
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
fun denyCallIntent(context: Context) =
Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL)
fun remoteHangupIntent(context: Context, callId: UUID) = Intent(context, WebRtcCallService::class.java)
fun remoteHangupIntent(context: Context, callId: UUID) =
Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_REMOTE_HANGUP)
.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) {
val intent = Intent(context, WebRtcCallService::class.java)
@ -174,7 +180,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
fun broadcastWantsToAnswer(context: Context, wantsToAnswer: Boolean) {
val intent = Intent(ACTION_WANTS_TO_ANSWER)
.putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer)
.putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer)
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
@ -182,13 +188,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
@JvmStatic
fun isCallActive(context: Context, resultReceiver: ResultReceiver) {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_IS_IN_CALL_QUERY)
.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
.setAction(ACTION_IS_IN_CALL_QUERY)
.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver)
context.startService(intent)
}
}
@Inject lateinit var callManager: CallManager
@Inject
lateinit var callManager: CallManager
private var wantsToAnswer = false
private var currentTimeouts = 0
@ -199,8 +206,17 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private val lockManager by lazy { LockManager(this) }
private val serviceExecutor = Executors.newSingleThreadExecutor()
private val timeoutExecutor = Executors.newScheduledThreadPool(1)
private val hangupOnCallAnswered = HangUpRtcOnPstnCallAnsweredListener {
ContextCompat.startForegroundService(this, hangupIntent(this))
private val hangupOnCallAnswered by lazy {
HangUpRtcOnPstnCallAnsweredListener {
ContextCompat.startForegroundService(this, hangupIntent(this))
}
}
private val hangupTelephonyCallback by lazy {
HangUpRtcTelephonyCallback {
ContextCompat.startForegroundService(this, hangupIntent(this))
}
}
private var networkChangedReceiver: NetworkChangeReceiver? = null
@ -258,7 +274,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
val action = intent.action
Log.i("Loki", "Handling ${intent.action}")
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_INCOMING_RING && isBusy(intent) -> handleBusyCall(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_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(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_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
@ -293,8 +313,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
registerIncomingPstnCallReceiver()
registerWiredHeadsetStateReceiver()
registerWantsToAnswerReceiver()
getSystemService(TelephonyManager::class.java)
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
getSystemService(TelephonyManager::class.java)
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
} else {
getSystemService(TelephonyManager::class.java)
.registerTelephonyCallback(serviceExecutor, hangupTelephonyCallback)
}
}
registerUncaughtExceptionHandler()
networkChangedReceiver = NetworkChangeReceiver(::networkChange)
networkChangedReceiver!!.register(this)
@ -318,7 +345,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
}
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() {
@ -339,7 +367,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private fun handleUpdateAudio(intent: Intent) {
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")
return
}
@ -419,8 +451,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
callManager.initializeAudioForCall()
callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING)
setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient)
callManager.insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_OUTGOING)
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
callManager.insertCallMessage(
recipient.address.serialize(),
CallMessageType.CALL_OUTGOING
)
scheduledTimeout = timeoutExecutor.schedule(
TimeoutRunnable(callId, this),
TIMEOUT_SECONDS,
TimeUnit.SECONDS
)
callManager.setAudioEnabled(true)
val expectedState = callManager.currentConnectionState
@ -429,15 +468,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
try {
val offerFuture = callManager.onOutgoingCall(this)
offerFuture.fail { e ->
if (isConsistentState(expectedState, expectedCallId, callManager.currentConnectionState, callManager.callId)) {
Log.e(TAG,e)
if (isConsistentState(
expectedState,
expectedCallId,
callManager.currentConnectionState,
callManager.callId
)
) {
Log.e(TAG, e)
callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE)
callManager.postConnectionError()
terminate()
}
}
} catch (e: Exception) {
Log.e(TAG,e)
Log.e(TAG, e)
callManager.postConnectionError()
terminate()
}
@ -476,7 +521,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
callManager.silenceIncomingRinger()
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.initializeVideo(this)
@ -487,7 +536,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
try {
val answerFuture = callManager.onIncomingCall(this)
answerFuture.fail { e ->
if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) {
if (isConsistentState(
expectedState,
expectedCallId,
callManager.currentConnectionState,
callManager.callId
)
) {
Log.e(TAG, e)
insertMissedCall(recipient, true)
callManager.postConnectionError()
@ -497,7 +552,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING)
callManager.setAudioEnabled(true)
} catch (e: Exception) {
Log.e(TAG,e)
Log.e(TAG, e)
callManager.postConnectionError()
terminate()
}
@ -518,6 +573,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private fun handleRemoteHangup(intent: Intent) {
if (callManager.callId != getCallId(intent)) {
Log.e(TAG, "Hangup for non-active call...")
stopForeground(true)
return
}
@ -555,7 +611,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
val callId = getCallId(intent)
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) {
terminate()
}
@ -567,14 +627,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
val sdpLineIndexes = intent.getIntArrayExtra(EXTRA_ICE_SDP_LINE_INDEX) ?: return
val sdps = intent.getStringArrayExtra(EXTRA_ICE_SDP) ?: return
if (sdpMids.size != sdpLineIndexes.size || sdpLineIndexes.size != sdps.size) {
Log.w(TAG,"sdp info not of equal length")
Log.w(TAG, "sdp info not of equal length")
return
}
val iceCandidates = sdpMids.indices.map { index ->
IceCandidate(
sdpMids[index],
sdpLineIndexes[index],
sdps[index]
sdpMids[index],
sdpLineIndexes[index],
sdps[index]
)
}
callManager.handleRemoteIceCandidate(iceCandidates, callId)
@ -597,7 +657,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private fun handleIsInCallQuery(intent: Intent) {
val listener = intent.getParcelableExtra<ResultReceiver>(EXTRA_RESULT_RECEIVER) ?: return
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())
}
@ -616,10 +680,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) {
Log.i("Loki", "Trying to re-connect")
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) {
Log.i("Loki", "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS")
scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS)
Log.i(
"Loki",
"Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS"
)
scheduledReconnect = timeoutExecutor.schedule(
CheckReconnectedRunnable(callId, this),
RECONNECT_SECONDS,
TimeUnit.SECONDS
)
} else {
Log.i("Loki", "Network isn't available, timing out")
handleLocalHangup(intent)
@ -627,12 +702,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
private fun handleCheckTimeout(intent: Intent) {
val callId = callManager.callId ?: return
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")
handleLocalHangup(intent)
}
@ -640,8 +718,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private fun setCallInProgressNotification(type: Int, recipient: Recipient?) {
startForeground(
CallNotificationBuilder.WEBRTC_NOTIFICATION,
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
CallNotificationBuilder.WEBRTC_NOTIFICATION,
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient)
)
if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) {
// start an intent for the fullscreen
@ -661,14 +739,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
private fun getRemoteRecipient(intent: Intent): Recipient {
val remoteAddress = intent.getParcelableExtra<Address>(EXTRA_RECIPIENT_ADDRESS)
?: throw AssertionError("No recipient in intent!")
?: throw AssertionError("No recipient in intent!")
return Recipient.from(this, remoteAddress, true)
}
private fun getCallId(intent: Intent) : UUID {
private fun getCallId(intent: Intent): UUID {
return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID
?: throw AssertionError("No callId in intent!")
?: throw AssertionError("No callId in intent!")
}
private fun insertMissedCall(recipient: Recipient, signal: Boolean) {
@ -680,10 +758,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
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() {
Log.d(TAG,"onDestroy()")
Log.d(TAG, "onDestroy()")
callManager.unregisterListener(this)
callReceiver?.let { receiver ->
unregisterReceiver(receiver)
@ -698,6 +779,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
wantsToAnswer = false
currentTimeouts = 0
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()
}
@ -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() {
val intent = Intent(context, WebRtcCallService::class.java)
.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() {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_CHECK_RECONNECT_TIMEOUT)
@ -727,26 +820,29 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
}
private class TimeoutRunnable(private val callId: UUID, private val context: Context): Runnable {
private class TimeoutRunnable(private val callId: UUID, private val context: Context) :
Runnable {
override fun run() {
val intent = Intent(context, WebRtcCallService::class.java)
.setAction(ACTION_CHECK_TIMEOUT)
.putExtra(EXTRA_CALL_ID, callId)
.setAction(ACTION_CHECK_TIMEOUT)
.putExtra(EXTRA_CALL_ID, callId)
context.startService(intent)
}
}
private abstract class FailureListener<V>(
expectedState: CallState,
expectedCallId: UUID?,
getState: () -> Pair<CallState, UUID?>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
expectedState: CallState,
expectedCallId: UUID?,
getState: () -> Pair<CallState, UUID?>
) : StateAwareListener<V>(expectedState, expectedCallId, getState) {
override fun onSuccessContinue(result: V) {}
}
private abstract class SuccessOnlyListener<V>(
expectedState: CallState,
expectedCallId: UUID?,
getState: () -> Pair<CallState, UUID>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
expectedState: CallState,
expectedCallId: UUID?,
getState: () -> Pair<CallState, UUID>
) : StateAwareListener<V>(expectedState, expectedCallId, getState) {
override fun onFailureContinue(throwable: Throwable?) {
Log.e(TAG, throwable)
throw AssertionError(throwable)
@ -754,9 +850,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
private abstract class StateAwareListener<V>(
private val expectedState: CallState,
private val expectedCallId: UUID?,
private val getState: ()->Pair<CallState, UUID?>): FutureTaskListener<V> {
private val expectedState: CallState,
private val expectedCallId: UUID?,
private val getState: () -> Pair<CallState, UUID?>
) : FutureTaskListener<V> {
companion object {
private val TAG = Log.tag(StateAwareListener::class.java)
@ -764,7 +861,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
override fun onSuccess(result: V) {
if (!isConsistentState()) {
Log.w(TAG,"State has changed since request, aborting success callback...")
Log.w(TAG, "State has changed since request, aborting success callback...")
} else {
onSuccessContinue(result)
}
@ -773,7 +870,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
override fun onFailure(exception: ExecutionException?) {
if (!isConsistentState()) {
Log.w(TAG, exception)
Log.w(TAG,"State has changed since request, aborting failure callback...")
Log.w(TAG, "State has changed since request, aborting failure callback...")
} else {
exception?.let {
onFailureContinue(it.cause)
@ -792,10 +889,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
}
private fun isConsistentState(
expectedState: CallState,
expectedCallId: UUID?,
currentState: CallState,
currentCallId: UUID?
expectedState: CallState,
expectedCallId: UUID?,
currentState: CallState,
currentCallId: UUID?
): Boolean {
return expectedState == currentState && expectedCallId == currentCallId
}
@ -817,17 +914,29 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
val intent = Intent(this, WebRtcCallService::class.java)
.setAction(ACTION_ICE_CONNECTED)
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.postConnectionEvent(Event.IceDisconnect) {
callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING)
if (callManager.isInitiator()) {
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 {
Log.i("Loki", "Starting timeout, awaiting new reconnect")
callManager.postConnectionEvent(Event.PrepareForNewOffer) {
scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
scheduledTimeout = timeoutExecutor.schedule(
TimeoutRunnable(callId, this),
TIMEOUT_SECONDS,
TimeUnit.SECONDS
)
}
}
}
@ -855,7 +964,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
override fun onDataChannel(p0: DataChannel?) {}
override fun onRenegotiationNeeded() {
Log.w(TAG,"onRenegotiationNeeded was called!")
Log.w(TAG, "onRenegotiationNeeded was called!")
}
override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) {}

View File

@ -1,20 +1,21 @@
package org.thoughtcrime.securesms.sskenvironment;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import android.content.Context;
import androidx.annotation.NonNull;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsession.utilities.SSKEnvironment;
import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.utilities.Log;
import java.util.ArrayList;
@ -198,12 +199,12 @@ public class TypingStatusRepository implements SSKEnvironment.TypingIndicatorsPr
if (device != typist.device) return false;
if (threadId != typist.threadId) return false;
return author.equals(typist.author);
return author.getAddress().equals(typist.author.getAddress());
}
@Override
public int hashCode() {
int result = author.hashCode();
int result = author.getAddress().hashCode();
result = 31 * result + device;
result = 31 * result + (int) (threadId ^ (threadId >>> 32));
return result;

View File

@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.BackupFileRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.service.LocalBackupListener
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
@ -74,44 +73,6 @@ object BackupUtil {
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
fun getLastBackupTimeString(context: Context, locale: Locale): String {
val timestamp = DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFileTime()

View File

@ -4,8 +4,6 @@ import android.database.Cursor;
import androidx.annotation.NonNull;
import java.util.Optional;
public final class CursorUtil {
@ -19,71 +17,8 @@ public final class CursorUtil {
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) {
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.Context
import android.content.Intent
import android.os.Build
import android.telephony.PhoneStateListener
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import androidx.annotation.RequiresApi
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.service.WebRtcCallService
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() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_SCREEN_OFF == intent.action) {

View File

@ -6,7 +6,6 @@ import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
import android.media.SoundPool
import android.os.Build
import android.os.HandlerThread
import network.loki.messenger.R
import org.session.libsignal.utilities.Log
@ -108,7 +107,7 @@ class SignalAudioManager(private val context: Context,
updateAudioDeviceState()
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

View File

@ -2,14 +2,15 @@ package org.thoughtcrime.securesms.webrtc.audio
import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothHeadset
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.media.AudioManager
import androidx.core.app.ActivityCompat
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
import java.util.concurrent.TimeUnit
@ -80,7 +81,6 @@ class SignalBluetoothManager(
bluetoothReceiver = BluetoothHeadsetBroadcastReceiver()
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")
state = State.UNAVAILABLE
}
@ -161,7 +161,8 @@ class SignalBluetoothManager(
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
}

View File

@ -49,7 +49,7 @@ public class LockManager {
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial");
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");
fullLock.setReferenceCounted(false);

View File

@ -69,6 +69,7 @@
</TableRow>
<TableRow
android:id="@+id/error_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="@dimen/small_spacing"
@ -94,6 +95,7 @@
</TableLayout>
<LinearLayout
android:id="@+id/resend_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/very_large_spacing"

View File

@ -1,26 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/mainContentContainer"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/emptyStateContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_centerInParent="true">
@ -35,4 +28,4 @@
</LinearLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -18,35 +18,13 @@
</LinearLayout>
<LinearLayout
android:id="@+id/mainContentContainer"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_gravity="center"
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
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbars="vertical"
tools:listitem="@layout/view_user"/>
<TextView
android:id="@+id/loadingTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/contact_selection_group_activity__finding_contacts"
android:textSize="@dimen/large_font_size" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
android:clipToPadding="false"
android:scrollbars="vertical"
tools:listitem="@layout/view_user"/>
</FrameLayout>

View File

@ -11,7 +11,7 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:background="@color/transparent_black_6" />
android:background="@color/transparent_black_30" />
<View
android:layout_width="84dp"

View File

@ -26,11 +26,6 @@
android:id="@+id/menu_message_details"
app:showAsAction="never" />
<item
android:title="@string/conversation_context__menu_message_details"
android:id="@+id/menu_context_select_message"
app:showAsAction="never" />
<item
android:title="@string/conversation_context__menu_copy_text"
android:id="@+id/menu_context_copy"

View File

@ -37,15 +37,9 @@ class ConversationViewModelTest: BaseViewModelTest() {
@Before
fun setUp() {
recipient = mock(Recipient::class.java)
whenever(repository.isOxenHostedOpenGroup(anyLong())).thenReturn(true)
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
}
@Test
fun `should emit group type on init`() = runBlockingTest {
assertTrue(viewModel.uiState.first().isOxenHostedOpenGroup)
}
@Test
fun `should save draft message`() {
val draft = "Hi there"

View File

@ -4,9 +4,9 @@ buildscript {
mavenCentral()
}
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 "com.google.gms:google-services:4.3.10"
classpath "com.google.gms:google-services:$googleServicesVersion"
classpath files('libs/gradle-witness.jar')
}
}
@ -51,6 +51,7 @@ allprojects {
project.ext {
androidMinimumSdkVersion = 23
androidCompileSdkVersion = 30
androidTargetSdkVersion = 31
androidCompileSdkVersion = 32
}
}

View File

@ -6,5 +6,5 @@ repositories {
}
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.enableJetifier=true
org.gradle.jvmargs=-Xmx4g
org.gradle.jvmargs=-Xmx8g
kotlinVersion=1.6.0
coroutinesVersion=1.6.0
kotlinxJsonVersion=1.3.0
lifecycleVersion=2.3.1
gradlePluginVersion=7.3.1
googleServicesVersion=4.3.12
kotlinVersion=1.6.21
coroutinesVersion=1.6.4
kotlinxJsonVersion=1.3.3
lifecycleVersion=2.5.1
daggerVersion=2.40.1
glideVersion=4.11.0
kovenantVersion=3.3.0
@ -13,4 +15,12 @@ curve25519Version=0.6.0
protobufVersion=2.5.0
okhttpVersion=3.12.1
jacksonDatabindVersion=2.9.8
mockitoKotlinVersion=4.0.0
appcompatVersion=1.5.1
materialVersion=1.7.0
preferenceVersion=1.2.0
coreVersion=1.8.0
junitVersion=4.13.2
mockitoKotlinVersion=4.0.0
testCoreVersion=1.4.0
pagingVersion=3.0.0

View File

@ -1,6 +1,6 @@
#Thu Dec 30 07:09:53 SAST 2021
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -19,17 +19,16 @@ android {
dependencies {
implementation project(":libsignal")
implementation project(":liblazysodium")
// implementation 'com.goterl:lazysodium-android:5.0.2@aar'
implementation "net.java.dev.jna:jna:5.8.0@aar"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'com.google.android.material:material:1.2.1'
implementation "androidx.core:core-ktx:$coreVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
implementation "com.google.android.material:material:$materialVersion"
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "com.google.dagger:hilt-android:$daggerVersion"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "com.github.bumptech.glide:glide:$glideVersion"
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.annimon:stream:1.1.8'
@ -43,7 +42,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
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.mockito:mockito-inline:4.0.0"
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-rule: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 "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
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.makeramen.roundedimageview.RoundedDrawable;
import org.session.libsession.R;
import org.session.libsession.utilities.ThemeUtil;
@ -34,7 +33,7 @@ public class ResourceContactPhoto implements FallbackContactPhoto {
Drawable background = TextDrawable.builder().buildRound(" ", inverted ? Color.WHITE : color);
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(resourceId));
foreground.setScaleType(ImageView.ScaleType.CENTER);
foreground.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
if (inverted) {
foreground.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);

View File

@ -108,6 +108,7 @@ interface StorageProtocol {
fun markAsSent(timestamp: Long, author: String)
fun markUnidentified(timestamp: Long, author: String)
fun setErrorMessage(timestamp: Long, author: String, error: Exception)
fun clearErrorMessage(messageID: Long)
fun setMessageServerHash(messageID: Long, serverHash: String)
// Closed Groups

View File

@ -96,7 +96,10 @@ object FileServerApi {
)
return send(request).map { response ->
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.messages.Destination
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.CallMessage
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.control.*
import org.session.libsession.messaging.messages.visible.LinkPreview
import org.session.libsession.messaging.messages.visible.Profile
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.libsignal.crypto.PushTransportDetails
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.Base64
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 org.session.libsignal.utilities.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
@ -337,6 +328,8 @@ object MessageSender {
message.serverHash?.let {
storage.setMessageServerHash(messageID, it)
}
// in case any errors from previous sends
storage.clearErrorMessage(messageID)
// Track the open group server message ID
if (message.openGroupServerMessageID != null && (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup)) {
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.JobQueue
import org.session.libsession.messaging.messages.Message
import org.session.libsession.messaging.messages.control.CallMessage
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.control.*
import org.session.libsession.messaging.messages.visible.Attachment
import org.session.libsession.messaging.messages.visible.Reaction
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.WebRtcUtils
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
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.*
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.crypto.ecc.ECKeyPair
import org.session.libsignal.messages.SignalServiceGroup
import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.utilities.*
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.removingIdPrefixIfNeeded
import org.session.libsignal.utilities.toHexString
import java.security.MessageDigest
import java.util.LinkedList
import java.util.*
import kotlin.math.min
internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
@ -307,6 +291,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
return@mapNotNull attachment
}
}
// Cancel any typing indicators if needed
cancelTypingIndicatorsIfNeeded(message.sender!!)
// Parse reaction if needed
val threadIsGroup = threadRecipient?.isGroupRecipient == true
message.reaction?.let { reaction ->
@ -332,8 +318,6 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
}
return messageID
}
// Cancel any typing indicators if needed
cancelTypingIndicatorsIfNeeded(message.sender!!)
return null
}

View File

@ -13,6 +13,7 @@ import org.session.libsignal.utilities.JsonUtil;
import org.session.libsignal.utilities.guava.Optional;
import java.io.IOException;
import java.util.Objects;
public class LinkPreview {
@ -75,4 +76,17 @@ public class LinkPreview {
public static LinkPreview deserialize(@NonNull String serialized) throws IOException {
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 {
implementation "androidx.annotation:annotation:1.2.0"
implementation "androidx.annotation:annotation:1.5.0"
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$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"
testImplementation "junit:junit:3.8.2"
testImplementation "junit:junit:$junitVersion"
testImplementation "org.assertj:assertj-core:1.7.1"
testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0"
}