diff --git a/app/build.gradle b/app/build.gradle index 7e710ff7b..3dcc19726 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' @@ -94,7 +95,8 @@ dependencies { implementation 'com.takisoft.fix:colorpicker:1.0.1' implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' - implementation 'org.signal:android-database-sqlcipher:3.5.9-S3' + implementation 'androidx.sqlite:sqlite-ktx:2.2.0' + implementation 'net.zetetic:sqlcipher-android:4.5.3@aar' implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') { exclude group: 'com.fasterxml.jackson.core' exclude group: 'org.freemarker' @@ -119,7 +121,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 +129,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 +143,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 +153,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 = 323 +def canonicalVersionName = "1.16.3" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, @@ -169,13 +171,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 +207,7 @@ android { versionName canonicalVersionName minSdkVersion androidMinimumSdkVersion - targetSdkVersion androidCompileSdkVersion + targetSdkVersion androidTargetSdkVersion multiDexEnabled = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 31c2466ae..5cdaec7a8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,7 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -31,6 +30,7 @@ android:required="false" /> + @@ -174,6 +174,7 @@ android:screenOrientation="portrait"/> @@ -405,42 +407,48 @@ android:authorities="network.loki.securesms.database.recipient" android:exported="false" /> - + - + - + - + - + + android:exported="false"> + android:enabled="true" + android:exported="true"> diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 31b108e97..e506967ff 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -48,6 +48,7 @@ import org.session.libsession.utilities.Util; import org.session.libsession.utilities.WindowDebouncer; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.utilities.dynamiclanguage.LocaleParser; +import org.session.libsignal.utilities.HTTP; import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.ThreadUtils; @@ -57,6 +58,8 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities; import org.thoughtcrime.securesms.database.EmojiSearchDatabase; import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.LokiAPIDatabase; +import org.thoughtcrime.securesms.database.Storage; +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.EmojiSearchData; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.dependencies.DatabaseModule; @@ -66,6 +69,7 @@ import org.thoughtcrime.securesms.groups.OpenGroupMigrator; import org.thoughtcrime.securesms.home.HomeActivity; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobs.FastJobStorage; import org.thoughtcrime.securesms.jobs.JobManagerFactories; import org.thoughtcrime.securesms.logging.AndroidLogger; @@ -236,6 +240,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO resubmitProfilePictureIfNeeded(); loadEmojiSearchIndexIfNeeded(); EmojiSource.refresh(); + + NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create(); + HTTP.INSTANCE.setConnectedToNetwork(networkConstraint::isMet); } @Override @@ -244,6 +251,12 @@ public class ApplicationContext extends Application implements DefaultLifecycleO Log.i(TAG, "App is now visible."); KeyCachingService.onAppForegrounded(this); + // If the user account hasn't been created or onboarding wasn't finished then don't start + // the pollers + if (TextSecurePreferences.getLocalNumber(this) == null || !TextSecurePreferences.hasSeenWelcomeScreen(this)) { + return; + } + ThreadUtils.queue(()->{ if (poller != null) { poller.setCaughtUp(false); @@ -481,6 +494,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 +511,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) { @@ -535,7 +550,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO TextSecurePreferences.setProfileName(this, displayName); } getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit(); - if (!deleteDatabase("signal.db")) { + if (!deleteDatabase(SQLCipherOpenHelper.DATABASE_NAME)) { Log.d("Loki", "Failed to delete database."); } Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index fa0fce7bd..b00ed7d2e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -74,10 +74,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) attachmentDatabase.setTransferState(messageID, attachmentId, attachmentState.value) } - override fun getMessageForQuote(timestamp: Long, author: Address): Pair? { + override fun getMessageForQuote(timestamp: Long, author: Address): Triple? { val messagingDatabase = DatabaseComponent.get(context).mmsSmsDatabase() val message = messagingDatabase.getMessageFor(timestamp, author) - return if (message != null) Pair(message.id, message.isMms) else null + return if (message != null) Triple(message.id, message.isMms, message.body) else null } override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List { @@ -176,6 +176,11 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) return messageDB.getMessageID(serverId, threadId) } + override fun getMessageIDs(serverIds: List, threadId: Long): Pair, List> { + val messageDB = DatabaseComponent.get(context).lokiMessageDatabase() + return messageDB.getMessageIDs(serverIds, threadId) + } + override fun deleteMessage(messageID: Long, isSms: Boolean) { val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).mmsDatabase() @@ -184,16 +189,27 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID) } - override fun updateMessageAsDeleted(timestamp: Long, author: String) { + override fun deleteMessages(messageIDs: List, threadId: Long, isSms: Boolean) { + val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() + else DatabaseComponent.get(context).mmsDatabase() + + messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId) + DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs) + DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs) + } + + override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? { val database = DatabaseComponent.get(context).mmsSmsDatabase() val address = Address.fromSerialized(author) - val message = database.getMessageFor(timestamp, address) ?: return + val message = database.getMessageFor(timestamp, address) ?: return null val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseComponent.get(context).mmsDatabase() else DatabaseComponent.get(context).smsDatabase() - messagingDatabase.markAsDeleted(message.id, message.isRead) + messagingDatabase.markAsDeleted(message.id, message.isRead, message.hasMention) if (message.isOutgoing) { messagingDatabase.deleteMessage(message.id) } + + return message.id } override fun getServerHashForMessage(messageID: Long): String? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt index 94c7517eb..84a9b6cfc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt @@ -13,6 +13,11 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private override fun onChange(selfChange: Boolean, uri: Uri?) { super.onChange(selfChange, uri) uri ?: return + + // There is an odd bug where we can get notified for changes to 'content://media/external' + // directly which is a protected folder, this code is to prevent that crash + if (uri.scheme == "content" && uri.host == "media" && uri.path == "/external") { return } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { queryRelativeDataColumn(uri) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java deleted file mode 100644 index 76342898b..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt deleted file mode 100644 index a94c866c0..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt +++ /dev/null @@ -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() - - 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(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(null) - val backupPassphrase = MutableLiveData(null) - - val processingBackupFile = MutableLiveData(false) - val backupImportResult = MutableLiveData(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() - 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 - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt index 33b8b6725..6b5d47a2e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt @@ -8,7 +8,7 @@ import androidx.annotation.WorkerThread import com.annimon.stream.function.Consumer import com.annimon.stream.function.Predicate import com.google.protobuf.ByteString -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.greenrobot.eventbus.EventBus import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt index ba1df97d5..b40c049bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt @@ -5,7 +5,7 @@ import android.content.ContentValues import android.content.Context import android.net.Uri import androidx.annotation.WorkerThread -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.greenrobot.eventbus.EventBus import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java deleted file mode 100644 index 5b2199896..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.TextView; - -import org.thoughtcrime.securesms.mms.GlideRequests; - -import org.thoughtcrime.securesms.mms.ImageSlide; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; - -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; - -import network.loki.messenger.R; -import okhttp3.HttpUrl; - -public class LinkPreviewView extends FrameLayout { - - private static final int TYPE_CONVERSATION = 0; - private static final int TYPE_COMPOSE = 1; - - private ViewGroup container; - private OutlinedThumbnailView thumbnail; - private TextView title; - private TextView site; - private View divider; - private View closeButton; - private View spinner; - - private int type; - private int defaultRadius; - private CornerMask cornerMask; - private Outliner outliner; - private CloseClickedListener closeClickedListener; - - public LinkPreviewView(Context context) { - super(context); - init(null); - } - - public LinkPreviewView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(attrs); - } - - private void init(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.link_preview, this); - - container = findViewById(R.id.linkpreview_container); - thumbnail = findViewById(R.id.linkpreview_thumbnail); - title = findViewById(R.id.linkpreview_title); - site = findViewById(R.id.linkpreview_site); - divider = findViewById(R.id.linkpreview_divider); - spinner = findViewById(R.id.linkpreview_progress_wheel); - closeButton = findViewById(R.id.linkpreview_close); - defaultRadius = getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius); - cornerMask = new CornerMask(this); - outliner = new Outliner(); - - outliner.setColor(getResources().getColor(R.color.transparent)); - - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.LinkPreviewView, 0, 0); - type = typedArray.getInt(R.styleable.LinkPreviewView_linkpreview_type, 0); - typedArray.recycle(); - } - - if (type == TYPE_COMPOSE) { - container.setBackgroundColor(Color.TRANSPARENT); - container.setPadding(0, 0, 0, 0); - divider.setVisibility(VISIBLE); - - closeButton.setOnClickListener(v -> { - if (closeClickedListener != null) { - closeClickedListener.onCloseClicked(); - } - }); - } - - setWillNotDraw(false); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (type == TYPE_COMPOSE) return; - - cornerMask.mask(canvas); - outliner.draw(canvas); - } - - public void setLoading() { - title.setVisibility(GONE); - site.setVisibility(GONE); - thumbnail.setVisibility(GONE); - spinner.setVisibility(VISIBLE); - closeButton.setVisibility(GONE); - } - - public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showCloseButton) { - setLinkPreview(glideRequests, linkPreview, showThumbnail); - if (showCloseButton) { - closeButton.setVisibility(VISIBLE); - } else { - closeButton.setVisibility(GONE); - } - } - - public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) { - title.setVisibility(VISIBLE); - site.setVisibility(VISIBLE); - thumbnail.setVisibility(VISIBLE); - spinner.setVisibility(GONE); - closeButton.setVisibility(VISIBLE); - - title.setText(linkPreview.getTitle()); - - HttpUrl url = HttpUrl.parse(linkPreview.getUrl()); - if (url != null) { - site.setText(url.topPrivateDomain()); - } - - if (showThumbnail && linkPreview.getThumbnail().isPresent()) { - thumbnail.setVisibility(VISIBLE); - thumbnail.setImageResource(glideRequests, new ImageSlide(getContext(), linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false); - thumbnail.showDownloadText(false); - } else { - thumbnail.setVisibility(GONE); - } - } - - public void setCorners(int topLeft, int topRight) { - cornerMask.setRadii(topLeft, topRight, 0, 0); - outliner.setRadii(topLeft, topRight, 0, 0); - thumbnail.setCorners(topLeft, defaultRadius, defaultRadius, defaultRadius); - postInvalidate(); - } - - public void setCloseClickedListener(@Nullable CloseClickedListener closeClickedListener) { - this.closeClickedListener = closeClickedListener; - } - - public void setDownloadClickedListener(SlidesClickedListener listener) { - thumbnail.setDownloadClickListener(listener); - } - - public interface CloseClickedListener { - void onCloseClicked(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java deleted file mode 100644 index 71bf8a280..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; - -import org.session.libsession.utilities.ThemeUtil; -import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView; - -import network.loki.messenger.R; - -public class OutlinedThumbnailView extends ThumbnailView { - - private CornerMask cornerMask; - private Outliner outliner; - - public OutlinedThumbnailView(Context context) { - super(context); - init(); - } - - public OutlinedThumbnailView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - private void init() { - cornerMask = new CornerMask(this); - outliner = new Outliner(); - - outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_image_outline_color)); - setWillNotDraw(false); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - cornerMask.mask(canvas); - outliner.draw(canvas); - } - - public void setCorners(int topLeft, int topRight, int bottomRight, int bottomLeft) { - cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft); - outliner.setRadii(topLeft, topRight, bottomRight, bottomLeft); - postInvalidate(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 0ded9f346..a827a7d26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -34,6 +34,8 @@ class ProfilePictureView @JvmOverloads constructor( private val profilePicturesCache = mutableMapOf() private val unknownRecipientDrawable = ResourceContactPhoto(R.drawable.ic_profile_default) .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) + private val unknownOpenGroupDrawable = ResourceContactPhoto(R.drawable.ic_notification) + .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) // endregion @@ -43,10 +45,8 @@ class ProfilePictureView @JvmOverloads constructor( val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey) return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey } - fun isOpenGroupWithProfilePicture(recipient: Recipient): Boolean { - return recipient.isOpenGroupRecipient && recipient.groupAvatarId != null - } - if (recipient.isGroupRecipient && !isOpenGroupWithProfilePicture(recipient)) { + + if (recipient.isClosedGroupRecipient) { val members = DatabaseComponent.get(context).groupDatabase() .getGroupMemberAddresses(recipient.address.toGroupString(), true) .sorted() @@ -107,7 +107,7 @@ class ProfilePictureView @JvmOverloads constructor( if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return val signalProfilePicture = recipient.contactPhoto val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject - val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}") + if (signalProfilePicture != null && avatar != "0" && avatar != "") { glide.clear(imageView) glide.load(signalProfilePicture) @@ -117,7 +117,12 @@ class ProfilePictureView @JvmOverloads constructor( .diskCacheStrategy(DiskCacheStrategy.NONE) .circleCrop() .into(imageView) + } else if (recipient.isOpenGroupRecipient && recipient.groupAvatarId == null) { + glide.clear(imageView) + imageView.setImageDrawable(unknownOpenGroupDrawable) } else { + val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}") + glide.clear(imageView) glide.load(placeholder) .placeholder(unknownRecipientDrawable) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java b/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java index 6214c5853..98a623eef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java @@ -52,19 +52,4 @@ public class StickerView extends FrameLayout { public void setOnLongClickListener(@Nullable OnLongClickListener l) { image.setOnLongClickListener(l); } - - public void setSticker(@NonNull GlideRequests glideRequests, @NonNull Slide stickerSlide) { - boolean showControls = stickerSlide.asAttachment().getDataUri() == null; - - image.setImageResource(glideRequests, stickerSlide, showControls, false); - missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE); - } - - public void setThumbnailClickListener(@NonNull SlideClickListener listener) { - image.setThumbnailClickListener(listener); - } - - public void setDownloadClickListener(@NonNull SlidesClickedListener listener) { - image.setDownloadClickListener(listener); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index d512e0924..4f0072cc2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -24,7 +24,7 @@ public class EmojiTextView extends AppCompatTextView { private static final char ELLIPSIS = '…'; private CharSequence previousText; - private BufferType previousBufferType; + private BufferType previousBufferType = BufferType.NORMAL; private float originalFontSize; private boolean useSystemEmoji; private boolean sizeChangeInProgress; @@ -49,6 +49,15 @@ public class EmojiTextView extends AppCompatTextView { } @Override public void setText(@Nullable CharSequence text, BufferType type) { + // No need to do anything special if the text is null or empty + if (text == null || text.length() == 0) { + previousText = text; + previousOverflowText = overflowText; + previousBufferType = type; + super.setText(text, type); + return; + } + EmojiParser.CandidateList candidates = EmojiProvider.getCandidates(text); if (scaleEmojis && candidates != null && candidates.allEmojis) { @@ -149,10 +158,15 @@ public class EmojiTextView extends AppCompatTextView { } private boolean unchanged(CharSequence text, CharSequence overflowText, BufferType bufferType) { - return Util.equals(previousText, text) && - Util.equals(previousOverflowText, overflowText) && - Util.equals(previousBufferType, bufferType) && - useSystemEmoji == useSystemEmoji() && + CharSequence finalPrevText = (previousText == null || previousText.length() == 0 ? "" : previousText); + CharSequence finalText = (text == null || text.length() == 0 ? "" : text); + CharSequence finalPrevOverflowText = (previousOverflowText == null || previousOverflowText.length() == 0 ? "" : previousOverflowText); + CharSequence finalOverflowText = (overflowText == null || overflowText.length() == 0 ? "" : overflowText); + + return Util.equals(finalPrevText, finalText) && + Util.equals(finalPrevOverflowText, finalOverflowText) && + Util.equals(previousBufferType, bufferType) && + useSystemEmoji == useSystemEmoji() && !sizeChangeInProgress; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt index 24637c434..0b0ddf4b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt @@ -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> { @@ -106,7 +95,7 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks) { 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() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt new file mode 100644 index 000000000..827c39454 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt @@ -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() { + 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() { + + override fun getRefreshKey(state: PagingState): 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() + + @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): LoadResult { + 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() + 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) } + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 620af8c90..c51ecedd4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -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 @@ -54,6 +41,8 @@ import nl.komponents.kovenant.ui.successUi import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.AttachmentDownloadJob +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.messages.control.DataExtractionNotification @@ -69,12 +58,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 @@ -109,11 +94,7 @@ 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 @@ -139,25 +120,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 @@ -304,6 +272,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe onDeselect(message, position, it) } }, + onAttachmentNeedsDownload = { attachmentId, mmsId -> + // Start download (on IO thread) + lifecycleScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) + } + }, glide = glide, lifecycleCoroutineScope = lifecycleScope ) @@ -361,11 +335,24 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe restoreDraftIfNeeded() setUpUiStateObserver() binding!!.scrollToBottomButton.setOnClickListener { - val layoutManager = binding?.conversationRecyclerView?.layoutManager ?: return@setOnClickListener + val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener + if (layoutManager.isSmoothScrolling) { binding?.conversationRecyclerView?.scrollToPosition(0) } else { - binding?.conversationRecyclerView?.smoothScrollToPosition(0) + // It looks like 'smoothScrollToPosition' will actually load all intermediate items in + // order to do the scroll, this can be very slow if there are a lot of messages so + // instead we check the current position and if there are more than 10 items to scroll + // we jump instantly to the 10th item and scroll from there (this should happen quick + // enough to give a similar scroll effect without having to load everything) + val position = layoutManager.findFirstVisibleItemPosition() + if (position > 10) { + binding?.conversationRecyclerView?.scrollToPosition(10) + } + + binding?.conversationRecyclerView?.post { + binding?.conversationRecyclerView?.smoothScrollToPosition(0) + } } } unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId) @@ -397,7 +384,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe super.onResume() ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId) val recipient = viewModel.recipient ?: return - threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient) + + lifecycleScope.launch(Dispatchers.IO) { + threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient) + } + contentResolver.registerContentObserver( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, @@ -489,6 +480,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // called from onCreate private fun setUpInputBar() { + binding!!.inputBar.isVisible = viewModel.openGroup == null || viewModel.openGroup?.canWrite == true binding!!.inputBar.delegate = this binding!!.inputBarRecordingView.delegate = this // GIF button @@ -643,7 +635,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe this ) { onOptionsItemSelected(it) } } - super.onPrepareOptionsMenu(menu) return true } @@ -1804,6 +1795,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } + override fun destroyActionMode() { + this.actionMode = null + } + private fun sendScreenshotNotification() { val recipient = viewModel.recipient ?: return if (recipient.isGroupRecipient) return @@ -1849,7 +1844,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() } } } @@ -1915,7 +1910,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) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 17a47a843..85d3c8e6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -39,10 +39,10 @@ class ConversationAdapter( private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int, VisibleMessageView) -> Unit, private val onDeselect: (MessageRecord, Int) -> Unit, + private val onAttachmentNeedsDownload: (Long, Long) -> Unit, private val glide: GlideRequests, lifecycleCoroutineScope: LifecycleCoroutineScope -) - : CursorRecyclerViewAdapter(context, cursor) { +) : CursorRecyclerViewAdapter(context, cursor) { private val messageDB by lazy { DatabaseComponent.get(context).mmsSmsDatabase() } private val contactDB by lazy { DatabaseComponent.get(context).sessionContactDatabase() } var selectedItems = mutableSetOf() @@ -120,7 +120,18 @@ class ConversationAdapter( } val contact = contactCache[senderIdHash] - visibleMessageView.bind(message, messageBefore, getMessageAfter(position, cursor), glide, searchQuery, contact, senderId, visibleMessageViewDelegate) + visibleMessageView.bind( + message, + messageBefore, + getMessageAfter(position, cursor), + glide, + searchQuery, + contact, + senderId, + visibleMessageViewDelegate, + onAttachmentNeedsDownload + ) + if (!message.isDeleted) { visibleMessageView.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, visibleMessageView, event) } visibleMessageView.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index c99596505..4a22113d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope import com.goterl.lazysodium.utils.KeyPair import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -48,11 +50,19 @@ class ConversationViewModel( } fun saveDraft(text: String) { - repository.saveDraft(threadId, text) + GlobalScope.launch(Dispatchers.IO) { + repository.saveDraft(threadId, text) + } } fun getDraft(): String? { - return repository.getDraft(threadId) + val draft: String? = repository.getDraft(threadId) + + viewModelScope.launch(Dispatchers.IO) { + repository.clearDrafts(threadId) + } + + return draft } fun inviteContacts(contacts: List) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 416c21328..d945b6a0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 8f0ddd8be..330534e23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -8,53 +8,39 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.RelativeLayout import android.widget.TextView import androidx.core.view.children import androidx.core.view.isVisible import network.loki.messenger.R import network.loki.messenger.databinding.AlbumThumbnailViewBinding -import org.session.libsession.messaging.jobs.AttachmentDownloadJob -import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.components.CornerMask -import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView +import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.util.ActivityDispatcher -class AlbumThumbnailView : FrameLayout { - - private lateinit var binding: AlbumThumbnailViewBinding - +class AlbumThumbnailView : RelativeLayout { companion object { const val MAX_ALBUM_DISPLAY_SIZE = 3 } + private val binding: AlbumThumbnailViewBinding by lazy { AlbumThumbnailViewBinding.bind(this) } + // region Lifecycle - constructor(context: Context) : super(context) { - initialize() - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - initialize() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - initialize() - } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) private val cornerMask by lazy { CornerMask(this) } private var slides: List = listOf() private var slideSize: Int = 0 - private fun initialize() { - binding = AlbumThumbnailViewBinding.inflate(LayoutInflater.from(context), this, true) - } - override fun dispatchDraw(canvas: Canvas?) { super.dispatchDraw(canvas) cornerMask.mask(canvas) @@ -63,26 +49,25 @@ class AlbumThumbnailView : FrameLayout { // region Interaction - fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient) { + fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient, onAttachmentNeedsDownload: (Long, Long) -> Unit) { val rawXInt = event.rawX.toInt() val rawYInt = event.rawY.toInt() val eventRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) val testRect = Rect() // test each album child - binding.albumCellContainer.findViewById(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child -> + binding.albumCellContainer.findViewById(R.id.album_thumbnail_root)?.children?.forEachIndexed forEach@{ index, child -> child.getGlobalVisibleRect(testRect) if (testRect.contains(eventRect)) { // hit intersects with this particular child - val slide = slides.getOrNull(index) ?: return + val slide = slides.getOrNull(index) ?: return@forEach // only open to downloaded images if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { - // restart download here + // Restart download here (on IO thread) (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> - val attachmentId = attachment.attachmentId.rowId - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mms.getId())) + onAttachmentNeedsDownload(attachment.attachmentId.rowId, mms.getId()) } } - if (slide.isInProgress) return + if (slide.isInProgress) return@forEach ActivityDispatcher.get(context)?.dispatchIntent { context -> MediaPreviewActivity.getPreviewIntent(context, slide, mms, threadRecipient) @@ -133,7 +118,7 @@ class AlbumThumbnailView : FrameLayout { else -> R.layout.album_thumbnail_3 // three stacked with additional text } - fun getThumbnailView(position: Int): KThumbnailView = when (position) { + fun getThumbnailView(position: Int): ThumbnailView = when (position) { 0 -> binding.albumCellContainer.findViewById(R.id.albumCellContainer).findViewById(R.id.album_cell_1) 1 -> binding.albumCellContainer.findViewById(R.id.albumCellContainer).findViewById(R.id.album_cell_2) 2 -> binding.albumCellContainer.findViewById(R.id.albumCellContainer).findViewById(R.id.album_cell_3) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt index c1fce3f50..66164f100 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt @@ -23,7 +23,7 @@ class LinkPreviewDraftView : LinearLayout { // Start out with the loader showing and the content view hidden binding = ViewLinkPreviewDraftBinding.inflate(LayoutInflater.from(context), this, true) binding.linkPreviewDraftContainer.isVisible = false - binding.thumbnailImageView.clipToOutline = true + binding.thumbnailImageView.root.clipToOutline = true binding.linkPreviewDraftCancelButton.setOnClickListener { cancel() } } @@ -31,10 +31,10 @@ class LinkPreviewDraftView : LinearLayout { // Hide the loader and show the content view binding.linkPreviewDraftContainer.isVisible = true binding.linkPreviewDraftLoader.isVisible = false - binding.thumbnailImageView.radius = toPx(4, resources) + binding.thumbnailImageView.root.radius = toPx(4, resources) if (linkPreview.getThumbnail().isPresent) { // This internally fetches the thumbnail - binding.thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false) + binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, null) } binding.linkPreviewDraftTitleTextView.text = linkPreview.title } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java deleted file mode 100644 index 826cfe7b3..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PorterDuff; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -import network.loki.messenger.R; - -public class TypingIndicatorView extends LinearLayout { - private boolean isActive; - private long startTime; - - private static final long CYCLE_DURATION = 1500; - private static final long DOT_DURATION = 600; - private static final float MIN_ALPHA = 0.4f; - private static final float MIN_SCALE = 0.75f; - - private View dot1; - private View dot2; - private View dot3; - - public TypingIndicatorView(Context context) { - super(context); - initialize(null); - } - - public TypingIndicatorView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - initialize(attrs); - } - - private void initialize(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.view_typing_indicator, this); - - setWillNotDraw(false); - - dot1 = findViewById(R.id.typing_dot1); - dot2 = findViewById(R.id.typing_dot2); - dot3 = findViewById(R.id.typing_dot3); - - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.TypingIndicatorView, 0, 0); - int tint = typedArray.getColor(R.styleable.TypingIndicatorView_typingIndicator_tint, Color.WHITE); - typedArray.recycle(); - - dot1.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY); - dot2.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY); - dot3.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY); - } - } - - @Override - protected void onDraw(Canvas canvas) { - if (!isActive) { - super.onDraw(canvas); - return; - } - - long timeInCycle = (System.currentTimeMillis() - startTime) % CYCLE_DURATION; - - render(dot1, timeInCycle, 0); - render(dot2, timeInCycle, 150); - render(dot3, timeInCycle, 300); - - super.onDraw(canvas); - postInvalidate(); - } - - private void render(View dot, long timeInCycle, long start) { - long end = start + DOT_DURATION; - long peak = start + (DOT_DURATION / 2); - - if (timeInCycle < start || timeInCycle > end) { - renderDefault(dot); - } else if (timeInCycle < peak) { - renderFadeIn(dot, timeInCycle, start); - } else { - renderFadeOut(dot, timeInCycle, peak); - } - } - - private void renderDefault(View dot) { - dot.setAlpha(MIN_ALPHA); - dot.setScaleX(MIN_SCALE); - dot.setScaleY(MIN_SCALE); - } - - private void renderFadeIn(View dot, long timeInCycle, long fadeInStart) { - float percent = (float) (timeInCycle - fadeInStart) / 300; - dot.setAlpha(MIN_ALPHA + (1 - MIN_ALPHA) * percent); - dot.setScaleX(MIN_SCALE + (1 - MIN_SCALE) * percent); - dot.setScaleY(MIN_SCALE + (1 - MIN_SCALE) * percent); - } - - private void renderFadeOut(View dot, long timeInCycle, long fadeOutStart) { - float percent = (float) (timeInCycle - fadeOutStart) / 300; - dot.setAlpha(1 - (1 - MIN_ALPHA) * percent); - dot.setScaleX(1 - (1 - MIN_SCALE) * percent); - dot.setScaleY(1 - (1 - MIN_SCALE) * percent); - } - - public void startAnimation() { - isActive = true; - startTime = System.currentTimeMillis(); - - postInvalidate(); - } - - public void stopAnimation() { - isActive = false; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt new file mode 100644 index 000000000..d1310bffb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt @@ -0,0 +1,105 @@ +package org.thoughtcrime.securesms.conversation.v2.components + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.PorterDuff +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import network.loki.messenger.R +import network.loki.messenger.databinding.ViewTypingIndicatorBinding + +class TypingIndicatorView : LinearLayout { + companion object { + private const val CYCLE_DURATION: Long = 1500 + private const val DOT_DURATION: Long = 600 + private const val MIN_ALPHA = 0.4f + private const val MIN_SCALE = 0.75f + } + + private val binding: ViewTypingIndicatorBinding by lazy { + val binding = ViewTypingIndicatorBinding.bind(this) + + if (tint != -1) { + binding.typingDot1.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY) + binding.typingDot2.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY) + binding.typingDot3.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY) + } + + return@lazy binding + } + + private var isActive = false + private var startTime: Long = 0 + private var tint: Int = -1 + + constructor(context: Context) : super(context) { initialize(null) } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize(attrs) } + + private fun initialize(attrs: AttributeSet?) { + setWillNotDraw(false) + + if (attrs != null) { + val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.TypingIndicatorView, 0, 0) + this.tint = typedArray.getColor(R.styleable.TypingIndicatorView_typingIndicator_tint, Color.WHITE) + typedArray.recycle() + } + } + + override fun onDraw(canvas: Canvas) { + if (!isActive) { + super.onDraw(canvas) + return + } + val timeInCycle = (System.currentTimeMillis() - startTime) % CYCLE_DURATION + render(binding.typingDot1, timeInCycle, 0) + render(binding.typingDot2, timeInCycle, 150) + render(binding.typingDot3, timeInCycle, 300) + super.onDraw(canvas) + postInvalidate() + } + + private fun render(dot: View?, timeInCycle: Long, start: Long) { + val end = start + DOT_DURATION + val peak = start + DOT_DURATION / 2 + if (timeInCycle < start || timeInCycle > end) { + renderDefault(dot) + } else if (timeInCycle < peak) { + renderFadeIn(dot, timeInCycle, start) + } else { + renderFadeOut(dot, timeInCycle, peak) + } + } + + private fun renderDefault(dot: View?) { + dot!!.alpha = MIN_ALPHA + dot.scaleX = MIN_SCALE + dot.scaleY = MIN_SCALE + } + + private fun renderFadeIn(dot: View?, timeInCycle: Long, fadeInStart: Long) { + val percent = (timeInCycle - fadeInStart).toFloat() / 300 + dot!!.alpha = MIN_ALPHA + (1 - MIN_ALPHA) * percent + dot.scaleX = MIN_SCALE + (1 - MIN_SCALE) * percent + dot.scaleY = MIN_SCALE + (1 - MIN_SCALE) * percent + } + + private fun renderFadeOut(dot: View?, timeInCycle: Long, fadeOutStart: Long) { + val percent = (timeInCycle - fadeOutStart).toFloat() / 300 + dot!!.alpha = 1 - (1 - MIN_ALPHA) * percent + dot.scaleX = 1 - (1 - MIN_SCALE) * percent + dot.scaleY = 1 - (1 - MIN_SCALE) * percent + } + + fun startAnimation() { + isActive = true + startTime = System.currentTimeMillis() + postInvalidate() + } + + fun stopAnimation() { + isActive = false + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt index 768d49146..3077d227e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt @@ -19,7 +19,7 @@ class TypingIndicatorViewContainer : LinearLayout { } fun setTypists(typists: List) { - if (typists.isEmpty()) { binding.typingIndicator.stopAnimation(); return } - binding.typingIndicator.startAnimation() + if (typists.isEmpty()) { binding.typingIndicator.root.stopAnimation(); return } + binding.typingIndicator.root.startAnimation() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index cab24ce8b..d475a6444 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -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) fun saveAttachment(messages: Set) fun reply(messages: Set) + fun destroyActionMode() } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java deleted file mode 100644 index 6d16f1f42..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java +++ /dev/null @@ -1,346 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.messages; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Handler; -import android.os.Looper; -import android.util.AttributeSet; -import android.view.HapticFeedbackConstants; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.Group; -import androidx.core.content.ContextCompat; - -import com.google.android.flexbox.FlexboxLayout; -import com.google.android.flexbox.JustifyContent; - -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.ThemeUtil; -import org.thoughtcrime.securesms.components.emoji.EmojiImageView; -import org.thoughtcrime.securesms.components.emoji.EmojiUtil; -import org.thoughtcrime.securesms.conversation.v2.ViewUtil; -import org.thoughtcrime.securesms.database.model.MessageId; -import org.thoughtcrime.securesms.database.model.ReactionRecord; -import org.thoughtcrime.securesms.util.NumberUtil; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import network.loki.messenger.R; - -public class EmojiReactionsView extends LinearLayout implements View.OnTouchListener { - - // Normally 6dp, but we have 1dp left+right margin on the pills themselves - private final int OUTER_MARGIN = ViewUtil.dpToPx(2); - private static final int DEFAULT_THRESHOLD = 5; - - private List records; - private long messageId; - private ViewGroup container; - private Group showLess; - private VisibleMessageViewDelegate delegate; - private Handler gestureHandler = new Handler(Looper.getMainLooper()); - private Runnable pressCallback; - private Runnable longPressCallback; - private long onDownTimestamp = 0; - private static long longPressDurationThreshold = 250; - private static long maxDoubleTapInterval = 200; - private boolean extended = false; - - public EmojiReactionsView(Context context) { - super(context); - init(null); - } - - public EmojiReactionsView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(attrs); - } - - private void init(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.view_emoji_reactions, this); - - this.container = findViewById(R.id.layout_emoji_container); - this.showLess = findViewById(R.id.group_show_less); - - records = new ArrayList<>(); - - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiReactionsView, 0, 0); - typedArray.recycle(); - } - } - - public void clear() { - this.records.clear(); - container.removeAllViews(); - } - - public void setReactions(long messageId, @NonNull List records, boolean outgoing, VisibleMessageViewDelegate delegate) { - this.delegate = delegate; - if (records.equals(this.records)) { - return; - } - - FlexboxLayout containerLayout = (FlexboxLayout) this.container; - containerLayout.setJustifyContent(outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START); - this.records.clear(); - this.records.addAll(records); - if (this.messageId != messageId) { - extended = false; - } - this.messageId = messageId; - - displayReactions(extended ? Integer.MAX_VALUE : DEFAULT_THRESHOLD); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (v.getTag() == null) return false; - - Reaction reaction = (Reaction) v.getTag(); - int action = event.getAction(); - if (action == MotionEvent.ACTION_DOWN) onDown(new MessageId(reaction.messageId, reaction.isMms)); - else if (action == MotionEvent.ACTION_CANCEL) removeLongPressCallback(); - else if (action == MotionEvent.ACTION_UP) onUp(reaction); - return true; - } - - private void displayReactions(int threshold) { - String userPublicKey = TextSecurePreferences.getLocalNumber(getContext()); - List reactions = buildSortedReactionsList(records, userPublicKey, threshold); - - container.removeAllViews(); - LinearLayout overflowContainer = new LinearLayout(getContext()); - overflowContainer.setOrientation(LinearLayout.HORIZONTAL); - int innerPadding = ViewUtil.dpToPx(4); - overflowContainer.setPaddingRelative(innerPadding,innerPadding,innerPadding,innerPadding); - - int pixelSize = ViewUtil.dpToPx(1); - - for (Reaction reaction : reactions) { - if (container.getChildCount() + 1 >= DEFAULT_THRESHOLD && threshold != Integer.MAX_VALUE && reactions.size() > threshold) { - if (overflowContainer.getParent() == null) { - container.addView(overflowContainer); - MarginLayoutParams overflowParams = (MarginLayoutParams) overflowContainer.getLayoutParams(); - overflowParams.height = ViewUtil.dpToPx(26); - overflowParams.setMargins(pixelSize, pixelSize, pixelSize, pixelSize); - overflowContainer.setLayoutParams(overflowParams); - overflowContainer.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.reaction_pill_background)); - } - View pill = buildPill(getContext(), this, reaction, true); - pill.setOnClickListener(v -> { - extended = true; - displayReactions(Integer.MAX_VALUE); - }); - pill.findViewById(R.id.reactions_pill_count).setVisibility(View.GONE); - pill.findViewById(R.id.reactions_pill_spacer).setVisibility(View.GONE); - overflowContainer.addView(pill); - } else { - View pill = buildPill(getContext(), this, reaction, false); - pill.setTag(reaction); - pill.setOnTouchListener(this); - MarginLayoutParams params = (MarginLayoutParams) pill.getLayoutParams(); - params.setMargins(pixelSize, pixelSize, pixelSize, pixelSize); - pill.setLayoutParams(params); - container.addView(pill); - } - } - - int overflowChildren = overflowContainer.getChildCount(); - int negativeMargin = ViewUtil.dpToPx(-8); - for (int i = 0; i < overflowChildren; i++) { - View child = overflowContainer.getChildAt(i); - MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams(); - if ((i == 0 && overflowChildren > 1) || i + 1 < overflowChildren) { - // if first and there is more than one child, or we are not the last child then set negative right margin - childParams.setMargins(0,0, negativeMargin, 0); - child.setLayoutParams(childParams); - } - } - - if (threshold == Integer.MAX_VALUE) { - showLess.setVisibility(VISIBLE); - for (int id : showLess.getReferencedIds()) { - findViewById(id).setOnClickListener(view -> { - extended = false; - displayReactions(DEFAULT_THRESHOLD); - }); - } - } else { - showLess.setVisibility(GONE); - } - } - - private void onReactionClicked(Reaction reaction) { - if (reaction.messageId != 0) { - MessageId messageId = new MessageId(reaction.messageId, reaction.isMms); - delegate.onReactionClicked(reaction.emoji, messageId, reaction.userWasSender); - } - } - - private static @NonNull List buildSortedReactionsList(@NonNull List records, String userPublicKey, int threshold) { - Map counters = new LinkedHashMap<>(); - - for (ReactionRecord record : records) { - String baseEmoji = EmojiUtil.getCanonicalRepresentation(record.getEmoji()); - Reaction info = counters.get(baseEmoji); - - if (info == null) { - info = new Reaction(record.getMessageId(), record.isMms(), record.getEmoji(), record.getCount(), record.getSortId(), record.getDateReceived(), userPublicKey.equals(record.getAuthor())); - } else { - info.update(record.getEmoji(), record.getCount(), record.getDateReceived(), userPublicKey.equals(record.getAuthor())); - } - - counters.put(baseEmoji, info); - } - - List reactions = new ArrayList<>(counters.values()); - - Collections.sort(reactions, Collections.reverseOrder()); - - if (reactions.size() >= threshold + 2 && threshold != Integer.MAX_VALUE) { - List shortened = new ArrayList<>(threshold + 2); - shortened.addAll(reactions.subList(0, threshold + 2)); - return shortened; - } else { - return reactions; - } - } - - private static View buildPill(@NonNull Context context, @NonNull ViewGroup parent, @NonNull Reaction reaction, boolean isCompact) { - View root = LayoutInflater.from(context).inflate(R.layout.reactions_pill, parent, false); - EmojiImageView emojiView = root.findViewById(R.id.reactions_pill_emoji); - TextView countView = root.findViewById(R.id.reactions_pill_count); - View spacer = root.findViewById(R.id.reactions_pill_spacer); - - if (isCompact) { - root.setPaddingRelative(1,1,1,1); - ViewGroup.LayoutParams layoutParams = root.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - root.setLayoutParams(layoutParams); - } - - if (reaction.emoji != null) { - emojiView.setImageEmoji(reaction.emoji); - - if (reaction.count >= 1) { - countView.setText(NumberUtil.getFormattedNumber(reaction.count)); - } else { - countView.setVisibility(GONE); - spacer.setVisibility(GONE); - } - } else { - emojiView.setVisibility(GONE); - spacer.setVisibility(GONE); - countView.setText(context.getString(R.string.ReactionsConversationView_plus, reaction.count)); - } - - if (reaction.userWasSender && !isCompact) { - root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected)); - countView.setTextColor(ThemeUtil.getThemedColor(context, R.attr.reactionsPillSelectedTextColor)); - } else { - if (!isCompact) { - root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background)); - } - } - - return root; - } - - private void onDown(MessageId messageId) { - removeLongPressCallback(); - Runnable newLongPressCallback = () -> { - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - if (delegate != null) { - delegate.onReactionLongClicked(messageId); - } - }; - this.longPressCallback = newLongPressCallback; - gestureHandler.postDelayed(newLongPressCallback, longPressDurationThreshold); - onDownTimestamp = new Date().getTime(); - } - - private void removeLongPressCallback() { - if (longPressCallback != null) { - gestureHandler.removeCallbacks(longPressCallback); - } - } - - private void onUp(Reaction reaction) { - if ((new Date().getTime() - onDownTimestamp) < longPressDurationThreshold) { - removeLongPressCallback(); - if (pressCallback != null) { - gestureHandler.removeCallbacks(pressCallback); - this.pressCallback = null; - } else { - Runnable newPressCallback = () -> { - onReactionClicked(reaction); - pressCallback = null; - }; - this.pressCallback = newPressCallback; - gestureHandler.postDelayed(newPressCallback, maxDoubleTapInterval); - } - } - } - - private static class Reaction implements Comparable { - private final long messageId; - private final boolean isMms; - private String emoji; - private long count; - private long sortIndex; - private long lastSeen; - private boolean userWasSender; - - Reaction(long messageId, boolean isMms, @Nullable String emoji, long count, long sortIndex, long lastSeen, boolean userWasSender) { - this.messageId = messageId; - this.isMms = isMms; - this.emoji = emoji; - this.count = count; - this.sortIndex = sortIndex; - this.lastSeen = lastSeen; - this.userWasSender = userWasSender; - } - - void update(@NonNull String emoji, long count, long lastSeen, boolean userWasSender) { - if (!this.userWasSender) { - if (userWasSender || lastSeen > this.lastSeen) { - this.emoji = emoji; - } - } - - this.count = this.count + count; - this.lastSeen = Math.max(this.lastSeen, lastSeen); - this.userWasSender = this.userWasSender || userWasSender; - } - - @NonNull Reaction merge(@NonNull Reaction other) { - this.count = this.count + other.count; - this.lastSeen = Math.max(this.lastSeen, other.lastSeen); - this.userWasSender = this.userWasSender || other.userWasSender; - return this; - } - - @Override - public int compareTo(Reaction rhs) { - Reaction lhs = this; - if (lhs.count == rhs.count ) { - return Long.compare(lhs.sortIndex, rhs.sortIndex); - } else { - return Long.compare(lhs.count, rhs.count); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt new file mode 100644 index 000000000..49e4b1044 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt @@ -0,0 +1,291 @@ +package org.thoughtcrime.securesms.conversation.v2.messages + +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.util.AttributeSet +import android.view.* +import android.view.View.OnTouchListener +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import com.google.android.flexbox.JustifyContent +import network.loki.messenger.R +import network.loki.messenger.databinding.ViewEmojiReactionsBinding +import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber +import org.session.libsession.utilities.ThemeUtil +import org.thoughtcrime.securesms.components.emoji.EmojiImageView +import org.thoughtcrime.securesms.components.emoji.EmojiUtil +import org.thoughtcrime.securesms.conversation.v2.ViewUtil +import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.ReactionRecord +import org.thoughtcrime.securesms.util.NumberUtil.getFormattedNumber +import java.util.* + +class EmojiReactionsView : ConstraintLayout, OnTouchListener { + companion object { + private const val DEFAULT_THRESHOLD = 5 + private const val longPressDurationThreshold: Long = 250 + private const val maxDoubleTapInterval: Long = 200 + } + + private val binding: ViewEmojiReactionsBinding by lazy { ViewEmojiReactionsBinding.bind(this) } + + // Normally 6dp, but we have 1dp left+right margin on the pills themselves + private val OUTER_MARGIN = ViewUtil.dpToPx(2) + private var records: MutableList? = null + private var messageId: Long = 0 + private var delegate: VisibleMessageViewDelegate? = null + private val gestureHandler = Handler(Looper.getMainLooper()) + private var pressCallback: Runnable? = null + private var longPressCallback: Runnable? = null + private var onDownTimestamp: Long = 0 + private var extended = false + + constructor(context: Context) : super(context) { init(null) } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init(attrs) } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init(attrs) } + + private fun init(attrs: AttributeSet?) { + records = ArrayList() + + if (attrs != null) { + val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.EmojiReactionsView, 0, 0) + typedArray.recycle() + } + } + + fun clear() { + records!!.clear() + binding.layoutEmojiContainer.removeAllViews() + } + + fun setReactions(messageId: Long, records: List, outgoing: Boolean, delegate: VisibleMessageViewDelegate?) { + this.delegate = delegate + if (records == this.records) { + return + } + + binding.layoutEmojiContainer.justifyContent = if (outgoing) JustifyContent.FLEX_END else JustifyContent.FLEX_START + this.records!!.clear() + this.records!!.addAll(records) + if (this.messageId != messageId) { + extended = false + } + this.messageId = messageId + displayReactions(if (extended) Int.MAX_VALUE else DEFAULT_THRESHOLD) + } + + override fun onTouch(v: View, event: MotionEvent): Boolean { + if (v.tag == null) return false + val reaction = v.tag as Reaction + val action = event.action + if (action == MotionEvent.ACTION_DOWN) onDown(MessageId(reaction.messageId, reaction.isMms)) else if (action == MotionEvent.ACTION_CANCEL) removeLongPressCallback() else if (action == MotionEvent.ACTION_UP) onUp(reaction) + return true + } + + private fun displayReactions(threshold: Int) { + val userPublicKey = getLocalNumber(context) + val reactions = buildSortedReactionsList(records!!, userPublicKey, threshold) + binding.layoutEmojiContainer.removeAllViews() + val overflowContainer = LinearLayout(context) + overflowContainer.orientation = LinearLayout.HORIZONTAL + val innerPadding = ViewUtil.dpToPx(4) + overflowContainer.setPaddingRelative(innerPadding, innerPadding, innerPadding, innerPadding) + val pixelSize = ViewUtil.dpToPx(1) + for (reaction in reactions) { + if (binding.layoutEmojiContainer.childCount + 1 >= DEFAULT_THRESHOLD && threshold != Int.MAX_VALUE && reactions.size > threshold) { + if (overflowContainer.parent == null) { + binding.layoutEmojiContainer.addView(overflowContainer) + val overflowParams = overflowContainer.layoutParams as MarginLayoutParams + overflowParams.height = ViewUtil.dpToPx(26) + overflowParams.setMargins(pixelSize, pixelSize, pixelSize, pixelSize) + overflowContainer.layoutParams = overflowParams + overflowContainer.background = ContextCompat.getDrawable(context, R.drawable.reaction_pill_background) + } + val pill = buildPill(context, this, reaction, true) + pill.setOnClickListener { v: View? -> + extended = true + displayReactions(Int.MAX_VALUE) + } + pill.findViewById(R.id.reactions_pill_count).visibility = GONE + pill.findViewById(R.id.reactions_pill_spacer).visibility = GONE + overflowContainer.addView(pill) + } else { + val pill = buildPill(context, this, reaction, false) + pill.tag = reaction + pill.setOnTouchListener(this) + val params = pill.layoutParams as MarginLayoutParams + params.setMargins(pixelSize, pixelSize, pixelSize, pixelSize) + pill.layoutParams = params + binding.layoutEmojiContainer.addView(pill) + } + } + val overflowChildren = overflowContainer.childCount + val negativeMargin = ViewUtil.dpToPx(-8) + for (i in 0 until overflowChildren) { + val child = overflowContainer.getChildAt(i) + val childParams = child.layoutParams as MarginLayoutParams + if (i == 0 && overflowChildren > 1 || i + 1 < overflowChildren) { + // if first and there is more than one child, or we are not the last child then set negative right margin + childParams.setMargins(0, 0, negativeMargin, 0) + child.layoutParams = childParams + } + } + if (threshold == Int.MAX_VALUE) { + binding.groupShowLess.visibility = VISIBLE + for (id in binding.groupShowLess.referencedIds) { + findViewById(id).setOnClickListener { view: View? -> + extended = false + displayReactions(DEFAULT_THRESHOLD) + } + } + } else { + binding.groupShowLess.visibility = GONE + } + } + + private fun buildSortedReactionsList(records: List, userPublicKey: String?, threshold: Int): List { + val counters: MutableMap = LinkedHashMap() + + records.forEach { + val baseEmoji = EmojiUtil.getCanonicalRepresentation(it.emoji) + val info = counters[baseEmoji] + + if (info == null) { + counters[baseEmoji] = Reaction(messageId, it.isMms, it.emoji, it.count, it.sortId, it.dateReceived, userPublicKey == it.author) + } + else { + info.update(it.emoji, it.count, it.dateReceived, userPublicKey == it.author) + } + } + + val reactions: List = ArrayList(counters.values) + Collections.sort(reactions, Collections.reverseOrder()) + + return if (reactions.size >= threshold + 2 && threshold != Int.MAX_VALUE) { + val shortened: MutableList = ArrayList(threshold + 2) + shortened.addAll(reactions.subList(0, threshold + 2)) + shortened + } else { + reactions + } + } + + private fun buildPill(context: Context, parent: ViewGroup, reaction: Reaction, isCompact: Boolean): View { + val root = LayoutInflater.from(context).inflate(R.layout.reactions_pill, parent, false) + val emojiView = root.findViewById(R.id.reactions_pill_emoji) + val countView = root.findViewById(R.id.reactions_pill_count) + val spacer = root.findViewById(R.id.reactions_pill_spacer) + if (isCompact) { + root.setPaddingRelative(1, 1, 1, 1) + val layoutParams = root.layoutParams + layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT + root.layoutParams = layoutParams + } + if (reaction.emoji != null) { + emojiView.setImageEmoji(reaction.emoji) + if (reaction.count >= 1) { + countView.text = getFormattedNumber(reaction.count) + } else { + countView.visibility = GONE + spacer.visibility = GONE + } + } else { + emojiView.visibility = GONE + spacer.visibility = GONE + countView.text = context.getString(R.string.ReactionsConversationView_plus, reaction.count) + } + if (reaction.userWasSender && !isCompact) { + root.background = ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected) + countView.setTextColor(ThemeUtil.getThemedColor(context, R.attr.reactionsPillSelectedTextColor)) + } else { + if (!isCompact) { + root.background = ContextCompat.getDrawable(context, R.drawable.reaction_pill_background) + } + } + return root + } + + private fun onReactionClicked(reaction: Reaction) { + if (reaction.messageId != 0L) { + val messageId = MessageId(reaction.messageId, reaction.isMms) + delegate!!.onReactionClicked(reaction.emoji!!, messageId, reaction.userWasSender) + } + } + + private fun onDown(messageId: MessageId) { + removeLongPressCallback() + val newLongPressCallback = Runnable { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + if (delegate != null) { + delegate!!.onReactionLongClicked(messageId) + } + } + longPressCallback = newLongPressCallback + gestureHandler.postDelayed(newLongPressCallback, longPressDurationThreshold) + onDownTimestamp = Date().time + } + + private fun removeLongPressCallback() { + if (longPressCallback != null) { + gestureHandler.removeCallbacks(longPressCallback!!) + } + } + + private fun onUp(reaction: Reaction) { + if (Date().time - onDownTimestamp < longPressDurationThreshold) { + removeLongPressCallback() + if (pressCallback != null) { + gestureHandler.removeCallbacks(pressCallback!!) + pressCallback = null + } else { + val newPressCallback = Runnable { + onReactionClicked(reaction) + pressCallback = null + } + pressCallback = newPressCallback + gestureHandler.postDelayed(newPressCallback, maxDoubleTapInterval) + } + } + } + + internal class Reaction( + internal val messageId: Long, + internal val isMms: Boolean, + internal var emoji: String?, + internal var count: Long, + internal val sortIndex: Long, + internal var lastSeen: Long, + internal var userWasSender: Boolean + ) : Comparable { + fun update(emoji: String, count: Long, lastSeen: Long, userWasSender: Boolean) { + if (!this.userWasSender) { + if (userWasSender || lastSeen > this.lastSeen) { + this.emoji = emoji + } + } + this.count = this.count + count + this.lastSeen = Math.max(this.lastSeen, lastSeen) + this.userWasSender = this.userWasSender || userWasSender + } + + fun merge(other: Reaction): Reaction { + count = count + other.count + lastSeen = Math.max(lastSeen, other.lastSeen) + userWasSender = userWasSender || other.userWasSender + return this + } + + override fun compareTo(other: Reaction?): Int { + if (other == null) { return -1 } + + if (this.count == other.count) { + return this.sortIndex.compareTo(other.sortIndex) + } + + return this.count.compareTo(other.count) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index cb6bb536f..45d353cc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -4,11 +4,9 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Rect import android.util.AttributeSet -import android.view.LayoutInflater import android.view.MotionEvent import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import network.loki.messenger.R import network.loki.messenger.databinding.ViewLinkPreviewBinding @@ -19,21 +17,16 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtiliti import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.ImageSlide -import org.thoughtcrime.securesms.util.UiModeUtilities class LinkPreviewView : LinearLayout { - private lateinit var binding: ViewLinkPreviewBinding + private val binding: ViewLinkPreviewBinding by lazy { ViewLinkPreviewBinding.bind(this) } private val cornerMask by lazy { CornerMask(this) } private var url: String? = null // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } - - private fun initialize() { - binding = ViewLinkPreviewBinding.inflate(LayoutInflater.from(context), this, true) - } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) // endregion // region Updating @@ -48,8 +41,8 @@ class LinkPreviewView : LinearLayout { // Thumbnail if (linkPreview.getThumbnail().isPresent) { // This internally fetches the thumbnail - binding.thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false, message) - binding.thumbnailImageView.loadIndicator.isVisible = false + binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false, message) + binding.thumbnailImageView.root.loadIndicator.isVisible = false } // Title binding.titleTextView.text = linkPreview.title @@ -80,7 +73,7 @@ class LinkPreviewView : LinearLayout { val rawYInt = event.rawY.toInt() val hitRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) val previewRect = Rect() - binding.mainLinkPreviewParent.getGlobalVisibleRect(previewRect) + binding.mainLinkPreviewContainer.getGlobalVisibleRect(previewRect) if (previewRect.contains(hitRect)) { openURL() return diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 91ab4c106..4e9140043 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -93,7 +93,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? val backgroundColor = context.getAccentColor() binding.quoteViewAttachmentPreviewContainer.backgroundTintList = ColorStateList.valueOf(backgroundColor) binding.quoteViewAttachmentPreviewImageView.isVisible = false - binding.quoteViewAttachmentThumbnailImageView.isVisible = false + binding.quoteViewAttachmentThumbnailImageView.root.isVisible = false when { attachments.audioSlide != null -> { binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_microphone) @@ -108,9 +108,9 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? attachments.thumbnailSlide != null -> { val slide = attachments.thumbnailSlide!! // This internally fetches the thumbnail - binding.quoteViewAttachmentThumbnailImageView.radius = toPx(4, resources) - binding.quoteViewAttachmentThumbnailImageView.setImageResource(glide, slide, false, false) - binding.quoteViewAttachmentThumbnailImageView.isVisible = true + binding.quoteViewAttachmentThumbnailImageView.root.radius = toPx(4, resources) + binding.quoteViewAttachmentThumbnailImageView.root.setImageResource(glide, slide, false, null) + binding.quoteViewAttachmentThumbnailImageView.root.isVisible = true binding.quoteViewBodyTextView.text = if (MediaUtil.isVideo(slide.asAttachment())) resources.getString(R.string.Slide_video) else resources.getString(R.string.Slide_image) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 472ddf370..3836daf54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -43,26 +43,30 @@ import org.thoughtcrime.securesms.util.getAccentColor import java.util.Locale import kotlin.math.roundToInt -class VisibleMessageContentView : LinearLayout { - private lateinit var binding: ViewVisibleMessageContentBinding +class VisibleMessageContentView : ConstraintLayout { + private val binding: ViewVisibleMessageContentBinding by lazy { ViewVisibleMessageContentBinding.bind(this) } var onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf() var onContentDoubleTap: (() -> Unit)? = null var delegate: VisibleMessageViewDelegate? = null var indexInAdapter: Int = -1 // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } - - private fun initialize() { - binding = ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(context), this, true) - } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) // endregion // region Updating - fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, - glide: GlideRequests, thread: Recipient, searchQuery: String?) { + fun bind( + message: MessageRecord, + isStartOfMessageCluster: Boolean, + isEndOfMessageCluster: Boolean, + glide: GlideRequests, + thread: Recipient, + searchQuery: String?, + contactIsTrusted: Boolean, + onAttachmentNeedsDownload: (Long, Long) -> Unit + ) { // Background val background = getBackground(message.isOutgoing) val color = if (message.isOutgoing) context.getAccentColor() @@ -77,28 +81,31 @@ class VisibleMessageContentView : LinearLayout { // reset visibilities / containers onContentClick.clear() - binding.albumThumbnailView.clearViews() + binding.albumThumbnailView.root.clearViews() onContentDoubleTap = null 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.root.isVisible = false + binding.untrustedView.root.isVisible = false + binding.voiceMessageView.root.isVisible = false + binding.documentView.root.isVisible = false + binding.albumThumbnailView.root.isVisible = false + binding.openGroupInvitationView.root.isVisible = false return } else { binding.deletedMessageView.root.isVisible = false } - // clear the body - binding.bodyTextView.text = null - binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null - - binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() - + binding.linkPreviewView.root.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() binding.pendingAttachmentView.root.isVisible = !mediaDownloaded && !mediaInProgress && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty() binding.voiceMessageView.root.isVisible = (mediaDownloaded || mediaInProgress) && message is MmsMessageRecord && message.slideDeck.audioSlide != null binding.documentView.root.isVisible = (mediaDownloaded || mediaInProgress) && message is MmsMessageRecord && message.slideDeck.documentSlide != null - binding.albumThumbnailView.isVisible = mediaThumbnailMessage + binding.albumThumbnailView.root.isVisible = mediaThumbnailMessage binding.openGroupInvitationView.root.isVisible = message.isOpenGroupInvitation var hideBody = false @@ -121,13 +128,33 @@ class VisibleMessageContentView : LinearLayout { delegate?.scrollToMessageIfPossible(quote.id) } } + val hasMedia = message.slideDeck.asAttachments().isNotEmpty() + } + + if (message is MmsMessageRecord) { + message.slideDeck.asAttachments().forEach { attach -> + val dbAttachment = attach as? DatabaseAttachment ?: return@forEach + val attachmentId = dbAttachment.attachmentId.rowId + if (attach.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING + && MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(attachmentId) == null) { + onAttachmentNeedsDownload(attachmentId, dbAttachment.mmsId) + } + } + message.linkPreviews.forEach { preview -> + val previewThumbnail = preview.getThumbnail().orNull() as? DatabaseAttachment ?: return@forEach + val attachmentId = previewThumbnail.attachmentId.rowId + if (previewThumbnail.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING + && MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(attachmentId) == null) { + onAttachmentNeedsDownload(attachmentId, previewThumbnail.mmsId) + } + } } when { // LINK PREVIEW message is MmsMessageRecord && message.linkPreviews.isNotEmpty() -> { - binding.linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster) - onContentClick.add { event -> binding.linkPreviewView.calculateHit(event) } + binding.linkPreviewView.root.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster) + onContentClick.add { event -> binding.linkPreviewView.root.calculateHit(event) } // Body text view is inside the link preview for layout convenience } // AUDIO @@ -177,21 +204,21 @@ class VisibleMessageContentView : LinearLayout { if (mediaDownloaded || mediaInProgress || message.isOutgoing) { // isStart and isEnd of cluster needed for calculating the mask for full bubble image groups // bind after add view because views are inflated and calculated during bind - binding.albumThumbnailView.bind( + binding.albumThumbnailView.root.bind( glideRequests = glide, message = message, isStart = isStartOfMessageCluster, isEnd = isEndOfMessageCluster ) - val layoutParams = binding.albumThumbnailView.layoutParams as ConstraintLayout.LayoutParams + val layoutParams = binding.albumThumbnailView.root.layoutParams as ConstraintLayout.LayoutParams layoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f - binding.albumThumbnailView.layoutParams = layoutParams + binding.albumThumbnailView.root.layoutParams = layoutParams onContentClick.add { event -> - binding.albumThumbnailView.calculateHitObject(event, message, thread) + binding.albumThumbnailView.root.calculateHitObject(event, message, thread, onAttachmentNeedsDownload) } } else { hideBody = true - binding.albumThumbnailView.clearViews() + binding.albumThumbnailView.root.clearViews() val firstAttachment = message.slideDeck.asAttachments().first() as? DatabaseAttachment firstAttachment?.let { attachment -> binding.pendingAttachmentView.root.bind( @@ -232,7 +259,7 @@ class VisibleMessageContentView : LinearLayout { } private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean = - listOf(albumThumbnailView, linkPreviewView, voiceMessageView.root, quoteView.root).none { it.isVisible } + listOf(albumThumbnailView.root, linkPreviewView.root, voiceMessageView.root, quoteView.root).none { it.isVisible } private fun getBackground(isOutgoing: Boolean): Drawable { val backgroundID = if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone @@ -247,8 +274,8 @@ class VisibleMessageContentView : LinearLayout { binding.openGroupInvitationView.root, binding.documentView.root, binding.quoteView.root, - binding.linkPreviewView, - binding.albumThumbnailView, + binding.linkPreviewView.root, + binding.albumThumbnailView.root, binding.bodyTextView ).forEach { view: View -> view.isVisible = false } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index a8429e390..5f730d311 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -85,7 +85,7 @@ class VisibleMessageView : LinearLayout { var onPress: ((event: MotionEvent) -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null - val messageContentView: VisibleMessageContentView by lazy { binding.messageContentView } + val messageContentView: VisibleMessageContentView by lazy { binding.messageContentView.root } companion object { const val swipeToReplyThreshold = 64.0f // dp @@ -108,7 +108,7 @@ class VisibleMessageView : LinearLayout { isHapticFeedbackEnabled = true setWillNotDraw(false) binding.messageInnerContainer.disableClipping() - binding.messageContentView.disableClipping() + binding.messageContentView.root.disableClipping() } // endregion @@ -122,6 +122,7 @@ class VisibleMessageView : LinearLayout { contact: Contact?, senderSessionID: String, delegate: VisibleMessageViewDelegate?, + onAttachmentNeedsDownload: (Long, Long) -> Unit ) { val threadID = message.threadId val thread = threadDb.getRecipientForThreadId(threadID) ?: return @@ -157,7 +158,8 @@ class VisibleMessageView : LinearLayout { binding.profilePictureView.root.update(message.individualRecipient) binding.profilePictureView.root.setOnClickListener { if (thread.isOpenGroupRecipient) { - if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) { + val openGroup = lokiThreadDb.getOpenGroupChat(threadID) + if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID) intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(senderSessionID)) @@ -190,49 +192,73 @@ class VisibleMessageView : LinearLayout { binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null binding.dateBreakTextView.isVisible = showDateBreak // Message status indicator - val (iconID, iconColor) = getMessageStatusImage(message) - if (iconID != null) { - val drawable = ContextCompat.getDrawable(context, iconID)?.mutate() - if (iconColor != null) { - drawable?.setTint(iconColor) - } - binding.messageStatusImageView.setImageDrawable(drawable) - } if (message.isOutgoing) { + val (iconID, iconColor, textId) = getMessageStatusImage(message) + if (textId != null) { + binding.messageStatusTextView.setText(textId) + + if (iconColor != null) { + binding.messageStatusTextView.setTextColor(iconColor) + } + } + if (iconID != null) { + val drawable = ContextCompat.getDrawable(context, iconID)?.mutate() + if (iconColor != null) { + drawable?.setTint(iconColor) + } + binding.messageStatusImageView.setImageDrawable(drawable) + } + val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId) - binding.messageStatusImageView.isVisible = - !message.isSent || message.id == lastMessageID + binding.messageStatusTextView.isVisible = ( + textId != null && ( + !message.isSent || + message.id == lastMessageID + ) + ) + binding.messageStatusImageView.isVisible = ( + iconID != null && ( + !message.isSent || + message.id == lastMessageID + ) + ) } else { + binding.messageStatusTextView.isVisible = false binding.messageStatusImageView.isVisible = false } // Expiration timer updateExpirationTimer(message) // Emoji Reactions - val emojiLayoutParams = binding.emojiReactionsView.layoutParams as ConstraintLayout.LayoutParams + val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f - binding.emojiReactionsView.layoutParams = emojiLayoutParams - val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } - if (message.reactions.isNotEmpty() && - (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) - ) { - binding.emojiReactionsView.setReactions(message.id, message.reactions, message.isOutgoing, delegate) - binding.emojiReactionsView.isVisible = true - } else { - binding.emojiReactionsView.isVisible = false + binding.emojiReactionsView.root.layoutParams = emojiLayoutParams + + if (message.reactions.isNotEmpty()) { + val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } + if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { + binding.emojiReactionsView.root.setReactions(message.id, message.reactions, message.isOutgoing, delegate) + binding.emojiReactionsView.root.isVisible = true + } else { + binding.emojiReactionsView.root.isVisible = false + } + } + else { + binding.emojiReactionsView.root.isVisible = false } // Populate content view - binding.messageContentView.indexInAdapter = indexInAdapter - binding.messageContentView.bind( + binding.messageContentView.root.indexInAdapter = indexInAdapter + binding.messageContentView.root.bind( message, isStartOfMessageCluster, isEndOfMessageCluster, glide, thread, - searchQuery + searchQuery, + onAttachmentNeedsDownload ) - binding.messageContentView.delegate = delegate - onDoubleTap = { binding.messageContentView.onContentDoubleTap?.invoke() } + binding.messageContentView.root.delegate = delegate + onDoubleTap = { binding.messageContentView.root.onContentDoubleTap?.invoke() } } private fun isStartOfMessageCluster(current: MessageRecord, previous: MessageRecord?, isGroupThread: Boolean): Boolean { @@ -255,19 +281,23 @@ class VisibleMessageView : LinearLayout { } } - private fun getMessageStatusImage(message: MessageRecord): Pair { + private fun getMessageStatusImage(message: MessageRecord): Triple { return when { - !message.isOutgoing -> null to null - message.isFailed -> R.drawable.ic_error to resources.getColor(R.color.destructive, context.theme) - message.isPending -> R.drawable.ic_circle_dot_dot_dot to null - message.isRead -> R.drawable.ic_filled_circle_check to null - else -> R.drawable.ic_circle_check to null + !message.isOutgoing -> Triple(null, null, null) + message.isFailed -> + Triple(R.drawable.ic_delivery_status_failed, resources.getColor(R.color.destructive, context.theme), R.string.delivery_status_failed) + message.isPending -> + Triple(R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending) + message.isRead -> + Triple(R.drawable.ic_delivery_status_read, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read) + else -> + Triple(R.drawable.ic_delivery_status_sent, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sent) } } private fun updateExpirationTimer(message: MessageRecord) { val container = binding.messageInnerContainer - val content = binding.messageContentView + val content = binding.messageContentView.root val expiration = binding.expirationTimerView val spacing = binding.messageContentSpacing container.removeAllViewsInLayout() @@ -318,7 +348,7 @@ class VisibleMessageView : LinearLayout { override fun onDraw(canvas: Canvas) { val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing) val iconSize = toPx(24, context.resources) - val left = binding.messageInnerContainer.left + binding.messageContentView.right + spacing + val left = binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.root.marginBottom - (iconSize / 2) val right = left + iconSize val bottom = top + iconSize @@ -340,7 +370,7 @@ class VisibleMessageView : LinearLayout { fun recycle() { binding.profilePictureView.root.recycle() - binding.messageContentView.recycle() + binding.messageContentView.root.recycle() } // endregion @@ -436,7 +466,7 @@ class VisibleMessageView : LinearLayout { } fun onContentClick(event: MotionEvent) { - binding.messageContentView.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) } + binding.messageContentView.root.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) } } private fun onPress(event: MotionEvent) { @@ -456,7 +486,7 @@ class VisibleMessageView : LinearLayout { } fun playVoiceMessage() { - binding.messageContentView.playVoiceMessage() + binding.messageContentView.root.playVoiceMessage() } // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt index 451368e1c..e1bf92c5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt @@ -78,7 +78,7 @@ class VoiceMessageView : RelativeLayout, AudioSlidePlayer.Listener { binding.voiceMessageViewDurationTextView.visibility = View.VISIBLE binding.voiceMessageViewDurationTextView.text = String.format("%01d:%02d", TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs), - TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs)) + TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs) % 60) } } } @@ -102,7 +102,7 @@ class VoiceMessageView : RelativeLayout, AudioSlidePlayer.Listener { this.progress = progress binding.voiceMessageViewDurationTextView.text = String.format("%01d:%02d", TimeUnit.MILLISECONDS.toMinutes(duration - (progress * duration.toDouble()).roundToLong()), - TimeUnit.MILLISECONDS.toSeconds(duration - (progress * duration.toDouble()).roundToLong())) + TimeUnit.MILLISECONDS.toSeconds(duration - (progress * duration.toDouble()).roundToLong()) % 60) val layoutParams = binding.progressView.layoutParams as RelativeLayout.LayoutParams layoutParams.width = (width.toFloat() * progress.toFloat()).roundToInt() binding.progressView.layoutParams = layoutParams diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java deleted file mode 100644 index 912253ecd..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java +++ /dev/null @@ -1,425 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.utilities; - -import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; - -import android.content.Context; -import android.content.res.TypedArray; -import android.net.Uri; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.UiThread; - -import com.bumptech.glide.RequestBuilder; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.load.resource.bitmap.FitCenter; -import com.bumptech.glide.load.resource.bitmap.RoundedCorners; -import com.bumptech.glide.request.RequestOptions; - -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.SettableFuture; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget; -import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget; -import org.thoughtcrime.securesms.components.TransferControlView; -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; -import org.thoughtcrime.securesms.mms.GlideRequest; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideClickListener; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; - -import java.util.Collections; -import java.util.Locale; - -import network.loki.messenger.R; - -public class ThumbnailView extends FrameLayout { - - private static final String TAG = ThumbnailView.class.getSimpleName(); - private static final int WIDTH = 0; - private static final int HEIGHT = 1; - private static final int MIN_WIDTH = 0; - private static final int MAX_WIDTH = 1; - private static final int MIN_HEIGHT = 2; - private static final int MAX_HEIGHT = 3; - - private ImageView image; - private View playOverlay; - private View loadIndicator; - private OnClickListener parentClickListener; - - private final int[] dimens = new int[2]; - private final int[] bounds = new int[4]; - private final int[] measureDimens = new int[2]; - - private Optional transferControls = Optional.absent(); - private SlideClickListener thumbnailClickListener = null; - private SlidesClickedListener downloadClickListener = null; - private Slide slide = null; - - public int radius; - - public ThumbnailView(Context context) { - this(context, null); - } - - public ThumbnailView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - inflate(context, R.layout.thumbnail_view, this); - - this.image = findViewById(R.id.thumbnail_image); - this.playOverlay = findViewById(R.id.play_overlay); - this.loadIndicator = findViewById(R.id.thumbnail_load_indicator); - super.setOnClickListener(new ThumbnailClickDispatcher()); - - if (attrs != null) { - TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0); - bounds[MIN_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0); - bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0); - bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0); - bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0); - radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0); - typedArray.recycle(); - } else { - radius = 0; - } - } - - @Override - protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) { - fillTargetDimensions(measureDimens, dimens, bounds); - if (measureDimens[WIDTH] == 0 && measureDimens[HEIGHT] == 0) { - super.onMeasure(originalWidthMeasureSpec, originalHeightMeasureSpec); - return; - } - - int finalWidth = measureDimens[WIDTH] + getPaddingLeft() + getPaddingRight(); - int finalHeight = measureDimens[HEIGHT] + getPaddingTop() + getPaddingBottom(); - - super.onMeasure(MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY)); - } - - @SuppressWarnings("SuspiciousNameCombination") - private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds) { - int dimensFilledCount = getNonZeroCount(dimens); - int boundsFilledCount = getNonZeroCount(bounds); - - if (dimensFilledCount == 0 || boundsFilledCount == 0) { - targetDimens[WIDTH] = 0; - targetDimens[HEIGHT] = 0; - return; - } - - double naturalWidth = dimens[WIDTH]; - double naturalHeight = dimens[HEIGHT]; - - int minWidth = bounds[MIN_WIDTH]; - int maxWidth = bounds[MAX_WIDTH]; - int minHeight = bounds[MIN_HEIGHT]; - int maxHeight = bounds[MAX_HEIGHT]; - - if (dimensFilledCount > 0 && dimensFilledCount < dimens.length) { - throw new IllegalStateException(String.format(Locale.ENGLISH, "Width or height has been specified, but not both. Dimens: %f x %f", - naturalWidth, naturalHeight)); - } - if (boundsFilledCount > 0 && boundsFilledCount < bounds.length) { - throw new IllegalStateException(String.format(Locale.ENGLISH, "One or more min/max dimensions have been specified, but not all. Bounds: [%d, %d, %d, %d]", - minWidth, maxWidth, minHeight, maxHeight)); - } - - double measuredWidth = naturalWidth; - double measuredHeight = naturalHeight; - - boolean widthInBounds = measuredWidth >= minWidth && measuredWidth <= maxWidth; - boolean heightInBounds = measuredHeight >= minHeight && measuredHeight <= maxHeight; - - if (!widthInBounds || !heightInBounds) { - double minWidthRatio = naturalWidth / minWidth; - double maxWidthRatio = naturalWidth / maxWidth; - double minHeightRatio = naturalHeight / minHeight; - double maxHeightRatio = naturalHeight / maxHeight; - - if (maxWidthRatio > 1 || maxHeightRatio > 1) { - if (maxWidthRatio >= maxHeightRatio) { - measuredWidth /= maxWidthRatio; - measuredHeight /= maxWidthRatio; - } else { - measuredWidth /= maxHeightRatio; - measuredHeight /= maxHeightRatio; - } - - measuredWidth = Math.max(measuredWidth, minWidth); - measuredHeight = Math.max(measuredHeight, minHeight); - - } else if (minWidthRatio < 1 || minHeightRatio < 1) { - if (minWidthRatio <= minHeightRatio) { - measuredWidth /= minWidthRatio; - measuredHeight /= minWidthRatio; - } else { - measuredWidth /= minHeightRatio; - measuredHeight /= minHeightRatio; - } - - measuredWidth = Math.min(measuredWidth, maxWidth); - measuredHeight = Math.min(measuredHeight, maxHeight); - } - } - - targetDimens[WIDTH] = (int) measuredWidth; - targetDimens[HEIGHT] = (int) measuredHeight; - } - - private int getNonZeroCount(int[] vals) { - int count = 0; - for (int val : vals) { - if (val > 0) { - count++; - } - } - return count; - } - - @Override - public void setOnClickListener(OnClickListener l) { - parentClickListener = l; - } - - @Override - public void setFocusable(boolean focusable) { - super.setFocusable(focusable); - if (transferControls.isPresent()) transferControls.get().setFocusable(focusable); - } - - @Override - public void setClickable(boolean clickable) { - super.setClickable(clickable); - if (transferControls.isPresent()) transferControls.get().setClickable(clickable); - } - - private TransferControlView getTransferControls() { - if (!transferControls.isPresent()) { - transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub)); - } - return transferControls.get(); - } - - public void setBounds(int minWidth, int maxWidth, int minHeight, int maxHeight) { - bounds[MIN_WIDTH] = minWidth; - bounds[MAX_WIDTH] = maxWidth; - bounds[MIN_HEIGHT] = minHeight; - bounds[MAX_HEIGHT] = maxHeight; - - forceLayout(); - } - - @UiThread - public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, - boolean showControls, boolean isPreview) - { - return setImageResource(glideRequests, slide, showControls, isPreview, 0, 0); - } - - @UiThread - public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, - boolean showControls, boolean isPreview, - int naturalWidth, int naturalHeight) - { - if (showControls) { - getTransferControls().setSlide(slide); - getTransferControls().setDownloadClickListener(new DownloadClickDispatcher()); - } else if (transferControls.isPresent()) { - getTransferControls().setVisibility(View.GONE); - } - - if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() && - (slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview)) - { - this.playOverlay.setVisibility(View.VISIBLE); - } else { - this.playOverlay.setVisibility(View.GONE); - } - - if (Util.equals(slide, this.slide)) { - Log.i(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri()); - return new SettableFuture<>(false); - } - - if (this.slide != null && this.slide.getFastPreflightId() != null && - this.slide.getFastPreflightId().equals(slide.getFastPreflightId())) - { - Log.i(TAG, "Not re-loading slide for fast preflight: " + slide.getFastPreflightId()); - this.slide = slide; - return new SettableFuture<>(false); - } - - Log.i(TAG, "loading part with id " + slide.asAttachment().getDataUri() - + ", progress " + slide.getTransferState() + ", fast preflight id: " + - slide.asAttachment().getFastPreflightId()); - - this.slide = slide; - - dimens[WIDTH] = naturalWidth; - dimens[HEIGHT] = naturalHeight; - invalidate(); - - SettableFuture result = new SettableFuture<>(); - - if (slide.getThumbnailUri() != null) { - buildThumbnailGlideRequest(glideRequests, slide).into(new GlideDrawableListeningTarget(image, result)); - } else if (slide.hasPlaceholder()) { - buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideBitmapListeningTarget(image, result)); - } else { - glideRequests.load(R.drawable.ic_image_white_24dp).centerInside().into(image); - result.set(false); - } - - return result; - } - - public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { - SettableFuture future = new SettableFuture<>(); - - if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); - - GlideRequest request = glideRequests.load(new DecryptableUri(uri)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(withCrossFade()); - - if (radius > 0) { - request = request.transforms(new CenterCrop(), new RoundedCorners(radius)); - } else { - request = request.transforms(new CenterCrop()); - } - - request.into(new GlideDrawableListeningTarget(image, future)); - - return future; - } - - public void setThumbnailClickListener(SlideClickListener listener) { - this.thumbnailClickListener = listener; - } - - public void setDownloadClickListener(SlidesClickedListener listener) { - this.downloadClickListener = listener; - } - - public void clear(GlideRequests glideRequests) { - glideRequests.clear(image); - - if (transferControls.isPresent()) { - getTransferControls().clear(); - } - - slide = null; - } - - public void showDownloadText(boolean showDownloadText) { - getTransferControls().setShowDownloadText(showDownloadText); - } - - public void showProgressSpinner() { - getTransferControls().showProgressSpinner(); - } - - public void setLoadIndicatorVisibile(boolean visible) { - this.loadIndicator.setVisibility(visible ? VISIBLE : GONE); - } - - protected void setRadius(int radius) { - this.radius = radius; - } - - private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { - GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getThumbnailUri())) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(withCrossFade()), new CenterCrop()); - - if (slide.isInProgress()) return request; - else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)); - } - - private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { - return applySizing(glideRequests.asBitmap() - .load(slide.getPlaceholderRes(getContext().getTheme())) - .diskCacheStrategy(DiskCacheStrategy.NONE), new FitCenter()); - } - - private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapTransformation fitting) { - int[] size = new int[2]; - fillTargetDimensions(size, dimens, bounds); - if (size[WIDTH] == 0 && size[HEIGHT] == 0) { - size[WIDTH] = getDefaultWidth(); - size[HEIGHT] = getDefaultHeight(); - } - - request = request.override(size[WIDTH], size[HEIGHT]); - - if (radius > 0) { - return request.transforms(fitting, new RoundedCorners(radius)); - } else { - return request.transforms(fitting); - } - } - - private int getDefaultWidth() { - ViewGroup.LayoutParams params = getLayoutParams(); - if (params != null) { - return Math.max(params.width, 0); - } - return 0; - } - - private int getDefaultHeight() { - ViewGroup.LayoutParams params = getLayoutParams(); - if (params != null) { - return Math.max(params.height, 0); - } - return 0; - } - - private class ThumbnailClickDispatcher implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (thumbnailClickListener != null && - slide != null && - slide.asAttachment().getDataUri() != null && - slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) - { - thumbnailClickListener.onClick(view, slide); - } else if (parentClickListener != null) { - parentClickListener.onClick(view); - } - } - } - - private class DownloadClickDispatcher implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (downloadClickListener != null && slide != null) { - downloadClickListener.onClick(view, Collections.singletonList(slide)); - } else { - Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + String.valueOf(slide) + " downloadClickListener: " + String.valueOf(downloadClickListener)); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt similarity index 82% rename from app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt rename to app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index 1ae290218..e15855667 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -2,14 +2,11 @@ package org.thoughtcrime.securesms.conversation.v2.utilities import android.content.Context import android.graphics.Bitmap -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri import android.util.AttributeSet -import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout -import androidx.core.content.ContextCompat import androidx.core.view.isVisible import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.CenterCrop @@ -29,31 +26,33 @@ import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri import org.thoughtcrime.securesms.mms.GlideRequest import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide +import kotlin.Boolean +import kotlin.Int +import kotlin.getValue +import kotlin.lazy +import kotlin.let -open class KThumbnailView: FrameLayout { - private lateinit var binding: ThumbnailViewBinding +open class ThumbnailView: FrameLayout { companion object { private const val WIDTH = 0 private const val HEIGHT = 1 } + private val binding: ThumbnailViewBinding by lazy { ThumbnailViewBinding.bind(this) } + // region Lifecycle constructor(context: Context) : super(context) { initialize(null) } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize(attrs) } - private val image by lazy { binding.thumbnailImage } - private val playOverlay by lazy { binding.playOverlay } val loadIndicator: View by lazy { binding.thumbnailLoadIndicator } - val downloadIndicator: View by lazy { binding.thumbnailDownloadIcon } private val dimensDelegate = ThumbnailDimensDelegate() private var slide: Slide? = null - private var radius: Int = 0 + var radius: Int = 0 private fun initialize(attrs: AttributeSet?) { - binding = ThumbnailViewBinding.inflate(LayoutInflater.from(context), this) if (attrs != null) { val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0) @@ -66,8 +65,6 @@ open class KThumbnailView: FrameLayout { typedArray.recycle() } - val background = ContextCompat.getColor(context, R.color.transparent_black_6) - binding.root.background = ColorDrawable(background) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { @@ -80,8 +77,8 @@ open class KThumbnailView: FrameLayout { val finalHeight: Int = adjustedDimens[HEIGHT] + paddingTop + paddingBottom super.onMeasure( - MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY) + MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY) ) } @@ -90,17 +87,17 @@ open class KThumbnailView: FrameLayout { // endregion // region Interaction - fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, mms: MmsMessageRecord): ListenableFuture { + fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, mms: MmsMessageRecord?): ListenableFuture { return setImageResource(glide, slide, isPreview, 0, 0, mms) } fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, naturalWidth: Int, - naturalHeight: Int, mms: MmsMessageRecord): ListenableFuture { + naturalHeight: Int, mms: MmsMessageRecord?): ListenableFuture { val currentSlide = this.slide - playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() && + binding.playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() && (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview)) if (equals(currentSlide, slide)) { @@ -116,8 +113,8 @@ open class KThumbnailView: FrameLayout { this.slide = slide - loadIndicator.isVisible = slide.isInProgress - downloadIndicator.isVisible = slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED + binding.thumbnailLoadIndicator.isVisible = slide.isInProgress + binding.thumbnailDownloadIcon.isVisible = slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED dimensDelegate.setDimens(naturalWidth, naturalHeight) invalidate() @@ -126,13 +123,13 @@ open class KThumbnailView: FrameLayout { when { slide.thumbnailUri != null -> { - buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(image, result)) + buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, result)) } slide.hasPlaceholder() -> { - buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(image, result)) + buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, result)) } else -> { - glide.clear(image) + glide.clear(binding.thumbnailImage) result.set(false) } } @@ -176,7 +173,7 @@ open class KThumbnailView: FrameLayout { } open fun clear(glideRequests: GlideRequests) { - glideRequests.clear(image) + glideRequests.clear(binding.thumbnailImage) slide = null } @@ -193,11 +190,8 @@ open class KThumbnailView: FrameLayout { request.transforms(CenterCrop()) } - request.into(GlideDrawableListeningTarget(image, future)) + request.into(GlideDrawableListeningTarget(binding.thumbnailImage, future)) return future } - - // endregion - } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 8806316bc..cd098777d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -33,8 +33,9 @@ import androidx.annotation.VisibleForTesting; import com.bumptech.glide.Glide; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONException; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; @@ -318,6 +319,28 @@ public class AttachmentDatabase extends Database { notifyAttachmentListeners(); } + @SuppressWarnings("ResultOfMethodCallIgnored") + void deleteAttachmentsForMessages(long[] mmsIds) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + Cursor cursor = null; + String mmsIdString = StringUtils.join(mmsIds, ','); + + try { + cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE}, MMS_ID + " IN (?)", + new String[] {mmsIdString}, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + deleteAttachmentOnDisk(cursor.getString(0), cursor.getString(1), cursor.getString(2)); + } + } finally { + if (cursor != null) + cursor.close(); + } + + database.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {mmsIdString}); + notifyAttachmentListeners(); + } + public void deleteAttachment(@NonNull AttachmentId id) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index ce950214f..b6b224589 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -23,7 +23,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.WindowDebouncer; import org.thoughtcrime.securesms.ApplicationContext; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index 74396e2a9..76fa8c5c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -19,7 +19,7 @@ package org.thoughtcrime.securesms.database; import android.content.Context; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt index e6c9b9614..f4d6530bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt @@ -1,9 +1,9 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues +import android.database.Cursor import androidx.core.database.getStringOrNull -import net.sqlcipher.Cursor -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.session.libsignal.utilities.Base64 fun SQLiteDatabase.get(table: String, query: String?, arguments: Array?, get: (Cursor) -> T): T? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java index 2dd8b2bf2..822e40129 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -6,7 +6,7 @@ import android.database.Cursor; import android.net.Uri; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import network.loki.messenger.R; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index feaccc398..584bf3a71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.database; - import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; @@ -12,7 +11,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.Address; @@ -319,6 +318,19 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt notifyConversationListListeners(); } + public boolean hasDownloadedProfilePicture(String groupId) { + try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{AVATAR}, GROUP_ID + " = ?", + new String[] {groupId}, + null, null, null)) + { + if (cursor != null && cursor.moveToNext()) { + return !cursor.isNull(0); + } + + return false; + } + } + public void updateMembers(String groupId, List
members) { Collections.sort(members); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index 81f8b62aa..a6fed5be8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -1,14 +1,14 @@ package org.thoughtcrime.securesms.database; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import org.apache.commons.lang3.StringUtils; import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -110,6 +110,11 @@ public class GroupReceiptDatabase extends Database { db.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)}); } + void deleteRowsForMessages(long[] mmsIds) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {StringUtils.join(mmsIds, ',')}); + } + void deleteAllRows() { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.delete(TABLE_NAME, null, null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java index f878e3061..ef4746923 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -5,7 +5,7 @@ import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt index 6aeadc2b7..b0f6a676c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt @@ -300,6 +300,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val lastHash = database.insertOrUpdate(lastMessageHashValueTable2, row, query, arrayOf( snode.toString(), publicKey, namespace.toString() )) } + override fun clearAllLastMessageHashes() { + val database = databaseHelper.writableDatabase + database.delete(lastMessageHashValueTable2, null, null) + } + override fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set? { val database = databaseHelper.readableDatabase val query = "${Companion.publicKey} = ? AND ${Companion.receivedMessageHashNamespace} = ?" @@ -321,6 +326,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(receivedMessageHashValuesTable, row, query, arrayOf( publicKey, namespace.toString() )) } + override fun clearReceivedMessageHashValues() { + val database = databaseHelper.writableDatabase + database.delete(receivedMessageHashValuesTable, null, null) + } + override fun getAuthToken(server: String): String? { val database = databaseHelper.readableDatabase return database.get(openGroupAuthTokenTable, "${Companion.server} = ?", wrap(server)) { cursor -> @@ -339,7 +349,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } override fun getLastMessageServerID(room: String, server: String): Long? { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase val index = "$server.$room" return database.get(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) { cursor -> cursor.getInt(lastMessageServerID) @@ -510,7 +520,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } fun getServerCapabilities(serverName: String): List { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase return database.get(serverCapabilitiesTable, "$server = ?", wrap(serverName)) { cursor -> cursor.getString(capabilities) }?.split(",") ?: emptyList() @@ -523,7 +533,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } fun getLastInboxMessageId(serverName: String): Long? { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase return database.get(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor -> cursor.getInt(lastInboxMessageServerId) }?.toLong() @@ -540,7 +550,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } fun getLastOutboxMessageId(serverName: String): Long? { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase return database.get(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor -> cursor.getInt(lastOutboxMessageServerId) }?.toLong() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index 3cfdd1301..45184c2d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context -import net.sqlcipher.database.SQLiteDatabase.CONFLICT_REPLACE +import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE import org.session.libsignal.database.LokiMessageDatabaseProtocol import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -77,6 +77,25 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.endTransaction() } + fun deleteMessages(messageIDs: List) { + val database = databaseHelper.writableDatabase + database.beginTransaction() + + database.delete( + messageIDTable, + "${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", + messageIDs.map { "$it" }.toTypedArray() + ) + database.delete( + messageThreadMappingTable, + "${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", + messageIDs.map { "$it" }.toTypedArray() + ) + + database.setTransactionSuccessful() + database.endTransaction() + } + /** * @return pair of sms or mms table-specific ID and whether it is in SMS table */ @@ -96,6 +115,37 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab } } + fun getMessageIDs(serverIDs: List, threadID: Long): Pair, List> { + val database = databaseHelper.readableDatabase + + // Retrieve the message ids + val messageIdCursor = database + .rawQuery( + """ + SELECT ${messageThreadMappingTable}.${messageID}, ${messageIDTable}.${messageType} + FROM ${messageThreadMappingTable} + JOIN ${messageIDTable} ON ${messageIDTable}.message_id = ${messageThreadMappingTable}.${messageID} + WHERE ( + ${messageThreadMappingTable}.${Companion.threadID} = $threadID AND + ${messageThreadMappingTable}.${Companion.serverID} IN (${serverIDs.joinToString(",")}) + ) + """ + ) + + val smsMessageIds: MutableList = mutableListOf() + val mmsMessageIds: MutableList = mutableListOf() + while (messageIdCursor.moveToNext()) { + if (messageIdCursor.getInt(1) == SMS_TYPE) { + smsMessageIds.add(messageIdCursor.getLong(0)) + } + else { + mmsMessageIds.add(messageIdCursor.getLong(0)) + } + } + + return Pair(smsMessageIds, mmsMessageIds) + } + override fun setServerID(messageID: Long, serverID: Long, isSms: Boolean) { val database = databaseHelper.writableDatabase val contentValues = ContentValues(3) @@ -136,6 +186,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 { @@ -178,6 +233,15 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) } + fun deleteMessageServerHashes(messageIDs: List) { + val database = databaseHelper.writableDatabase + database.delete( + messageHashTable, + "${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", + messageIDs.map { "$it" }.toTypedArray() + ) + } + fun migrateThreadId(legacyThreadId: Long, newThreadId: Long) { val database = databaseHelper.writableDatabase val contentValues = ContentValues(1) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index c87e491bc..134ea6e45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -7,7 +7,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.utilities.Address; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index ffde5ca02..d3ba31747 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -5,7 +5,7 @@ import android.content.Context; import android.database.Cursor; import android.text.TextUtils; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Document; @@ -39,9 +39,10 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsSent(long messageId, boolean secure); public abstract void markUnidentified(long messageId, boolean unidentified); - public abstract void markAsDeleted(long messageId, boolean read); + public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention); public abstract boolean deleteMessage(long messageId); + public abstract boolean deleteMessages(long[] messageId, long threadId); public abstract void updateThreadId(long fromId, long toId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 59d9e3533..fb815107a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -356,17 +356,19 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa db.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString())) } - override fun markAsDeleted(messageId: Long, read: Boolean) { + override fun markAsDeleted(messageId: Long, read: Boolean, hasMention: Boolean) { val database = databaseHelper.writableDatabase val contentValues = ContentValues() contentValues.put(READ, 1) contentValues.put(BODY, "") + contentValues.put(HAS_MENTION, 0) database.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString())) val attachmentDatabase = get(context).attachmentDatabase() queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) }) val threadId = getThreadIdForMessage(messageId) if (!read) { - get(context).threadDatabase().decrementUnread(threadId, 1) + val mentionChange = if (hasMention) { 1 } else { 0 } + get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange) } updateMailboxBitmask( messageId, @@ -659,6 +661,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa contentValues.put(EXPIRES_IN, retrieved.expiresIn) contentValues.put(READ, if (retrieved.isExpirationUpdate) 1 else 0) contentValues.put(UNIDENTIFIED, retrieved.isUnidentified) + contentValues.put(HAS_MENTION, retrieved.hasMention()) contentValues.put(MESSAGE_REQUEST_RESPONSE, retrieved.isMessageRequestResponse) if (!contentValues.containsKey(DATE_SENT)) { contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)) @@ -690,7 +693,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) { if (runIncrement) { - get(context).threadDatabase().incrementUnread(threadId, 1) + val mentionAmount = if (retrieved.hasMention()) { 1 } else { 0 } + get(context).threadDatabase().incrementUnread(threadId, 1, mentionAmount) } if (runThreadUpdate) { get(context).threadDatabase().update(threadId, true) @@ -978,6 +982,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa return threadDeleted } + override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean { + val attachmentDatabase = get(context).attachmentDatabase() + val groupReceiptDatabase = get(context).groupReceiptDatabase() + + queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) }) + groupReceiptDatabase.deleteRowsForMessages(messageIds) + + val database = databaseHelper.writableDatabase + database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(","))) + + val threadDeleted = get(context).threadDatabase().update(threadId, false) + notifyConversationListeners(threadId) + notifyStickerListeners() + notifyStickerPackListeners() + return threadDeleted + } + override fun updateThreadId(fromId: Long, toId: Long) { val contentValues = ContentValues(1) contentValues.put(THREAD_ID, toId) @@ -1310,7 +1331,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa message.outgoingQuote!!.missing, SlideDeck(context, message.outgoingQuote!!.attachments!!) ) else null, - message.sharedContacts, message.linkPreviews, listOf(), false + message.sharedContacts, message.linkPreviews, listOf(), false, false ) } @@ -1354,6 +1375,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) var readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(READ_RECEIPT_COUNT)) val subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID)) + val hasMention = (cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1) if (!isReadReceiptsEnabled(context)) { readReceiptCount = 0 } @@ -1371,7 +1393,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa dateSent, dateReceived, deliveryReceiptCount, threadId, contentLocationBytes, messageSize, expiry, status, transactionIdBytes, mailbox, slideDeck, - readReceiptCount + readReceiptCount, hasMention ) } @@ -1405,6 +1427,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)) val expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED)) val unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1 + val hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1 if (!isReadReceiptsEnabled(context)) { readReceiptCount = 0 } @@ -1441,7 +1464,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck!!, partCount, box, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, - readReceiptCount, quote, contacts, previews, reactions, unidentified + readReceiptCount, quote, contacts, previews, reactions, unidentified, hasMention ) } @@ -1634,5 +1657,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa const val CREATE_MESSAGE_REQUEST_RESPONSE_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $MESSAGE_REQUEST_RESPONSE INTEGER DEFAULT 0;" const val CREATE_REACTIONS_UNREAD_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_UNREAD INTEGER DEFAULT 0;" const val CREATE_REACTIONS_LAST_SEEN_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_LAST_SEEN INTEGER DEFAULT 0;" + const val CREATE_HAS_MENTION_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $HAS_MENTION INTEGER DEFAULT 0;" } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index c4fe3d243..f3110a5c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -24,6 +24,8 @@ public interface MmsSmsColumns { public static final String REACTIONS_UNREAD = "reactions_unread"; public static final String REACTIONS_LAST_SEEN = "reactions_last_seen"; + public static final String HAS_MENTION = "has_mention"; + public static class Types { protected static final long TOTAL_MASK = 0xFFFFFFFF; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 3fcb1e724..c7f9d6132 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -22,8 +22,8 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteQueryBuilder; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteQueryBuilder; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Util; @@ -75,7 +75,9 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, MmsDatabase.LINK_PREVIEWS, - ReactionDatabase.REACTION_JSON_ALIAS}; + ReactionDatabase.REACTION_JSON_ALIAS, + MmsSmsColumns.HAS_MENTION + }; public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -112,6 +114,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; @@ -128,7 +188,7 @@ public class MmsSmsDatabase extends Database { } public Cursor getConversationSnippet(long threadId) { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; return queryTables(PROJECTION, selection, order, null); @@ -145,7 +205,7 @@ public class MmsSmsDatabase extends Database { } public Cursor getUnread() { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC"; + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0"; return queryTables(PROJECTION, selection, order, null); @@ -180,7 +240,7 @@ public class MmsSmsDatabase extends Database { } public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull Address address) { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) { @@ -199,16 +259,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)) { @@ -279,7 +339,9 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_MISSING, MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, - MmsDatabase.LINK_PREVIEWS}; + MmsDatabase.LINK_PREVIEWS, + MmsSmsColumns.HAS_MENTION + }; String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT, SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED, @@ -306,7 +368,9 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_MISSING, MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, - MmsDatabase.LINK_PREVIEWS}; + MmsDatabase.LINK_PREVIEWS, + MmsSmsColumns.HAS_MENTION + }; SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); @@ -350,6 +414,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(MmsDatabase.STATUS); mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED); mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE); + mmsColumnsPresent.add(MmsSmsColumns.HAS_MENTION); mmsColumnsPresent.add(AttachmentDatabase.ROW_ID); mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID); @@ -412,6 +477,7 @@ public class MmsSmsDatabase extends Database { smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED); smsColumnsPresent.add(SmsDatabase.STATUS); smsColumnsPresent.add(SmsDatabase.UNIDENTIFIED); + smsColumnsPresent.add(MmsSmsColumns.HAS_MENTION); smsColumnsPresent.add(ReactionDatabase.ROW_ID); smsColumnsPresent.add(ReactionDatabase.MESSAGE_ID); smsColumnsPresent.add(ReactionDatabase.IS_MMS); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java index d1ba25aa7..b832d04df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java @@ -6,7 +6,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; import org.session.libsignal.utilities.Log; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.session.libsignal.utilities.Base64; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt index 74e452db0..87c0b6c18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt @@ -48,6 +48,14 @@ class ReactionDatabase(context: Context, helper: SQLCipherOpenHelper) : Database ) """.trimIndent() + @JvmField + val CREATE_INDEXS = arrayOf( + "CREATE INDEX IF NOT EXISTS reaction_message_id_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.MESSAGE_ID + ");", + "CREATE INDEX IF NOT EXISTS reaction_is_mms_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.IS_MMS + ");", + "CREATE INDEX IF NOT EXISTS reaction_message_id_is_mms_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.MESSAGE_ID + ", " + ReactionDatabase.IS_MMS + ");", + "CREATE INDEX IF NOT EXISTS reaction_sort_id_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.SORT_ID + ");", + ) + @JvmField val CREATE_REACTION_TRIGGERS = arrayOf( """ diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 9dbea1ed1..ab4cb9f2e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -11,7 +11,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.MaterialColor; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java index 37efc9a43..eac6a5fbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java @@ -1,13 +1,13 @@ package org.thoughtcrime.securesms.database; import android.content.Context; +import android.database.Cursor; import androidx.annotation.NonNull; import com.annimon.stream.Stream; -import net.sqlcipher.Cursor; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Util; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt index 148afdcb2..3e37c5c41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import androidx.core.database.getStringOrNull -import net.sqlcipher.Cursor +import android.database.Cursor import org.session.libsession.messaging.contacts.Contact import org.session.libsignal.utilities.Base64 import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt index 595168fdf..4425e3d85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context -import net.sqlcipher.Cursor +import android.database.Cursor import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 118878452..76c3e6b9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -28,9 +28,10 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteStatement; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteStatement; +import org.apache.commons.lang3.StringUtils; import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage; @@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import java.io.IOException; import java.security.SecureRandom; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -121,6 +123,9 @@ public class SmsDatabase extends MessagingDatabase { public static String CREATE_REACTIONS_UNREAD_COMMAND = "ALTER TABLE "+ TABLE_NAME + " " + "ADD COLUMN " + REACTIONS_UNREAD + " INTEGER DEFAULT 0;"; + public static String CREATE_HAS_MENTION_COMMAND = "ALTER TABLE "+ TABLE_NAME + " " + + "ADD COLUMN " + HAS_MENTION + " INTEGER DEFAULT 0;"; + private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); @@ -206,14 +211,17 @@ public class SmsDatabase extends MessagingDatabase { } @Override - public void markAsDeleted(long messageId, boolean read) { + public void markAsDeleted(long messageId, boolean read, boolean hasMention) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(READ, 1); contentValues.put(BODY, ""); + contentValues.put(HAS_MENTION, 0); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); long threadId = getThreadIdForMessage(messageId); - if (!read) { DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1); } + if (!read) { + DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1, (hasMention ? 1 : 0)); + } updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE); } @@ -444,6 +452,7 @@ public class SmsDatabase extends MessagingDatabase { values.put(SUBSCRIPTION_ID, message.getSubscriptionId()); values.put(EXPIRES_IN, message.getExpiresIn()); values.put(UNIDENTIFIED, message.isUnidentified()); + values.put(HAS_MENTION, message.hasMention()); if (!TextUtils.isEmpty(message.getPseudoSubject())) values.put(SUBJECT, message.getPseudoSubject()); @@ -462,7 +471,7 @@ public class SmsDatabase extends MessagingDatabase { long messageId = db.insert(TABLE_NAME, null, values); if (unread && runIncrement) { - DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1); + DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1, (message.hasMention() ? 1 : 0)); } if (runThreadUpdate) { @@ -596,6 +605,30 @@ public class SmsDatabase extends MessagingDatabase { return threadDeleted; } + @Override + public boolean deleteMessages(long[] messageIds, long threadId) { + String[] argsArray = new String[messageIds.length]; + String[] argValues = new String[messageIds.length]; + Arrays.fill(argsArray, "?"); + + for (int i = 0; i < messageIds.length; i++) { + argValues[i] = (messageIds[i] + ""); + } + + String combinedMessageIdArgss = StringUtils.join(messageIds, ','); + String combinedMessageIds = StringUtils.join(messageIds, ','); + Log.i("MessageDatabase", "Deleting: " + combinedMessageIds); + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.delete( + TABLE_NAME, + ID + " IN (" + StringUtils.join(argsArray, ',') + ")", + argValues + ); + boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); + notifyConversationListeners(threadId); + return threadDeleted; + } + @Override public void updateThreadId(long fromId, long toId) { ContentValues contentValues = new ContentValues(1); @@ -741,7 +774,7 @@ public class SmsDatabase extends MessagingDatabase { 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), threadId, 0, new LinkedList(), message.getExpiresIn(), - System.currentTimeMillis(), 0, false, Collections.emptyList()); + System.currentTimeMillis(), 0, false, Collections.emptyList(), false); } } @@ -782,6 +815,7 @@ public class SmsDatabase extends MessagingDatabase { long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)); boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.UNIDENTIFIED)) == 1; + boolean hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.HAS_MENTION)) == 1; if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -795,7 +829,7 @@ public class SmsDatabase extends MessagingDatabase { recipient, dateSent, dateReceived, deliveryReceiptCount, type, threadId, status, mismatches, - expiresIn, expireStarted, readReceiptCount, unidentified, reactions); + expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention); } private List getMismatches(String document) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 122d85223..e43102086 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -2,31 +2,23 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri +import org.session.libsession.avatars.AvatarHelper 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.Profile import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -36,11 +28,12 @@ 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.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair @@ -58,6 +51,7 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol +import java.security.MessageDigest class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { @@ -69,16 +63,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseComponent.get(context).lokiAPIDatabase().getUserX25519KeyPair() } - override fun getUserDisplayName(): String? { - return TextSecurePreferences.getProfileName(context) - } - - override fun getUserProfileKey(): ByteArray? { - return ProfileKeyUtil.getProfileKey(context) - } - - override fun getUserProfilePictureURL(): String? { - return TextSecurePreferences.getProfilePictureURL(context) + override fun getUserProfile(): Profile { + val displayName = TextSecurePreferences.getProfileName(context)!! + val profileKey = ProfileKeyUtil.getProfileKey(context) + val profilePictureUrl = TextSecurePreferences.getProfilePictureURL(context) + return Profile(displayName, profileKey, profilePictureUrl) } override fun setUserProfilePictureURL(newValue: String) { @@ -115,9 +104,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, threadDb.setRead(threadId, updateLastSeen) } - override fun incrementUnread(threadId: Long, amount: Int) { + override fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) { val threadDb = DatabaseComponent.get(context).threadDatabase() - threadDb.incrementUnread(threadId, amount) + threadDb.incrementUnread(threadId, amount, unreadMentionAmount) } override fun updateThread(threadId: Long, unarchive: Boolean) { @@ -335,6 +324,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue) } + override fun hasDownloadedProfilePicture(groupID: String): Boolean { + return DatabaseComponent.get(context).groupDatabase().hasDownloadedProfilePicture(groupID) + } + override fun getReceivedMessageTimestamps(): Set { return SessionMetaProtocol.getTimestamps() } @@ -428,6 +421,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) } @@ -467,7 +465,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) { val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) - val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) + val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true, false) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseComponent.get(context).smsDatabase() @@ -562,8 +560,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseComponent.get(context).groupDatabase().allGroups } - override fun addOpenGroup(urlAsString: String) { - OpenGroupManager.addOpenGroup(urlAsString, context) + override fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? { + return OpenGroupManager.addOpenGroup(urlAsString, context) } override fun onOpenGroupAdded(server: String) { @@ -775,6 +773,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, false, false, false, + false, Optional.absent(), Optional.absent(), Optional.absent(), @@ -804,6 +803,25 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val smsDb = DatabaseComponent.get(context).smsDatabase() val sender = Recipient.from(context, fromSerialized(senderPublicKey), false) val threadId = threadDB.getOrCreateThreadIdFor(sender) + val profile = response.profile + if (profile != null) { + val profileManager = SSKEnvironment.shared.profileManager + val name = profile.displayName!! + if (name.isNotEmpty()) { + profileManager.setName(context, sender, name) + } + val newProfileKey = profile.profileKey + + val needsProfilePicture = !AvatarHelper.avatarFileExists(context, sender.address) + val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true + val profileKeyChanged = (sender.profileKey == null || !MessageDigest.isEqual(sender.profileKey, newProfileKey)) + + if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { + profileManager.setProfileKey(context, sender, newProfileKey!!) + profileManager.setUnidentifiedAccessMode(context, sender, Recipient.UnidentifiedAccessMode.UNKNOWN) + profileManager.setProfilePictureURL(context, sender, profile.profilePictureURL!!) + } + } threadDB.setHasSent(threadId, true) val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase() val mappings = mutableMapOf() @@ -849,6 +867,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, false, false, true, + false, Optional.absent(), Optional.absent(), Optional.absent(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 58a92fdf0..959cd82da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -32,7 +32,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.Address; @@ -87,6 +87,7 @@ public class ThreadDatabase extends Database { private static final String SNIPPET_CHARSET = "snippet_cs"; public static final String READ = "read"; public static final String UNREAD_COUNT = "unread_count"; + public static final String UNREAD_MENTION_COUNT = "unread_mention_count"; public static final String TYPE = "type"; private static final String ERROR = "error"; public static final String SNIPPET_TYPE = "snippet_type"; @@ -117,7 +118,7 @@ public class ThreadDatabase extends Database { }; private static final String[] THREAD_PROJECTION = { - ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE, + ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, TYPE, ERROR, SNIPPET_TYPE, SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED }; @@ -135,6 +136,11 @@ public class ThreadDatabase extends Database { "ADD COLUMN " + IS_PINNED + " INTEGER DEFAULT 0;"; } + public static String getUnreadMentionCountCommand() { + return "ALTER TABLE "+ TABLE_NAME + " " + + "ADD COLUMN " + UNREAD_MENTION_COUNT + " INTEGER DEFAULT 0;"; + } + public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } @@ -293,6 +299,7 @@ public class ThreadDatabase extends Database { ContentValues contentValues = new ContentValues(1); contentValues.put(READ, 1); contentValues.put(UNREAD_COUNT, 0); + contentValues.put(UNREAD_MENTION_COUNT, 0); if (lastSeen) { contentValues.put(LAST_SEEN, System.currentTimeMillis()); @@ -312,20 +319,28 @@ public class ThreadDatabase extends Database { }}; } - public void incrementUnread(long threadId, int amount) { + public void incrementUnread(long threadId, int amount, int unreadMentionAmount) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + - UNREAD_COUNT + " = " + UNREAD_COUNT + " + ? WHERE " + ID + " = ?", - new String[] {String.valueOf(amount), - String.valueOf(threadId)}); + UNREAD_COUNT + " = " + UNREAD_COUNT + " + ?, " + + UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " + ? WHERE " + ID + " = ?", + new String[] { + String.valueOf(amount), + String.valueOf(unreadMentionAmount), + String.valueOf(threadId) + }); } - public void decrementUnread(long threadId, int amount) { + public void decrementUnread(long threadId, int amount, int unreadMentionAmount) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + - UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0", - new String[] {String.valueOf(amount), - String.valueOf(threadId)}); + UNREAD_COUNT + " = " + UNREAD_COUNT + " - ?, " + + UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0", + new String[] { + String.valueOf(amount), + String.valueOf(unreadMentionAmount), + String.valueOf(threadId) + }); } public void setDistributionType(long threadId, int distributionType) { @@ -502,15 +517,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 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); @@ -913,6 +936,7 @@ public class ThreadDatabase extends Database { long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT)); + int unreadMentionCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_MENTION_COUNT)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); boolean archived = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.ARCHIVED)) != 0; int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); @@ -928,7 +952,7 @@ public class ThreadDatabase extends Database { } return new ThreadRecord(body, snippetUri, recipient, date, count, - unreadCount, threadId, deliveryReceiptCount, status, type, + unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type, distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index d24631f27..fe1b9f785 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -1,14 +1,18 @@ package org.thoughtcrime.securesms.database.helpers; - +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteDatabaseHook; -import net.sqlcipher.database.SQLiteOpenHelper; +import net.zetetic.database.sqlcipher.SQLiteConnection; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; +import net.zetetic.database.sqlcipher.SQLiteException; +import net.zetetic.database.sqlcipher.SQLiteOpenHelper; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsignal.utilities.Log; @@ -35,6 +39,11 @@ import org.thoughtcrime.securesms.database.SessionContactDatabase; import org.thoughtcrime.securesms.database.SessionJobDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.notifications.NotificationChannels; + +import java.io.File; + +import network.loki.messenger.R; public class SQLCipherOpenHelper extends SQLiteOpenHelper { @@ -76,40 +85,158 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV37 = 58; private static final int lokiV38 = 59; private static final int lokiV39 = 60; + private static final int lokiV40 = 61; + private static final int lokiV41 = 62; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV39; - private static final String DATABASE_NAME = "signal.db"; + private static final int DATABASE_VERSION = lokiV41; + private static final int MIN_DATABASE_VERSION = lokiV7; + private static final String CIPHER3_DATABASE_NAME = "signal.db"; + public static final String DATABASE_NAME = "signal_v4.db"; private final Context context; private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { - super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() { + super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() { @Override - public void preKey(SQLiteDatabase db) { - db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;"); - db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;"); + public void preKey(SQLiteConnection connection) { + SQLCipherOpenHelper.applySQLCipherPragmas(connection, true); } @Override - public void postKey(SQLiteDatabase db) { - db.rawExecSQL("PRAGMA kdf_iter = '1';"); - db.rawExecSQL("PRAGMA cipher_page_size = 4096;"); + public void postKey(SQLiteConnection connection) { + SQLCipherOpenHelper.applySQLCipherPragmas(connection, true); + // if not vacuumed in a while, perform that operation long currentTime = System.currentTimeMillis(); // 7 days if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) { - db.rawExecSQL("VACUUM;"); + connection.execute("VACUUM;", null, null); TextSecurePreferences.setLastVacuumNow(context); } } - }); + }, true); this.context = context.getApplicationContext(); this.databaseSecret = databaseSecret; } + private static void applySQLCipherPragmas(SQLiteConnection connection, boolean useSQLCipher4) { + if (useSQLCipher4) { + connection.execute("PRAGMA kdf_iter = '256000';", null, null); + } + else { + connection.execute("PRAGMA cipher_compatibility = 3;", null, null); + connection.execute("PRAGMA kdf_iter = '1';", null, null); + } + + connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + } + + private static SQLiteDatabase open(String path, DatabaseSecret databaseSecret, boolean useSQLCipher4) throws SQLiteException { + return SQLiteDatabase.openDatabase(path, databaseSecret.asString(), null, SQLiteDatabase.OPEN_READWRITE, new SQLiteDatabaseHook() { + @Override + public void preKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); } + + @Override + public void postKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); } + }); + } + + public static void migrateSqlCipher3To4IfNeeded(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) throws Exception { + String oldDbPath = context.getDatabasePath(CIPHER3_DATABASE_NAME).getPath(); + File oldDbFile = new File(oldDbPath); + + // If the old SQLCipher3 database file doesn't exist then no need to do anything + if (!oldDbFile.exists()) { return; } + + try { + // Define the location for the new database + String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath(); + File newDbFile = new File(newDbPath); + + // If the new database file already exists then check if it's valid first, if it's in an + // invalid state we should delete it and try to migrate again + if (newDbFile.exists()) { + // If the old database hasn't been modified since the new database was created, then we can + // assume the user hasn't downgraded for some reason and made changes to the old database and + // can remove the old database file (it won't be used anymore) + if (oldDbFile.lastModified() <= newDbFile.lastModified()) { + // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past +// //noinspection ResultOfMethodCallIgnored +// oldDbFile.delete(); + return; + } + + // If the old database does have newer changes then the new database could have stale/invalid + // data and we should re-migrate to avoid losing any data or issues + if (!newDbFile.delete()) { + throw new Exception("Failed to remove invalid new database"); + } + } + + if (!newDbFile.createNewFile()) { + throw new Exception("Failed to create new database"); + } + + // Open the old database and extract it's version + SQLiteDatabase oldDb = SQLCipherOpenHelper.open(oldDbPath, databaseSecret, false); + int oldDbVersion = oldDb.getVersion(); + + // Export the old database to the new one (will have the default 'kdf_iter' and 'page_size' settings) + oldDb.rawExecSQL( + String.format("ATTACH DATABASE '%s' AS sqlcipher4 KEY '%s'", newDbPath, databaseSecret.asString()) + ); + Cursor cursor = oldDb.rawQuery("SELECT sqlcipher_export('sqlcipher4')"); + cursor.moveToLast(); + cursor.close(); + oldDb.rawExecSQL("DETACH DATABASE sqlcipher4"); + oldDb.close(); + + // Open the newly migrated database (to ensure it works) and set it's version so we don't try + // to run any of our custom migrations + SQLiteDatabase newDb = SQLCipherOpenHelper.open(newDbPath, databaseSecret, true); + newDb.setVersion(oldDbVersion); + newDb.close(); + + // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past + // Remove the old database file since it will no longer be used +// //noinspection ResultOfMethodCallIgnored +// oldDbFile.delete(); + } + catch (Exception e) { + Log.e(TAG, "Migration from SQLCipher3 to SQLCipher4 failed", e); + + // Notify the user of the issue so they know they can downgrade until the issue is fixed + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + String channelId = context.getString(R.string.NotificationChannel_failures); + + if (NotificationChannels.supported()) { + NotificationChannel channel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH); + channel.enableVibration(true); + notificationManager.createNotificationChannel(channel); + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) + .setSmallIcon(R.drawable.ic_notification) + .setColor(context.getResources().getColor(R.color.textsecure_primary)) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .setContentTitle(context.getString(R.string.ErrorNotifier_migration)) + .setContentText(context.getString(R.string.ErrorNotifier_migration_downgrade)) + .setAutoCancel(true); + + if (!NotificationChannels.supported()) { + builder.setPriority(NotificationCompat.PRIORITY_HIGH); + } + + notificationManager.notify(5874, builder.build()); + + // Throw the error (app will crash but there is nothing else we can do unfortunately) + throw e; + } + } + @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SmsDatabase.CREATE_TABLE); @@ -181,6 +308,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND); + db.execSQL(ThreadDatabase.getUnreadMentionCountCommand()); + db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND); + db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -189,6 +319,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { executeStatements(db, DraftDatabase.CREATE_INDEXS); executeStatements(db, GroupDatabase.CREATE_INDEXS); executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES); + executeStatements(db, ReactionDatabase.CREATE_INDEXS); executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS); @@ -199,9 +330,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { @Override public void onConfigure(SQLiteDatabase db) { super.onConfigure(db); - // Loki - Enable write ahead logging mode and increase the cache size. - // This should be disabled if we ever run into serious race condition bugs. - db.enableWriteAheadLogging(); + db.execSQL("PRAGMA cache_size = 10000"); } @@ -419,6 +548,16 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } if (oldVersion < lokiV39) { + executeStatements(db, ReactionDatabase.CREATE_INDEXS); + } + + if (oldVersion < lokiV40) { + db.execSQL(ThreadDatabase.getUnreadMentionCountCommand()); + db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND); + db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND); + } + + if (oldVersion < lokiV41) { db.execSQL(RecipientDatabase.getCreateAutoDownloadCommand()); db.execSQL(RecipientDatabase.getUpdateAutoDownloadValuesCommand()); } @@ -429,14 +568,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } - public SQLiteDatabase getReadableDatabase() { - return getReadableDatabase(databaseSecret.asString()); - } - - public SQLiteDatabase getWritableDatabase() { - return getWritableDatabase(databaseSecret.asString()); - } - public void markCurrent(SQLiteDatabase db) { db.setVersion(DATABASE_VERSION); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 570cb48bc..1b566169d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -57,12 +57,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { long expiresIn, long expireStarted, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, @NonNull List linkPreviews, - @NonNull List reactions, boolean unidentified) + @NonNull List reactions, boolean unidentified, boolean hasMention) { super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, - linkPreviews, unidentified, reactions); + linkPreviews, unidentified, reactions, hasMention); this.partCount = partCount; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index da7110375..ba01ffd9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -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 @@ -50,7 +51,8 @@ public abstract class MessageRecord extends DisplayRecord { private final long expireStarted; private final boolean unidentified; public final long id; - private final List reactions; + private final List reactions; + private final boolean hasMention; public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -62,7 +64,7 @@ public abstract class MessageRecord extends DisplayRecord { List mismatches, List networkFailures, long expiresIn, long expireStarted, - int readReceiptCount, boolean unidentified, List reactions) + int readReceiptCount, boolean unidentified, List reactions, boolean hasMention) { super(body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); @@ -74,6 +76,7 @@ public abstract class MessageRecord extends DisplayRecord { this.expireStarted = expireStarted; this.unidentified = unidentified; this.reactions = reactions; + this.hasMention = hasMention; } public long getId() { @@ -96,6 +99,8 @@ public abstract class MessageRecord extends DisplayRecord { } public long getExpireStarted() { return expireStarted; } + public boolean getHasMention() { return hasMention; } + public boolean isMediaPending() { return false; } @@ -140,14 +145,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 getReactions() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 46e419962..9f34f3fa0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -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; @@ -25,9 +27,9 @@ public abstract class MmsMessageRecord extends MessageRecord { List networkFailures, long expiresIn, long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, - @NonNull List linkPreviews, boolean unidentified, List reactions) + @NonNull List linkPreviews, boolean unidentified, List reactions, boolean hasMention) { - super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, unidentified, reactions); + super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention); this.slideDeck = slideDeck; this.quote = quote; this.contacts.addAll(contacts); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index c1c87800d..9fb404787 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -50,12 +50,12 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord { long dateSent, long dateReceived, int deliveryReceiptCount, long threadId, byte[] contentLocation, long messageSize, long expiry, int status, byte[] transactionId, long mailbox, - SlideDeck slideDeck, int readReceiptCount) + SlideDeck slideDeck, int readReceiptCount, boolean hasMention) { super(id, "", conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, emptyList(), emptyList(), - 0, 0, slideDeck, readReceiptCount, null, emptyList(), emptyList(), false, emptyList()); + 0, 0, slideDeck, readReceiptCount, null, emptyList(), emptyList(), false, emptyList(), hasMention); this.contentLocation = contentLocation; this.messageSize = messageSize; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java index e79626c66..4fd22ce8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java @@ -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); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index c1d50def2..83ee921a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -43,12 +43,12 @@ public class SmsMessageRecord extends MessageRecord { long type, long threadId, int status, List mismatches, long expiresIn, long expireStarted, - int readReceiptCount, boolean unidentified, List reactions) + int readReceiptCount, boolean unidentified, List reactions, boolean hasMention) { super(id, body, recipient, individualRecipient, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, mismatches, new LinkedList<>(), - expiresIn, expireStarted, readReceiptCount, unidentified, reactions); + expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention); } public long getType() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 6ce69a591..dfc4c1bc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -45,39 +45,35 @@ public class ThreadRecord extends DisplayRecord { private @Nullable final Uri snippetUri; private final long count; private final int unreadCount; + private final int unreadMentionCount; private final int distributionType; private final boolean archived; private final long expiresIn; private final long lastSeen; private final boolean pinned; - private final int recipientHash; public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, @NonNull Recipient recipient, long date, long count, int unreadCount, - long threadId, int deliveryReceiptCount, int status, long snippetType, - int distributionType, boolean archived, long expiresIn, long lastSeen, - int readReceiptCount, boolean pinned) + int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, + long snippetType, int distributionType, boolean archived, long expiresIn, + long lastSeen, int readReceiptCount, boolean pinned) { super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); - this.snippetUri = snippetUri; - this.count = count; - this.unreadCount = unreadCount; - this.distributionType = distributionType; - this.archived = archived; - this.expiresIn = expiresIn; - this.lastSeen = lastSeen; - this.pinned = pinned; - this.recipientHash = recipient.hashCode(); + this.snippetUri = snippetUri; + this.count = count; + this.unreadCount = unreadCount; + this.unreadMentionCount = unreadMentionCount; + this.distributionType = distributionType; + this.archived = archived; + this.expiresIn = expiresIn; + this.lastSeen = lastSeen; + this.pinned = pinned; } public @Nullable Uri getSnippetUri() { return snippetUri; } - public int getRecipientHash() { - return recipientHash; - } - @Override public SpannableString getDisplayBody(@NonNull Context context) { if (isGroupUpdateMessage()) { @@ -153,6 +149,10 @@ public class ThreadRecord extends DisplayRecord { return unreadCount; } + public int getUnreadMentionCount() { + return unreadMentionCount; + } + public long getDate() { return getDateReceived(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index 1b59edeb9..35ccb0f55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -6,7 +6,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.StorageProtocol import org.thoughtcrime.securesms.attachments.DatabaseAttachmentProvider @@ -23,7 +23,7 @@ object DatabaseModule { @JvmStatic fun init(context: Context) { - SQLiteDatabase.loadLibs(context) + System.loadLibrary("sqlcipher") } @Provides @@ -34,6 +34,7 @@ object DatabaseModule { @Singleton fun provideOpenHelper(@ApplicationContext context: Context): SQLCipherOpenHelper { val dbSecret = DatabaseSecretProvider(context).orCreateDatabaseSecret + SQLCipherOpenHelper.migrateSqlCipher3To4IfNeeded(context, dbSecret) return SQLCipherOpenHelper(context, dbSecret) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index d39ba709d..ef4726910 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -58,14 +58,14 @@ object OpenGroupManager { } @WorkerThread - fun add(server: String, room: String, publicKey: String, context: Context) { + fun add(server: String, room: String, publicKey: String, context: Context): OpenGroupApi.RoomInfo? { val openGroupID = "$server.$room" var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) val storage = MessagingModuleConfiguration.shared.storage val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() // Check it it's added already val existingOpenGroup = threadDB.getOpenGroupChat(threadID) - if (existingOpenGroup != null) { return } + if (existingOpenGroup != null) { return null } // Clear any existing data if needed storage.removeLastDeletionServerID(room, server) storage.removeLastMessageServerID(room, server) @@ -73,18 +73,17 @@ object OpenGroupManager { storage.removeLastOutboxMessageId(server) // Store the public key storage.setOpenGroupPublicKey(server, publicKey) - // Get capabilities - val capabilities = OpenGroupApi.getCapabilities(server).get() + // Get capabilities & room info + val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get() storage.setServerCapabilities(server, capabilities.capabilities) - // Get room info - val info = OpenGroupApi.getRoomInfo(room, server).get() storage.setUserCount(room, server, info.activeUsers) // Create the group locally if not available already if (threadID < 0) { threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId } - val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey) + val openGroup = OpenGroup(server = server, room = room, publicKey = publicKey, name = info.name, imageId = info.imageId, canWrite = info.write, infoUpdates = info.infoUpdates) threadDB.setOpenGroupChat(openGroup, threadID) + return info } fun restartPollerForServer(server: String) { @@ -130,12 +129,13 @@ object OpenGroupManager { } } - fun addOpenGroup(urlAsString: String, context: Context) { - val url = HttpUrl.parse(urlAsString) ?: return + fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? { + val url = HttpUrl.parse(urlAsString) ?: return null val server = OpenGroup.getServer(urlAsString) - val room = url.pathSegments().firstOrNull() ?: return - val publicKey = url.queryParameter("public_key") ?: return - add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function + val room = url.pathSegments().firstOrNull() ?: return null + val publicKey = url.queryParameter("public_key") ?: return null + + return add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function } fun updateOpenGroup(openGroup: OpenGroup, context: Context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index bfa9b1448..c6a6e1f7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -25,17 +25,17 @@ import org.thoughtcrime.securesms.util.getAccentColor import java.util.Locale class ConversationView : LinearLayout { - private lateinit var binding: ViewConversationBinding + private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) } private val screenWidth = Resources.getSystem().displayMetrics.widthPixels var thread: ThreadRecord? = null // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - private fun initialize() { - binding = ViewConversationBinding.inflate(LayoutInflater.from(context), this, true) + override fun onFinishInflate() { + super.onFinishInflate() layoutParams = RecyclerView.LayoutParams(screenWidth, RecyclerView.LayoutParams.WRAP_CONTENT) } // endregion @@ -53,7 +53,7 @@ class ConversationView : LinearLayout { } else { binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) } - background = if (thread.unreadCount > 0) { + binding.root.background = if (thread.unreadCount > 0) { ContextCompat.getDrawable(context, R.drawable.conversation_unread_background) } else { ContextCompat.getDrawable(context, R.drawable.conversation_view_background) @@ -79,8 +79,9 @@ class ConversationView : LinearLayout { binding.unreadCountTextView.text = formattedUnreadCount val textSize = if (unreadCount < 1000) 12.0f else 10.0f binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) - binding.unreadCountIndicator.background.setTint(context.getAccentColor()) binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead) + binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) + binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup) val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() binding.conversationViewDisplayNameTextView.text = senderDisplayName @@ -99,11 +100,11 @@ class ConversationView : LinearLayout { binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE if (isTyping) { - binding.typingIndicatorView.startAnimation() + binding.typingIndicatorView.root.startAnimation() } else { - binding.typingIndicatorView.stopAnimation() + binding.typingIndicatorView.root.stopAnimation() } - binding.typingIndicatorView.visibility = if (isTyping) View.VISIBLE else View.GONE + binding.typingIndicatorView.root.visibility = if (isTyping) View.VISIBLE else View.GONE binding.statusIndicatorImageView.visibility = View.VISIBLE when { !thread.isOutgoing -> binding.statusIndicatorImageView.visibility = View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index b21eb6ff1..f1a9c8ed9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -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) @@ -202,7 +202,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() } - // Set up typing observer + withContext(Dispatchers.Main) { updateProfileButton() TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect { @@ -365,6 +365,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), setupMessageRequestsBanner() updateEmptyState() } + + ApplicationContext.getInstance(this@HomeActivity).typingStatusRepository.typingThreads.observe(this) { threadIds -> + homeAdapter.typingThreadIDs = (threadIds ?: setOf()) + } } private fun updateEmptyState() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index 3efa841b5..4273794f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -1,12 +1,14 @@ package org.thoughtcrime.securesms.home import android.content.Context +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_ID +import network.loki.messenger.R import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.mms.GlideRequests @@ -63,6 +65,8 @@ class HomeAdapter( lateinit var glide: GlideRequests var typingThreadIDs = setOf() set(value) { + if (field == value) { return } + field = value // TODO: replace this with a diffed update or a partial change set with payloads notifyDataSetChanged() @@ -74,19 +78,20 @@ class HomeAdapter( HeaderFooterViewHolder(header!!) } ITEM -> { - val view = ConversationView(context) - view.setOnClickListener { view.thread?.let { listener.onConversationClick(it) } } - view.setOnLongClickListener { - view.thread?.let { listener.onLongConversationClick(it) } + val conversationView = LayoutInflater.from(parent.context).inflate(R.layout.view_conversation, parent, false) as ConversationView + val viewHolder = ConversationViewHolder(conversationView) + viewHolder.view.setOnClickListener { viewHolder.view.thread?.let { listener.onConversationClick(it) } } + viewHolder.view.setOnLongClickListener { + viewHolder.view.thread?.let { listener.onLongConversationClick(it) } true } - ViewHolder(view) + viewHolder } else -> throw Exception("viewType $viewType isn't valid") } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is ViewHolder) { + if (holder is ConversationViewHolder) { val offset = if (hasHeaderView()) position - 1 else position val thread = data[offset] val isTyping = typingThreadIDs.contains(thread.threadId) @@ -95,7 +100,7 @@ class HomeAdapter( } override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - if (holder is ViewHolder) { + if (holder is ConversationViewHolder) { holder.view.recycle() } else { super.onViewRecycled(holder) @@ -108,7 +113,7 @@ class HomeAdapter( override fun getItemCount(): Int = data.size + if (hasHeaderView()) 1 else 0 - class ViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view) + class ConversationViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view) class HeaderFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt index fcaf565e0..1baec2085 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -22,22 +22,28 @@ class HomeDiffUtil( val newItem = new[newItemPosition] // return early to save getDisplayBody or expensive calls - val sameCount = oldItem.count == newItem.count - if (!sameCount) return false - val sameUnreads = oldItem.unreadCount == newItem.unreadCount - if (!sameUnreads) return false - val samePinned = oldItem.isPinned == newItem.isPinned - if (!samePinned) return false - val sameRecipientHash = oldItem.recipientHash == newItem.recipientHash - if (!sameRecipientHash) return false - val sameSnippet = oldItem.getDisplayBody(context) == newItem.getDisplayBody(context) - if (!sameSnippet) return false - val sameSendStatus = oldItem.isFailed == newItem.isFailed && oldItem.isDelivered == newItem.isDelivered - && oldItem.isSent == newItem.isSent && oldItem.isPending == newItem.isPending - if (!sameSendStatus) return false + var isSameItem = true - // all same - return true + if (isSameItem) { isSameItem = (oldItem.count == newItem.count) } + if (isSameItem) { isSameItem = (oldItem.unreadCount == newItem.unreadCount) } + if (isSameItem) { isSameItem = (oldItem.isPinned == newItem.isPinned) } + + // Note: For some reason the 'hashCode' value can change after initialisation so we can't cache it + if (isSameItem) { isSameItem = (oldItem.recipient.hashCode() == newItem.recipient.hashCode()) } + + // Note: Two instances of 'SpannableString' may not equate even though their content matches + if (isSameItem) { isSameItem = (oldItem.getDisplayBody(context).toString() == newItem.getDisplayBody(context).toString()) } + + if (isSameItem) { + isSameItem = ( + oldItem.isFailed == newItem.isFailed && + oldItem.isDelivered == newItem.isDelivered && + oldItem.isSent == newItem.isSent && + oldItem.isPending == newItem.isPending + ) + } + + return isSameItem } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index 7603d3922..2c64ded86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java index 62f2ee6b2..dc1d2afcf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java @@ -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."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt index fac0a402e..89a841dc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt @@ -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() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java index 02ccf8551..6db38e6bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java @@ -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); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt index 48a572552..5a0438e15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt @@ -44,7 +44,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor } override fun doWork(): Result { - if (TextSecurePreferences.getLocalNumber(context) == null) { + if (TextSecurePreferences.getLocalNumber(context) == null || !TextSecurePreferences.hasSeenWelcomeScreen(context)) { Log.v(TAG, "User not registered yet.") return Result.failure() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index 55a4fcd29..728462903 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -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 cache = new HashMap(); @@ -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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java index 1ffd74be6..dc0e52abc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index 81332e87d..a4871f0fc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -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); @@ -52,8 +52,8 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) { String displayName = recipient.toShortString(); - if (threadRecipient.isOpenGroupRecipient()) { - displayName = getOpenGroupDisplayName(recipient); + if (threadRecipient.isGroupRecipient()) { + displayName = getGroupDisplayName(recipient, threadRecipient.isOpenGroupRecipient()); } if (privacy.isDisplayContact()) { setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName)); @@ -78,8 +78,8 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) { String displayName = sender.toShortString(); - if (threadRecipient.isOpenGroupRecipient()) { - displayName = getOpenGroupDisplayName(sender); + if (threadRecipient.isGroupRecipient()) { + displayName = getGroupDisplayName(sender, threadRecipient.isOpenGroupRecipient()); } if (privacy.isDisplayMessage()) { SpannableStringBuilder builder = new SpannableStringBuilder(); @@ -113,14 +113,15 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu } /** - * @param recipient the * individual * recipient for which to get the open group display name. + * @param recipient the * individual * recipient for which to get the display name. + * @param openGroupRecipient whether in an open group context */ - private String getOpenGroupDisplayName(Recipient recipient) { + private String getGroupDisplayName(Recipient recipient, boolean openGroupRecipient) { SessionContactDatabase contactDB = DatabaseComponent.get(context).sessionContactDatabase(); String sessionID = recipient.getAddress().serialize(); Contact contact = contactDB.getContactWithSessionID(sessionID); if (contact == null) { return sessionID; } - String displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + String displayName = contact.displayName(openGroupRecipient ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR); if (displayName == null) { return sessionID; } return displayName; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java index 991989e8d..0d5775117 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index fe934e229..108aa12c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java index 1d19c2c8e..935d575c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 7925b8556..da0896d05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -117,15 +117,15 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil setNumber(messageCount); } - public void setPrimaryMessageBody(@NonNull Recipient threadRecipients, + public void setPrimaryMessageBody(@NonNull Recipient threadRecipient, @NonNull Recipient individualRecipient, @NonNull CharSequence message, @Nullable SlideDeck slideDeck) { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipients.isOpenGroupRecipient()) { - String displayName = getOpenGroupDisplayName(individualRecipient); + if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { + String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -214,8 +214,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipient.isOpenGroupRecipient()) { - String displayName = getOpenGroupDisplayName(individualRecipient); + if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { + String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -334,14 +334,15 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil } /** - * @param recipient the * individual * recipient for which to get the open group display name. + * @param recipient the * individual * recipient for which to get the display name. + * @param openGroupRecipient whether in an open group context */ - private String getOpenGroupDisplayName(Recipient recipient) { + private String getGroupDisplayName(Recipient recipient, boolean openGroupRecipient) { SessionContactDatabase contactDB = DatabaseComponent.get(context).sessionContactDatabase(); String sessionID = recipient.getAddress().serialize(); Contact contact = contactDB.getContactWithSessionID(sessionID); if (contact == null) { return sessionID; } - String displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + String displayName = contact.displayName(openGroupRecipient ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR); if (displayName == null) { return sessionID; } return displayName; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt index ee1631a00..31117ae94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt @@ -22,8 +22,10 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ActivityLinkDeviceBinding import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding +import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.MnemonicCodec +import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.Log @@ -39,6 +41,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { private lateinit var binding: ActivityLinkDeviceBinding + internal val database: LokiAPIDatabaseProtocol + get() = SnodeModule.shared.storage private val adapter = LinkDeviceActivityAdapter(this) private var restoreJob: Job? = null @@ -99,6 +103,11 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel if (restoreJob?.isActive == true) return restoreJob = lifecycleScope.launch { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + // RestoreActivity handles seed this way val keyPairGenerationResult = KeyPairUtilities.generate(seed) val x25519KeyPair = keyPairGenerationResult.x25519KeyPair diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index 92583d89e..9cf9c3d04 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt index 6a1c785ad..5531fea49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt @@ -13,8 +13,10 @@ import android.view.View import android.widget.Toast import network.loki.messenger.R import network.loki.messenger.databinding.ActivityRecoveryPhraseRestoreBinding +import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.MnemonicCodec +import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.hexEncodedPublicKey @@ -26,6 +28,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { private lateinit var binding: ActivityRecoveryPhraseRestoreBinding + internal val database: LokiAPIDatabaseProtocol + get() = SnodeModule.shared.storage // region Lifecycle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -64,6 +68,11 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { private fun restore() { val mnemonic = binding.mnemonicEditText.text.toString() try { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + val loadFileContents: (String) -> String = { fileName -> MnemonicUtilities.loadFileContents(this, fileName) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt index 0105fbedf..b6fdaf9cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt @@ -18,8 +18,10 @@ import android.widget.Toast import com.goterl.lazysodium.utils.KeyPair import network.loki.messenger.R import network.loki.messenger.databinding.ActivityRegisterBinding +import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.ecc.ECKeyPair +import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.hexEncodedPublicKey import org.thoughtcrime.securesms.BaseActionBarActivity @@ -29,6 +31,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo class RegisterActivity : BaseActionBarActivity() { private lateinit var binding: ActivityRegisterBinding + internal val database: LokiAPIDatabaseProtocol + get() = SnodeModule.shared.storage private var seed: ByteArray? = null private var ed25519KeyPair: KeyPair? = null private var x25519KeyPair: ECKeyPair? = null @@ -109,6 +113,11 @@ class RegisterActivity : BaseActionBarActivity() { // region Interaction private fun register() { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + KeyPairUtilities.store(this, seed!!, ed25519KeyPair!!, x25519KeyPair!!) val userHexEncodedPublicKey = x25519KeyPair!!.hexEncodedPublicKey val registrationID = KeyHelper.generateRegistrationId(false) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt index 90ffbd4b1..f6efd041c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 00f5d72c7..2d6789401 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -35,6 +35,7 @@ interface ConversationRepository { fun maybeGetRecipientForThreadId(threadId: Long): Recipient? fun saveDraft(threadId: Long, text: String) fun getDraft(threadId: Long): String? + fun clearDrafts(threadId: Long) fun inviteContacts(threadId: Long, contacts: List) fun setBlocked(recipient: Recipient, blocked: Boolean) fun deleteLocally(recipient: Recipient, message: MessageRecord) @@ -98,10 +99,13 @@ class DefaultConversationRepository @Inject constructor( override fun getDraft(threadId: Long): String? { val drafts = draftDb.getDrafts(threadId) - draftDb.clearDrafts(threadId) return drafts.find { it.type == DraftDatabase.Draft.TEXT }?.value } + override fun clearDrafts(threadId: Long) { + draftDb.clearDrafts(threadId) + } + override fun inviteContacts(threadId: Long, contacts: List) { val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return for (contact in contacts) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java index a33f4dd11..ddfe85515 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java @@ -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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java index 4523ab364..58e3f1a69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java @@ -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; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 13701300b..0516dc285 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -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 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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java index 4a83707dd..a0ef945c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index b6d2e2bc5..f42b55b5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -111,6 +111,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM duration * 1000L, true, false, false, + false, Optional.absent(), groupInfo, Optional.absent(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java b/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java index 0581883c5..52a259d5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java @@ -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()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java index 9e79b93d6..402c0f652 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java index 4091d953a..f24906c5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java @@ -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); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java index 323617d81..eea6ba00f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index 0f10a93b0..d09933ab8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -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, 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, + 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(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(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
(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( - expectedState: CallState, - expectedCallId: UUID?, - getState: () -> Pair): StateAwareListener(expectedState, expectedCallId, getState) { + expectedState: CallState, + expectedCallId: UUID?, + getState: () -> Pair + ) : StateAwareListener(expectedState, expectedCallId, getState) { override fun onSuccessContinue(result: V) {} } private abstract class SuccessOnlyListener( - expectedState: CallState, - expectedCallId: UUID?, - getState: () -> Pair): StateAwareListener(expectedState, expectedCallId, getState) { + expectedState: CallState, + expectedCallId: UUID?, + getState: () -> Pair + ) : StateAwareListener(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( - private val expectedState: CallState, - private val expectedCallId: UUID?, - private val getState: ()->Pair): FutureTaskListener { + private val expectedState: CallState, + private val expectedCallId: UUID?, + private val getState: () -> Pair + ) : FutureTaskListener { 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?) {} diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java index c1d6e5369..a18ad8211 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java @@ -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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt index eaaf06f45..074278cb9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java index e328e34e0..bd3d65b9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -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 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 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 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 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(); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt new file mode 100644 index 000000000..10d507a53 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -0,0 +1,428 @@ +package org.thoughtcrime.securesms.util + +import android.content.Context +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.messages.signal.IncomingTextMessage +import org.session.libsession.messaging.messages.signal.OutgoingTextMessage +import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.GroupUtil +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.crypto.ecc.Curve +import org.session.libsignal.messages.SignalServiceGroup +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.guava.Optional +import org.session.libsignal.utilities.hexEncodedPublicKey +import org.thoughtcrime.securesms.crypto.KeyPairUtilities +import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.groups.GroupManager +import java.security.SecureRandom +import java.util.* +import kotlin.random.asKotlinRandom + +object MockDataGenerator { + private var printProgress = true + private var hasStartedGenerationThisRun = false + + // FIXME: Update this to run in a transaction instead of individual db writes (should drastically speed it up) + fun generateMockData(context: Context) { + // Don't re-generate the mock data if it already exists + val mockDataExistsRecipient = Recipient.from(context, Address.fromSerialized("MockDatabaseThread"), false) + val storage = MessagingModuleConfiguration.shared.storage + val threadDb = DatabaseComponent.get(context).threadDatabase() + val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase() + val contactDb = DatabaseComponent.get(context).sessionContactDatabase() + val recipientDb = DatabaseComponent.get(context).recipientDatabase() + val smsDb = DatabaseComponent.get(context).smsDatabase() + + if (hasStartedGenerationThisRun || threadDb.getThreadIdIfExistsFor(mockDataExistsRecipient) != -1L) { + hasStartedGenerationThisRun = true + return + } + + /// The mock data generation is quite slow, there are 3 parts which take a decent amount of time (deleting the account afterwards will + /// also take a long time): + /// Generating the threads & content - ~3m per 100 + val dmThreadCount: Int = 1000 + val closedGroupThreadCount: Int = 50 + val openGroupThreadCount: Int = 20 + val messageRangePerThread: List = listOf(0..500) + val dmRandomSeed: String = "1111" + val cgRandomSeed: String = "2222" + val ogRandomSeed: String = "3333" + val chunkSize: Int = 1000 // Chunk up the thread writing to prevent memory issues + val stringContent: List = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 ".map { it.toString() } + val wordContent: List = listOf("alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat") + val timestampNow: Long = System.currentTimeMillis() + val userSessionId: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + val logProgress: ((String, String) -> Unit) = logProgress@{ title, event -> + if (!printProgress) { return@logProgress } + + Log.i("[MockDataGenerator]", "${System.currentTimeMillis()} $title - $event") + } + + hasStartedGenerationThisRun = true + + // FIXME: Make sure this data doesn't go off device somehow? + logProgress("", "Start") + + // First create the thread used to indicate that the mock data has been generated + threadDb.getOrCreateThreadIdFor(mockDataExistsRecipient) + + // -- DM Thread + val dmThreadRandomGenerator: SecureRandom = SecureRandom(dmRandomSeed.toByteArray()) + var dmThreadIndex: Int = 0 + logProgress("DM Threads", "Start Generating $dmThreadCount threads") + + while (dmThreadIndex < dmThreadCount) { + val remainingThreads: Int = (dmThreadCount - dmThreadIndex) + + (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> + val threadIndex: Int = (dmThreadIndex + index) + + logProgress("DM Thread $threadIndex", "Start") + + val dataBytes = (0 until 16).map { dmThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomSessionId: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val isMessageRequest: Boolean = dmThreadRandomGenerator.nextBoolean() + val contactNameLength: Int = (5 + dmThreadRandomGenerator.nextInt(15)) + + val numMessages: Int = ( + messageRangePerThread[threadIndex % messageRangePerThread.count()].first + + dmThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) + ) + + // Generate the thread + val recipient = Recipient.from(context, Address.fromSerialized(randomSessionId), false) + val contact = Contact(randomSessionId) + val threadId = threadDb.getOrCreateThreadIdFor(recipient) + + // Generate the contact + val contactIsApproved: Boolean = (!isMessageRequest || dmThreadRandomGenerator.nextBoolean()) + contactDb.setContact(contact) + contactDb.setContactIsTrusted(contact, true, threadId) + recipientDb.setApproved(recipient, contactIsApproved) + recipientDb.setApprovedMe(recipient, (!isMessageRequest && (dmThreadRandomGenerator.nextInt(10) < 8))) // 80% approved the current user + + contact.name = (0 until dmThreadRandomGenerator.nextInt(contactNameLength)) + .map { stringContent.random(dmThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + recipientDb.setProfileName(recipient, contact.name) + contactDb.setContact(contact) + + // Generate the message history (Note: Unapproved message requests will only include incoming messages) + logProgress("DM Thread $threadIndex", "Generate $numMessages Messages") + (0 until numMessages).forEach { index -> + val isIncoming: Boolean = ( + dmThreadRandomGenerator.nextBoolean() && + (!isMessageRequest || contactIsApproved) + ) + val messageWords: Int = (1 + dmThreadRandomGenerator.nextInt(19)) + + if (isIncoming) { + smsDb.insertMessageInbox( + IncomingTextMessage( + recipient.address, + 1, + (timestampNow - (index * 5000)), + (0 until messageWords) + .map { wordContent.random(dmThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + Optional.absent(), + 0, + false, + -1, + false + ), + (timestampNow - (index * 5000)), + false, + false + ) + } + else { + smsDb.insertMessageOutbox( + threadId, + OutgoingTextMessage( + recipient, + (0 until messageWords) + .map { wordContent.random(dmThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + 0, + -1, + (timestampNow - (index * 5000)) + ), + (timestampNow - (index * 5000)), + false + ) + } + } + + logProgress("DM Thread $threadIndex", "Done") + } + logProgress("DM Threads", "Done") + + dmThreadIndex += chunkSize + } + logProgress("DM Threads", "Done") + + // -- Closed Group + + val cgThreadRandomGenerator: SecureRandom = SecureRandom(cgRandomSeed.toByteArray()) + var cgThreadIndex: Int = 0 + logProgress("Closed Group Threads", "Start Generating $closedGroupThreadCount threads") + + while (cgThreadIndex < closedGroupThreadCount) { + val remainingThreads: Int = (closedGroupThreadCount - cgThreadIndex) + + (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> + val threadIndex: Int = (cgThreadIndex + index) + + logProgress("Closed Group Thread $threadIndex", "Start") + + val dataBytes = (0 until 16).map { cgThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomGroupPublicKey: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val groupNameLength: Int = (5 + cgThreadRandomGenerator.nextInt(15)) + val groupName: String = (0 until groupNameLength) + .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val numGroupMembers: Int = cgThreadRandomGenerator.nextInt (10) + val numMessages: Int = ( + messageRangePerThread[threadIndex % messageRangePerThread.count()].first + + cgThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) + ) + + // Generate the Contacts in the group + val members: MutableList = mutableListOf(userSessionId) + logProgress("Closed Group Thread $threadIndex", "Generate $numGroupMembers Contacts") + + (0 until numGroupMembers).forEach { + val contactBytes = (0 until 16).map { cgThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomSessionId: String = KeyPairUtilities.generate(contactBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val contactNameLength: Int = (5 + cgThreadRandomGenerator.nextInt(15)) + + val recipient = Recipient.from(context, Address.fromSerialized(randomSessionId), false) + val contact = Contact(randomSessionId) + contactDb.setContact(contact) + recipientDb.setApproved(recipient, true) + recipientDb.setApprovedMe(recipient, true) + + contact.name = (0 until cgThreadRandomGenerator.nextInt(contactNameLength)) + .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + recipientDb.setProfileName(recipient, contact.name) + contactDb.setContact(contact) + members.add(randomSessionId) + } + + val groupId = GroupUtil.doubleEncodeGroupID(randomGroupPublicKey) + val threadId = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupId)) + val adminUserId = members.random(cgThreadRandomGenerator.asKotlinRandom()) + storage.createGroup( + groupId, + groupName, + members.map { Address.fromSerialized(it) }, + null, + null, + listOf(Address.fromSerialized(adminUserId)), + timestampNow + ) + storage.setProfileSharing(Address.fromSerialized(groupId), true) + storage.addClosedGroupPublicKey(randomGroupPublicKey) + + // Add the group to the user's set of public keys to poll for and store the key pair + val encryptionKeyPair = Curve.generateKeyPair() + storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, randomGroupPublicKey) + storage.setExpirationTimer(groupId, 0) + + // Add the group created message + if (userSessionId == adminUserId) { + storage.insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, groupName, members, listOf(adminUserId), threadId, (timestampNow - (numMessages * 5000))) + } + else { + storage.insertIncomingInfoMessage(context, adminUserId, groupId, SignalServiceGroup.Type.CREATION, groupName, members, listOf(adminUserId), (timestampNow - (numMessages * 5000))) + } + + // Generate the message history (Note: Unapproved message requests will only include incoming messages) + logProgress("Closed Group Thread $threadIndex", "Generate $numMessages Messages") + + (0 until numGroupMembers).forEach { + val messageWords: Int = (1 + cgThreadRandomGenerator.nextInt(19)) + val senderId: String = members.random(cgThreadRandomGenerator.asKotlinRandom()) + + if (senderId != userSessionId) { + smsDb.insertMessageInbox( + IncomingTextMessage( + Address.fromSerialized(senderId), + 1, + (timestampNow - (index * 5000)), + (0 until messageWords) + .map { wordContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + Optional.absent(), + 0, + false, + -1, + false + ), + (timestampNow - (index * 5000)), + false, + false + ) + } + else { + smsDb.insertMessageOutbox( + threadId, + OutgoingTextMessage( + threadDb.getRecipientForThreadId(threadId), + (0 until messageWords) + .map { wordContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + 0, + -1, + (timestampNow - (index * 5000)) + ), + (timestampNow - (index * 5000)), + false + ) + } + } + + logProgress("Closed Group Thread $threadIndex", "Done") + } + + cgThreadIndex += chunkSize + } + logProgress("Closed Group Threads", "Done") + + // --Open Group + + val ogThreadRandomGenerator: SecureRandom = SecureRandom(cgRandomSeed.toByteArray()) + var ogThreadIndex: Int = 0 + logProgress("Open Group Threads", "Start Generating $openGroupThreadCount threads") + + while (ogThreadIndex < openGroupThreadCount) { + val remainingThreads: Int = (openGroupThreadCount - ogThreadIndex) + + (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> + val threadIndex: Int = (ogThreadIndex + index) + + logProgress("Open Group Thread $threadIndex", "Start") + + val dataBytes = (0 until 32).map { ogThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomGroupPublicKey: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val serverNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) + val roomNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) + val roomDescriptionLength: Int = (10 + ogThreadRandomGenerator.nextInt(40)) + val serverName: String = (0 until serverNameLength) + .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val roomName: String = (0 until roomNameLength) + .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val roomDescription: String = (0 until roomDescriptionLength) + .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val numGroupMembers: Int = ogThreadRandomGenerator.nextInt(250) + val numMessages: Int = ( + messageRangePerThread[threadIndex % messageRangePerThread.count()].first + + ogThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) + ) + + // Generate the Contacts in the group + val members: MutableList = mutableListOf(userSessionId) + logProgress("Open Group Thread $threadIndex", "Generate $numGroupMembers Contacts") + + (0 until numGroupMembers).forEach { + val contactBytes = (0 until 16).map { ogThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomSessionId: String = KeyPairUtilities.generate(contactBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val contactNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) + + val recipient = Recipient.from(context, Address.fromSerialized(randomSessionId), false) + val contact = Contact(randomSessionId) + contactDb.setContact(contact) + recipientDb.setApproved(recipient, true) + recipientDb.setApprovedMe(recipient, true) + + contact.name = (0 until ogThreadRandomGenerator.nextInt(contactNameLength)) + .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + recipientDb.setProfileName(recipient, contact.name) + contactDb.setContact(contact) + members.add(randomSessionId) + } + + // Create the open group model and the thread + val openGroupId = "$serverName.$roomName" + val threadId = GroupManager.createOpenGroup(openGroupId, context, null, roomName).threadId + val hasBlinding: Boolean = ogThreadRandomGenerator.nextBoolean() + + // Generate the capabilities and other data + storage.setOpenGroupPublicKey(serverName, randomGroupPublicKey) + storage.setServerCapabilities( + serverName, + ( + listOf(OpenGroupApi.Capability.SOGS.name.lowercase()) + + if (hasBlinding) { listOf(OpenGroupApi.Capability.BLIND.name.lowercase()) } else { emptyList() } + ) + ) + storage.setUserCount(roomName, serverName, numGroupMembers) + lokiThreadDB.setOpenGroupChat(OpenGroup(server = serverName, room = roomName, publicKey = randomGroupPublicKey, name = roomName, imageId = null, canWrite = true, infoUpdates = 0), threadId) + + // Generate the message history (Note: Unapproved message requests will only include incoming messages) + logProgress("Open Group Thread $threadIndex", "Generate $numMessages Messages") + + (0 until numMessages).forEach { index -> + val messageWords: Int = (1 + ogThreadRandomGenerator.nextInt(19)) + val senderId: String = members.random(ogThreadRandomGenerator.asKotlinRandom()) + + if (senderId != userSessionId) { + smsDb.insertMessageInbox( + IncomingTextMessage( + Address.fromSerialized(senderId), + 1, + (timestampNow - (index * 5000)), + (0 until messageWords) + .map { wordContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + Optional.absent(), + 0, + false, + -1, + false + ), + (timestampNow - (index * 5000)), + false, + false + ) + } else { + smsDb.insertMessageOutbox( + threadId, + OutgoingTextMessage( + threadDb.getRecipientForThreadId(threadId), + (0 until messageWords) + .map { wordContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + 0, + -1, + (timestampNow - (index * 5000)) + ), + (timestampNow - (index * 5000)), + false + ) + } + } + + logProgress("Open Group Thread $threadIndex", "Done") + } + + ogThreadIndex += chunkSize + } + + logProgress("Open Group Threads", "Done") + logProgress("", "Complete") + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt deleted file mode 100644 index a1105cfff..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt +++ /dev/null @@ -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.") - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 006da2b63..b7a9b6fd6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -1,7 +1,9 @@ package org.thoughtcrime.securesms.webrtc import android.content.Context +import android.content.pm.PackageManager import android.telephony.TelephonyManager +import androidx.core.content.ContextCompat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.serialization.json.Json @@ -176,8 +178,22 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va _callStateEvents.value = newState } - fun isBusy(context: Context, callId: UUID) = callId != this.callId && (currentConnectionState != CallState.Idle - || context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE) + fun isBusy(context: Context, callId: UUID): Boolean { + // Make sure we have the permission before accessing the callState + if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + return ( + callId != this.callId && ( + currentConnectionState != CallState.Idle || + context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE + ) + ) + } + + return ( + callId != this.callId && + currentConnectionState != CallState.Idle + ) + } fun isPreOffer() = currentConnectionState == CallState.RemotePreOffer diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index f007ace97..bb41c7c97 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -12,6 +12,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.utilities.WebRtcUtils +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient @@ -29,6 +30,10 @@ import org.webrtc.IceCandidate class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) { + companion object { + private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L + } + init { lifecycle.coroutineScope.launch(IO) { while (isActive) { @@ -53,6 +58,13 @@ class CallMessageProcessor(private val context: Context, private val textSecureP } continue } + + val isVeryExpired = (nextMessage.sentTimestamp?:0) + VERY_EXPIRED_TIME < SnodeAPI.nowWithOffset + if (isVeryExpired) { + Log.e("Loki", "Dropping very expired call message") + continue + } + when (nextMessage.type) { OFFER -> incomingCall(nextMessage) ANSWER -> incomingAnswer(nextMessage) @@ -78,7 +90,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP private fun incomingHangup(callMessage: CallMessage) { val callId = callMessage.callId ?: return val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId) - ContextCompat.startForegroundService(context, hangupIntent) + context.startService(hangupIntent) } private fun incomingAnswer(callMessage: CallMessage) { @@ -91,7 +103,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP sdp = sdp, callId = callId ) - ContextCompat.startForegroundService(context, answerIntent) + context.startService(answerIntent) } private fun handleIceCandidates(callMessage: CallMessage) { @@ -120,7 +132,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, callTime = callMessage.sentTimestamp!! ) - ContextCompat.startForegroundService(context, incomingIntent) + context.startService(incomingIntent) } private fun incomingCall(callMessage: CallMessage) { @@ -134,8 +146,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, callTime = callMessage.sentTimestamp!! ) - ContextCompat.startForegroundService(context, incomingIntent) - + context.startService(incomingIntent) } private fun CallMessage.iceCandidates(): List { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt index 955356c7d..09db0022d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt index 2b4d34807..67514c58b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt @@ -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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt index 84a36ee82..0a80cacef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java index a7fac62bb..59c05af91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java @@ -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); diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_failed.png new file mode 100644 index 000000000..c5de641a6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_delivery_status_failed.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_read.png index 307190cae..bc55b7dfe 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_delivery_status_read.png and b/app/src/main/res/drawable-hdpi/ic_delivery_status_read.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_sending.png index fbdcef358..1b8991d98 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_delivery_status_sending.png and b/app/src/main/res/drawable-hdpi/ic_delivery_status_sending.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_sent.png index 72b685aee..96a7b6340 100644 Binary files a/app/src/main/res/drawable-hdpi/ic_delivery_status_sent.png and b/app/src/main/res/drawable-hdpi/ic_delivery_status_sent.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_failed.png new file mode 100644 index 000000000..b13352d07 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_delivery_status_failed.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_read.png index eee83ef59..072dac7b3 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_delivery_status_read.png and b/app/src/main/res/drawable-mdpi/ic_delivery_status_read.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_sending.png index b34ea32b8..f9b7fe3b7 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_delivery_status_sending.png and b/app/src/main/res/drawable-mdpi/ic_delivery_status_sending.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_sent.png index ff6bf0fac..26fceea79 100644 Binary files a/app/src/main/res/drawable-mdpi/ic_delivery_status_sent.png and b/app/src/main/res/drawable-mdpi/ic_delivery_status_sent.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_failed.png new file mode 100644 index 000000000..79aaa03f2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_delivery_status_failed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_read.png index 79c4857f6..af79508ab 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_delivery_status_read.png and b/app/src/main/res/drawable-xhdpi/ic_delivery_status_read.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_sending.png index aca7fe7ef..74b8694dd 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_delivery_status_sending.png and b/app/src/main/res/drawable-xhdpi/ic_delivery_status_sending.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_sent.png index 811a54373..094d8b34c 100644 Binary files a/app/src/main/res/drawable-xhdpi/ic_delivery_status_sent.png and b/app/src/main/res/drawable-xhdpi/ic_delivery_status_sent.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_failed.png new file mode 100644 index 000000000..4ed4e2d8b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_failed.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_read.png index 474a57072..69376c9a2 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_read.png and b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_read.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sending.png index 4a81c629d..3dce4a05e 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sending.png and b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sending.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sent.png index f679aaf24..302afb837 100644 Binary files a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sent.png and b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sent.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_failed.png new file mode 100644 index 000000000..6780212b8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_failed.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_read.png index c138886aa..d0b16705c 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_read.png and b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_read.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sending.png index 461c2ea63..411dc9d50 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sending.png and b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sending.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sent.png index 47bd9acd3..657d454f6 100644 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sent.png and b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sent.png differ diff --git a/app/src/main/res/drawable/ic_filled_circle_check.xml b/app/src/main/res/drawable/ic_filled_circle_check.xml index 06c3466fd..99589252b 100644 --- a/app/src/main/res/drawable/ic_filled_circle_check.xml +++ b/app/src/main/res/drawable/ic_filled_circle_check.xml @@ -8,6 +8,6 @@ android:fillColor="?android:textColorPrimary" android:pathData="M6.5,6.5m-6.5,0a6.5,6.5 0,1 1,13 0a6.5,6.5 0,1 1,-13 0"/> diff --git a/app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml b/app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml new file mode 100644 index 000000000..5f21434d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_message_detail.xml b/app/src/main/res/layout/activity_message_detail.xml index c5a7f12bf..49c1af54e 100644 --- a/app/src/main/res/layout/activity_message_detail.xml +++ b/app/src/main/res/layout/activity_message_detail.xml @@ -69,6 +69,7 @@ - - - - - - + android:layout_height="match_parent" /> @@ -35,4 +28,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/album_thumbnail_1.xml b/app/src/main/res/layout/album_thumbnail_1.xml index cf0f5d489..cee81ba3e 100644 --- a/app/src/main/res/layout/album_thumbnail_1.xml +++ b/app/src/main/res/layout/album_thumbnail_1.xml @@ -6,7 +6,7 @@ android:layout_width="@dimen/media_bubble_default_dimens" android:layout_height="@dimen/media_bubble_default_dimens"> - - - - - - - @@ -20,4 +21,4 @@ android:layout_gravity="center" android:layout="@layout/transfer_controls_stub" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_selection_list_fragment.xml b/app/src/main/res/layout/contact_selection_list_fragment.xml index 7a297bab2..48221f632 100644 --- a/app/src/main/res/layout/contact_selection_list_fragment.xml +++ b/app/src/main/res/layout/contact_selection_list_fragment.xml @@ -18,35 +18,13 @@ - - - - - - - - - - - + android:clipToPadding="false" + android:scrollbars="vertical" + tools:listitem="@layout/view_user"/> diff --git a/app/src/main/res/layout/link_preview.xml b/app/src/main/res/layout/link_preview.xml deleted file mode 100644 index f76ad1010..000000000 --- a/app/src/main/res/layout/link_preview.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/media_overview_gallery_item.xml b/app/src/main/res/layout/media_overview_gallery_item.xml index 607261175..a4c3f324a 100644 --- a/app/src/main/res/layout/media_overview_gallery_item.xml +++ b/app/src/main/res/layout/media_overview_gallery_item.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" android:padding="2dp"> - - - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/transparent_black_6"> - + diff --git a/app/src/main/res/layout/view_conversation.xml b/app/src/main/res/layout/view_conversation.xml index 8f26f17c7..08c3693c1 100644 --- a/app/src/main/res/layout/view_conversation.xml +++ b/app/src/main/res/layout/view_conversation.xml @@ -1,5 +1,5 @@ - - + android:layout_height="wrap_content"> - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/unreadCountIndicator" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constrainedWidth="true" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintHorizontal_bias="0" + android:drawablePadding="4dp" + android:maxLines="1" + android:ellipsize="end" + android:textAlignment="viewStart" + android:textSize="@dimen/medium_font_size" + android:textStyle="bold" + android:textColor="?android:textColorPrimary" + app:drawableTint="?conversation_pinned_icon_color" + tools:drawableRight="@drawable/ic_pin" + tools:text="I'm a very long display name. What are you going to do about it?" /> + + + tools:text="8" + tools:textColor="?android:textColorPrimary" /> - + + + + + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:paddingBottom="3dp" + android:textColor="?unreadIndicatorTextColor" + android:textSize="@dimen/very_small_font_size" + android:textStyle="bold" + android:text="@" + tools:textColor="?android:textColorPrimary" /> - - - - - + - + - - + diff --git a/app/src/main/res/layout/view_conversation_typing_container.xml b/app/src/main/res/layout/view_conversation_typing_container.xml index d89c7c46d..ef5cda506 100644 --- a/app/src/main/res/layout/view_conversation_typing_container.xml +++ b/app/src/main/res/layout/view_conversation_typing_container.xml @@ -17,7 +17,7 @@ android:background="@drawable/message_bubble_background_received_alone" android:backgroundTint="?message_received_background_color"> - - @@ -65,4 +66,4 @@ android:visibility="gone" app:constraint_referenced_ids="image_view_show_less, text_view_show_less"/> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_link_preview.xml b/app/src/main/res/layout/view_link_preview.xml index 096ff5dac..dd2e133be 100644 --- a/app/src/main/res/layout/view_link_preview.xml +++ b/app/src/main/res/layout/view_link_preview.xml @@ -1,54 +1,47 @@ - + android:orientation="horizontal" + android:gravity="center"> - + - + - - - - - - - + android:scaleType="centerCrop" /> - + - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_link_preview_draft.xml b/app/src/main/res/layout/view_link_preview_draft.xml index 65e2cf7fd..bf7cd3ebb 100644 --- a/app/src/main/res/layout/view_link_preview_draft.xml +++ b/app/src/main/res/layout/view_link_preview_draft.xml @@ -26,7 +26,7 @@ android:src="@drawable/ic_link" app:tint="?android:textColorPrimary" /> - - - - + android:layout_height="match_parent"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 48c2d1d8e..c07ca8bd7 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -1,5 +1,6 @@ - - - - @@ -104,32 +100,37 @@ - - + + - - - - + app:layout_constraintTop_toBottomOf="@+id/emojiReactionsView" + tools:tint="@color/classic_dark_1" + android:src="@drawable/ic_delivery_status_sent" /> diff --git a/app/src/main/res/layout/view_visible_message_content.xml b/app/src/main/res/layout/view_visible_message_content.xml index ee7a6b535..5ec4e82a3 100644 --- a/app/src/main/res/layout/view_visible_message_content.xml +++ b/app/src/main/res/layout/view_visible_message_content.xml @@ -1,5 +1,5 @@ - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_voice_message.xml b/app/src/main/res/layout/view_voice_message.xml index 535c7f234..9895ad95a 100644 --- a/app/src/main/res/layout/view_voice_message.xml +++ b/app/src/main/res/layout/view_voice_message.xml @@ -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" /> - - + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 888b0af63..f0d04162b 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -164,6 +164,7 @@ + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3fd16ffc6..d3b9d9b25 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -141,22 +141,25 @@ #57C9FA - #111111 + #000000 #1A1C28 #252735 #2B2D40 #3D4A5D #A6A9CE - #FFFFFF + #5CAACC + #FFFFFF - #19345D - #6A6E90 - #5CAACC - #B3EDF2 - #E7F3F4 - #ECFAFB - #FCFFFF + #000000 + #19345D + #6A6E90 + #5CAACC + #B3EDF2 + #E7F3F4 + #ECFAFB + #FCFFFF - #EA5545 + #FF3A3A + #E12D19 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 084b023ad..4b0063527 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -865,6 +865,12 @@ Join Navigate Back Close Dialog + Database Upgrade Failed + Please contact support to report the error. + Sending + Read + Sent + Failed to send Search Conversation All Media Pin Conversation diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ffdea8228..2f3a78b2b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -202,6 +202,7 @@ diff --git a/build.gradle b/build.gradle index a318cea58..7e7e14f00 100644 --- a/build.gradle +++ b/build.gradle @@ -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 } } \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 2bce14c43..a039f0df9 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -6,5 +6,5 @@ repositories { } dependencies { - implementation 'com.android.tools.build:apksig:4.0.1' + implementation 'com.android.tools.build:apksig:4.0.2' } diff --git a/gradle.properties b/gradle.properties index d8668cfa1..fa51fdbca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea8f95616..cd825d084 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/libsession/build.gradle b/libsession/build.gradle index 0515b3562..dd8959958 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -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" diff --git a/libsession/src/main/java/org/session/libsession/avatars/ResourceContactPhoto.java b/libsession/src/main/java/org/session/libsession/avatars/ResourceContactPhoto.java index f55c59a0f..742b03e9f 100644 --- a/libsession/src/main/java/org/session/libsession/avatars/ResourceContactPhoto.java +++ b/libsession/src/main/java/org/session/libsession/avatars/ResourceContactPhoto.java @@ -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); diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index eb40df6e0..24324ccb7 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -20,8 +20,10 @@ interface MessageDataProvider { * @return pair of sms or mms table-specific ID and whether it is in SMS table */ fun getMessageID(serverId: Long, threadId: Long): Pair? + fun getMessageIDs(serverIDs: List, threadID: Long): Pair, List> fun deleteMessage(messageID: Long, isSms: Boolean) - fun updateMessageAsDeleted(timestamp: Long, author: String) + fun deleteMessages(messageIDs: List, threadId: Long, isSms: Boolean) + fun updateMessageAsDeleted(timestamp: Long, author: String): Long? fun getServerHashForMessage(messageID: Long): String? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? @@ -36,7 +38,7 @@ interface MessageDataProvider { fun isOutgoingMessage(timestamp: Long): Boolean fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) fun handleFailedAttachmentUpload(attachmentId: Long) - fun getMessageForQuote(timestamp: Long, author: Address): Pair? + fun getMessageForQuote(timestamp: Long, author: Address): Triple? fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List fun getMessageBodyFor(timestamp: Long, author: String): String fun getAttachmentIDsFor(messageID: Long): List diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 17fbdf75d..0ce893a1a 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -12,10 +12,12 @@ 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.visible.Attachment +import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -34,9 +36,7 @@ interface StorageProtocol { // General fun getUserPublicKey(): String? fun getUserX25519KeyPair(): ECKeyPair - fun getUserDisplayName(): String? - fun getUserProfileKey(): ByteArray? - fun getUserProfilePictureURL(): String? + fun getUserProfile(): Profile fun setUserProfilePictureURL(newProfilePicture: String) // Signal fun getOrGenerateRegistrationID(): Int @@ -66,7 +66,7 @@ interface StorageProtocol { fun getAllOpenGroups(): Map fun updateOpenGroup(openGroup: OpenGroup) fun getOpenGroup(threadId: Long): OpenGroup? - fun addOpenGroup(urlAsString: String) + fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? fun onOpenGroupAdded(server: String) fun hasBackgroundGroupAddJob(groupJoinUrl: String): Boolean fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) @@ -80,6 +80,7 @@ interface StorageProtocol { // Open Group Metadata fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) + fun hasDownloadedProfilePicture(groupID: String): Boolean fun setUserCount(room: String, server: String, newValue: Int) // Last Message Server ID @@ -108,6 +109,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 @@ -179,7 +181,7 @@ interface StorageProtocol { */ fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List, runIncrement: Boolean, runThreadUpdate: Boolean): Long? fun markConversationAsRead(threadId: Long, updateLastSeen: Boolean) - fun incrementUnread(threadId: Long, amount: Int) + fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) fun updateThread(threadId: Long, unarchive: Boolean) fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertMessageRequestResponse(response: MessageRequestResponse) diff --git a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt index b2ca605d2..01fae1f50 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt @@ -77,7 +77,11 @@ object FileServerApi { OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).map { it.body ?: throw Error.ParsingFailed }.fail { e -> - Log.e("Loki", "File server request failed.", e) + when (e) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> Log.e("Loki", "File server request failed due to error: ${e.message}") + else -> Log.e("Loki", "File server request failed", e) + } } } else { Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) @@ -96,7 +100,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 } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt index aa37e0f0a..c679724b9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt @@ -41,15 +41,10 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { } // get image storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) - val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(openGroup.room, openGroup.server, false).get() - storage.setServerCapabilities(openGroup.server, capabilities.capabilities) - val imageId = info.imageId - storage.addOpenGroup(openGroup.joinUrl()) + val info = storage.addOpenGroup(openGroup.joinUrl()) + val imageId = info?.imageId if (imageId != null) { - val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(openGroup.server, openGroup.room, imageId).get() - val groupId = GroupUtil.getEncodedOpenGroupID("${openGroup.server}.${openGroup.room}".toByteArray()) - storage.updateProfilePicture(groupId, bytes) - storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) + JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.room, openGroup.server)) } Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") storage.onOpenGroupAdded(openGroup.server) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index 07c104cfd..18a8cc4ae 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -11,13 +11,11 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.ParsedMessage import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.messaging.sending_receiving.MessageReceiver -import org.session.libsession.messaging.sending_receiving.handle -import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions -import org.session.libsession.messaging.sending_receiving.handleVisibleMessage +import org.session.libsession.messaging.sending_receiving.* import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities @@ -94,12 +92,23 @@ class BatchMessageReceiveJob( threadMap[threadID]!! += parsedParams } } catch (e: Exception) { - Log.e(TAG, "Couldn't receive message.", e) - if (e is MessageReceiver.Error && !e.isRetryable) { - Log.e(TAG, "Message failed permanently",e) - } else { - Log.e(TAG, "Message failed",e) - failures += messageParameters + when (e) { + is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> { + Log.i(TAG, "Couldn't receive message, failed with error: ${e.message}") + } + is MessageReceiver.Error -> { + if (!e.isRetryable) { + Log.e(TAG, "Couldn't receive message, failed permanently", e) + } + else { + Log.e(TAG, "Couldn't receive message, failed", e) + failures += messageParameters + } + } + else -> { + Log.e(TAG, "Couldn't receive message, failed", e) + failures += messageParameters + } } } } @@ -108,25 +117,42 @@ class BatchMessageReceiveJob( runBlocking(Dispatchers.IO) { val deferredThreadMap = threadMap.entries.map { (threadId, messages) -> async { - val messageIds = mutableListOf>() + // The LinkedHashMap should preserve insertion order + val messageIds = linkedMapOf>() + messages.forEach { (parameters, message, proto) -> try { - if (message is VisibleMessage) { - val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID, - runIncrement = false, - runThreadUpdate = false, - runProfileUpdate = true - ) - if (messageId != null && message.reaction == null) { - val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( - IdPrefix.BLINDED, it.publicKey.asBytes).hexString } - messageIds += messageId to (message.sender == localUserPublicKey || isUserBlindedSender) + when (message) { + is VisibleMessage -> { + val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID, + runIncrement = false, + runThreadUpdate = false, + runProfileUpdate = true + ) + + if (messageId != null && message.reaction == null) { + val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( + IdPrefix.BLINDED, it.publicKey.asBytes).hexString } + messageIds[messageId] = Pair( + (message.sender == localUserPublicKey || isUserBlindedSender), + message.hasMention + ) + } + parameters.openGroupMessageServerID?.let { + MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions) + } } - parameters.openGroupMessageServerID?.let { - MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions) + + is UnsendRequest -> { + val deletedMessageId = MessageReceiver.handleUnsendRequest(message) + + // If we removed a message then ensure it isn't in the 'messageIds' + if (deletedMessageId != null) { + messageIds.remove(deletedMessageId) + } } - } else { - MessageReceiver.handle(message, proto, openGroupID) + + else -> MessageReceiver.handle(message, proto, openGroupID) } } catch (e: Exception) { Log.e(TAG, "Couldn't process message.", e) @@ -139,14 +165,20 @@ class BatchMessageReceiveJob( } } // increment unreads, notify, and update thread - val unreadFromMine = messageIds.indexOfLast { (_,fromMe) -> fromMe } - var trueUnreadCount = messageIds.filter { (_,fromMe) -> !fromMe }.size + val unreadFromMine = messageIds.map { it.value.first }.indexOfLast { it } + var trueUnreadCount = messageIds.filter { !it.value.first }.size + var trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size if (unreadFromMine >= 0) { - trueUnreadCount -= (unreadFromMine + 1) storage.markConversationAsRead(threadId, false) + + val trueUnreadIds = messageIds.keys.toList().subList(unreadFromMine + 1, messageIds.keys.count()) + trueUnreadCount = trueUnreadIds.size + trueUnreadMentionCount = messageIds + .filter { trueUnreadIds.contains(it.key) && !it.value.first && it.value.second } + .size } if (trueUnreadCount > 0) { - storage.incrementUnread(threadId, trueUnreadCount) + storage.incrementUnread(threadId, trueUnreadCount, trueUnreadMentionCount) } storage.updateThread(threadId, true) SSKEnvironment.shared.notificationManager.updateNotification(context, threadId) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt index 38e8831fb..02f792117 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt @@ -14,10 +14,9 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { override fun execute() { val storage = MessagingModuleConfiguration.shared.storage + val imageId = storage.getOpenGroup(room, server)?.imageId ?: return try { - val info = OpenGroupApi.getRoomInfo(room, server).get() - val imageId = info.imageId ?: return - val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, info.token, imageId).get() + val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, room, imageId).get() val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) storage.updateProfilePicture(groupId, bytes) storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 215d20834..8e46f275f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -26,7 +26,7 @@ class JobQueue : JobDelegate { private val jobTimestampMap = ConcurrentHashMap() private val rxDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val rxMediaDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() - private val openGroupDispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() + private val openGroupDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher() private val txDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = CoroutineScope(Dispatchers.Default) + SupervisorJob() private val queue = Channel(UNLIMITED) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt index cdd9e0a3a..8ce1adf48 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt @@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.OnionRequestAPI +import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log class MessageSendJob(val message: Message, val destination: Destination) : Job { @@ -67,14 +68,25 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { val promise = MessageSender.send(this.message, this.destination).success { this.handleSuccess() }.fail { exception -> - Log.e(TAG, "Couldn't send message due to error: $exception.") - if (exception is MessageSender.Error) { - if (!exception.isRetryable) { this.handlePermanentFailure(exception) } + var logStacktrace = true + + when (exception) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> { + logStacktrace = false + + if (exception.statusCode == 429) { this.handlePermanentFailure(exception) } + else { this.handleFailure(exception) } + } + is MessageSender.Error -> { + if (!exception.isRetryable) { this.handlePermanentFailure(exception) } + else { this.handleFailure(exception) } + } + else -> this.handleFailure(exception) } - if (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 429) { - this.handlePermanentFailure(exception) - } - this.handleFailure(exception) + + if (logStacktrace) { Log.e(TAG, "Couldn't send message due to error", exception) } + else { Log.e(TAG, "Couldn't send message due to error: ${exception.message}") } } try { promise.get() diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt index c4180c002..1fb2d0df2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt @@ -23,14 +23,27 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val numberToDelete = messageServerIds.size Log.d(TAG, "Deleting $numberToDelete messages") - var numberDeleted = 0 - messageServerIds.forEach { serverId -> - val (messageId, isSms) = dataProvider.getMessageID(serverId, threadId) ?: return@forEach - dataProvider.deleteMessage(messageId, isSms) - numberDeleted++ + + // FIXME: This entire process should probably run in a transaction (with the attachment deletion happening only if it succeeded) + try { + val messageIds = dataProvider.getMessageIDs(messageServerIds.toList(), threadId) + + // Delete the SMS messages + if (messageIds.first.isNotEmpty()) { + dataProvider.deleteMessages(messageIds.first, threadId, true) + } + + // Delete the MMS messages + if (messageIds.second.isNotEmpty()) { + dataProvider.deleteMessages(messageIds.second, threadId, false) + } + + Log.d(TAG, "Deleted ${messageIds.first.size + messageIds.second.size} messages successfully") + delegate?.handleJobSucceeded(this) + } + catch (e: Exception) { + delegate?.handleJobFailed(this, e) } - Log.d(TAG, "Deleted $numberDeleted messages successfully") - delegate?.handleJobSucceeded(this) } override fun serialize(): Data = Data.Builder() diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt index f5a65e4ca..614a6eb81 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt @@ -1,15 +1,22 @@ package org.session.libsession.messaging.messages.control +import com.google.protobuf.ByteString +import org.session.libsession.messaging.messages.visible.Profile import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Log -class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() { +class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = null) : ControlMessage() { override val isSelfSendValid: Boolean = true override fun toProto(): SignalServiceProtos.Content? { + val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder() + profile?.displayName?.let { profileProto.displayName = it } + profile?.profilePictureURL?.let { profileProto.profilePicture = it } val messageRequestResponseProto = SignalServiceProtos.MessageRequestResponse.newBuilder() .setIsApproved(isApproved) + .setProfile(profileProto.build()) + profile?.profileKey?.let { messageRequestResponseProto.profileKey = ByteString.copyFrom(it) } return try { SignalServiceProtos.Content.newBuilder() .setMessageRequestResponse(messageRequestResponseProto.build()) @@ -26,7 +33,13 @@ class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() { fun fromProto(proto: SignalServiceProtos.Content): MessageRequestResponse? { val messageRequestResponseProto = if (proto.hasMessageRequestResponse()) proto.messageRequestResponse else return null val isApproved = messageRequestResponseProto.isApproved - return MessageRequestResponse(isApproved) + val profileProto = messageRequestResponseProto.profile + val profile = Profile().apply { + displayName = profileProto.displayName + profileKey = if (messageRequestResponseProto.hasProfileKey()) messageRequestResponseProto.profileKey.toByteArray() else null + profilePictureURL = profileProto.profilePicture + } + return MessageRequestResponse(isApproved, profile) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index e5160b754..ab24234e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -29,6 +29,7 @@ public class IncomingMediaMessage { private final boolean expirationUpdate; private final boolean unidentified; private final boolean messageRequestResponse; + private final boolean hasMention; private final DataExtractionNotificationInfoMessage dataExtractionNotification; private final QuoteModel quote; @@ -44,6 +45,7 @@ public class IncomingMediaMessage { boolean expirationUpdate, boolean unidentified, boolean messageRequestResponse, + boolean hasMention, Optional body, Optional group, Optional> attachments, @@ -63,6 +65,7 @@ public class IncomingMediaMessage { this.quote = quote.orNull(); this.unidentified = unidentified; this.messageRequestResponse = messageRequestResponse; + this.hasMention = hasMention; if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.INSTANCE.getEncodedId(group.get())); else this.groupId = null; @@ -81,7 +84,8 @@ public class IncomingMediaMessage { Optional> linkPreviews) { return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false, - false, false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); + false, false, message.getHasMention(), Optional.fromNullable(message.getText()), + group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); } public int getSubscriptionId() { @@ -124,6 +128,10 @@ public class IncomingMediaMessage { return groupId != null; } + public boolean hasMention() { + return hasMention; + } + public boolean isScreenshotDataExtraction() { if (dataExtractionNotification == null) return false; else { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java index 93347f527..ca8e89f1e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java @@ -43,24 +43,25 @@ public class IncomingTextMessage implements Parcelable { private final long expiresInMillis; private final boolean unidentified; private final int callType; + private final boolean hasMention; private boolean isOpenGroupInvitation = false; public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified) { - this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1); + long expiresInMillis, boolean unidentified, boolean hasMention) { + this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1, hasMention); } public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified, int callType) { - this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, callType, true); + long expiresInMillis, boolean unidentified, int callType, boolean hasMention) { + this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, callType, hasMention, true); } public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified, int callType, boolean isPush) { + long expiresInMillis, boolean unidentified, int callType, boolean hasMention, boolean isPush) { this.message = encodedBody; this.sender = sender; this.senderDeviceId = senderDeviceId; @@ -74,6 +75,7 @@ public class IncomingTextMessage implements Parcelable { this.expiresInMillis = expiresInMillis; this.unidentified = unidentified; this.callType = callType; + this.hasMention = hasMention; if (group.isPresent()) { this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get())); @@ -98,6 +100,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = in.readInt() == 1; this.isOpenGroupInvitation = in.readInt() == 1; this.callType = in.readInt(); + this.hasMention = in.readInt() == 1; } public IncomingTextMessage(IncomingTextMessage base, String newBody) { @@ -116,6 +119,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = base.isUnidentified(); this.isOpenGroupInvitation = base.isOpenGroupInvitation(); this.callType = base.callType; + this.hasMention = base.hasMention; } public static IncomingTextMessage from(VisibleMessage message, @@ -123,7 +127,7 @@ public class IncomingTextMessage implements Parcelable { Optional group, long expiresInMillis) { - return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false); + return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false, message.getHasMention()); } public static IncomingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, Address sender, Long sentTimestamp) @@ -133,7 +137,7 @@ public class IncomingTextMessage implements Parcelable { if (url == null || name == null) { return null; } // FIXME: Doing toJSON() to get the body here is weird String body = UpdateMessageData.Companion.buildOpenGroupInvitation(url, name).toJSON(); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), 0, false); + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), 0, false, false); incomingTextMessage.isOpenGroupInvitation = true; return incomingTextMessage; } @@ -142,7 +146,7 @@ public class IncomingTextMessage implements Parcelable { Address sender, Optional group, long sentTimestamp) { - return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal(), false); + return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal(), false, false); } public int getSubscriptionId() { @@ -207,6 +211,8 @@ public class IncomingTextMessage implements Parcelable { public boolean isOpenGroupInvitation() { return isOpenGroupInvitation; } + public boolean hasMention() { return hasMention; } + public boolean isCallInfo() { int callMessageTypeLength = CallMessageType.values().length; return callType >= 0 && callType < callMessageTypeLength; @@ -240,5 +246,6 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(unidentified ? 1 : 0); out.writeInt(isOpenGroupInvitation ? 1 : 0); out.writeInt(callType); + out.writeInt(hasMention ? 1 : 0); } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index a163b667d..08cb2c4b0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -85,8 +85,8 @@ public class OutgoingMediaMessage { previews = Collections.singletonList(linkPreview); } return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, - recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(), - previews, Collections.emptyList(), Collections.emptyList()); + recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, + Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); } public Recipient getRecipient() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt index cf792e6a8..ce6b61524 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt @@ -25,7 +25,7 @@ class Profile() { } } - internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() { + constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() { this.displayName = displayName this.profileKey = profileKey this.profilePictureURL = profilePictureURL diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index bf1364b31..381ffa6f7 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -24,6 +24,7 @@ class VisibleMessage : Message() { var profile: Profile? = null var openGroupInvitation: OpenGroupInvitation? = null var reaction: Reaction? = null + var hasMention: Boolean = false override val isSelfSendValid: Boolean = true diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt index 9efeaf15d..6a2a771e0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt @@ -11,16 +11,19 @@ data class OpenGroup( val id: String, val name: String, val publicKey: String, + val imageId: String?, val infoUpdates: Int, + val canWrite: Boolean, ) { - - constructor(server: String, room: String, name: String, infoUpdates: Int, publicKey: String) : this( + constructor(server: String, room: String, publicKey: String, name: String, imageId: String?, canWrite: Boolean, infoUpdates: Int) : this( server = server, room = room, id = "$server.$room", name = name, publicKey = publicKey, + imageId = imageId, infoUpdates = infoUpdates, + canWrite = canWrite ) companion object { @@ -29,13 +32,14 @@ data class OpenGroup( return try { val json = JsonUtil.fromJson(jsonAsString) if (!json.has("room")) return null - val room = json.get("room").asText().toLowerCase(Locale.US) - val server = json.get("server").asText().toLowerCase(Locale.US) + val room = json.get("room").asText().lowercase(Locale.US) + val server = json.get("server").asText().lowercase(Locale.US) val displayName = json.get("displayName").asText() val publicKey = json.get("publicKey").asText() + val imageId = json.get("imageId")?.asText() + val canWrite = json.get("canWrite")?.asText()?.toBoolean() ?: true val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0 - val capabilities = json.get("capabilities")?.asText()?.split(",") ?: emptyList() - OpenGroup(server, room, displayName, infoUpdates, publicKey) + OpenGroup(server = server, room = room, name = displayName, publicKey = publicKey, imageId = imageId, canWrite = canWrite, infoUpdates = infoUpdates) } catch (e: Exception) { Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e); null @@ -53,12 +57,14 @@ data class OpenGroup( } } - fun toJson(): Map = mapOf( + fun toJson(): Map = mapOf( "room" to room, "server" to server, - "displayName" to name, "publicKey" to publicKey, + "displayName" to name, + "imageId" to imageId, "infoUpdates" to infoUpdates.toString(), + "canWrite" to canWrite.toString() ) val joinURL: String get() = "$server/$room?public_key=$publicKey" diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index daa735aa4..46eff4b03 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -91,7 +91,7 @@ object OpenGroupApi { val created: Long = 0, val activeUsers: Int = 0, val activeUsersCutoff: Int = 0, - val imageId: Long? = null, + val imageId: String? = null, val pinnedMessages: List = emptyList(), val admin: Boolean = false, val globalAdmin: Boolean = false, @@ -148,7 +148,7 @@ object OpenGroupApi { ) enum class Capability { - BLIND, REACTIONS + SOGS, BLIND, REACTIONS } @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) @@ -337,7 +337,7 @@ object OpenGroupApi { .plus(request.verb.rawValue.toByteArray()) .plus("/${request.endpoint.value}".toByteArray()) .plus(bodyHash) - if (serverCapabilities.contains(Capability.BLIND.name.lowercase())) { + if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) { SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair -> pubKey = SessionId( IdPrefix.BLINDED, @@ -383,7 +383,11 @@ object OpenGroupApi { } return if (request.useOnionRouting) { OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey).fail { e -> - Log.e("SOGS", "Failed onion request", e) + when (e) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> Log.e("SOGS", "Failed onion request: ${e.message}") + else -> Log.e("SOGS", "Failed onion request", e) + } } } else { Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) @@ -395,13 +399,13 @@ object OpenGroupApi { fun downloadOpenGroupProfilePicture( server: String, roomID: String, - imageId: Long + imageId: String ): Promise { val request = Request( verb = GET, room = roomID, server = server, - endpoint = Endpoint.RoomFileIndividual(roomID, imageId.toString()) + endpoint = Endpoint.RoomFileIndividual(roomID, imageId) ) return getResponseBody(request) } @@ -794,16 +798,14 @@ object OpenGroupApi { private fun sequentialBatch( server: String, - requests: MutableList>, - authRequired: Boolean = true + requests: MutableList> ): Promise>, Exception> { val request = Request( verb = POST, room = null, server = server, endpoint = Endpoint.Sequence, - parameters = requests.map { it.request }, - isAuthRequired = authRequired + parameters = requests.map { it.request } ) return getBatchResponseJson(request, requests) } @@ -912,8 +914,7 @@ object OpenGroupApi { fun getCapabilitiesAndRoomInfo( room: String, - server: String, - authRequired: Boolean = true + server: String ): Promise, Exception> { val requests = mutableListOf>( BatchRequestInfo( @@ -933,7 +934,7 @@ object OpenGroupApi { responseType = object : TypeReference(){} ) ) - return sequentialBatch(server, requests, authRequired).map { + return sequentialBatch(server, requests).map { val capabilities = it.firstOrNull()?.body as? Capabilities ?: throw Error.ParsingFailed val roomInfo = it.lastOrNull()?.body as? RoomInfo ?: throw Error.ParsingFailed capabilities to roomInfo diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index d6a4618d9..3cc23da1e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -12,9 +12,9 @@ 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.MessageRequestResponse import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.LinkPreview -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Quote import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi @@ -32,12 +32,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 @@ -118,14 +113,10 @@ object MessageSender { } // Attach the user's profile if needed if (message is VisibleMessage) { - val displayName = storage.getUserDisplayName()!! - val profileKey = storage.getUserProfileKey() - val profilePictureUrl = storage.getUserProfilePictureURL() - if (profileKey != null && profilePictureUrl != null) { - message.profile = Profile(displayName, profileKey, profilePictureUrl) - } else { - message.profile = Profile(displayName) - } + message.profile = storage.getUserProfile() + } + if (message is MessageRequestResponse) { + message.profile = storage.getUserProfile() } // Convert it to protobuf val proto = message.toProto() ?: throw Error.ProtoConversionFailed @@ -257,14 +248,7 @@ object MessageSender { try { // Attach the user's profile if needed if (message is VisibleMessage) { - val displayName = storage.getUserDisplayName()!! - val profileKey = storage.getUserProfileKey() - val profilePictureUrl = storage.getUserProfilePictureURL() - if (profileKey != null && profilePictureUrl != null) { - message.profile = Profile(displayName, profileKey, profilePictureUrl) - } else { - message.profile = Profile(displayName) - } + message.profile = storage.getUserProfile() } when (destination) { is Destination.OpenGroup -> { @@ -337,6 +321,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 diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index f2d757c5f..74b54df6d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -190,22 +190,24 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { storage.addContacts(message.contacts) } -fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { +fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? { val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() - if (message.sender != message.author && (message.sender != userPublicKey && userPublicKey != null)) { return } + if (message.sender != message.author && (message.sender != userPublicKey && userPublicKey != null)) { return null } val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider - val timestamp = message.timestamp ?: return - val author = message.author ?: return - val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return + val timestamp = message.timestamp ?: return null + val author = message.author ?: return null + val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return null messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> SnodeAPI.deleteMessage(author, listOf(serverHash)) } - messageDataProvider.updateMessageAsDeleted(timestamp, author) + val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author) if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { SSKEnvironment.shared.notificationManager.updateNotification(context) } + + return deletedMessageId } fun handleMessageRequestResponse(message: MessageRequestResponse) { @@ -265,6 +267,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, } // Parse quote if needed var quoteModel: QuoteModel? = null + var quoteMessageBody: String? = null if (message.quote != null && proto.dataMessage.hasQuote()) { val quote = proto.dataMessage.quote @@ -276,6 +279,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author) + quoteMessageBody = messageInfo?.third quoteModel = if (messageInfo != null) { val attachments = if (messageInfo.second) messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList() QuoteModel(quote.id, author,null,false, attachments) @@ -308,6 +312,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 -> @@ -320,6 +326,20 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, storage.removeReaction(reaction.emoji!!, reaction.timestamp!!, reaction.publicKey!!, threadIsGroup) } } ?: run { + // A user is mentioned if their public key is in the body of a message or one of their messages + // was quoted + val messageText = message.text + message.hasMention = listOf(userPublicKey, userBlindedKey) + .filterNotNull() + .any { key -> + return@any ( + messageText != null && + messageText.contains("@$key") + ) || ( + (quoteModel?.author?.serialize() ?: "") == key + ) + } + // Persist the message message.threadID = threadID val messageID = @@ -343,8 +363,6 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, } return messageID } - // Cancel any typing indicators if needed - cancelTypingIndicatorsIfNeeded(message.sender!!) return null } @@ -434,7 +452,7 @@ private fun MessageReceiver.handleClosedGroupControlMessage(message: ClosedGroup private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMessage) { val kind = message.kind!! as? ClosedGroupControlMessage.Kind.New ?: return val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false) - if (!recipient.isApproved) return + if (!recipient.isApproved && !recipient.isLocalNumber) return val groupPublicKey = kind.publicKey.toByteArray().toHexString() val members = kind.members.map { it.toByteArray().toHexString() } val admins = kind.admins.map { it.toByteArray().toHexString() } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java index 8aa8102a6..b2b7cfc7d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java @@ -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); + } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 145155e97..353b4e1bf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -59,7 +59,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S fun poll(isPostCapabilitiesRetry: Boolean = false): Promise { val storage = MessagingModuleConfiguration.shared.storage val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room } - rooms.forEach { downloadGroupAvatarIfNeeded(it) } + return OpenGroupApi.poll(rooms, server).successBackground { responses -> responses.filterNot { it.body == null }.forEach { response -> when (response.endpoint) { @@ -117,15 +117,18 @@ class OpenGroupPoller(private val server: String, private val executorService: S ) { val storage = MessagingModuleConfiguration.shared.storage val groupId = "$server.$roomToken" + val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) val existingOpenGroup = storage.getOpenGroup(roomToken, server) val publicKey = existingOpenGroup?.publicKey ?: return val openGroup = OpenGroup( server = server, room = pollInfo.token, - name = pollInfo.details?.name ?: "", - infoUpdates = pollInfo.details?.infoUpdates ?: 0, + name = if (pollInfo.details != null) { pollInfo.details.name } else { existingOpenGroup.name }, publicKey = publicKey, + imageId = if (pollInfo.details != null) { pollInfo.details.imageId } else { existingOpenGroup.imageId }, + canWrite = pollInfo.write, + infoUpdates = if (pollInfo.details != null) { pollInfo.details.infoUpdates } else { existingOpenGroup.infoUpdates } ) // - Open Group changes storage.updateOpenGroup(openGroup) @@ -155,6 +158,22 @@ class OpenGroupPoller(private val server: String, private val executorService: S GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) }) } + + if ( + ( + pollInfo.details != null && + pollInfo.details.imageId != null && ( + pollInfo.details.imageId != existingOpenGroup.imageId || + !storage.hasDownloadedProfilePicture(dbGroupId) + ) + ) || ( + pollInfo.details == null && + existingOpenGroup.imageId != null && + !storage.hasDownloadedProfilePicture(dbGroupId) + ) + ) { + JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server)) + } } private fun handleMessages( @@ -284,16 +303,4 @@ class OpenGroupPoller(private val server: String, private val executorService: S JobQueue.shared.add(deleteJob) } } - - private fun downloadGroupAvatarIfNeeded(room: String) { - val storage = MessagingModuleConfiguration.shared.storage - if (storage.getGroupAvatarDownloadJob(server, room) != null) return - val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) - storage.getGroup(groupId)?.let { - if (System.currentTimeMillis() > it.updatedTimestamp + TimeUnit.DAYS.toMillis(7)) { - JobQueue.shared.add(GroupAvatarDownloadJob(room, server)) - } - } - } - } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index f93a7b243..bcce887a5 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -78,8 +78,8 @@ object OnionRequestAPI { // endregion class HTTPRequestFailedBlindingRequiredException(statusCode: Int, json: Map<*, *>, destination: String): HTTPRequestFailedAtDestinationException(statusCode, json, destination) - open class HTTPRequestFailedAtDestinationException(val statusCode: Int, val json: Map<*, *>, val destination: String) - : Exception("HTTP request failed at destination ($destination) with status code $statusCode.") + open class HTTPRequestFailedAtDestinationException(statusCode: Int, json: Map<*, *>, val destination: String) + : HTTP.HTTPRequestFailedException(statusCode, json, "HTTP request failed at destination ($destination) with status code $statusCode.") class InsufficientSnodesException : Exception("Couldn't find enough snodes to build a path.") private data class OnionBuildingResult( diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 9a1795895..807759464 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -56,6 +56,10 @@ object SnodeAPI { * user's clock is incorrect. */ internal var clockOffset = 0L + + val nowWithOffset + get() = System.currentTimeMillis() + clockOffset + internal var forkInfo by observable(database.getForkInfo()) { _, oldValue, newValue -> if (newValue > oldValue) { Log.d("Loki", "Setting new fork info new: $newValue, old: $oldValue") diff --git a/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt b/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt index 0a61d1ede..b850baa25 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt @@ -2,6 +2,7 @@ package org.session.libsession.utilities import okhttp3.HttpUrl import org.session.libsession.messaging.file_server.FileServerApi +import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log import java.io.* @@ -40,7 +41,11 @@ object DownloadUtilities { outputStream.write(it) } } catch (e: Exception) { - Log.e("Loki", "Couldn't download attachment.", e) + when (e) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> Log.e("Loki", "Couldn't download attachment due to error: ${e.message}") + else -> Log.e("Loki", "Couldn't download attachment", e) + } throw e } } diff --git a/libsignal/build.gradle b/libsignal/build.gradle index 681fdfa12..1ea5f2de0 100644 --- a/libsignal/build.gradle +++ b/libsignal/build.gradle @@ -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" } diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index a0d3ac0ab..5d4fc6d5b 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -287,7 +287,9 @@ message ConfigurationMessage { message MessageRequestResponse { // @required - required bool isApproved = 1; // Whether the request was approved + required bool isApproved = 1; + optional bytes profileKey = 2; + optional DataMessage.LokiProfile profile = 3; } message ReceiptMessage { diff --git a/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt index a1866bf21..18880f553 100644 --- a/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt @@ -16,8 +16,10 @@ interface LokiAPIDatabaseProtocol { fun setSwarm(publicKey: String, newValue: Set) fun getLastMessageHashValue(snode: Snode, publicKey: String, namespace: Int): String? fun setLastMessageHashValue(snode: Snode, publicKey: String, newValue: String, namespace: Int) + fun clearAllLastMessageHashes() fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set? fun setReceivedMessageHashValues(publicKey: String, newValue: Set, namespace: Int) + fun clearReceivedMessageHashValues() fun getAuthToken(server: String): String? fun setAuthToken(server: String, newValue: String?) fun setUserCount(room: String, server: String, newValue: Int) diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index ba11666c4..7c44087f8 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -5523,6 +5523,20 @@ public final class SignalServiceProtos { org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAttachmentsOrBuilder( int index); + // optional .signalservice.GroupContext group = 3; + /** + * optional .signalservice.GroupContext group = 3; + */ + boolean hasGroup(); + /** + * optional .signalservice.GroupContext group = 3; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupContext getGroup(); + /** + * optional .signalservice.GroupContext group = 3; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder getGroupOrBuilder(); + // optional uint32 flags = 4; /** * optional uint32 flags = 4; @@ -5672,20 +5686,6 @@ public final class SignalServiceProtos { */ com.google.protobuf.ByteString getSyncTargetBytes(); - - // optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - boolean hasGroupMessage(); - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getGroupMessage(); - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder getGroupMessageOrBuilder(); } /** * Protobuf type {@code signalservice.DataMessage} @@ -5751,29 +5751,42 @@ public final class SignalServiceProtos { attachments_.add(input.readMessage(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.PARSER, extensionRegistry)); break; } - case 32: { + case 26: { + org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder subBuilder = null; + if (((bitField0_ & 0x00000002) == 0x00000002)) { + subBuilder = group_.toBuilder(); + } + group_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupContext.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(group_); + group_ = subBuilder.buildPartial(); + } bitField0_ |= 0x00000002; + break; + } + case 32: { + bitField0_ |= 0x00000004; flags_ = input.readUInt32(); break; } case 40: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; expireTimer_ = input.readUInt32(); break; } case 50: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; profileKey_ = input.readBytes(); break; } case 56: { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; timestamp_ = input.readUInt64(); break; } case 66: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder subBuilder = null; - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { subBuilder = quote_.toBuilder(); } quote_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.PARSER, extensionRegistry); @@ -5781,20 +5794,20 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(quote_); quote_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000040; break; } case 82: { - if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) { + if (!((mutable_bitField0_ & 0x00000100) == 0x00000100)) { preview_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000080; + mutable_bitField0_ |= 0x00000100; } preview_.add(input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.PARSER, extensionRegistry)); break; } case 90: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder subBuilder = null; - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { subBuilder = reaction_.toBuilder(); } reaction_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.PARSER, extensionRegistry); @@ -5802,12 +5815,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(reaction_); reaction_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; break; } case 810: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder subBuilder = null; - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { subBuilder = profile_.toBuilder(); } profile_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.PARSER, extensionRegistry); @@ -5815,12 +5828,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(profile_); profile_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000100; break; } case 818: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder subBuilder = null; - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { subBuilder = openGroupInvitation_.toBuilder(); } openGroupInvitation_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.PARSER, extensionRegistry); @@ -5828,12 +5841,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(openGroupInvitation_); openGroupInvitation_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; break; } case 834: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (((bitField0_ & 0x00000400) == 0x00000400)) { subBuilder = closedGroupControlMessage_.toBuilder(); } closedGroupControlMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.PARSER, extensionRegistry); @@ -5841,25 +5854,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(closedGroupControlMessage_); closedGroupControlMessage_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000400; break; } case 842: { - bitField0_ |= 0x00000400; - syncTarget_ = input.readBytes(); - break; - } - case 962: { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000800) == 0x00000800)) { - subBuilder = groupMessage_.toBuilder(); - } - groupMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(groupMessage_); - groupMessage_ = subBuilder.buildPartial(); - } bitField0_ |= 0x00000800; + syncTarget_ = input.readBytes(); break; } } @@ -5873,7 +5873,7 @@ public final class SignalServiceProtos { if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { attachments_ = java.util.Collections.unmodifiableList(attachments_); } - if (((mutable_bitField0_ & 0x00000080) == 0x00000080)) { + if (((mutable_bitField0_ & 0x00000100) == 0x00000100)) { preview_ = java.util.Collections.unmodifiableList(preview_); } this.unknownFields = unknownFields.build(); @@ -10396,1125 +10396,6 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.DataMessage.OpenGroupInvitation) } - public interface GroupMessageOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional .signalservice.GroupDeleteMessage deleteMessage = 31; - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - boolean hasDeleteMessage(); - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDeleteMessage(); - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder getDeleteMessageOrBuilder(); - - // optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - boolean hasMemberLeftMessage(); - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getMemberLeftMessage(); - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder getMemberLeftMessageOrBuilder(); - - // optional .signalservice.GroupInviteMessage inviteMessage = 33; - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - boolean hasInviteMessage(); - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getInviteMessage(); - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder getInviteMessageOrBuilder(); - - // optional .signalservice.GroupPromoteMessage promoteMessage = 34; - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - boolean hasPromoteMessage(); - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getPromoteMessage(); - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder getPromoteMessageOrBuilder(); - } - /** - * Protobuf type {@code signalservice.DataMessage.GroupMessage} - */ - public static final class GroupMessage extends - com.google.protobuf.GeneratedMessage - implements GroupMessageOrBuilder { - // Use GroupMessage.newBuilder() to construct. - private GroupMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private GroupMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final GroupMessage defaultInstance; - public static GroupMessage getDefaultInstance() { - return defaultInstance; - } - - public GroupMessage getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GroupMessage( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 250: { - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - subBuilder = deleteMessage_.toBuilder(); - } - deleteMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(deleteMessage_); - deleteMessage_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000001; - break; - } - case 258: { - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000002) == 0x00000002)) { - subBuilder = memberLeftMessage_.toBuilder(); - } - memberLeftMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(memberLeftMessage_); - memberLeftMessage_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000002; - break; - } - case 266: { - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000004) == 0x00000004)) { - subBuilder = inviteMessage_.toBuilder(); - } - inviteMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(inviteMessage_); - inviteMessage_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000004; - break; - } - case 274: { - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000008) == 0x00000008)) { - subBuilder = promoteMessage_.toBuilder(); - } - promoteMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(promoteMessage_); - promoteMessage_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000008; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public GroupMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GroupMessage(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional .signalservice.GroupDeleteMessage deleteMessage = 31; - public static final int DELETEMESSAGE_FIELD_NUMBER = 31; - private org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage deleteMessage_; - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public boolean hasDeleteMessage() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDeleteMessage() { - return deleteMessage_; - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder getDeleteMessageOrBuilder() { - return deleteMessage_; - } - - // optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - public static final int MEMBERLEFTMESSAGE_FIELD_NUMBER = 32; - private org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage memberLeftMessage_; - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public boolean hasMemberLeftMessage() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getMemberLeftMessage() { - return memberLeftMessage_; - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder getMemberLeftMessageOrBuilder() { - return memberLeftMessage_; - } - - // optional .signalservice.GroupInviteMessage inviteMessage = 33; - public static final int INVITEMESSAGE_FIELD_NUMBER = 33; - private org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage inviteMessage_; - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public boolean hasInviteMessage() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getInviteMessage() { - return inviteMessage_; - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder getInviteMessageOrBuilder() { - return inviteMessage_; - } - - // optional .signalservice.GroupPromoteMessage promoteMessage = 34; - public static final int PROMOTEMESSAGE_FIELD_NUMBER = 34; - private org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage promoteMessage_; - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public boolean hasPromoteMessage() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getPromoteMessage() { - return promoteMessage_; - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder getPromoteMessageOrBuilder() { - return promoteMessage_; - } - - private void initFields() { - deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); - memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); - inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); - promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - if (hasDeleteMessage()) { - if (!getDeleteMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasInviteMessage()) { - if (!getInviteMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - if (hasPromoteMessage()) { - if (!getPromoteMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeMessage(31, deleteMessage_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeMessage(32, memberLeftMessage_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeMessage(33, inviteMessage_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeMessage(34, promoteMessage_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(31, deleteMessage_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(32, memberLeftMessage_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(33, inviteMessage_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(34, promoteMessage_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.DataMessage.GroupMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getDeleteMessageFieldBuilder(); - getMemberLeftMessageFieldBuilder(); - getInviteMessageFieldBuilder(); - getPromoteMessageFieldBuilder(); - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - if (deleteMessageBuilder_ == null) { - deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); - } else { - deleteMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000001); - if (memberLeftMessageBuilder_ == null) { - memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); - } else { - memberLeftMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000002); - if (inviteMessageBuilder_ == null) { - inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); - } else { - inviteMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); - if (promoteMessageBuilder_ == null) { - promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); - } else { - promoteMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000008); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage build() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - if (deleteMessageBuilder_ == null) { - result.deleteMessage_ = deleteMessage_; - } else { - result.deleteMessage_ = deleteMessageBuilder_.build(); - } - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - if (memberLeftMessageBuilder_ == null) { - result.memberLeftMessage_ = memberLeftMessage_; - } else { - result.memberLeftMessage_ = memberLeftMessageBuilder_.build(); - } - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - if (inviteMessageBuilder_ == null) { - result.inviteMessage_ = inviteMessage_; - } else { - result.inviteMessage_ = inviteMessageBuilder_.build(); - } - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - if (promoteMessageBuilder_ == null) { - result.promoteMessage_ = promoteMessage_; - } else { - result.promoteMessage_ = promoteMessageBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance()) return this; - if (other.hasDeleteMessage()) { - mergeDeleteMessage(other.getDeleteMessage()); - } - if (other.hasMemberLeftMessage()) { - mergeMemberLeftMessage(other.getMemberLeftMessage()); - } - if (other.hasInviteMessage()) { - mergeInviteMessage(other.getInviteMessage()); - } - if (other.hasPromoteMessage()) { - mergePromoteMessage(other.getPromoteMessage()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - if (hasDeleteMessage()) { - if (!getDeleteMessage().isInitialized()) { - - return false; - } - } - if (hasInviteMessage()) { - if (!getInviteMessage().isInitialized()) { - - return false; - } - } - if (hasPromoteMessage()) { - if (!getPromoteMessage().isInitialized()) { - - return false; - } - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional .signalservice.GroupDeleteMessage deleteMessage = 31; - private org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder> deleteMessageBuilder_; - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public boolean hasDeleteMessage() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDeleteMessage() { - if (deleteMessageBuilder_ == null) { - return deleteMessage_; - } else { - return deleteMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public Builder setDeleteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage value) { - if (deleteMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - deleteMessage_ = value; - onChanged(); - } else { - deleteMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000001; - return this; - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public Builder setDeleteMessage( - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder builderForValue) { - if (deleteMessageBuilder_ == null) { - deleteMessage_ = builderForValue.build(); - onChanged(); - } else { - deleteMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000001; - return this; - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public Builder mergeDeleteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage value) { - if (deleteMessageBuilder_ == null) { - if (((bitField0_ & 0x00000001) == 0x00000001) && - deleteMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance()) { - deleteMessage_ = - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.newBuilder(deleteMessage_).mergeFrom(value).buildPartial(); - } else { - deleteMessage_ = value; - } - onChanged(); - } else { - deleteMessageBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000001; - return this; - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public Builder clearDeleteMessage() { - if (deleteMessageBuilder_ == null) { - deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); - onChanged(); - } else { - deleteMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000001); - return this; - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder getDeleteMessageBuilder() { - bitField0_ |= 0x00000001; - onChanged(); - return getDeleteMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder getDeleteMessageOrBuilder() { - if (deleteMessageBuilder_ != null) { - return deleteMessageBuilder_.getMessageOrBuilder(); - } else { - return deleteMessage_; - } - } - /** - * optional .signalservice.GroupDeleteMessage deleteMessage = 31; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder> - getDeleteMessageFieldBuilder() { - if (deleteMessageBuilder_ == null) { - deleteMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder>( - deleteMessage_, - getParentForChildren(), - isClean()); - deleteMessage_ = null; - } - return deleteMessageBuilder_; - } - - // optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - private org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder> memberLeftMessageBuilder_; - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public boolean hasMemberLeftMessage() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getMemberLeftMessage() { - if (memberLeftMessageBuilder_ == null) { - return memberLeftMessage_; - } else { - return memberLeftMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public Builder setMemberLeftMessage(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage value) { - if (memberLeftMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - memberLeftMessage_ = value; - onChanged(); - } else { - memberLeftMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000002; - return this; - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public Builder setMemberLeftMessage( - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder builderForValue) { - if (memberLeftMessageBuilder_ == null) { - memberLeftMessage_ = builderForValue.build(); - onChanged(); - } else { - memberLeftMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000002; - return this; - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public Builder mergeMemberLeftMessage(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage value) { - if (memberLeftMessageBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002) && - memberLeftMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance()) { - memberLeftMessage_ = - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.newBuilder(memberLeftMessage_).mergeFrom(value).buildPartial(); - } else { - memberLeftMessage_ = value; - } - onChanged(); - } else { - memberLeftMessageBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000002; - return this; - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public Builder clearMemberLeftMessage() { - if (memberLeftMessageBuilder_ == null) { - memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); - onChanged(); - } else { - memberLeftMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder getMemberLeftMessageBuilder() { - bitField0_ |= 0x00000002; - onChanged(); - return getMemberLeftMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder getMemberLeftMessageOrBuilder() { - if (memberLeftMessageBuilder_ != null) { - return memberLeftMessageBuilder_.getMessageOrBuilder(); - } else { - return memberLeftMessage_; - } - } - /** - * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder> - getMemberLeftMessageFieldBuilder() { - if (memberLeftMessageBuilder_ == null) { - memberLeftMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder>( - memberLeftMessage_, - getParentForChildren(), - isClean()); - memberLeftMessage_ = null; - } - return memberLeftMessageBuilder_; - } - - // optional .signalservice.GroupInviteMessage inviteMessage = 33; - private org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder> inviteMessageBuilder_; - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public boolean hasInviteMessage() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getInviteMessage() { - if (inviteMessageBuilder_ == null) { - return inviteMessage_; - } else { - return inviteMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public Builder setInviteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage value) { - if (inviteMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - inviteMessage_ = value; - onChanged(); - } else { - inviteMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public Builder setInviteMessage( - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder builderForValue) { - if (inviteMessageBuilder_ == null) { - inviteMessage_ = builderForValue.build(); - onChanged(); - } else { - inviteMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public Builder mergeInviteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage value) { - if (inviteMessageBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004) && - inviteMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance()) { - inviteMessage_ = - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.newBuilder(inviteMessage_).mergeFrom(value).buildPartial(); - } else { - inviteMessage_ = value; - } - onChanged(); - } else { - inviteMessageBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public Builder clearInviteMessage() { - if (inviteMessageBuilder_ == null) { - inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); - onChanged(); - } else { - inviteMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder getInviteMessageBuilder() { - bitField0_ |= 0x00000004; - onChanged(); - return getInviteMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder getInviteMessageOrBuilder() { - if (inviteMessageBuilder_ != null) { - return inviteMessageBuilder_.getMessageOrBuilder(); - } else { - return inviteMessage_; - } - } - /** - * optional .signalservice.GroupInviteMessage inviteMessage = 33; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder> - getInviteMessageFieldBuilder() { - if (inviteMessageBuilder_ == null) { - inviteMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder>( - inviteMessage_, - getParentForChildren(), - isClean()); - inviteMessage_ = null; - } - return inviteMessageBuilder_; - } - - // optional .signalservice.GroupPromoteMessage promoteMessage = 34; - private org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder> promoteMessageBuilder_; - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public boolean hasPromoteMessage() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getPromoteMessage() { - if (promoteMessageBuilder_ == null) { - return promoteMessage_; - } else { - return promoteMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public Builder setPromoteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage value) { - if (promoteMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - promoteMessage_ = value; - onChanged(); - } else { - promoteMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00000008; - return this; - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public Builder setPromoteMessage( - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder builderForValue) { - if (promoteMessageBuilder_ == null) { - promoteMessage_ = builderForValue.build(); - onChanged(); - } else { - promoteMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000008; - return this; - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public Builder mergePromoteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage value) { - if (promoteMessageBuilder_ == null) { - if (((bitField0_ & 0x00000008) == 0x00000008) && - promoteMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance()) { - promoteMessage_ = - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.newBuilder(promoteMessage_).mergeFrom(value).buildPartial(); - } else { - promoteMessage_ = value; - } - onChanged(); - } else { - promoteMessageBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000008; - return this; - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public Builder clearPromoteMessage() { - if (promoteMessageBuilder_ == null) { - promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); - onChanged(); - } else { - promoteMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000008); - return this; - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder getPromoteMessageBuilder() { - bitField0_ |= 0x00000008; - onChanged(); - return getPromoteMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder getPromoteMessageOrBuilder() { - if (promoteMessageBuilder_ != null) { - return promoteMessageBuilder_.getMessageOrBuilder(); - } else { - return promoteMessage_; - } - } - /** - * optional .signalservice.GroupPromoteMessage promoteMessage = 34; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder> - getPromoteMessageFieldBuilder() { - if (promoteMessageBuilder_ == null) { - promoteMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder>( - promoteMessage_, - getParentForChildren(), - isClean()); - promoteMessage_ = null; - } - return promoteMessageBuilder_; - } - - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.GroupMessage) - } - - static { - defaultInstance = new GroupMessage(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.DataMessage.GroupMessage) - } - public interface ClosedGroupControlMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -11637,26 +10518,6 @@ public final class SignalServiceProtos { * optional uint32 expirationTimer = 8; */ int getExpirationTimer(); - - // optional bytes memberPrivateKey = 9; - /** - * optional bytes memberPrivateKey = 9; - */ - boolean hasMemberPrivateKey(); - /** - * optional bytes memberPrivateKey = 9; - */ - com.google.protobuf.ByteString getMemberPrivateKey(); - - // optional bytes privateKey = 10; - /** - * optional bytes privateKey = 10; - */ - boolean hasPrivateKey(); - /** - * optional bytes privateKey = 10; - */ - com.google.protobuf.ByteString getPrivateKey(); } /** * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage} @@ -11772,16 +10633,6 @@ public final class SignalServiceProtos { expirationTimer_ = input.readUInt32(); break; } - case 74: { - bitField0_ |= 0x00000020; - memberPrivateKey_ = input.readBytes(); - break; - } - case 82: { - bitField0_ |= 0x00000040; - privateKey_ = input.readBytes(); - break; - } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -11879,46 +10730,6 @@ public final class SignalServiceProtos { * MEMBER_LEFT = 7; */ MEMBER_LEFT(5, 7), - /** - * INVITE = 9; - * - *
-         * publicKey, name, memberPrivateKey
-         * 
- */ - INVITE(6, 9), - /** - * PROMOTE = 10; - * - *
-         * publicKey, privateKey
-         * 
- */ - PROMOTE(7, 10), - /** - * DELETE_GROUP = 11; - * - *
-         * publicKey, members
-         * 
- */ - DELETE_GROUP(8, 11), - /** - * DELETE_MESSAGES = 12; - * - *
-         * publicKey
-         * 
- */ - DELETE_MESSAGES(9, 12), - /** - * DELETE_ATTACHMENTS = 13; - * - *
-         * publicKey
-         * 
- */ - DELETE_ATTACHMENTS(10, 13), ; /** @@ -11965,46 +10776,6 @@ public final class SignalServiceProtos { * MEMBER_LEFT = 7; */ public static final int MEMBER_LEFT_VALUE = 7; - /** - * INVITE = 9; - * - *
-         * publicKey, name, memberPrivateKey
-         * 
- */ - public static final int INVITE_VALUE = 9; - /** - * PROMOTE = 10; - * - *
-         * publicKey, privateKey
-         * 
- */ - public static final int PROMOTE_VALUE = 10; - /** - * DELETE_GROUP = 11; - * - *
-         * publicKey, members
-         * 
- */ - public static final int DELETE_GROUP_VALUE = 11; - /** - * DELETE_MESSAGES = 12; - * - *
-         * publicKey
-         * 
- */ - public static final int DELETE_MESSAGES_VALUE = 12; - /** - * DELETE_ATTACHMENTS = 13; - * - *
-         * publicKey
-         * 
- */ - public static final int DELETE_ATTACHMENTS_VALUE = 13; public final int getNumber() { return value; } @@ -12017,11 +10788,6 @@ public final class SignalServiceProtos { case 5: return MEMBERS_ADDED; case 6: return MEMBERS_REMOVED; case 7: return MEMBER_LEFT; - case 9: return INVITE; - case 10: return PROMOTE; - case 11: return DELETE_GROUP; - case 12: return DELETE_MESSAGES; - case 13: return DELETE_ATTACHMENTS; default: return null; } } @@ -12840,38 +11606,6 @@ public final class SignalServiceProtos { return expirationTimer_; } - // optional bytes memberPrivateKey = 9; - public static final int MEMBERPRIVATEKEY_FIELD_NUMBER = 9; - private com.google.protobuf.ByteString memberPrivateKey_; - /** - * optional bytes memberPrivateKey = 9; - */ - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - * optional bytes memberPrivateKey = 9; - */ - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - - // optional bytes privateKey = 10; - public static final int PRIVATEKEY_FIELD_NUMBER = 10; - private com.google.protobuf.ByteString privateKey_; - /** - * optional bytes privateKey = 10; - */ - public boolean hasPrivateKey() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - * optional bytes privateKey = 10; - */ - public com.google.protobuf.ByteString getPrivateKey() { - return privateKey_; - } - private void initFields() { type_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.NEW; publicKey_ = com.google.protobuf.ByteString.EMPTY; @@ -12881,8 +11615,6 @@ public final class SignalServiceProtos { admins_ = java.util.Collections.emptyList(); wrappers_ = java.util.Collections.emptyList(); expirationTimer_ = 0; - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - privateKey_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -12936,12 +11668,6 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeUInt32(8, expirationTimer_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBytes(9, memberPrivateKey_); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeBytes(10, privateKey_); - } getUnknownFields().writeTo(output); } @@ -12993,14 +11719,6 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(8, expirationTimer_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(9, memberPrivateKey_); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(10, privateKey_); - } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -13143,10 +11861,6 @@ public final class SignalServiceProtos { } expirationTimer_ = 0; bitField0_ = (bitField0_ & ~0x00000080); - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000100); - privateKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000200); return this; } @@ -13218,14 +11932,6 @@ public final class SignalServiceProtos { to_bitField0_ |= 0x00000010; } result.expirationTimer_ = expirationTimer_; - if (((from_bitField0_ & 0x00000100) == 0x00000100)) { - to_bitField0_ |= 0x00000020; - } - result.memberPrivateKey_ = memberPrivateKey_; - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000040; - } - result.privateKey_ = privateKey_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -13305,12 +12011,6 @@ public final class SignalServiceProtos { if (other.hasExpirationTimer()) { setExpirationTimer(other.getExpirationTimer()); } - if (other.hasMemberPrivateKey()) { - setMemberPrivateKey(other.getMemberPrivateKey()); - } - if (other.hasPrivateKey()) { - setPrivateKey(other.getPrivateKey()); - } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -14050,78 +12750,6 @@ public final class SignalServiceProtos { return this; } - // optional bytes memberPrivateKey = 9; - private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes memberPrivateKey = 9; - */ - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - /** - * optional bytes memberPrivateKey = 9; - */ - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - /** - * optional bytes memberPrivateKey = 9; - */ - public Builder setMemberPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000100; - memberPrivateKey_ = value; - onChanged(); - return this; - } - /** - * optional bytes memberPrivateKey = 9; - */ - public Builder clearMemberPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000100); - memberPrivateKey_ = getDefaultInstance().getMemberPrivateKey(); - onChanged(); - return this; - } - - // optional bytes privateKey = 10; - private com.google.protobuf.ByteString privateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes privateKey = 10; - */ - public boolean hasPrivateKey() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - /** - * optional bytes privateKey = 10; - */ - public com.google.protobuf.ByteString getPrivateKey() { - return privateKey_; - } - /** - * optional bytes privateKey = 10; - */ - public Builder setPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000200; - privateKey_ = value; - onChanged(); - return this; - } - /** - * optional bytes privateKey = 10; - */ - public Builder clearPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000200); - privateKey_ = getDefaultInstance().getPrivateKey(); - onChanged(); - return this; - } - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.ClosedGroupControlMessage) } @@ -15229,6 +13857,28 @@ public final class SignalServiceProtos { return attachments_.get(index); } + // optional .signalservice.GroupContext group = 3; + public static final int GROUP_FIELD_NUMBER = 3; + private org.session.libsignal.protos.SignalServiceProtos.GroupContext group_; + /** + * optional .signalservice.GroupContext group = 3; + */ + public boolean hasGroup() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContext getGroup() { + return group_; + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder getGroupOrBuilder() { + return group_; + } + // optional uint32 flags = 4; public static final int FLAGS_FIELD_NUMBER = 4; private int flags_; @@ -15236,7 +13886,7 @@ public final class SignalServiceProtos { * optional uint32 flags = 4; */ public boolean hasFlags() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 flags = 4; @@ -15252,7 +13902,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public boolean hasExpireTimer() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional uint32 expireTimer = 5; @@ -15268,7 +13918,7 @@ public final class SignalServiceProtos { * optional bytes profileKey = 6; */ public boolean hasProfileKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional bytes profileKey = 6; @@ -15284,7 +13934,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public boolean hasTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional uint64 timestamp = 7; @@ -15300,7 +13950,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Quote quote = 8; */ public boolean hasQuote() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional .signalservice.DataMessage.Quote quote = 8; @@ -15358,7 +14008,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Reaction reaction = 11; */ public boolean hasReaction() { - return ((bitField0_ & 0x00000040) == 0x00000040); + return ((bitField0_ & 0x00000080) == 0x00000080); } /** * optional .signalservice.DataMessage.Reaction reaction = 11; @@ -15380,7 +14030,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.LokiProfile profile = 101; */ public boolean hasProfile() { - return ((bitField0_ & 0x00000080) == 0x00000080); + return ((bitField0_ & 0x00000100) == 0x00000100); } /** * optional .signalservice.DataMessage.LokiProfile profile = 101; @@ -15402,7 +14052,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ public boolean hasOpenGroupInvitation() { - return ((bitField0_ & 0x00000100) == 0x00000100); + return ((bitField0_ & 0x00000200) == 0x00000200); } /** * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; @@ -15424,7 +14074,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; */ public boolean hasClosedGroupControlMessage() { - return ((bitField0_ & 0x00000200) == 0x00000200); + return ((bitField0_ & 0x00000400) == 0x00000400); } /** * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; @@ -15446,7 +14096,7 @@ public final class SignalServiceProtos { * optional string syncTarget = 105; */ public boolean hasSyncTarget() { - return ((bitField0_ & 0x00000400) == 0x00000400); + return ((bitField0_ & 0x00000800) == 0x00000800); } /** * optional string syncTarget = 105; @@ -15482,31 +14132,10 @@ public final class SignalServiceProtos { } } - // optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - public static final int GROUPMESSAGE_FIELD_NUMBER = 120; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage groupMessage_; - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public boolean hasGroupMessage() { - return ((bitField0_ & 0x00000800) == 0x00000800); - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getGroupMessage() { - return groupMessage_; - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder getGroupMessageOrBuilder() { - return groupMessage_; - } - private void initFields() { body_ = ""; attachments_ = java.util.Collections.emptyList(); + group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); flags_ = 0; expireTimer_ = 0; profileKey_ = com.google.protobuf.ByteString.EMPTY; @@ -15518,7 +14147,6 @@ public final class SignalServiceProtos { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance(); closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance(); syncTarget_ = ""; - groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -15531,6 +14159,12 @@ public final class SignalServiceProtos { return false; } } + if (hasGroup()) { + if (!getGroup().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } if (hasQuote()) { if (!getQuote().isInitialized()) { memoizedIsInitialized = 0; @@ -15561,12 +14195,6 @@ public final class SignalServiceProtos { return false; } } - if (hasGroupMessage()) { - if (!getGroupMessage().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } memoizedIsInitialized = 1; return true; } @@ -15581,40 +14209,40 @@ public final class SignalServiceProtos { output.writeMessage(2, attachments_.get(i)); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(4, flags_); + output.writeMessage(3, group_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeUInt32(5, expireTimer_); + output.writeUInt32(4, flags_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(6, profileKey_); + output.writeUInt32(5, expireTimer_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeUInt64(7, timestamp_); + output.writeBytes(6, profileKey_); } if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeUInt64(7, timestamp_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeMessage(8, quote_); } for (int i = 0; i < preview_.size(); i++) { output.writeMessage(10, preview_.get(i)); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { output.writeMessage(11, reaction_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { output.writeMessage(101, profile_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { output.writeMessage(102, openGroupInvitation_); } - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (((bitField0_ & 0x00000400) == 0x00000400)) { output.writeMessage(104, closedGroupControlMessage_); } - if (((bitField0_ & 0x00000400) == 0x00000400)) { - output.writeBytes(105, getSyncTargetBytes()); - } if (((bitField0_ & 0x00000800) == 0x00000800)) { - output.writeMessage(120, groupMessage_); + output.writeBytes(105, getSyncTargetBytes()); } getUnknownFields().writeTo(output); } @@ -15635,21 +14263,25 @@ public final class SignalServiceProtos { } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(4, flags_); + .computeMessageSize(3, group_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(5, expireTimer_); + .computeUInt32Size(4, flags_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(6, profileKey_); + .computeUInt32Size(5, expireTimer_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(7, timestamp_); + .computeBytesSize(6, profileKey_); } if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(7, timestamp_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(8, quote_); } @@ -15657,29 +14289,25 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(10, preview_.get(i)); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(11, reaction_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(101, profile_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(102, openGroupInvitation_); } - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (((bitField0_ & 0x00000400) == 0x00000400)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(104, closedGroupControlMessage_); } - if (((bitField0_ & 0x00000400) == 0x00000400)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(105, getSyncTargetBytes()); - } if (((bitField0_ & 0x00000800) == 0x00000800)) { size += com.google.protobuf.CodedOutputStream - .computeMessageSize(120, groupMessage_); + .computeBytesSize(105, getSyncTargetBytes()); } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; @@ -15790,13 +14418,13 @@ public final class SignalServiceProtos { private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getAttachmentsFieldBuilder(); + getGroupFieldBuilder(); getQuoteFieldBuilder(); getPreviewFieldBuilder(); getReactionFieldBuilder(); getProfileFieldBuilder(); getOpenGroupInvitationFieldBuilder(); getClosedGroupControlMessageFieldBuilder(); - getGroupMessageFieldBuilder(); } } private static Builder create() { @@ -15813,23 +14441,29 @@ public final class SignalServiceProtos { } else { attachmentsBuilder_.clear(); } - flags_ = 0; + if (groupBuilder_ == null) { + group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); + } else { + groupBuilder_.clear(); + } bitField0_ = (bitField0_ & ~0x00000004); - expireTimer_ = 0; + flags_ = 0; bitField0_ = (bitField0_ & ~0x00000008); - profileKey_ = com.google.protobuf.ByteString.EMPTY; + expireTimer_ = 0; bitField0_ = (bitField0_ & ~0x00000010); - timestamp_ = 0L; + profileKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000020); + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000040); if (quoteBuilder_ == null) { quote_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance(); } else { quoteBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000040); + bitField0_ = (bitField0_ & ~0x00000080); if (previewBuilder_ == null) { preview_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000100); } else { previewBuilder_.clear(); } @@ -15838,32 +14472,26 @@ public final class SignalServiceProtos { } else { reactionBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000200); if (profileBuilder_ == null) { profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); } else { profileBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000200); + bitField0_ = (bitField0_ & ~0x00000400); if (openGroupInvitationBuilder_ == null) { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance(); } else { openGroupInvitationBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000400); + bitField0_ = (bitField0_ & ~0x00000800); if (closedGroupControlMessageBuilder_ == null) { closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance(); } else { closedGroupControlMessageBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000800); - syncTarget_ = ""; bitField0_ = (bitField0_ & ~0x00001000); - if (groupMessageBuilder_ == null) { - groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); - } else { - groupMessageBuilder_.clear(); - } + syncTarget_ = ""; bitField0_ = (bitField0_ & ~0x00002000); return this; } @@ -15909,80 +14537,80 @@ public final class SignalServiceProtos { if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000002; } - result.flags_ = flags_; + if (groupBuilder_ == null) { + result.group_ = group_; + } else { + result.group_ = groupBuilder_.build(); + } if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000004; } - result.expireTimer_ = expireTimer_; + result.flags_ = flags_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000008; } - result.profileKey_ = profileKey_; + result.expireTimer_ = expireTimer_; if (((from_bitField0_ & 0x00000020) == 0x00000020)) { to_bitField0_ |= 0x00000010; } - result.timestamp_ = timestamp_; + result.profileKey_ = profileKey_; if (((from_bitField0_ & 0x00000040) == 0x00000040)) { to_bitField0_ |= 0x00000020; } + result.timestamp_ = timestamp_; + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000040; + } if (quoteBuilder_ == null) { result.quote_ = quote_; } else { result.quote_ = quoteBuilder_.build(); } if (previewBuilder_ == null) { - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { preview_ = java.util.Collections.unmodifiableList(preview_); - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000100); } result.preview_ = preview_; } else { result.preview_ = previewBuilder_.build(); } - if (((from_bitField0_ & 0x00000100) == 0x00000100)) { - to_bitField0_ |= 0x00000040; + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000080; } if (reactionBuilder_ == null) { result.reaction_ = reaction_; } else { result.reaction_ = reactionBuilder_.build(); } - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000080; + if (((from_bitField0_ & 0x00000400) == 0x00000400)) { + to_bitField0_ |= 0x00000100; } if (profileBuilder_ == null) { result.profile_ = profile_; } else { result.profile_ = profileBuilder_.build(); } - if (((from_bitField0_ & 0x00000400) == 0x00000400)) { - to_bitField0_ |= 0x00000100; + if (((from_bitField0_ & 0x00000800) == 0x00000800)) { + to_bitField0_ |= 0x00000200; } if (openGroupInvitationBuilder_ == null) { result.openGroupInvitation_ = openGroupInvitation_; } else { result.openGroupInvitation_ = openGroupInvitationBuilder_.build(); } - if (((from_bitField0_ & 0x00000800) == 0x00000800)) { - to_bitField0_ |= 0x00000200; + if (((from_bitField0_ & 0x00001000) == 0x00001000)) { + to_bitField0_ |= 0x00000400; } if (closedGroupControlMessageBuilder_ == null) { result.closedGroupControlMessage_ = closedGroupControlMessage_; } else { result.closedGroupControlMessage_ = closedGroupControlMessageBuilder_.build(); } - if (((from_bitField0_ & 0x00001000) == 0x00001000)) { - to_bitField0_ |= 0x00000400; - } - result.syncTarget_ = syncTarget_; if (((from_bitField0_ & 0x00002000) == 0x00002000)) { to_bitField0_ |= 0x00000800; } - if (groupMessageBuilder_ == null) { - result.groupMessage_ = groupMessage_; - } else { - result.groupMessage_ = groupMessageBuilder_.build(); - } + result.syncTarget_ = syncTarget_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -16030,6 +14658,9 @@ public final class SignalServiceProtos { } } } + if (other.hasGroup()) { + mergeGroup(other.getGroup()); + } if (other.hasFlags()) { setFlags(other.getFlags()); } @@ -16049,7 +14680,7 @@ public final class SignalServiceProtos { if (!other.preview_.isEmpty()) { if (preview_.isEmpty()) { preview_ = other.preview_; - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000100); } else { ensurePreviewIsMutable(); preview_.addAll(other.preview_); @@ -16062,7 +14693,7 @@ public final class SignalServiceProtos { previewBuilder_.dispose(); previewBuilder_ = null; preview_ = other.preview_; - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000100); previewBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPreviewFieldBuilder() : null; @@ -16084,13 +14715,10 @@ public final class SignalServiceProtos { mergeClosedGroupControlMessage(other.getClosedGroupControlMessage()); } if (other.hasSyncTarget()) { - bitField0_ |= 0x00001000; + bitField0_ |= 0x00002000; syncTarget_ = other.syncTarget_; onChanged(); } - if (other.hasGroupMessage()) { - mergeGroupMessage(other.getGroupMessage()); - } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -16102,6 +14730,12 @@ public final class SignalServiceProtos { return false; } } + if (hasGroup()) { + if (!getGroup().isInitialized()) { + + return false; + } + } if (hasQuote()) { if (!getQuote().isInitialized()) { @@ -16132,12 +14766,6 @@ public final class SignalServiceProtos { return false; } } - if (hasGroupMessage()) { - if (!getGroupMessage().isInitialized()) { - - return false; - } - } return true; } @@ -16474,13 +15102,130 @@ public final class SignalServiceProtos { return attachmentsBuilder_; } + // optional .signalservice.GroupContext group = 3; + private org.session.libsignal.protos.SignalServiceProtos.GroupContext group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupContext, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder> groupBuilder_; + /** + * optional .signalservice.GroupContext group = 3; + */ + public boolean hasGroup() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContext getGroup() { + if (groupBuilder_ == null) { + return group_; + } else { + return groupBuilder_.getMessage(); + } + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public Builder setGroup(org.session.libsignal.protos.SignalServiceProtos.GroupContext value) { + if (groupBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + group_ = value; + onChanged(); + } else { + groupBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public Builder setGroup( + org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder builderForValue) { + if (groupBuilder_ == null) { + group_ = builderForValue.build(); + onChanged(); + } else { + groupBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public Builder mergeGroup(org.session.libsignal.protos.SignalServiceProtos.GroupContext value) { + if (groupBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + group_ != org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance()) { + group_ = + org.session.libsignal.protos.SignalServiceProtos.GroupContext.newBuilder(group_).mergeFrom(value).buildPartial(); + } else { + group_ = value; + } + onChanged(); + } else { + groupBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public Builder clearGroup() { + if (groupBuilder_ == null) { + group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); + onChanged(); + } else { + groupBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder getGroupBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getGroupFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.GroupContext group = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder getGroupOrBuilder() { + if (groupBuilder_ != null) { + return groupBuilder_.getMessageOrBuilder(); + } else { + return group_; + } + } + /** + * optional .signalservice.GroupContext group = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupContext, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder> + getGroupFieldBuilder() { + if (groupBuilder_ == null) { + groupBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupContext, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder>( + group_, + getParentForChildren(), + isClean()); + group_ = null; + } + return groupBuilder_; + } + // optional uint32 flags = 4; private int flags_ ; /** * optional uint32 flags = 4; */ public boolean hasFlags() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional uint32 flags = 4; @@ -16492,7 +15237,7 @@ public final class SignalServiceProtos { * optional uint32 flags = 4; */ public Builder setFlags(int value) { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; flags_ = value; onChanged(); return this; @@ -16501,7 +15246,7 @@ public final class SignalServiceProtos { * optional uint32 flags = 4; */ public Builder clearFlags() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); flags_ = 0; onChanged(); return this; @@ -16513,7 +15258,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public boolean hasExpireTimer() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional uint32 expireTimer = 5; @@ -16525,7 +15270,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public Builder setExpireTimer(int value) { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; expireTimer_ = value; onChanged(); return this; @@ -16534,7 +15279,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public Builder clearExpireTimer() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); expireTimer_ = 0; onChanged(); return this; @@ -16546,7 +15291,7 @@ public final class SignalServiceProtos { * optional bytes profileKey = 6; */ public boolean hasProfileKey() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional bytes profileKey = 6; @@ -16561,7 +15306,7 @@ public final class SignalServiceProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; profileKey_ = value; onChanged(); return this; @@ -16570,7 +15315,7 @@ public final class SignalServiceProtos { * optional bytes profileKey = 6; */ public Builder clearProfileKey() { - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); profileKey_ = getDefaultInstance().getProfileKey(); onChanged(); return this; @@ -16582,7 +15327,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public boolean hasTimestamp() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional uint64 timestamp = 7; @@ -16594,7 +15339,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public Builder setTimestamp(long value) { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000040; timestamp_ = value; onChanged(); return this; @@ -16603,7 +15348,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000040); timestamp_ = 0L; onChanged(); return this; @@ -16617,7 +15362,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Quote quote = 8; */ public boolean hasQuote() { - return ((bitField0_ & 0x00000040) == 0x00000040); + return ((bitField0_ & 0x00000080) == 0x00000080); } /** * optional .signalservice.DataMessage.Quote quote = 8; @@ -16642,7 +15387,7 @@ public final class SignalServiceProtos { } else { quoteBuilder_.setMessage(value); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; return this; } /** @@ -16656,7 +15401,7 @@ public final class SignalServiceProtos { } else { quoteBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; return this; } /** @@ -16664,7 +15409,7 @@ public final class SignalServiceProtos { */ public Builder mergeQuote(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote value) { if (quoteBuilder_ == null) { - if (((bitField0_ & 0x00000040) == 0x00000040) && + if (((bitField0_ & 0x00000080) == 0x00000080) && quote_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance()) { quote_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.newBuilder(quote_).mergeFrom(value).buildPartial(); @@ -16675,7 +15420,7 @@ public final class SignalServiceProtos { } else { quoteBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; return this; } /** @@ -16688,14 +15433,14 @@ public final class SignalServiceProtos { } else { quoteBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000040); + bitField0_ = (bitField0_ & ~0x00000080); return this; } /** * optional .signalservice.DataMessage.Quote quote = 8; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder getQuoteBuilder() { - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000080; onChanged(); return getQuoteFieldBuilder().getBuilder(); } @@ -16730,9 +15475,9 @@ public final class SignalServiceProtos { private java.util.List preview_ = java.util.Collections.emptyList(); private void ensurePreviewIsMutable() { - if (!((bitField0_ & 0x00000080) == 0x00000080)) { + if (!((bitField0_ & 0x00000100) == 0x00000100)) { preview_ = new java.util.ArrayList(preview_); - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000100; } } @@ -16881,7 +15626,7 @@ public final class SignalServiceProtos { public Builder clearPreview() { if (previewBuilder_ == null) { preview_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000100); onChanged(); } else { previewBuilder_.clear(); @@ -16958,7 +15703,7 @@ public final class SignalServiceProtos { previewBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder>( preview_, - ((bitField0_ & 0x00000080) == 0x00000080), + ((bitField0_ & 0x00000100) == 0x00000100), getParentForChildren(), isClean()); preview_ = null; @@ -16974,7 +15719,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Reaction reaction = 11; */ public boolean hasReaction() { - return ((bitField0_ & 0x00000100) == 0x00000100); + return ((bitField0_ & 0x00000200) == 0x00000200); } /** * optional .signalservice.DataMessage.Reaction reaction = 11; @@ -16999,7 +15744,7 @@ public final class SignalServiceProtos { } else { reactionBuilder_.setMessage(value); } - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; return this; } /** @@ -17013,7 +15758,7 @@ public final class SignalServiceProtos { } else { reactionBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; return this; } /** @@ -17021,7 +15766,7 @@ public final class SignalServiceProtos { */ public Builder mergeReaction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction value) { if (reactionBuilder_ == null) { - if (((bitField0_ & 0x00000100) == 0x00000100) && + if (((bitField0_ & 0x00000200) == 0x00000200) && reaction_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance()) { reaction_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.newBuilder(reaction_).mergeFrom(value).buildPartial(); @@ -17032,7 +15777,7 @@ public final class SignalServiceProtos { } else { reactionBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; return this; } /** @@ -17045,14 +15790,14 @@ public final class SignalServiceProtos { } else { reactionBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000200); return this; } /** * optional .signalservice.DataMessage.Reaction reaction = 11; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder getReactionBuilder() { - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000200; onChanged(); return getReactionFieldBuilder().getBuilder(); } @@ -17091,7 +15836,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.LokiProfile profile = 101; */ public boolean hasProfile() { - return ((bitField0_ & 0x00000200) == 0x00000200); + return ((bitField0_ & 0x00000400) == 0x00000400); } /** * optional .signalservice.DataMessage.LokiProfile profile = 101; @@ -17116,7 +15861,7 @@ public final class SignalServiceProtos { } else { profileBuilder_.setMessage(value); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000400; return this; } /** @@ -17130,7 +15875,7 @@ public final class SignalServiceProtos { } else { profileBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000400; return this; } /** @@ -17138,7 +15883,7 @@ public final class SignalServiceProtos { */ public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { if (profileBuilder_ == null) { - if (((bitField0_ & 0x00000200) == 0x00000200) && + if (((bitField0_ & 0x00000400) == 0x00000400) && profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.newBuilder(profile_).mergeFrom(value).buildPartial(); @@ -17149,7 +15894,7 @@ public final class SignalServiceProtos { } else { profileBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000400; return this; } /** @@ -17162,14 +15907,14 @@ public final class SignalServiceProtos { } else { profileBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000200); + bitField0_ = (bitField0_ & ~0x00000400); return this; } /** * optional .signalservice.DataMessage.LokiProfile profile = 101; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000400; onChanged(); return getProfileFieldBuilder().getBuilder(); } @@ -17208,7 +15953,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ public boolean hasOpenGroupInvitation() { - return ((bitField0_ & 0x00000400) == 0x00000400); + return ((bitField0_ & 0x00000800) == 0x00000800); } /** * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; @@ -17233,7 +15978,7 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.setMessage(value); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000800; return this; } /** @@ -17247,7 +15992,7 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000800; return this; } /** @@ -17255,7 +16000,7 @@ public final class SignalServiceProtos { */ public Builder mergeOpenGroupInvitation(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation value) { if (openGroupInvitationBuilder_ == null) { - if (((bitField0_ & 0x00000400) == 0x00000400) && + if (((bitField0_ & 0x00000800) == 0x00000800) && openGroupInvitation_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance()) { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.newBuilder(openGroupInvitation_).mergeFrom(value).buildPartial(); @@ -17266,7 +16011,7 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000800; return this; } /** @@ -17279,14 +16024,14 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000400); + bitField0_ = (bitField0_ & ~0x00000800); return this; } /** * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder getOpenGroupInvitationBuilder() { - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000800; onChanged(); return getOpenGroupInvitationFieldBuilder().getBuilder(); } @@ -17325,7 +16070,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; */ public boolean hasClosedGroupControlMessage() { - return ((bitField0_ & 0x00000800) == 0x00000800); + return ((bitField0_ & 0x00001000) == 0x00001000); } /** * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; @@ -17350,7 +16095,7 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.setMessage(value); } - bitField0_ |= 0x00000800; + bitField0_ |= 0x00001000; return this; } /** @@ -17364,7 +16109,7 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000800; + bitField0_ |= 0x00001000; return this; } /** @@ -17372,7 +16117,7 @@ public final class SignalServiceProtos { */ public Builder mergeClosedGroupControlMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage value) { if (closedGroupControlMessageBuilder_ == null) { - if (((bitField0_ & 0x00000800) == 0x00000800) && + if (((bitField0_ & 0x00001000) == 0x00001000) && closedGroupControlMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance()) { closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.newBuilder(closedGroupControlMessage_).mergeFrom(value).buildPartial(); @@ -17383,7 +16128,7 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000800; + bitField0_ |= 0x00001000; return this; } /** @@ -17396,14 +16141,14 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000800); + bitField0_ = (bitField0_ & ~0x00001000); return this; } /** * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder getClosedGroupControlMessageBuilder() { - bitField0_ |= 0x00000800; + bitField0_ |= 0x00001000; onChanged(); return getClosedGroupControlMessageFieldBuilder().getBuilder(); } @@ -17440,7 +16185,7 @@ public final class SignalServiceProtos { * optional string syncTarget = 105; */ public boolean hasSyncTarget() { - return ((bitField0_ & 0x00001000) == 0x00001000); + return ((bitField0_ & 0x00002000) == 0x00002000); } /** * optional string syncTarget = 105; @@ -17480,7 +16225,7 @@ public final class SignalServiceProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00001000; + bitField0_ |= 0x00002000; syncTarget_ = value; onChanged(); return this; @@ -17489,7 +16234,7 @@ public final class SignalServiceProtos { * optional string syncTarget = 105; */ public Builder clearSyncTarget() { - bitField0_ = (bitField0_ & ~0x00001000); + bitField0_ = (bitField0_ & ~0x00002000); syncTarget_ = getDefaultInstance().getSyncTarget(); onChanged(); return this; @@ -17502,129 +16247,12 @@ public final class SignalServiceProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00001000; + bitField0_ |= 0x00002000; syncTarget_ = value; onChanged(); return this; } - // optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder> groupMessageBuilder_; - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public boolean hasGroupMessage() { - return ((bitField0_ & 0x00002000) == 0x00002000); - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getGroupMessage() { - if (groupMessageBuilder_ == null) { - return groupMessage_; - } else { - return groupMessageBuilder_.getMessage(); - } - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public Builder setGroupMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage value) { - if (groupMessageBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - groupMessage_ = value; - onChanged(); - } else { - groupMessageBuilder_.setMessage(value); - } - bitField0_ |= 0x00002000; - return this; - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public Builder setGroupMessage( - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder builderForValue) { - if (groupMessageBuilder_ == null) { - groupMessage_ = builderForValue.build(); - onChanged(); - } else { - groupMessageBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00002000; - return this; - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public Builder mergeGroupMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage value) { - if (groupMessageBuilder_ == null) { - if (((bitField0_ & 0x00002000) == 0x00002000) && - groupMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance()) { - groupMessage_ = - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.newBuilder(groupMessage_).mergeFrom(value).buildPartial(); - } else { - groupMessage_ = value; - } - onChanged(); - } else { - groupMessageBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00002000; - return this; - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public Builder clearGroupMessage() { - if (groupMessageBuilder_ == null) { - groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); - onChanged(); - } else { - groupMessageBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00002000); - return this; - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder getGroupMessageBuilder() { - bitField0_ |= 0x00002000; - onChanged(); - return getGroupMessageFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder getGroupMessageOrBuilder() { - if (groupMessageBuilder_ != null) { - return groupMessageBuilder_.getMessageOrBuilder(); - } else { - return groupMessage_; - } - } - /** - * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder> - getGroupMessageFieldBuilder() { - if (groupMessageBuilder_ == null) { - groupMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder>( - groupMessage_, - getParentForChildren(), - isClean()); - groupMessage_ = null; - } - return groupMessageBuilder_; - } - // @@protoc_insertion_point(builder_scope:signalservice.DataMessage) } @@ -17636,2224 +16264,6 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.DataMessage) } - public interface GroupDeleteMessageOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // required bytes publicKey = 1; - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - boolean hasPublicKey(); - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getPublicKey(); - - // required bytes lastEncryptionKey = 2; - /** - * required bytes lastEncryptionKey = 2; - * - *
-     * @required
-     * 
- */ - boolean hasLastEncryptionKey(); - /** - * required bytes lastEncryptionKey = 2; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getLastEncryptionKey(); - } - /** - * Protobuf type {@code signalservice.GroupDeleteMessage} - */ - public static final class GroupDeleteMessage extends - com.google.protobuf.GeneratedMessage - implements GroupDeleteMessageOrBuilder { - // Use GroupDeleteMessage.newBuilder() to construct. - private GroupDeleteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private GroupDeleteMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final GroupDeleteMessage defaultInstance; - public static GroupDeleteMessage getDefaultInstance() { - return defaultInstance; - } - - public GroupDeleteMessage getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GroupDeleteMessage( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - publicKey_ = input.readBytes(); - break; - } - case 18: { - bitField0_ |= 0x00000002; - lastEncryptionKey_ = input.readBytes(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public GroupDeleteMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GroupDeleteMessage(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // required bytes publicKey = 1; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_; - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - // required bytes lastEncryptionKey = 2; - public static final int LASTENCRYPTIONKEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString lastEncryptionKey_; - /** - * required bytes lastEncryptionKey = 2; - * - *
-     * @required
-     * 
- */ - public boolean hasLastEncryptionKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required bytes lastEncryptionKey = 2; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getLastEncryptionKey() { - return lastEncryptionKey_; - } - - private void initFields() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasLastEncryptionKey()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, lastEncryptionKey_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, lastEncryptionKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupDeleteMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - publicKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000001); - lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.publicKey_ = publicKey_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.lastEncryptionKey_ = lastEncryptionKey_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasLastEncryptionKey()) { - setLastEncryptionKey(other.getLastEncryptionKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - if (!hasPublicKey()) { - - return false; - } - if (!hasLastEncryptionKey()) { - - return false; - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // required bytes publicKey = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - publicKey_ = value; - onChanged(); - return this; - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - // required bytes lastEncryptionKey = 2; - private com.google.protobuf.ByteString lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes lastEncryptionKey = 2; - * - *
-       * @required
-       * 
- */ - public boolean hasLastEncryptionKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required bytes lastEncryptionKey = 2; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getLastEncryptionKey() { - return lastEncryptionKey_; - } - /** - * required bytes lastEncryptionKey = 2; - * - *
-       * @required
-       * 
- */ - public Builder setLastEncryptionKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - lastEncryptionKey_ = value; - onChanged(); - return this; - } - /** - * required bytes lastEncryptionKey = 2; - * - *
-       * @required
-       * 
- */ - public Builder clearLastEncryptionKey() { - bitField0_ = (bitField0_ & ~0x00000002); - lastEncryptionKey_ = getDefaultInstance().getLastEncryptionKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupDeleteMessage) - } - - static { - defaultInstance = new GroupDeleteMessage(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupDeleteMessage) - } - - public interface GroupMemberLeftMessageOrBuilder - extends com.google.protobuf.MessageOrBuilder { - } - /** - * Protobuf type {@code signalservice.GroupMemberLeftMessage} - * - *
-   * the pubkey of the member left is included as part of the closed group encryption logic (senderIdentity on desktop)
-   * 
- */ - public static final class GroupMemberLeftMessage extends - com.google.protobuf.GeneratedMessage - implements GroupMemberLeftMessageOrBuilder { - // Use GroupMemberLeftMessage.newBuilder() to construct. - private GroupMemberLeftMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private GroupMemberLeftMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final GroupMemberLeftMessage defaultInstance; - public static GroupMemberLeftMessage getDefaultInstance() { - return defaultInstance; - } - - public GroupMemberLeftMessage getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GroupMemberLeftMessage( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public GroupMemberLeftMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GroupMemberLeftMessage(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private void initFields() { - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupMemberLeftMessage} - * - *
-     * the pubkey of the member left is included as part of the closed group encryption logic (senderIdentity on desktop)
-     * 
- */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage(this); - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance()) return this; - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupMemberLeftMessage) - } - - static { - defaultInstance = new GroupMemberLeftMessage(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupMemberLeftMessage) - } - - public interface GroupInviteMessageOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // required bytes publicKey = 1; - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - boolean hasPublicKey(); - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getPublicKey(); - - // required string name = 2; - /** - * required string name = 2; - * - *
-     * @required
-     * 
- */ - boolean hasName(); - /** - * required string name = 2; - * - *
-     * @required
-     * 
- */ - java.lang.String getName(); - /** - * required string name = 2; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString - getNameBytes(); - - // required bytes memberPrivateKey = 3; - /** - * required bytes memberPrivateKey = 3; - * - *
-     * @required
-     * 
- */ - boolean hasMemberPrivateKey(); - /** - * required bytes memberPrivateKey = 3; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getMemberPrivateKey(); - } - /** - * Protobuf type {@code signalservice.GroupInviteMessage} - */ - public static final class GroupInviteMessage extends - com.google.protobuf.GeneratedMessage - implements GroupInviteMessageOrBuilder { - // Use GroupInviteMessage.newBuilder() to construct. - private GroupInviteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private GroupInviteMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final GroupInviteMessage defaultInstance; - public static GroupInviteMessage getDefaultInstance() { - return defaultInstance; - } - - public GroupInviteMessage getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GroupInviteMessage( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - publicKey_ = input.readBytes(); - break; - } - case 18: { - bitField0_ |= 0x00000002; - name_ = input.readBytes(); - break; - } - case 26: { - bitField0_ |= 0x00000004; - memberPrivateKey_ = input.readBytes(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public GroupInviteMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GroupInviteMessage(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // required bytes publicKey = 1; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_; - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - // required string name = 2; - public static final int NAME_FIELD_NUMBER = 2; - private java.lang.Object name_; - /** - * required string name = 2; - * - *
-     * @required
-     * 
- */ - public boolean hasName() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required string name = 2; - * - *
-     * @required
-     * 
- */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } - } - /** - * required string name = 2; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // required bytes memberPrivateKey = 3; - public static final int MEMBERPRIVATEKEY_FIELD_NUMBER = 3; - private com.google.protobuf.ByteString memberPrivateKey_; - /** - * required bytes memberPrivateKey = 3; - * - *
-     * @required
-     * 
- */ - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * required bytes memberPrivateKey = 3; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - - private void initFields() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - name_ = ""; - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasName()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasMemberPrivateKey()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getNameBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, memberPrivateKey_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getNameBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, memberPrivateKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupInviteMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - publicKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000001); - name_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.publicKey_ = publicKey_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.name_ = name_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.memberPrivateKey_ = memberPrivateKey_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasName()) { - bitField0_ |= 0x00000002; - name_ = other.name_; - onChanged(); - } - if (other.hasMemberPrivateKey()) { - setMemberPrivateKey(other.getMemberPrivateKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - if (!hasPublicKey()) { - - return false; - } - if (!hasName()) { - - return false; - } - if (!hasMemberPrivateKey()) { - - return false; - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // required bytes publicKey = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - publicKey_ = value; - onChanged(); - return this; - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - // required string name = 2; - private java.lang.Object name_ = ""; - /** - * required string name = 2; - * - *
-       * @required
-       * 
- */ - public boolean hasName() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required string name = 2; - * - *
-       * @required
-       * 
- */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - name_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * required string name = 2; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * required string name = 2; - * - *
-       * @required
-       * 
- */ - public Builder setName( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - name_ = value; - onChanged(); - return this; - } - /** - * required string name = 2; - * - *
-       * @required
-       * 
- */ - public Builder clearName() { - bitField0_ = (bitField0_ & ~0x00000002); - name_ = getDefaultInstance().getName(); - onChanged(); - return this; - } - /** - * required string name = 2; - * - *
-       * @required
-       * 
- */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - name_ = value; - onChanged(); - return this; - } - - // required bytes memberPrivateKey = 3; - private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes memberPrivateKey = 3; - * - *
-       * @required
-       * 
- */ - public boolean hasMemberPrivateKey() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * required bytes memberPrivateKey = 3; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getMemberPrivateKey() { - return memberPrivateKey_; - } - /** - * required bytes memberPrivateKey = 3; - * - *
-       * @required
-       * 
- */ - public Builder setMemberPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - memberPrivateKey_ = value; - onChanged(); - return this; - } - /** - * required bytes memberPrivateKey = 3; - * - *
-       * @required
-       * 
- */ - public Builder clearMemberPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000004); - memberPrivateKey_ = getDefaultInstance().getMemberPrivateKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupInviteMessage) - } - - static { - defaultInstance = new GroupInviteMessage(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupInviteMessage) - } - - public interface GroupPromoteMessageOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // required bytes publicKey = 1; - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - boolean hasPublicKey(); - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getPublicKey(); - - // required bytes encryptedPrivateKey = 2; - /** - * required bytes encryptedPrivateKey = 2; - * - *
-     * @required
-     * 
- */ - boolean hasEncryptedPrivateKey(); - /** - * required bytes encryptedPrivateKey = 2; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getEncryptedPrivateKey(); - } - /** - * Protobuf type {@code signalservice.GroupPromoteMessage} - */ - public static final class GroupPromoteMessage extends - com.google.protobuf.GeneratedMessage - implements GroupPromoteMessageOrBuilder { - // Use GroupPromoteMessage.newBuilder() to construct. - private GroupPromoteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private GroupPromoteMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final GroupPromoteMessage defaultInstance; - public static GroupPromoteMessage getDefaultInstance() { - return defaultInstance; - } - - public GroupPromoteMessage getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GroupPromoteMessage( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - publicKey_ = input.readBytes(); - break; - } - case 18: { - bitField0_ |= 0x00000002; - encryptedPrivateKey_ = input.readBytes(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public GroupPromoteMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GroupPromoteMessage(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // required bytes publicKey = 1; - public static final int PUBLICKEY_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString publicKey_; - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required bytes publicKey = 1; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - - // required bytes encryptedPrivateKey = 2; - public static final int ENCRYPTEDPRIVATEKEY_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString encryptedPrivateKey_; - /** - * required bytes encryptedPrivateKey = 2; - * - *
-     * @required
-     * 
- */ - public boolean hasEncryptedPrivateKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required bytes encryptedPrivateKey = 2; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getEncryptedPrivateKey() { - return encryptedPrivateKey_; - } - - private void initFields() { - publicKey_ = com.google.protobuf.ByteString.EMPTY; - encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - if (!hasPublicKey()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasEncryptedPrivateKey()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, publicKey_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, encryptedPrivateKey_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, publicKey_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, encryptedPrivateKey_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupPromoteMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - publicKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000001); - encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage build() { - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.publicKey_ = publicKey_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.encryptedPrivateKey_ = encryptedPrivateKey_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance()) return this; - if (other.hasPublicKey()) { - setPublicKey(other.getPublicKey()); - } - if (other.hasEncryptedPrivateKey()) { - setEncryptedPrivateKey(other.getEncryptedPrivateKey()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - if (!hasPublicKey()) { - - return false; - } - if (!hasEncryptedPrivateKey()) { - - return false; - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // required bytes publicKey = 1; - private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public boolean hasPublicKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getPublicKey() { - return publicKey_; - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public Builder setPublicKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - publicKey_ = value; - onChanged(); - return this; - } - /** - * required bytes publicKey = 1; - * - *
-       * @required
-       * 
- */ - public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000001); - publicKey_ = getDefaultInstance().getPublicKey(); - onChanged(); - return this; - } - - // required bytes encryptedPrivateKey = 2; - private com.google.protobuf.ByteString encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes encryptedPrivateKey = 2; - * - *
-       * @required
-       * 
- */ - public boolean hasEncryptedPrivateKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required bytes encryptedPrivateKey = 2; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getEncryptedPrivateKey() { - return encryptedPrivateKey_; - } - /** - * required bytes encryptedPrivateKey = 2; - * - *
-       * @required
-       * 
- */ - public Builder setEncryptedPrivateKey(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - encryptedPrivateKey_ = value; - onChanged(); - return this; - } - /** - * required bytes encryptedPrivateKey = 2; - * - *
-       * @required
-       * 
- */ - public Builder clearEncryptedPrivateKey() { - bitField0_ = (bitField0_ & ~0x00000002); - encryptedPrivateKey_ = getDefaultInstance().getEncryptedPrivateKey(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupPromoteMessage) - } - - static { - defaultInstance = new GroupPromoteMessage(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupPromoteMessage) - } - public interface CallMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -21156,643 +17566,6 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.CallMessage) } - public interface SharedConfigMessageOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // required .signalservice.SharedConfigMessage.Type type = 1; - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-     * @required
-     * 
- */ - boolean hasType(); - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-     * @required
-     * 
- */ - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type getType(); - - // required bytes data = 2; - /** - * required bytes data = 2; - */ - boolean hasData(); - /** - * required bytes data = 2; - */ - com.google.protobuf.ByteString getData(); - } - /** - * Protobuf type {@code signalservice.SharedConfigMessage} - */ - public static final class SharedConfigMessage extends - com.google.protobuf.GeneratedMessage - implements SharedConfigMessageOrBuilder { - // Use SharedConfigMessage.newBuilder() to construct. - private SharedConfigMessage(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private SharedConfigMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final SharedConfigMessage defaultInstance; - public static SharedConfigMessage getDefaultInstance() { - return defaultInstance; - } - - public SharedConfigMessage getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private SharedConfigMessage( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - int rawValue = input.readEnum(); - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type value = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(1, rawValue); - } else { - bitField0_ |= 0x00000001; - type_ = value; - } - break; - } - case 18: { - bitField0_ |= 0x00000002; - data_ = input.readBytes(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.class, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public SharedConfigMessage parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new SharedConfigMessage(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - /** - * Protobuf enum {@code signalservice.SharedConfigMessage.Type} - */ - public enum Type - implements com.google.protobuf.ProtocolMessageEnum { - /** - * USER = 1; - */ - USER(0, 1), - /** - * CLOSED_GROUP_INFO = 2; - */ - CLOSED_GROUP_INFO(1, 2), - /** - * ENCRYPTION_KEYS = 3; - */ - ENCRYPTION_KEYS(2, 3), - /** - * CONVERSATION_READ_STATE = 4; - */ - CONVERSATION_READ_STATE(3, 4), - ; - - /** - * USER = 1; - */ - public static final int USER_VALUE = 1; - /** - * CLOSED_GROUP_INFO = 2; - */ - public static final int CLOSED_GROUP_INFO_VALUE = 2; - /** - * ENCRYPTION_KEYS = 3; - */ - public static final int ENCRYPTION_KEYS_VALUE = 3; - /** - * CONVERSATION_READ_STATE = 4; - */ - public static final int CONVERSATION_READ_STATE_VALUE = 4; - - - public final int getNumber() { return value; } - - public static Type valueOf(int value) { - switch (value) { - case 1: return USER; - case 2: return CLOSED_GROUP_INFO; - case 3: return ENCRYPTION_KEYS; - case 4: return CONVERSATION_READ_STATE; - default: return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { - return internalValueMap; - } - private static com.google.protobuf.Internal.EnumLiteMap - internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public Type findValueByNumber(int number) { - return Type.valueOf(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { - return getDescriptor().getValues().get(index); - } - public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { - return getDescriptor(); - } - public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDescriptor().getEnumTypes().get(0); - } - - private static final Type[] VALUES = values(); - - public static Type valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int index; - private final int value; - - private Type(int index, int value) { - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:signalservice.SharedConfigMessage.Type) - } - - private int bitField0_; - // required .signalservice.SharedConfigMessage.Type type = 1; - public static final int TYPE_FIELD_NUMBER = 1; - private org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type type_; - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-     * @required
-     * 
- */ - public boolean hasType() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-     * @required
-     * 
- */ - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type getType() { - return type_; - } - - // required bytes data = 2; - public static final int DATA_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString data_; - /** - * required bytes data = 2; - */ - public boolean hasData() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required bytes data = 2; - */ - public com.google.protobuf.ByteString getData() { - return data_; - } - - private void initFields() { - type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; - data_ = com.google.protobuf.ByteString.EMPTY; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - if (!hasType()) { - memoizedIsInitialized = 0; - return false; - } - if (!hasData()) { - memoizedIsInitialized = 0; - return false; - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeEnum(1, type_.getNumber()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, data_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeEnumSize(1, type_.getNumber()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, data_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.SharedConfigMessage} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.class, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; - bitField0_ = (bitField0_ & ~0x00000001); - data_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage build() { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result = new org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.type_ = type_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.data_ = data_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance()) return this; - if (other.hasType()) { - setType(other.getType()); - } - if (other.hasData()) { - setData(other.getData()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - if (!hasType()) { - - return false; - } - if (!hasData()) { - - return false; - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // required .signalservice.SharedConfigMessage.Type type = 1; - private org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-       * @required
-       * 
- */ - public boolean hasType() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-       * @required
-       * 
- */ - public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type getType() { - return type_; - } - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-       * @required
-       * 
- */ - public Builder setType(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - type_ = value; - onChanged(); - return this; - } - /** - * required .signalservice.SharedConfigMessage.Type type = 1; - * - *
-       * @required
-       * 
- */ - public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000001); - type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; - onChanged(); - return this; - } - - // required bytes data = 2; - private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY; - /** - * required bytes data = 2; - */ - public boolean hasData() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * required bytes data = 2; - */ - public com.google.protobuf.ByteString getData() { - return data_; - } - /** - * required bytes data = 2; - */ - public Builder setData(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - data_ = value; - onChanged(); - return this; - } - /** - * required bytes data = 2; - */ - public Builder clearData() { - bitField0_ = (bitField0_ & ~0x00000002); - data_ = getDefaultInstance().getData(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.SharedConfigMessage) - } - - static { - defaultInstance = new SharedConfigMessage(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.SharedConfigMessage) - } - public interface ConfigurationMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -25731,6 +21504,30 @@ public final class SignalServiceProtos { * */ boolean getIsApproved(); + + // optional bytes profileKey = 2; + /** + * optional bytes profileKey = 2; + */ + boolean hasProfileKey(); + /** + * optional bytes profileKey = 2; + */ + com.google.protobuf.ByteString getProfileKey(); + + // optional .signalservice.DataMessage.LokiProfile profile = 3; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + boolean hasProfile(); + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile(); + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder(); } /** * Protobuf type {@code signalservice.MessageRequestResponse} @@ -25788,6 +21585,24 @@ public final class SignalServiceProtos { isApproved_ = input.readBool(); break; } + case 18: { + bitField0_ |= 0x00000002; + profileKey_ = input.readBytes(); + break; + } + case 26: { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder subBuilder = null; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + subBuilder = profile_.toBuilder(); + } + profile_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(profile_); + profile_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000004; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -25852,8 +21667,48 @@ public final class SignalServiceProtos { return isApproved_; } + // optional bytes profileKey = 2; + public static final int PROFILEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString profileKey_; + /** + * optional bytes profileKey = 2; + */ + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes profileKey = 2; + */ + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + + // optional .signalservice.DataMessage.LokiProfile profile = 3; + public static final int PROFILE_FIELD_NUMBER = 3; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public boolean hasProfile() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + return profile_; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + return profile_; + } + private void initFields() { isApproved_ = false; + profileKey_ = com.google.protobuf.ByteString.EMPTY; + profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -25874,6 +21729,12 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeBool(1, isApproved_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, profileKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeMessage(3, profile_); + } getUnknownFields().writeTo(output); } @@ -25887,6 +21748,14 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeBoolSize(1, isApproved_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, profileKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, profile_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -25995,6 +21864,7 @@ public final class SignalServiceProtos { } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getProfileFieldBuilder(); } } private static Builder create() { @@ -26005,6 +21875,14 @@ public final class SignalServiceProtos { super.clear(); isApproved_ = false; bitField0_ = (bitField0_ & ~0x00000001); + profileKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + if (profileBuilder_ == null) { + profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); + } else { + profileBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -26037,6 +21915,18 @@ public final class SignalServiceProtos { to_bitField0_ |= 0x00000001; } result.isApproved_ = isApproved_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.profileKey_ = profileKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + if (profileBuilder_ == null) { + result.profile_ = profile_; + } else { + result.profile_ = profileBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -26056,6 +21946,12 @@ public final class SignalServiceProtos { if (other.hasIsApproved()) { setIsApproved(other.getIsApproved()); } + if (other.hasProfileKey()) { + setProfileKey(other.getProfileKey()); + } + if (other.hasProfile()) { + mergeProfile(other.getProfile()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -26136,6 +22032,159 @@ public final class SignalServiceProtos { return this; } + // optional bytes profileKey = 2; + private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes profileKey = 2; + */ + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes profileKey = 2; + */ + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + /** + * optional bytes profileKey = 2; + */ + public Builder setProfileKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + profileKey_ = value; + onChanged(); + return this; + } + /** + * optional bytes profileKey = 2; + */ + public Builder clearProfileKey() { + bitField0_ = (bitField0_ & ~0x00000002); + profileKey_ = getDefaultInstance().getProfileKey(); + onChanged(); + return this; + } + + // optional .signalservice.DataMessage.LokiProfile profile = 3; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> profileBuilder_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public boolean hasProfile() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + if (profileBuilder_ == null) { + return profile_; + } else { + return profileBuilder_.getMessage(); + } + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder setProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + profile_ = value; + onChanged(); + } else { + profileBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder setProfile( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder builderForValue) { + if (profileBuilder_ == null) { + profile_ = builderForValue.build(); + onChanged(); + } else { + profileBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { + profile_ = + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.newBuilder(profile_).mergeFrom(value).buildPartial(); + } else { + profile_ = value; + } + onChanged(); + } else { + profileBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder clearProfile() { + if (profileBuilder_ == null) { + profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); + onChanged(); + } else { + profileBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getProfileFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + if (profileBuilder_ != null) { + return profileBuilder_.getMessageOrBuilder(); + } else { + return profile_; + } + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> + getProfileFieldBuilder() { + if (profileBuilder_ == null) { + profileBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder>( + profile_, + getParentForChildren(), + isClean()); + profile_ = null; + } + return profileBuilder_; + } + // @@protoc_insertion_point(builder_scope:signalservice.MessageRequestResponse) } @@ -28540,6 +24589,1398 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.AttachmentPointer) } + public interface GroupContextOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional bytes id = 1; + /** + * optional bytes id = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasId(); + /** + * optional bytes id = 1; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getId(); + + // optional .signalservice.GroupContext.Type type = 2; + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+     * @required
+     * 
+ */ + boolean hasType(); + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+     * @required
+     * 
+ */ + org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type getType(); + + // optional string name = 3; + /** + * optional string name = 3; + */ + boolean hasName(); + /** + * optional string name = 3; + */ + java.lang.String getName(); + /** + * optional string name = 3; + */ + com.google.protobuf.ByteString + getNameBytes(); + + // repeated string members = 4; + /** + * repeated string members = 4; + */ + java.util.List + getMembersList(); + /** + * repeated string members = 4; + */ + int getMembersCount(); + /** + * repeated string members = 4; + */ + java.lang.String getMembers(int index); + /** + * repeated string members = 4; + */ + com.google.protobuf.ByteString + getMembersBytes(int index); + + // optional .signalservice.AttachmentPointer avatar = 5; + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + boolean hasAvatar(); + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAvatar(); + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAvatarOrBuilder(); + + // repeated string admins = 6; + /** + * repeated string admins = 6; + */ + java.util.List + getAdminsList(); + /** + * repeated string admins = 6; + */ + int getAdminsCount(); + /** + * repeated string admins = 6; + */ + java.lang.String getAdmins(int index); + /** + * repeated string admins = 6; + */ + com.google.protobuf.ByteString + getAdminsBytes(int index); + } + /** + * Protobuf type {@code signalservice.GroupContext} + */ + public static final class GroupContext extends + com.google.protobuf.GeneratedMessage + implements GroupContextOrBuilder { + // Use GroupContext.newBuilder() to construct. + private GroupContext(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private GroupContext(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final GroupContext defaultInstance; + public static GroupContext getDefaultInstance() { + return defaultInstance; + } + + public GroupContext getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GroupContext( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + id_ = input.readBytes(); + break; + } + case 16: { + int rawValue = input.readEnum(); + org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type value = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(2, rawValue); + } else { + bitField0_ |= 0x00000002; + type_ = value; + } + break; + } + case 26: { + bitField0_ |= 0x00000004; + name_ = input.readBytes(); + break; + } + case 34: { + if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) { + members_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000008; + } + members_.add(input.readBytes()); + break; + } + case 42: { + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder subBuilder = null; + if (((bitField0_ & 0x00000008) == 0x00000008)) { + subBuilder = avatar_.toBuilder(); + } + avatar_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(avatar_); + avatar_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000008; + break; + } + case 50: { + if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { + admins_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000020; + } + admins_.add(input.readBytes()); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) { + members_ = new com.google.protobuf.UnmodifiableLazyStringList(members_); + } + if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { + admins_ = new com.google.protobuf.UnmodifiableLazyStringList(admins_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupContext.class, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public GroupContext parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GroupContext(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + /** + * Protobuf enum {@code signalservice.GroupContext.Type} + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + /** + * UNKNOWN = 0; + */ + UNKNOWN(0, 0), + /** + * UPDATE = 1; + */ + UPDATE(1, 1), + /** + * DELIVER = 2; + */ + DELIVER(2, 2), + /** + * QUIT = 3; + */ + QUIT(3, 3), + /** + * REQUEST_INFO = 4; + */ + REQUEST_INFO(4, 4), + ; + + /** + * UNKNOWN = 0; + */ + public static final int UNKNOWN_VALUE = 0; + /** + * UPDATE = 1; + */ + public static final int UPDATE_VALUE = 1; + /** + * DELIVER = 2; + */ + public static final int DELIVER_VALUE = 2; + /** + * QUIT = 3; + */ + public static final int QUIT_VALUE = 3; + /** + * REQUEST_INFO = 4; + */ + public static final int REQUEST_INFO_VALUE = 4; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 0: return UNKNOWN; + case 1: return UPDATE; + case 2: return DELIVER; + case 3: return QUIT; + case 4: return REQUEST_INFO; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDescriptor().getEnumTypes().get(0); + } + + private static final Type[] VALUES = values(); + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:signalservice.GroupContext.Type) + } + + private int bitField0_; + // optional bytes id = 1; + public static final int ID_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString id_; + /** + * optional bytes id = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional bytes id = 1; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getId() { + return id_; + } + + // optional .signalservice.GroupContext.Type type = 2; + public static final int TYPE_FIELD_NUMBER = 2; + private org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type type_; + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+     * @required
+     * 
+ */ + public boolean hasType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+     * @required
+     * 
+ */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type getType() { + return type_; + } + + // optional string name = 3; + public static final int NAME_FIELD_NUMBER = 3; + private java.lang.Object name_; + /** + * optional string name = 3; + */ + public boolean hasName() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string name = 3; + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + name_ = s; + } + return s; + } + } + /** + * optional string name = 3; + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // repeated string members = 4; + public static final int MEMBERS_FIELD_NUMBER = 4; + private com.google.protobuf.LazyStringList members_; + /** + * repeated string members = 4; + */ + public java.util.List + getMembersList() { + return members_; + } + /** + * repeated string members = 4; + */ + public int getMembersCount() { + return members_.size(); + } + /** + * repeated string members = 4; + */ + public java.lang.String getMembers(int index) { + return members_.get(index); + } + /** + * repeated string members = 4; + */ + public com.google.protobuf.ByteString + getMembersBytes(int index) { + return members_.getByteString(index); + } + + // optional .signalservice.AttachmentPointer avatar = 5; + public static final int AVATAR_FIELD_NUMBER = 5; + private org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer avatar_; + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public boolean hasAvatar() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAvatar() { + return avatar_; + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAvatarOrBuilder() { + return avatar_; + } + + // repeated string admins = 6; + public static final int ADMINS_FIELD_NUMBER = 6; + private com.google.protobuf.LazyStringList admins_; + /** + * repeated string admins = 6; + */ + public java.util.List + getAdminsList() { + return admins_; + } + /** + * repeated string admins = 6; + */ + public int getAdminsCount() { + return admins_.size(); + } + /** + * repeated string admins = 6; + */ + public java.lang.String getAdmins(int index) { + return admins_.get(index); + } + /** + * repeated string admins = 6; + */ + public com.google.protobuf.ByteString + getAdminsBytes(int index) { + return admins_.getByteString(index); + } + + private void initFields() { + id_ = com.google.protobuf.ByteString.EMPTY; + type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; + name_ = ""; + members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); + admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (hasAvatar()) { + if (!getAvatar().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeEnum(2, type_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, getNameBytes()); + } + for (int i = 0; i < members_.size(); i++) { + output.writeBytes(4, members_.getByteString(i)); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeMessage(5, avatar_); + } + for (int i = 0; i < admins_.size(); i++) { + output.writeBytes(6, admins_.getByteString(i)); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, id_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(2, type_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getNameBytes()); + } + { + int dataSize = 0; + for (int i = 0; i < members_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(members_.getByteString(i)); + } + size += dataSize; + size += 1 * getMembersList().size(); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(5, avatar_); + } + { + int dataSize = 0; + for (int i = 0; i < admins_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(admins_.getByteString(i)); + } + size += dataSize; + size += 1 * getAdminsList().size(); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupContext prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.GroupContext} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupContext.class, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupContext.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getAvatarFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + id_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; + bitField0_ = (bitField0_ & ~0x00000002); + name_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + if (avatarBuilder_ == null) { + avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); + } else { + avatarBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupContext getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupContext build() { + org.session.libsignal.protos.SignalServiceProtos.GroupContext result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupContext buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.GroupContext result = new org.session.libsignal.protos.SignalServiceProtos.GroupContext(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.id_ = id_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.name_ = name_; + if (((bitField0_ & 0x00000008) == 0x00000008)) { + members_ = new com.google.protobuf.UnmodifiableLazyStringList( + members_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.members_ = members_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000008; + } + if (avatarBuilder_ == null) { + result.avatar_ = avatar_; + } else { + result.avatar_ = avatarBuilder_.build(); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + admins_ = new com.google.protobuf.UnmodifiableLazyStringList( + admins_); + bitField0_ = (bitField0_ & ~0x00000020); + } + result.admins_ = admins_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupContext) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupContext)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupContext other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasName()) { + bitField0_ |= 0x00000004; + name_ = other.name_; + onChanged(); + } + if (!other.members_.isEmpty()) { + if (members_.isEmpty()) { + members_ = other.members_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureMembersIsMutable(); + members_.addAll(other.members_); + } + onChanged(); + } + if (other.hasAvatar()) { + mergeAvatar(other.getAvatar()); + } + if (!other.admins_.isEmpty()) { + if (admins_.isEmpty()) { + admins_ = other.admins_; + bitField0_ = (bitField0_ & ~0x00000020); + } else { + ensureAdminsIsMutable(); + admins_.addAll(other.admins_); + } + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (hasAvatar()) { + if (!getAvatar().isInitialized()) { + + return false; + } + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.GroupContext parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupContext) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional bytes id = 1; + private com.google.protobuf.ByteString id_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes id = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional bytes id = 1; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getId() { + return id_; + } + /** + * optional bytes id = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setId(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + id_ = value; + onChanged(); + return this; + } + /** + * optional bytes id = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = getDefaultInstance().getId(); + onChanged(); + return this; + } + + // optional .signalservice.GroupContext.Type type = 2; + private org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+       * @required
+       * 
+ */ + public boolean hasType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+       * @required
+       * 
+ */ + public org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type getType() { + return type_; + } + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setType(org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + type_ = value; + onChanged(); + return this; + } + /** + * optional .signalservice.GroupContext.Type type = 2; + * + *
+       * @required
+       * 
+ */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000002); + type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; + onChanged(); + return this; + } + + // optional string name = 3; + private java.lang.Object name_ = ""; + /** + * optional string name = 3; + */ + public boolean hasName() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string name = 3; + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string name = 3; + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string name = 3; + */ + public Builder setName( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + name_ = value; + onChanged(); + return this; + } + /** + * optional string name = 3; + */ + public Builder clearName() { + bitField0_ = (bitField0_ & ~0x00000004); + name_ = getDefaultInstance().getName(); + onChanged(); + return this; + } + /** + * optional string name = 3; + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + name_ = value; + onChanged(); + return this; + } + + // repeated string members = 4; + private com.google.protobuf.LazyStringList members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureMembersIsMutable() { + if (!((bitField0_ & 0x00000008) == 0x00000008)) { + members_ = new com.google.protobuf.LazyStringArrayList(members_); + bitField0_ |= 0x00000008; + } + } + /** + * repeated string members = 4; + */ + public java.util.List + getMembersList() { + return java.util.Collections.unmodifiableList(members_); + } + /** + * repeated string members = 4; + */ + public int getMembersCount() { + return members_.size(); + } + /** + * repeated string members = 4; + */ + public java.lang.String getMembers(int index) { + return members_.get(index); + } + /** + * repeated string members = 4; + */ + public com.google.protobuf.ByteString + getMembersBytes(int index) { + return members_.getByteString(index); + } + /** + * repeated string members = 4; + */ + public Builder setMembers( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMembersIsMutable(); + members_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string members = 4; + */ + public Builder addMembers( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMembersIsMutable(); + members_.add(value); + onChanged(); + return this; + } + /** + * repeated string members = 4; + */ + public Builder addAllMembers( + java.lang.Iterable values) { + ensureMembersIsMutable(); + super.addAll(values, members_); + onChanged(); + return this; + } + /** + * repeated string members = 4; + */ + public Builder clearMembers() { + members_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + /** + * repeated string members = 4; + */ + public Builder addMembersBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMembersIsMutable(); + members_.add(value); + onChanged(); + return this; + } + + // optional .signalservice.AttachmentPointer avatar = 5; + private org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> avatarBuilder_; + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public boolean hasAvatar() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAvatar() { + if (avatarBuilder_ == null) { + return avatar_; + } else { + return avatarBuilder_.getMessage(); + } + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public Builder setAvatar(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { + if (avatarBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + avatar_ = value; + onChanged(); + } else { + avatarBuilder_.setMessage(value); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public Builder setAvatar( + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { + if (avatarBuilder_ == null) { + avatar_ = builderForValue.build(); + onChanged(); + } else { + avatarBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public Builder mergeAvatar(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { + if (avatarBuilder_ == null) { + if (((bitField0_ & 0x00000010) == 0x00000010) && + avatar_ != org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance()) { + avatar_ = + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.newBuilder(avatar_).mergeFrom(value).buildPartial(); + } else { + avatar_ = value; + } + onChanged(); + } else { + avatarBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000010; + return this; + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public Builder clearAvatar() { + if (avatarBuilder_ == null) { + avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); + onChanged(); + } else { + avatarBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder getAvatarBuilder() { + bitField0_ |= 0x00000010; + onChanged(); + return getAvatarFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAvatarOrBuilder() { + if (avatarBuilder_ != null) { + return avatarBuilder_.getMessageOrBuilder(); + } else { + return avatar_; + } + } + /** + * optional .signalservice.AttachmentPointer avatar = 5; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> + getAvatarFieldBuilder() { + if (avatarBuilder_ == null) { + avatarBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder>( + avatar_, + getParentForChildren(), + isClean()); + avatar_ = null; + } + return avatarBuilder_; + } + + // repeated string admins = 6; + private com.google.protobuf.LazyStringList admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureAdminsIsMutable() { + if (!((bitField0_ & 0x00000020) == 0x00000020)) { + admins_ = new com.google.protobuf.LazyStringArrayList(admins_); + bitField0_ |= 0x00000020; + } + } + /** + * repeated string admins = 6; + */ + public java.util.List + getAdminsList() { + return java.util.Collections.unmodifiableList(admins_); + } + /** + * repeated string admins = 6; + */ + public int getAdminsCount() { + return admins_.size(); + } + /** + * repeated string admins = 6; + */ + public java.lang.String getAdmins(int index) { + return admins_.get(index); + } + /** + * repeated string admins = 6; + */ + public com.google.protobuf.ByteString + getAdminsBytes(int index) { + return admins_.getByteString(index); + } + /** + * repeated string admins = 6; + */ + public Builder setAdmins( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAdminsIsMutable(); + admins_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string admins = 6; + */ + public Builder addAdmins( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAdminsIsMutable(); + admins_.add(value); + onChanged(); + return this; + } + /** + * repeated string admins = 6; + */ + public Builder addAllAdmins( + java.lang.Iterable values) { + ensureAdminsIsMutable(); + super.addAll(values, admins_); + onChanged(); + return this; + } + /** + * repeated string admins = 6; + */ + public Builder clearAdmins() { + admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000020); + onChanged(); + return this; + } + /** + * repeated string admins = 6; + */ + public Builder addAdminsBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAdminsIsMutable(); + admins_.add(value); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.GroupContext) + } + + static { + defaultInstance = new GroupContext(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.GroupContext) + } + private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_Envelope_descriptor; private static @@ -28600,11 +26041,6 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_DataMessage_OpenGroupInvitation_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_DataMessage_GroupMessage_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor; private static @@ -28620,36 +26056,11 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupDeleteMessage_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupMemberLeftMessage_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupInviteMessage_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupInviteMessage_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupPromoteMessage_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_CallMessage_descriptor; private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_CallMessage_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_SharedConfigMessage_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_SharedConfigMessage_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_ConfigurationMessage_descriptor; private static @@ -28680,6 +26091,11 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_AttachmentPointer_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_GroupContext_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_GroupContext_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -28716,104 +26132,91 @@ public final class SignalServiceProtos { "(\014\"\226\001\n\032DataExtractionNotification\022<\n\004typ" + "e\030\001 \002(\0162..signalservice.DataExtractionNo" + "tification.Type\022\021\n\ttimestamp\030\002 \001(\004\"\'\n\004Ty" + - "pe\022\016\n\nSCREENSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\216\021\n\013" + + "pe\022\016\n\nSCREENSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\361\r\n\013" + "DataMessage\022\014\n\004body\030\001 \001(\t\0225\n\013attachments" + "\030\002 \003(\0132 .signalservice.AttachmentPointer", - "\022\r\n\005flags\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r\022\022\n\n" + - "profileKey\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022/\n\005q" + - "uote\030\010 \001(\0132 .signalservice.DataMessage.Q" + - "uote\0223\n\007preview\030\n \003(\0132\".signalservice.Da" + - "taMessage.Preview\0225\n\010reaction\030\013 \001(\0132#.si" + - "gnalservice.DataMessage.Reaction\0227\n\007prof" + - "ile\030e \001(\0132&.signalservice.DataMessage.Lo" + - "kiProfile\022K\n\023openGroupInvitation\030f \001(\0132." + - ".signalservice.DataMessage.OpenGroupInvi" + - "tation\022W\n\031closedGroupControlMessage\030h \001(", - "\01324.signalservice.DataMessage.ClosedGrou" + - "pControlMessage\022\022\n\nsyncTarget\030i \001(\t\022=\n\014g" + - "roupMessage\030x \001(\0132\'.signalservice.DataMe" + - "ssage.GroupMessage\032\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004" + - "\022\016\n\006author\030\002 \002(\t\022\014\n\004text\030\003 \001(\t\022F\n\013attach" + - "ments\030\004 \003(\01321.signalservice.DataMessage." + - "Quote.QuotedAttachment\032\231\001\n\020QuotedAttachm" + - "ent\022\023\n\013contentType\030\001 \001(\t\022\020\n\010fileName\030\002 \001" + - "(\t\0223\n\tthumbnail\030\003 \001(\0132 .signalservice.At" + - "tachmentPointer\022\r\n\005flags\030\004 \001(\r\"\032\n\005Flags\022", - "\021\n\rVOICE_MESSAGE\020\001\032V\n\007Preview\022\013\n\003url\030\001 \002" + - "(\t\022\r\n\005title\030\002 \001(\t\022/\n\005image\030\003 \001(\0132 .signa" + - "lservice.AttachmentPointer\032:\n\013LokiProfil" + - "e\022\023\n\013displayName\030\001 \001(\t\022\026\n\016profilePicture" + - "\030\002 \001(\t\0320\n\023OpenGroupInvitation\022\013\n\003url\030\001 \002" + - "(\t\022\014\n\004name\030\003 \002(\t\032\200\002\n\014GroupMessage\0228\n\rdel" + - "eteMessage\030\037 \001(\0132!.signalservice.GroupDe" + - "leteMessage\022@\n\021memberLeftMessage\030 \001(\0132%" + - ".signalservice.GroupMemberLeftMessage\0228\n" + - "\rinviteMessage\030! \001(\0132!.signalservice.Gro", - "upInviteMessage\022:\n\016promoteMessage\030\" \001(\0132" + - "\".signalservice.GroupPromoteMessage\032\203\005\n\031" + - "ClosedGroupControlMessage\022G\n\004type\030\001 \002(\0162" + - "9.signalservice.DataMessage.ClosedGroupC" + - "ontrolMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022\014\n" + - "\004name\030\003 \001(\t\0221\n\021encryptionKeyPair\030\004 \001(\0132\026" + - ".signalservice.KeyPair\022\017\n\007members\030\005 \003(\014\022" + - "\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.sign" + - "alservice.DataMessage.ClosedGroupControl" + - "Message.KeyPairWrapper\022\027\n\017expirationTime", - "r\030\010 \001(\r\022\030\n\020memberPrivateKey\030\t \001(\014\022\022\n\npri" + - "vateKey\030\n \001(\014\032=\n\016KeyPairWrapper\022\021\n\tpubli" + - "cKey\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"\312\001\n" + - "\004Type\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022" + - "\017\n\013NAME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017M" + - "EMBERS_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\022\n\n\006INV" + - "ITE\020\t\022\013\n\007PROMOTE\020\n\022\020\n\014DELETE_GROUP\020\013\022\023\n\017" + - "DELETE_MESSAGES\020\014\022\026\n\022DELETE_ATTACHMENTS\020" + - "\r\032\222\001\n\010Reaction\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002" + - "(\t\022\r\n\005emoji\030\003 \001(\t\022:\n\006action\030\004 \002(\0162*.sign", - "alservice.DataMessage.Reaction.Action\"\037\n" + - "\006Action\022\t\n\005REACT\020\000\022\n\n\006REMOVE\020\001\"$\n\005Flags\022" + - "\033\n\027EXPIRATION_TIMER_UPDATE\020\002\"B\n\022GroupDel" + - "eteMessage\022\021\n\tpublicKey\030\001 \002(\014\022\031\n\021lastEnc" + - "ryptionKey\030\002 \002(\014\"\030\n\026GroupMemberLeftMessa" + - "ge\"O\n\022GroupInviteMessage\022\021\n\tpublicKey\030\001 " + - "\002(\014\022\014\n\004name\030\002 \002(\t\022\030\n\020memberPrivateKey\030\003 " + - "\002(\014\"E\n\023GroupPromoteMessage\022\021\n\tpublicKey\030" + - "\001 \002(\014\022\033\n\023encryptedPrivateKey\030\002 \002(\014\"\352\001\n\013C" + - "allMessage\022-\n\004type\030\001 \002(\0162\037.signalservice", - ".CallMessage.Type\022\014\n\004sdps\030\002 \003(\t\022\027\n\017sdpML" + - "ineIndexes\030\003 \003(\r\022\017\n\007sdpMids\030\004 \003(\t\022\014\n\004uui" + - "d\030\005 \002(\t\"f\n\004Type\022\r\n\tPRE_OFFER\020\006\022\t\n\005OFFER\020" + - "\001\022\n\n\006ANSWER\020\002\022\026\n\022PROVISIONAL_ANSWER\020\003\022\022\n" + - "\016ICE_CANDIDATES\020\004\022\014\n\010END_CALL\020\005\"\265\001\n\023Shar" + - "edConfigMessage\0225\n\004type\030\001 \002(\0162\'.signalse" + - "rvice.SharedConfigMessage.Type\022\014\n\004data\030\002" + - " \002(\014\"Y\n\004Type\022\010\n\004USER\020\001\022\025\n\021CLOSED_GROUP_I" + - "NFO\020\002\022\023\n\017ENCRYPTION_KEYS\020\003\022\033\n\027CONVERSATI" + - "ON_READ_STATE\020\004\"\245\004\n\024ConfigurationMessage", - "\022E\n\014closedGroups\030\001 \003(\0132/.signalservice.C" + - "onfigurationMessage.ClosedGroup\022\022\n\nopenG" + - "roups\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016prof" + - "ilePicture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022=\n\010" + - "contacts\030\006 \003(\0132+.signalservice.Configura" + - "tionMessage.Contact\032\233\001\n\013ClosedGroup\022\021\n\tp" + - "ublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encrypti" + - "onKeyPair\030\003 \001(\0132\026.signalservice.KeyPair\022" + - "\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027\n\017expi" + - "rationTimer\030\006 \001(\r\032\223\001\n\007Contact\022\021\n\tpublicK", - "ey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePicture" + - "\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\022\022\n\nisApproved" + - "\030\005 \001(\010\022\021\n\tisBlocked\030\006 \001(\010\022\024\n\014didApproveM" + - "e\030\007 \001(\010\",\n\026MessageRequestResponse\022\022\n\nisA" + - "pproved\030\001 \002(\010\"u\n\016ReceiptMessage\0220\n\004type\030" + - "\001 \002(\0162\".signalservice.ReceiptMessage.Typ" + - "e\022\021\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY" + - "\020\000\022\010\n\004READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id" + - "\030\001 \002(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014" + - "\022\014\n\004size\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006dig", - "est\030\006 \001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001" + - "(\r\022\r\n\005width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007cap" + - "tion\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOIC" + - "E_MESSAGE\020\001B3\n\034org.session.libsignal.pro" + - "tosB\023SignalServiceProtos" + "\022*\n\005group\030\003 \001(\0132\033.signalservice.GroupCon" + + "text\022\r\n\005flags\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r" + + "\022\022\n\nprofileKey\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022" + + "/\n\005quote\030\010 \001(\0132 .signalservice.DataMessa" + + "ge.Quote\0223\n\007preview\030\n \003(\0132\".signalservic" + + "e.DataMessage.Preview\0225\n\010reaction\030\013 \001(\0132" + + "#.signalservice.DataMessage.Reaction\0227\n\007" + + "profile\030e \001(\0132&.signalservice.DataMessag" + + "e.LokiProfile\022K\n\023openGroupInvitation\030f \001" + + "(\0132..signalservice.DataMessage.OpenGroup", + "Invitation\022W\n\031closedGroupControlMessage\030" + + "h \001(\01324.signalservice.DataMessage.Closed" + + "GroupControlMessage\022\022\n\nsyncTarget\030i \001(\t\032" + + "\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n" + + "\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(\01321.signa" + + "lservice.DataMessage.Quote.QuotedAttachm" + + "ent\032\231\001\n\020QuotedAttachment\022\023\n\013contentType\030" + + "\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n\tthumbnail\030\003 \001" + + "(\0132 .signalservice.AttachmentPointer\022\r\n\005" + + "flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\032", + "V\n\007Preview\022\013\n\003url\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/" + + "\n\005image\030\003 \001(\0132 .signalservice.Attachment" + + "Pointer\032:\n\013LokiProfile\022\023\n\013displayName\030\001 " + + "\001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023OpenGroup" + + "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003" + + "\n\031ClosedGroupControlMessage\022G\n\004type\030\001 \002(" + + "\01629.signalservice.DataMessage.ClosedGrou" + + "pControlMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022" + + "\014\n\004name\030\003 \001(\t\0221\n\021encryptionKeyPair\030\004 \001(\013" + + "2\026.signalservice.KeyPair\022\017\n\007members\030\005 \003(", + "\014\022\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.si" + + "gnalservice.DataMessage.ClosedGroupContr" + + "olMessage.KeyPairWrapper\022\027\n\017expirationTi" + + "mer\030\010 \001(\r\032=\n\016KeyPairWrapper\022\021\n\tpublicKey" + + "\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"r\n\004Type" + + "\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NA" + + "ME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBER" + + "S_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\032\222\001\n\010Reactio" + + "n\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\r\n\005emoji\030\003" + + " \001(\t\022:\n\006action\030\004 \002(\0162*.signalservice.Dat", + "aMessage.Reaction.Action\"\037\n\006Action\022\t\n\005RE" + + "ACT\020\000\022\n\n\006REMOVE\020\001\"$\n\005Flags\022\033\n\027EXPIRATION" + + "_TIMER_UPDATE\020\002\"\352\001\n\013CallMessage\022-\n\004type\030" + + "\001 \002(\0162\037.signalservice.CallMessage.Type\022\014" + + "\n\004sdps\030\002 \003(\t\022\027\n\017sdpMLineIndexes\030\003 \003(\r\022\017\n" + + "\007sdpMids\030\004 \003(\t\022\014\n\004uuid\030\005 \002(\t\"f\n\004Type\022\r\n\t" + + "PRE_OFFER\020\006\022\t\n\005OFFER\020\001\022\n\n\006ANSWER\020\002\022\026\n\022PR" + + "OVISIONAL_ANSWER\020\003\022\022\n\016ICE_CANDIDATES\020\004\022\014" + + "\n\010END_CALL\020\005\"\245\004\n\024ConfigurationMessage\022E\n" + + "\014closedGroups\030\001 \003(\0132/.signalservice.Conf", + "igurationMessage.ClosedGroup\022\022\n\nopenGrou" + + "ps\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016profile" + + "Picture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022=\n\010con" + + "tacts\030\006 \003(\0132+.signalservice.Configuratio" + + "nMessage.Contact\032\233\001\n\013ClosedGroup\022\021\n\tpubl" + + "icKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encryptionK" + + "eyPair\030\003 \001(\0132\026.signalservice.KeyPair\022\017\n\007" + + "members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027\n\017expirat" + + "ionTimer\030\006 \001(\r\032\223\001\n\007Contact\022\021\n\tpublicKey\030" + + "\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePicture\030\003 ", + "\001(\t\022\022\n\nprofileKey\030\004 \001(\014\022\022\n\nisApproved\030\005 " + + "\001(\010\022\021\n\tisBlocked\030\006 \001(\010\022\024\n\014didApproveMe\030\007" + + " \001(\010\"y\n\026MessageRequestResponse\022\022\n\nisAppr" + + "oved\030\001 \002(\010\022\022\n\nprofileKey\030\002 \001(\014\0227\n\007profil" + + "e\030\003 \001(\0132&.signalservice.DataMessage.Loki" + + "Profile\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002(\0162" + + "\".signalservice.ReceiptMessage.Type\022\021\n\tt" + + "imestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004" + + "READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \002(\006" + + "\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004si", + "ze\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 " + + "\001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005" + + "width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013" + + " \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESS" + + "AGE\020\001\"\365\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004ty" + + "pe\030\002 \001(\0162 .signalservice.GroupContext.Ty" + + "pe\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006ava" + + "tar\030\005 \001(\0132 .signalservice.AttachmentPoin" + + "ter\022\016\n\006admins\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000" + + "\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014R", + "EQUEST_INFO\020\004B3\n\034org.session.libsignal.p" + + "rotosB\023SignalServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -28861,7 +26264,7 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, - new java.lang.String[] { "Body", "Attachments", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", "GroupMessage", }); + new java.lang.String[] { "Body", "Attachments", "Group", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", }); internal_static_signalservice_DataMessage_Quote_descriptor = internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_Quote_fieldAccessorTable = new @@ -28892,18 +26295,12 @@ public final class SignalServiceProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_OpenGroupInvitation_descriptor, new java.lang.String[] { "Url", "Name", }); - internal_static_signalservice_DataMessage_GroupMessage_descriptor = - internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(4); - internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_DataMessage_GroupMessage_descriptor, - new java.lang.String[] { "DeleteMessage", "MemberLeftMessage", "InviteMessage", "PromoteMessage", }); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor = - internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(5); + internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(4); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor, - new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpirationTimer", "MemberPrivateKey", "PrivateKey", }); + new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpirationTimer", }); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor = internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable = new @@ -28911,49 +26308,19 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor, new java.lang.String[] { "PublicKey", "EncryptedKeyPair", }); internal_static_signalservice_DataMessage_Reaction_descriptor = - internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(6); + internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(5); internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_Reaction_descriptor, new java.lang.String[] { "Id", "Author", "Emoji", "Action", }); - internal_static_signalservice_GroupDeleteMessage_descriptor = - getDescriptor().getMessageTypes().get(7); - internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupDeleteMessage_descriptor, - new java.lang.String[] { "PublicKey", "LastEncryptionKey", }); - internal_static_signalservice_GroupMemberLeftMessage_descriptor = - getDescriptor().getMessageTypes().get(8); - internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupMemberLeftMessage_descriptor, - new java.lang.String[] { }); - internal_static_signalservice_GroupInviteMessage_descriptor = - getDescriptor().getMessageTypes().get(9); - internal_static_signalservice_GroupInviteMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupInviteMessage_descriptor, - new java.lang.String[] { "PublicKey", "Name", "MemberPrivateKey", }); - internal_static_signalservice_GroupPromoteMessage_descriptor = - getDescriptor().getMessageTypes().get(10); - internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupPromoteMessage_descriptor, - new java.lang.String[] { "PublicKey", "EncryptedPrivateKey", }); internal_static_signalservice_CallMessage_descriptor = - getDescriptor().getMessageTypes().get(11); + getDescriptor().getMessageTypes().get(7); internal_static_signalservice_CallMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_CallMessage_descriptor, new java.lang.String[] { "Type", "Sdps", "SdpMLineIndexes", "SdpMids", "Uuid", }); - internal_static_signalservice_SharedConfigMessage_descriptor = - getDescriptor().getMessageTypes().get(12); - internal_static_signalservice_SharedConfigMessage_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_SharedConfigMessage_descriptor, - new java.lang.String[] { "Type", "Data", }); internal_static_signalservice_ConfigurationMessage_descriptor = - getDescriptor().getMessageTypes().get(13); + getDescriptor().getMessageTypes().get(8); internal_static_signalservice_ConfigurationMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ConfigurationMessage_descriptor, @@ -28971,23 +26338,29 @@ public final class SignalServiceProtos { internal_static_signalservice_ConfigurationMessage_Contact_descriptor, new java.lang.String[] { "PublicKey", "Name", "ProfilePicture", "ProfileKey", "IsApproved", "IsBlocked", "DidApproveMe", }); internal_static_signalservice_MessageRequestResponse_descriptor = - getDescriptor().getMessageTypes().get(14); + getDescriptor().getMessageTypes().get(9); internal_static_signalservice_MessageRequestResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_MessageRequestResponse_descriptor, - new java.lang.String[] { "IsApproved", }); + new java.lang.String[] { "IsApproved", "ProfileKey", "Profile", }); internal_static_signalservice_ReceiptMessage_descriptor = - getDescriptor().getMessageTypes().get(15); + getDescriptor().getMessageTypes().get(10); internal_static_signalservice_ReceiptMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ReceiptMessage_descriptor, new java.lang.String[] { "Type", "Timestamp", }); internal_static_signalservice_AttachmentPointer_descriptor = - getDescriptor().getMessageTypes().get(16); + getDescriptor().getMessageTypes().get(11); internal_static_signalservice_AttachmentPointer_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_AttachmentPointer_descriptor, new java.lang.String[] { "Id", "ContentType", "Key", "Size", "Thumbnail", "Digest", "FileName", "Flags", "Width", "Height", "Caption", "Url", }); + internal_static_signalservice_GroupContext_descriptor = + getDescriptor().getMessageTypes().get(12); + internal_static_signalservice_GroupContext_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_GroupContext_descriptor, + new java.lang.String[] { "Id", "Type", "Name", "Members", "Avatar", "Admins", }); return null; } }; diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt b/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt index aea1fce2d..5eac7cecd 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt @@ -12,6 +12,7 @@ import javax.net.ssl.SSLContext import javax.net.ssl.X509TrustManager object HTTP { + var isConnectedToNetwork: (() -> Boolean) = { false } private val seedNodeConnection by lazy { OkHttpClient().newBuilder() @@ -64,8 +65,12 @@ object HTTP { private const val timeout: Long = 120 - class HTTPRequestFailedException(val statusCode: Int, val json: Map<*, *>?) - : kotlin.Exception("HTTP request failed with status code $statusCode.") + open class HTTPRequestFailedException( + val statusCode: Int, + val json: Map<*, *>?, + message: String = "HTTP request failed with status code $statusCode" + ) : kotlin.Exception(message) + class HTTPNoNetworkException : HTTPRequestFailedException(0, null, "No network connection") enum class Verb(val rawValue: String) { GET("GET"), PUT("PUT"), POST("POST"), DELETE("DELETE") @@ -120,8 +125,11 @@ object HTTP { response = connection.newCall(request.build()).execute() } catch (exception: Exception) { Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.") + + if (!isConnectedToNetwork()) { throw HTTPNoNetworkException() } + // Override the actual error so that we can correctly catch failed requests in OnionRequestAPI - throw HTTPRequestFailedException(0, null) + throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}") } return when (val statusCode = response.code()) { 200 -> {