From 5d7d141d0c712e1486155509301d14aeb71b9802 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 25 Jan 2023 17:19:23 +1100 Subject: [PATCH 001/244] Added a missing parameter to the SMS & MSS projections --- .../java/org/thoughtcrime/securesms/database/MmsDatabase.kt | 1 + .../java/org/thoughtcrime/securesms/database/SmsDatabase.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 63975d3eb..f0b849ef0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -1579,6 +1579,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa SHARED_CONTACTS, LINK_PREVIEWS, UNIDENTIFIED, + HAS_MENTION, "json_group_array(json_object(" + "'" + AttachmentDatabase.ROW_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.ROW_ID + ", " + "'" + AttachmentDatabase.UNIQUE_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.UNIQUE_ID + ", " + 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 ddd12edc8..a5014aacb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -105,7 +105,7 @@ public class SmsDatabase extends MessagingDatabase { PROTOCOL, READ, STATUS, TYPE, REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, DELIVERY_RECEIPT_COUNT, MISMATCHED_IDENTITIES, SUBSCRIPTION_ID, EXPIRES_IN, EXPIRE_STARTED, - NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED, + NOTIFIED, READ_RECEIPT_COUNT, UNIDENTIFIED, HAS_MENTION, "json_group_array(json_object(" + "'" + ReactionDatabase.ROW_ID + "', " + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.ROW_ID + ", " + "'" + ReactionDatabase.MESSAGE_ID + "', " + ReactionDatabase.TABLE_NAME + "." + ReactionDatabase.MESSAGE_ID + ", " + From 7ffe48b5ed580de50c82192696baacddd3b8018e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 27 Jan 2023 15:19:02 +1100 Subject: [PATCH 002/244] Fixed an issue where the message bubble could be sized incorrectly --- .../conversation/v2/messages/VisibleMessageContentView.kt | 3 +++ 1 file changed, 3 insertions(+) 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 d53ab4568..f8e655e70 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 @@ -101,6 +101,9 @@ class VisibleMessageContentView : ConstraintLayout { binding.deletedMessageView.root.isVisible = false } + // Note: Need to clear the body to prevent the message bubble getting incorrectly + // sized based on text content from a recycled view + binding.bodyTextView.text = null binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null binding.linkPreviewView.root.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() binding.untrustedView.root.isVisible = !contactIsTrusted && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty() From 4e38b75f57147668eac6629bf19d398927de69b1 Mon Sep 17 00:00:00 2001 From: hjubb Date: Mon, 30 Jan 2023 16:49:41 +1100 Subject: [PATCH 003/244] build: update build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3dcc19726..fdb064f30 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,8 +159,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 323 -def canonicalVersionName = "1.16.3" +def canonicalVersionCode = 331 +def canonicalVersionName = "1.16.4" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 025673513564462e9c5bc1b6cc11d57c9d23a9d9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 3 Feb 2023 13:33:52 +1100 Subject: [PATCH 004/244] Fixed a few bugs, added logging and removed some old code Added the ability to copy the sessionId of open group URL from the conversation menu Added additional logging to the BatchMessageReceiveJob to make future debugging easier Removed the OpenGroupMigrator Updated the JobQueue logging to provide more insight Fixed an issue where the database migrations weren't blocking which could result in failing/crashing SQL queries Fixed an issue where the new database file wouldn't be removed if a migration error was thrown Fixed an issue where the new database could exist in an invalid state and the app wouldn't attempt to remigrate Fixed an incorrectly throw exception in the PassphrasePromptActivity --- .../securesms/ApplicationContext.java | 4 - .../securesms/PassphrasePromptActivity.java | 3 +- .../conversation/v2/ConversationActivityV2.kt | 12 + .../v2/menus/ConversationMenuHelper.kt | 12 + .../securesms/database/ThreadDatabase.java | 72 ----- .../database/helpers/SQLCipherOpenHelper.java | 95 ++++-- .../securesms/groups/OpenGroupMigrator.kt | 139 --------- .../notifications/BackgroundPollWorker.kt | 2 +- .../res/menu/menu_conversation_open_group.xml | 4 + app/src/main/res/values/strings.xml | 1 + .../securesms/util/OpenGroupMigrationTests.kt | 281 ------------------ .../messaging/jobs/AttachmentDownloadJob.kt | 22 +- .../messaging/jobs/AttachmentUploadJob.kt | 24 +- .../messaging/jobs/BackgroundGroupAddJob.kt | 8 +- .../messaging/jobs/BatchMessageReceiveJob.kt | 20 +- .../messaging/jobs/GroupAvatarDownloadJob.kt | 6 +- .../session/libsession/messaging/jobs/Job.kt | 2 +- .../libsession/messaging/jobs/JobDelegate.kt | 6 +- .../libsession/messaging/jobs/JobQueue.kt | 34 ++- .../messaging/jobs/MessageReceiveJob.kt | 24 +- .../messaging/jobs/MessageSendJob.kt | 28 +- .../messaging/jobs/NotifyPNServerJob.kt | 14 +- .../messaging/jobs/OpenGroupDeleteJob.kt | 6 +- .../messaging/jobs/TrimThreadJob.kt | 4 +- 24 files changed, 204 insertions(+), 619 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupMigrator.kt delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/util/OpenGroupMigrationTests.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index ef4f5c46a..a95b6c28c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.dependencies.DatabaseModule; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.groups.OpenGroupManager; -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; @@ -206,9 +205,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO storage, messageDataProvider, ()-> KeyPairUtilities.INSTANCE.getUserED25519KeyPair(this)); - // migrate session open group data - OpenGroupMigrator.migrate(getDatabaseComponent()); - // end migration callMessageProcessor = new CallMessageProcessor(this, textSecurePreferences, ProcessLifecycleOwner.get().getLifecycle(), storage); Log.i(TAG, "onCreate()"); startKovenant(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java index 63b42c493..afc993df8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java @@ -210,8 +210,7 @@ public class PassphrasePromptActivity extends BaseActionBarActivity { try { signature = biometricSecretProvider.getOrCreateBiometricSignature(this); hasSignatureObject = true; - throw new InvalidKeyException("e"); - } catch (InvalidKeyException e) { + } catch (Exception e) { signature = null; hasSignatureObject = false; Log.e(TAG, "Error getting / creating signature", e); 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 7adf5f7dd..8ba8f1068 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 @@ -963,6 +963,18 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() } + override fun copyOpenGroupUrl(thread: Recipient) { + if (!thread.isOpenGroupRecipient) { return } + + val threadId = threadDb.getThreadIdIfExistsFor(thread) ?: return + val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return + + val clip = ClipData.newPlainText("Community URL", openGroup.joinURL) + val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager + manager.setPrimaryClip(clip) + Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + } + override fun showExpiringMessagesDialog(thread: Recipient) { if (thread.isClosedGroupRecipient) { val group = groupDb.getGroup(thread.address.toGroupString()).orNull() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index 663dd2e25..8a6c84aec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -78,6 +78,10 @@ object ConversationMenuHelper { inflater.inflate(R.menu.menu_conversation_expiration_off, menu) } } + // One-on-one chat menu allows copying the session id + if (thread.isContactRecipient) { + inflater.inflate(R.menu.menu_conversation_copy_session_id, menu) + } // One-on-one chat menu (options that should only be present for one-on-one chats) if (thread.isContactRecipient) { if (thread.isBlocked) { @@ -154,6 +158,7 @@ object ConversationMenuHelper { R.id.menu_block -> { block(context, thread, deleteThread = false) } R.id.menu_block_delete -> { blockAndDelete(context, thread) } R.id.menu_copy_session_id -> { copySessionID(context, thread) } + R.id.menu_copy_open_group_url -> { copyOpenGroupUrl(context, thread) } R.id.menu_edit_group -> { editClosedGroup(context, thread) } R.id.menu_leave_group -> { leaveClosedGroup(context, thread) } R.id.menu_invite_to_open_group -> { inviteContacts(context, thread) } @@ -270,6 +275,12 @@ object ConversationMenuHelper { listener.copySessionID(thread.address.toString()) } + private fun copyOpenGroupUrl(context: Context, thread: Recipient) { + if (!thread.isOpenGroupRecipient) { return } + val listener = context as? ConversationMenuListener ?: return + listener.copyOpenGroupUrl(thread) + } + private fun editClosedGroup(context: Context, thread: Recipient) { if (!thread.isClosedGroupRecipient) { return } val intent = Intent(context, EditClosedGroupActivity::class.java) @@ -344,6 +355,7 @@ object ConversationMenuHelper { fun block(deleteThread: Boolean = false) fun unblock() fun copySessionID(sessionId: String) + fun copyOpenGroupUrl(thread: Recipient) fun showExpiringMessagesDialog(thread: Recipient) } 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 26b20ab00..976a21595 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -57,7 +57,6 @@ import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.groups.OpenGroupMigrator; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; @@ -800,77 +799,6 @@ public class ThreadDatabase extends Database { return query; } - @NotNull - public List getHttpOxenOpenGroups() { - String where = TABLE_NAME+"."+ADDRESS+" LIKE ?"; - String selection = OpenGroupMigrator.HTTP_PREFIX+OpenGroupMigrator.OPEN_GET_SESSION_TRAILING_DOT_ENCODED +"%"; - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String query = createQuery(where, 0); - Cursor cursor = db.rawQuery(query, new String[]{selection}); - - if (cursor == null) { - return Collections.emptyList(); - } - List threads = new ArrayList<>(); - try { - Reader reader = readerFor(cursor); - ThreadRecord record; - while ((record = reader.getNext()) != null) { - threads.add(record); - } - } finally { - cursor.close(); - } - return threads; - } - - @NotNull - public List getLegacyOxenOpenGroups() { - String where = TABLE_NAME+"."+ADDRESS+" LIKE ?"; - String selection = OpenGroupMigrator.LEGACY_GROUP_ENCODED_ID+"%"; - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String query = createQuery(where, 0); - Cursor cursor = db.rawQuery(query, new String[]{selection}); - - if (cursor == null) { - return Collections.emptyList(); - } - List threads = new ArrayList<>(); - try { - Reader reader = readerFor(cursor); - ThreadRecord record; - while ((record = reader.getNext()) != null) { - threads.add(record); - } - } finally { - cursor.close(); - } - return threads; - } - - @NotNull - public List getHttpsOxenOpenGroups() { - String where = TABLE_NAME+"."+ADDRESS+" LIKE ?"; - String selection = OpenGroupMigrator.NEW_GROUP_ENCODED_ID+"%"; - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String query = createQuery(where, 0); - Cursor cursor = db.rawQuery(query, new String[]{selection}); - if (cursor == null) { - return Collections.emptyList(); - } - List threads = new ArrayList<>(); - try { - Reader reader = readerFor(cursor); - ThreadRecord record; - while ((record = reader.getNext()) != null) { - threads.add(record); - } - } finally { - cursor.close(); - } - return threads; - } - public void migrateEncodedGroup(long threadId, @NotNull String newEncodedGroupId) { ContentValues contentValues = new ContentValues(1); contentValues.put(ADDRESS, newEncodedGroupId); 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 4a48aa244..df69c239f 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 @@ -97,25 +97,40 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { - super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() { - @Override - public void preKey(SQLiteConnection connection) { - SQLCipherOpenHelper.applySQLCipherPragmas(connection, true); - } - - @Override - 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) { - connection.execute("VACUUM;", null, null); - TextSecurePreferences.setLastVacuumNow(context); + super( + context, + DATABASE_NAME, + databaseSecret.asString(), + null, + DATABASE_VERSION, + MIN_DATABASE_VERSION, + null, + new SQLiteDatabaseHook() { + @Override + public void preKey(SQLiteConnection connection) { + SQLCipherOpenHelper.applySQLCipherPragmas(connection, true); } - } - }, true); + + @Override + 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) { + connection.execute("VACUUM;", null, null); + TextSecurePreferences.setLastVacuumNow(context); + } + } + }, + // Note: Now that we support concurrent database reads the migrations are actually non-blocking + // because of this we need to initially open the database with writeAheadLogging (WAL mode) disabled + // and enable it once the database officially opens it's connection (which will cause it to re-connect + // in WAL mode) - this is a little inefficient but will prevent SQL-related errors/crashes due to + // incomplete migrations + false + ); this.context = context.getApplicationContext(); this.databaseSecret = databaseSecret; @@ -150,11 +165,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { // 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); + // Define the location for the new database + String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath(); + File newDbFile = new File(newDbPath); + try { // 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()) { @@ -162,10 +177,24 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { // 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; + try { + SQLiteDatabase newDb = SQLCipherOpenHelper.open(newDbPath, databaseSecret, true); + int version = newDb.getVersion(); + newDb.close(); + + // Make sure the new database has it's version set correctly (if not then the migration didn't + // fully succeed and the database will try to create all it's tables and immediately fail so + // we will need to remove and remigrate) + if (version > 0) { + // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past +// //noinspection ResultOfMethodCallIgnored +// oldDbFile.delete(); + return; + } + } + catch (Exception e) { + Log.i(TAG, "Failed to retrieve version from new database, assuming invalid and remigrating"); + } } // If the old database does have newer changes then the new database could have stale/invalid @@ -207,6 +236,11 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { catch (Exception e) { Log.e(TAG, "Migration from SQLCipher3 to SQLCipher4 failed", e); + // If an exception was thrown then we should remove the new database file (it's probably invalid) + if (!newDbFile.delete()) { + Log.e(TAG, "Unable to delete invalid new database file"); + } + // 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); @@ -559,6 +593,15 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } + @Override + public void onOpen(SQLiteDatabase db) { + super.onOpen(db); + + // Now that the database is officially open (ie. the migrations are completed) we want to enable + // write ahead logging (WAL mode) to officially support concurrent read connections + db.enableWriteAheadLogging(); + } + public void markCurrent(SQLiteDatabase db) { db.setVersion(DATABASE_VERSION); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupMigrator.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupMigrator.kt deleted file mode 100644 index 642d19161..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupMigrator.kt +++ /dev/null @@ -1,139 +0,0 @@ -package org.thoughtcrime.securesms.groups - -import androidx.annotation.VisibleForTesting -import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.utilities.Hex -import org.thoughtcrime.securesms.database.model.ThreadRecord -import org.thoughtcrime.securesms.dependencies.DatabaseComponent - -object OpenGroupMigrator { - const val HTTP_PREFIX = "__loki_public_chat_group__!687474703a2f2f" - private const val HTTPS_PREFIX = "__loki_public_chat_group__!68747470733a2f2f" - const val OPEN_GET_SESSION_TRAILING_DOT_ENCODED = "6f70656e2e67657473657373696f6e2e6f72672e" - const val LEGACY_GROUP_ENCODED_ID = "__loki_public_chat_group__!687474703a2f2f3131362e3230332e37302e33332e" // old IP based toByteArray() - const val NEW_GROUP_ENCODED_ID = "__loki_public_chat_group__!68747470733a2f2f6f70656e2e67657473657373696f6e2e6f72672e" // new URL based toByteArray() - - data class OpenGroupMapping(val stub: String, val legacyThreadId: Long, val newThreadId: Long?) - - @VisibleForTesting - fun Recipient.roomStub(): String? { - if (!isOpenGroupRecipient) return null - val serialized = address.serialize() - if (serialized.startsWith(LEGACY_GROUP_ENCODED_ID)) { - return serialized.replace(LEGACY_GROUP_ENCODED_ID,"") - } else if (serialized.startsWith(NEW_GROUP_ENCODED_ID)) { - return serialized.replace(NEW_GROUP_ENCODED_ID,"") - } else if (serialized.startsWith(HTTP_PREFIX + OPEN_GET_SESSION_TRAILING_DOT_ENCODED)) { - return serialized.replace(HTTP_PREFIX + OPEN_GET_SESSION_TRAILING_DOT_ENCODED, "") - } - return null - } - - @VisibleForTesting - fun getExistingMappings(legacy: List, new: List): List { - val legacyStubsMapping = legacy.mapNotNull { thread -> - val stub = thread.recipient.roomStub() - stub?.let { it to thread.threadId } - } - val newStubsMapping = new.mapNotNull { thread -> - val stub = thread.recipient.roomStub() - stub?.let { it to thread.threadId } - } - return legacyStubsMapping.map { (legacyEncodedStub, legacyId) -> - // get 'new' open group thread ID if stubs match - OpenGroupMapping( - legacyEncodedStub, - legacyId, - newStubsMapping.firstOrNull { (newEncodedStub, _) -> newEncodedStub == legacyEncodedStub }?.second - ) - } - } - - @JvmStatic - fun migrate(databaseComponent: DatabaseComponent) { - // migrate thread db - val threadDb = databaseComponent.threadDatabase() - - val legacyOpenGroups = threadDb.legacyOxenOpenGroups - val httpBasedNewGroups = threadDb.httpOxenOpenGroups - if (legacyOpenGroups.isEmpty() && httpBasedNewGroups.isEmpty()) return // no need to migrate - - val newOpenGroups = threadDb.httpsOxenOpenGroups - val firstStepMigration = getExistingMappings(legacyOpenGroups, newOpenGroups) - - val secondStepMigration = getExistingMappings(httpBasedNewGroups, newOpenGroups) - - val groupDb = databaseComponent.groupDatabase() - val lokiApiDb = databaseComponent.lokiAPIDatabase() - val smsDb = databaseComponent.smsDatabase() - val mmsDb = databaseComponent.mmsDatabase() - val lokiMessageDatabase = databaseComponent.lokiMessageDatabase() - val lokiThreadDatabase = databaseComponent.lokiThreadDatabase() - - firstStepMigration.forEach { (stub, old, new) -> - val legacyEncodedGroupId = LEGACY_GROUP_ENCODED_ID+stub - if (new == null) { - val newEncodedGroupId = NEW_GROUP_ENCODED_ID+stub - // migrate thread and group encoded values - threadDb.migrateEncodedGroup(old, newEncodedGroupId) - groupDb.migrateEncodedGroup(legacyEncodedGroupId, newEncodedGroupId) - // migrate Loki API DB values - // decode the hex to bytes, decode byte array to string i.e. "oxen" or "session" - val decodedStub = Hex.fromStringCondensed(stub).decodeToString() - val legacyLokiServerId = "${OpenGroupApi.legacyDefaultServer}.$decodedStub" - val newLokiServerId = "${OpenGroupApi.defaultServer}.$decodedStub" - lokiApiDb.migrateLegacyOpenGroup(legacyLokiServerId, newLokiServerId) - // migrate loki thread db server info - val oldServerInfo = lokiThreadDatabase.getOpenGroupChat(old) - val newServerInfo = oldServerInfo!!.copy(server = OpenGroupApi.defaultServer, id = newLokiServerId) - lokiThreadDatabase.setOpenGroupChat(newServerInfo, old) - } else { - // has a legacy and a new one - // migrate SMS and MMS tables - smsDb.migrateThreadId(old, new) - mmsDb.migrateThreadId(old, new) - lokiMessageDatabase.migrateThreadId(old, new) - // delete group for legacy ID - groupDb.delete(legacyEncodedGroupId) - // delete thread for legacy ID - threadDb.deleteConversation(old) - lokiThreadDatabase.removeOpenGroupChat(old) - } - // maybe migrate jobs here - } - - secondStepMigration.forEach { (stub, old, new) -> - val legacyEncodedGroupId = HTTP_PREFIX + OPEN_GET_SESSION_TRAILING_DOT_ENCODED + stub - if (new == null) { - val newEncodedGroupId = NEW_GROUP_ENCODED_ID+stub - // migrate thread and group encoded values - threadDb.migrateEncodedGroup(old, newEncodedGroupId) - groupDb.migrateEncodedGroup(legacyEncodedGroupId, newEncodedGroupId) - // migrate Loki API DB values - // decode the hex to bytes, decode byte array to string i.e. "oxen" or "session" - val decodedStub = Hex.fromStringCondensed(stub).decodeToString() - val legacyLokiServerId = "${OpenGroupApi.httpDefaultServer}.$decodedStub" - val newLokiServerId = "${OpenGroupApi.defaultServer}.$decodedStub" - lokiApiDb.migrateLegacyOpenGroup(legacyLokiServerId, newLokiServerId) - // migrate loki thread db server info - val oldServerInfo = lokiThreadDatabase.getOpenGroupChat(old) - val newServerInfo = oldServerInfo!!.copy(server = OpenGroupApi.defaultServer, id = newLokiServerId) - lokiThreadDatabase.setOpenGroupChat(newServerInfo, old) - } else { - // has a legacy and a new one - // migrate SMS and MMS tables - smsDb.migrateThreadId(old, new) - mmsDb.migrateThreadId(old, new) - lokiMessageDatabase.migrateThreadId(old, new) - // delete group for legacy ID - groupDb.delete(legacyEncodedGroupId) - // delete thread for legacy ID - threadDb.deleteConversation(old) - lokiThreadDatabase.removeOpenGroupChat(old) - } - // maybe migrate jobs here - } - - } -} \ No newline at end of file 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 5a0438e15..9b8ea5824 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt @@ -60,7 +60,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor // FIXME: Using a job here seems like a bad idea... MessageReceiveParameters(envelope.toByteArray(), serverHash, null) } - BatchMessageReceiveJob(params).executeAsync() + BatchMessageReceiveJob(params).executeAsync("background") } promises.add(dmsPromise) diff --git a/app/src/main/res/menu/menu_conversation_open_group.xml b/app/src/main/res/menu/menu_conversation_open_group.xml index 6ff025aad..1bbb2d76d 100644 --- a/app/src/main/res/menu/menu_conversation_open_group.xml +++ b/app/src/main/res/menu/menu_conversation_open_group.xml @@ -2,6 +2,10 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0ee94cf1..7c39714c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,6 +77,7 @@ Attachment exceeds size limits for the type of message you\'re sending. Unable to record audio! There is no app available to handle this link on your device. + Copy Community URL Add members Session needs microphone access to send audio messages. Session needs microphone access to send audio messages, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Microphone\". diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/OpenGroupMigrationTests.kt b/app/src/test/java/org/thoughtcrime/securesms/util/OpenGroupMigrationTests.kt deleted file mode 100644 index dcf8ca231..000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/util/OpenGroupMigrationTests.kt +++ /dev/null @@ -1,281 +0,0 @@ -package org.thoughtcrime.securesms.util - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test -import org.mockito.kotlin.KStubbing -import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.doAnswer -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoMoreInteractions -import org.session.libsession.messaging.open_groups.OpenGroup -import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.database.GroupDatabase -import org.thoughtcrime.securesms.database.LokiAPIDatabase -import org.thoughtcrime.securesms.database.LokiMessageDatabase -import org.thoughtcrime.securesms.database.LokiThreadDatabase -import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.SmsDatabase -import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.database.model.ThreadRecord -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.groups.OpenGroupMigrator -import org.thoughtcrime.securesms.groups.OpenGroupMigrator.OpenGroupMapping -import org.thoughtcrime.securesms.groups.OpenGroupMigrator.roomStub - -class OpenGroupMigrationTests { - - companion object { - const val EXAMPLE_LEGACY_ENCODED_OPEN_GROUP = "__loki_public_chat_group__!687474703a2f2f3131362e3230332e37302e33332e6f78656e" - const val EXAMPLE_NEW_ENCODED_OPEN_GROUP = "__loki_public_chat_group__!68747470733a2f2f6f70656e2e67657473657373696f6e2e6f72672e6f78656e" - const val OXEN_STUB_HEX = "6f78656e" - - const val EXAMPLE_LEGACY_SERVER_ID = "http://116.203.70.33.oxen" - const val EXAMPLE_NEW_SERVER_ID = "https://open.getsession.org.oxen" - - const val LEGACY_THREAD_ID = 1L - const val NEW_THREAD_ID = 2L - } - - private fun legacyOpenGroupRecipient(additionalMocks: ((KStubbing) -> Unit) ? = null) = mock { - on { address } doReturn Address.fromSerialized(EXAMPLE_LEGACY_ENCODED_OPEN_GROUP) - on { isOpenGroupRecipient } doReturn true - additionalMocks?.let { it(this) } - } - - private fun newOpenGroupRecipient(additionalMocks: ((KStubbing) -> Unit) ? = null) = mock { - on { address } doReturn Address.fromSerialized(EXAMPLE_NEW_ENCODED_OPEN_GROUP) - on { isOpenGroupRecipient } doReturn true - additionalMocks?.let { it(this) } - } - - private fun legacyThreadRecord(additionalRecipientMocks: ((KStubbing) -> Unit) ? = null, additionalThreadMocks: ((KStubbing) -> Unit)? = null) = mock { - val returnedRecipient = legacyOpenGroupRecipient(additionalRecipientMocks) - on { recipient } doReturn returnedRecipient - on { threadId } doReturn LEGACY_THREAD_ID - } - - private fun newThreadRecord(additionalRecipientMocks: ((KStubbing) -> Unit)? = null, additionalThreadMocks: ((KStubbing) -> Unit)? = null) = mock { - val returnedRecipient = newOpenGroupRecipient(additionalRecipientMocks) - on { recipient } doReturn returnedRecipient - on { threadId } doReturn NEW_THREAD_ID - } - - @Test - fun `it should generate the correct room stubs for legacy groups`() { - val mockRecipient = legacyOpenGroupRecipient() - assertEquals(OXEN_STUB_HEX, mockRecipient.roomStub()) - } - - @Test - fun `it should generate the correct room stubs for new groups`() { - val mockNewRecipient = newOpenGroupRecipient() - assertEquals(OXEN_STUB_HEX, mockNewRecipient.roomStub()) - } - - @Test - fun `it should return correct mappings`() { - val legacyThread = legacyThreadRecord() - val newThread = newThreadRecord() - - val expectedMapping = listOf( - OpenGroupMapping(OXEN_STUB_HEX, LEGACY_THREAD_ID, NEW_THREAD_ID) - ) - - assertTrue(expectedMapping.containsAll(OpenGroupMigrator.getExistingMappings(listOf(legacyThread), listOf(newThread)))) - } - - @Test - fun `it should return no mappings if there are no legacy open groups`() { - val mappings = OpenGroupMigrator.getExistingMappings(listOf(), listOf()) - assertTrue(mappings.isEmpty()) - } - - @Test - fun `it should return no mappings if there are only new open groups`() { - val newThread = newThreadRecord() - val mappings = OpenGroupMigrator.getExistingMappings(emptyList(), listOf(newThread)) - assertTrue(mappings.isEmpty()) - } - - @Test - fun `it should return null new thread in mappings if there are only legacy open groups`() { - val legacyThread = legacyThreadRecord() - val mappings = OpenGroupMigrator.getExistingMappings(listOf(legacyThread), emptyList()) - val expectedMappings = listOf( - OpenGroupMapping(OXEN_STUB_HEX, LEGACY_THREAD_ID, null) - ) - assertTrue(expectedMappings.containsAll(mappings)) - } - - @Test - fun `test migration thread DB calls legacy and returns if no legacy official groups`() { - val mockedThreadDb = mock { - on { legacyOxenOpenGroups } doReturn emptyList() - } - val mockedDbComponent = mock { - on { threadDatabase() } doReturn mockedThreadDb - } - - OpenGroupMigrator.migrate(mockedDbComponent) - - verify(mockedDbComponent).threadDatabase() - verify(mockedThreadDb).legacyOxenOpenGroups - verifyNoMoreInteractions(mockedThreadDb) - } - - @Test - fun `it should migrate on thread, group and loki dbs with correct values for legacy only migration`() { - // mock threadDB - val capturedThreadId = argumentCaptor() - val capturedNewEncoded = argumentCaptor() - val mockedThreadDb = mock { - val legacyThreadRecord = legacyThreadRecord() - on { legacyOxenOpenGroups } doReturn listOf(legacyThreadRecord) - on { httpsOxenOpenGroups } doReturn emptyList() - on { migrateEncodedGroup(capturedThreadId.capture(), capturedNewEncoded.capture()) } doAnswer {} - } - - // mock groupDB - val capturedGroupLegacyEncoded = argumentCaptor() - val capturedGroupNewEncoded = argumentCaptor() - val mockedGroupDb = mock { - on { - migrateEncodedGroup( - capturedGroupLegacyEncoded.capture(), - capturedGroupNewEncoded.capture() - ) - } doAnswer {} - } - - // mock LokiAPIDB - val capturedLokiLegacyGroup = argumentCaptor() - val capturedLokiNewGroup = argumentCaptor() - val mockedLokiApi = mock { - on { migrateLegacyOpenGroup(capturedLokiLegacyGroup.capture(), capturedLokiNewGroup.capture()) } doAnswer {} - } - - val pubKey = OpenGroupApi.defaultServerPublicKey - val room = "oxen" - val legacyServer = OpenGroupApi.legacyDefaultServer - val newServer = OpenGroupApi.defaultServer - - val lokiThreadOpenGroup = argumentCaptor() - val mockedLokiThreadDb = mock { - on { getOpenGroupChat(eq(LEGACY_THREAD_ID)) } doReturn OpenGroup(legacyServer, room, "Oxen", 0, pubKey) - on { setOpenGroupChat(lokiThreadOpenGroup.capture(), eq(LEGACY_THREAD_ID)) } doAnswer {} - } - - val mockedDbComponent = mock { - on { threadDatabase() } doReturn mockedThreadDb - on { groupDatabase() } doReturn mockedGroupDb - on { lokiAPIDatabase() } doReturn mockedLokiApi - on { lokiThreadDatabase() } doReturn mockedLokiThreadDb - } - - OpenGroupMigrator.migrate(mockedDbComponent) - - // expect threadDB migration to reflect new thread values: - // thread ID = 1, encoded ID = new encoded ID - assertEquals(LEGACY_THREAD_ID, capturedThreadId.firstValue) - assertEquals(EXAMPLE_NEW_ENCODED_OPEN_GROUP, capturedNewEncoded.firstValue) - - // expect groupDB migration to reflect new thread values: - // legacy encoded ID, new encoded ID - assertEquals(EXAMPLE_LEGACY_ENCODED_OPEN_GROUP, capturedGroupLegacyEncoded.firstValue) - assertEquals(EXAMPLE_NEW_ENCODED_OPEN_GROUP, capturedGroupNewEncoded.firstValue) - - // expect Loki API DB migration to reflect new thread values: - assertEquals("${OpenGroupApi.legacyDefaultServer}.oxen", capturedLokiLegacyGroup.firstValue) - assertEquals("${OpenGroupApi.defaultServer}.oxen", capturedLokiNewGroup.firstValue) - - assertEquals(newServer, lokiThreadOpenGroup.firstValue.server) - - } - - @Test - fun `it should migrate and delete legacy thread with conflicting new and old values`() { - - // mock threadDB - val capturedThreadId = argumentCaptor() - val mockedThreadDb = mock { - val legacyThreadRecord = legacyThreadRecord() - val newThreadRecord = newThreadRecord() - on { legacyOxenOpenGroups } doReturn listOf(legacyThreadRecord) - on { httpsOxenOpenGroups } doReturn listOf(newThreadRecord) - on { deleteConversation(capturedThreadId.capture()) } doAnswer {} - } - - // mock groupDB - val capturedGroupLegacyEncoded = argumentCaptor() - val mockedGroupDb = mock { - on { delete(capturedGroupLegacyEncoded.capture()) } doReturn true - } - - // mock LokiAPIDB - val capturedLokiLegacyGroup = argumentCaptor() - val capturedLokiNewGroup = argumentCaptor() - val mockedLokiApi = mock { - on { migrateLegacyOpenGroup(capturedLokiLegacyGroup.capture(), capturedLokiNewGroup.capture()) } doAnswer {} - } - - // mock messaging dbs - val migrateMmsFromThreadId = argumentCaptor() - val migrateMmsToThreadId = argumentCaptor() - - val mockedMmsDb = mock { - on { migrateThreadId(migrateMmsFromThreadId.capture(), migrateMmsToThreadId.capture()) } doAnswer {} - } - - val migrateSmsFromThreadId = argumentCaptor() - val migrateSmsToThreadId = argumentCaptor() - val mockedSmsDb = mock { - on { migrateThreadId(migrateSmsFromThreadId.capture(), migrateSmsToThreadId.capture()) } doAnswer {} - } - - val lokiFromThreadId = argumentCaptor() - val lokiToThreadId = argumentCaptor() - val mockedLokiMessageDatabase = mock { - on { migrateThreadId(lokiFromThreadId.capture(), lokiToThreadId.capture()) } doAnswer {} - } - - val mockedLokiThreadDb = mock { - on { removeOpenGroupChat(eq(LEGACY_THREAD_ID)) } doAnswer {} - } - - val mockedDbComponent = mock { - on { threadDatabase() } doReturn mockedThreadDb - on { groupDatabase() } doReturn mockedGroupDb - on { lokiAPIDatabase() } doReturn mockedLokiApi - on { mmsDatabase() } doReturn mockedMmsDb - on { smsDatabase() } doReturn mockedSmsDb - on { lokiMessageDatabase() } doReturn mockedLokiMessageDatabase - on { lokiThreadDatabase() } doReturn mockedLokiThreadDb - } - - OpenGroupMigrator.migrate(mockedDbComponent) - - // should delete thread by thread ID - assertEquals(LEGACY_THREAD_ID, capturedThreadId.firstValue) - - // should delete group by legacy encoded ID - assertEquals(EXAMPLE_LEGACY_ENCODED_OPEN_GROUP, capturedGroupLegacyEncoded.firstValue) - - // should migrate SMS from legacy thread ID to new thread ID - assertEquals(LEGACY_THREAD_ID, migrateSmsFromThreadId.firstValue) - assertEquals(NEW_THREAD_ID, migrateSmsToThreadId.firstValue) - - // should migrate MMS from legacy thread ID to new thread ID - assertEquals(LEGACY_THREAD_ID, migrateMmsFromThreadId.firstValue) - assertEquals(NEW_THREAD_ID, migrateMmsToThreadId.firstValue) - - } - - - -} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt index 826df3ef8..ef1d7567b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt @@ -42,7 +42,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) private val TS_INCOMING_MESSAGE_ID_KEY = "tsIncoming_message_id" } - override fun execute() { + override fun execute(dispatcherName: String) { val storage = MessagingModuleConfiguration.shared.storage val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val threadID = storage.getThreadIdForMms(databaseMessageID) @@ -59,7 +59,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) Log.d("AttachmentDownloadJob", "Setting attachment state = failed, don't have attachment") messageDataProvider.setAttachmentState(AttachmentState.FAILED, AttachmentId(attachmentID,0), databaseMessageID) } - this.handlePermanentFailure(exception) + this.handlePermanentFailure(dispatcherName, exception) } else if (exception == Error.DuplicateData) { attachment?.let { id -> Log.d("AttachmentDownloadJob", "Setting attachment state = done from duplicate data") @@ -68,7 +68,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) Log.d("AttachmentDownloadJob", "Setting attachment state = done from duplicate data") messageDataProvider.setAttachmentState(AttachmentState.DONE, AttachmentId(attachmentID,0), databaseMessageID) } - this.handleSuccess() + this.handleSuccess(dispatcherName) } else { if (failureCount + 1 >= maxFailureCount) { attachment?.let { id -> @@ -79,7 +79,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) messageDataProvider.setAttachmentState(AttachmentState.FAILED, AttachmentId(attachmentID,0), databaseMessageID) } } - this.handleFailure(exception) + this.handleFailure(dispatcherName, exception) } } @@ -150,7 +150,7 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) Log.d("AttachmentDownloadJob", "deleting tempfile") tempFile.delete() Log.d("AttachmentDownloadJob", "succeeding job") - handleSuccess() + handleSuccess(dispatcherName) } catch (e: Exception) { Log.e("AttachmentDownloadJob", "Error processing attachment download", e) tempFile?.delete() @@ -169,17 +169,17 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) } } - private fun handleSuccess() { + private fun handleSuccess(dispatcherName: String) { Log.w("AttachmentDownloadJob", "Attachment downloaded successfully.") - delegate?.handleJobSucceeded(this) + delegate?.handleJobSucceeded(this, dispatcherName) } - private fun handlePermanentFailure(e: Exception) { - delegate?.handleJobFailedPermanently(this, e) + private fun handlePermanentFailure(dispatcherName: String, e: Exception) { + delegate?.handleJobFailedPermanently(this, dispatcherName, e) } - private fun handleFailure(e: Exception) { - delegate?.handleJobFailed(this, e) + private fun handleFailure(dispatcherName: String, e: Exception) { + delegate?.handleJobFailed(this, dispatcherName, e) } private fun createTempFile(): File { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt index 360207af4..cd4189a65 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentUploadJob.kt @@ -45,29 +45,29 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess private val MESSAGE_SEND_JOB_ID_KEY = "message_send_job_id" } - override fun execute() { + override fun execute(dispatcherName: String) { try { val storage = MessagingModuleConfiguration.shared.storage val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val attachment = messageDataProvider.getScaledSignalAttachmentStream(attachmentID) - ?: return handleFailure(Error.NoAttachment) + ?: return handleFailure(dispatcherName, Error.NoAttachment) val openGroup = storage.getOpenGroup(threadID.toLong()) if (openGroup != null) { val keyAndResult = upload(attachment, openGroup.server, false) { OpenGroupApi.upload(it, openGroup.room, openGroup.server) } - handleSuccess(attachment, keyAndResult.first, keyAndResult.second) + handleSuccess(dispatcherName, attachment, keyAndResult.first, keyAndResult.second) } else { val keyAndResult = upload(attachment, FileServerApi.server, true) { FileServerApi.upload(it) } - handleSuccess(attachment, keyAndResult.first, keyAndResult.second) + handleSuccess(dispatcherName, attachment, keyAndResult.first, keyAndResult.second) } } catch (e: java.lang.Exception) { if (e == Error.NoAttachment) { - this.handlePermanentFailure(e) + this.handlePermanentFailure(dispatcherName, e) } else { - this.handleFailure(e) + this.handleFailure(dispatcherName, e) } } } @@ -104,9 +104,9 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess return Pair(key, UploadResult(id, "${server}/file/$id", digest)) } - private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) { + private fun handleSuccess(dispatcherName: String, attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) { Log.d(TAG, "Attachment uploaded successfully.") - delegate?.handleJobSucceeded(this) + delegate?.handleJobSucceeded(this, dispatcherName) val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider messageDataProvider.handleSuccessfulAttachmentUpload(attachmentID, attachment, attachmentKey, uploadResult) if (attachment.contentType.startsWith("audio/")) { @@ -144,16 +144,16 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess storage.resumeMessageSendJobIfNeeded(messageSendJobID) } - private fun handlePermanentFailure(e: Exception) { + private fun handlePermanentFailure(dispatcherName: String, e: Exception) { Log.w(TAG, "Attachment upload failed permanently due to error: $this.") - delegate?.handleJobFailedPermanently(this, e) + delegate?.handleJobFailedPermanently(this, dispatcherName, e) MessagingModuleConfiguration.shared.messageDataProvider.handleFailedAttachmentUpload(attachmentID) failAssociatedMessageSendJob(e) } - private fun handleFailure(e: Exception) { + private fun handleFailure(dispatcherName: String, e: Exception) { Log.w(TAG, "Attachment upload failed due to error: $this.") - delegate?.handleJobFailed(this, e) + delegate?.handleJobFailed(this, dispatcherName, e) if (failureCount + 1 >= maxFailureCount) { failAssociatedMessageSendJob(e) } 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 c679724b9..ef67408fb 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 @@ -29,14 +29,14 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { return "$server.$room" } - override fun execute() { + override fun execute(dispatcherName: String) { try { val openGroup = OpenGroupUrlParser.parseUrl(joinUrl) val storage = MessagingModuleConfiguration.shared.storage val allOpenGroups = storage.getAllOpenGroups().map { it.value.joinURL } if (allOpenGroups.contains(openGroup.joinUrl())) { Log.e("OpenGroupDispatcher", "Failed to add group because", DuplicateGroupException()) - delegate?.handleJobFailed(this, DuplicateGroupException()) + delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException()) return } // get image @@ -50,11 +50,11 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { storage.onOpenGroupAdded(openGroup.server) } catch (e: Exception) { Log.e("OpenGroupDispatcher", "Failed to add group because",e) - delegate?.handleJobFailed(this, e) + delegate?.handleJobFailed(this, dispatcherName, e) return } Log.d("Loki", "Group added successfully") - delegate?.handleJobSucceeded(this) + delegate?.handleJobSucceeded(this, dispatcherName) } override fun serialize(): Data = Data.Builder() 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 18a8cc4ae..54a6551dc 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 @@ -66,11 +66,11 @@ class BatchMessageReceiveJob( return storage.getOrCreateThreadIdFor(senderOrSync, message.groupPublicKey, openGroupID) } - override fun execute() { - executeAsync().get() + override fun execute(dispatcherName: String) { + executeAsync(dispatcherName).get() } - fun executeAsync(): Promise { + fun executeAsync(dispatcherName: String): Promise { return task { val threadMap = mutableMapOf>() val storage = MessagingModuleConfiguration.shared.storage @@ -188,19 +188,21 @@ class BatchMessageReceiveJob( deferredThreadMap.awaitAll() } if (failures.isEmpty()) { - handleSuccess() + handleSuccess(dispatcherName) } else { - handleFailure() + handleFailure(dispatcherName) } } } - private fun handleSuccess() { - this.delegate?.handleJobSucceeded(this) + private fun handleSuccess(dispatcherName: String) { + Log.i(TAG, "Completed processing of ${messages.size} messages") + this.delegate?.handleJobSucceeded(this, dispatcherName) } - private fun handleFailure() { - this.delegate?.handleJobFailed(this, Exception("One or more jobs resulted in failure")) + private fun handleFailure(dispatcherName: String) { + Log.i(TAG, "Handling failure of ${failures.size} messages (${messages.size - failures.size} processed successfully)") + this.delegate?.handleJobFailed(this, dispatcherName, Exception("One or more jobs resulted in failure")) } override fun serialize(): Data { 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 02f792117..6429d760a 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 @@ -12,7 +12,7 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { override var failureCount: Int = 0 override val maxFailureCount: Int = 10 - override fun execute() { + override fun execute(dispatcherName: String) { val storage = MessagingModuleConfiguration.shared.storage val imageId = storage.getOpenGroup(room, server)?.imageId ?: return try { @@ -20,9 +20,9 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) storage.updateProfilePicture(groupId, bytes) storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) - delegate?.handleJobSucceeded(this) + delegate?.handleJobSucceeded(this, dispatcherName) } catch (e: Exception) { - delegate?.handleJobFailed(this, e) + delegate?.handleJobFailed(this, dispatcherName, e) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt index 74feb83a6..74e324f0e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/Job.kt @@ -17,7 +17,7 @@ interface Job { internal const val MAX_BUFFER_SIZE = 1_000_000 // bytes } - fun execute() + fun execute(dispatcherName: String) fun serialize(): Data diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobDelegate.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobDelegate.kt index 535ea27f3..769458ab6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobDelegate.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobDelegate.kt @@ -2,7 +2,7 @@ package org.session.libsession.messaging.jobs interface JobDelegate { - fun handleJobSucceeded(job: Job) - fun handleJobFailed(job: Job, error: Exception) - fun handleJobFailedPermanently(job: Job, error: Exception) + fun handleJobSucceeded(job: Job, dispatcherName: String) + fun handleJobFailed(job: Job, dispatcherName: String, error: Exception) + fun handleJobFailedPermanently(job: Job, dispatcherName: String, error: Exception) } \ No newline at end of file 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 8e46f275f..b78590c72 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 @@ -53,7 +53,7 @@ class JobQueue : JobDelegate { } if (openGroupId.isNullOrEmpty()) { Log.e("OpenGroupDispatcher", "Open Group ID was null on ${job.javaClass.simpleName}") - handleJobFailedPermanently(job, NullPointerException("Open Group ID was null")) + handleJobFailedPermanently(job, name, NullPointerException("Open Group ID was null")) } else { val groupChannel = if (!openGroupChannels.containsKey(openGroupId)) { Log.d("OpenGroupDispatcher", "Creating ${openGroupId.hashCode()} channel") @@ -95,9 +95,16 @@ class JobQueue : JobDelegate { } private fun Job.process(dispatcherName: String) { - Log.d(dispatcherName,"processJob: ${javaClass.simpleName}") + Log.d(dispatcherName,"processJob: ${javaClass.simpleName} (id: $id)") delegate = this@JobQueue - execute() + + try { + execute(dispatcherName) + } + catch (e: Exception) { + Log.d(dispatcherName, "unhandledJobException: ${javaClass.simpleName} (id: $id)") + this@JobQueue.handleJobFailed(this, dispatcherName, e) + } } init { @@ -177,7 +184,7 @@ class JobQueue : JobDelegate { return } if (!pendingJobIds.add(id)) { - Log.e("Loki","tried to re-queue pending/in-progress job") + Log.e("Loki","tried to re-queue pending/in-progress job (id: $id)") return } queue.trySend(job) @@ -196,7 +203,7 @@ class JobQueue : JobDelegate { } } pendingJobs.sortedBy { it.id }.forEach { job -> - Log.i("Loki", "Resuming pending job of type: ${job::class.simpleName}.") + Log.i("Loki", "Resuming pending job of type: ${job::class.simpleName} (id: ${job.id}).") queue.trySend(job) // Offer always called on unlimited capacity } } @@ -223,21 +230,21 @@ class JobQueue : JobDelegate { } } - override fun handleJobSucceeded(job: Job) { + override fun handleJobSucceeded(job: Job, dispatcherName: String) { val jobId = job.id ?: return MessagingModuleConfiguration.shared.storage.markJobAsSucceeded(jobId) pendingJobIds.remove(jobId) } - override fun handleJobFailed(job: Job, error: Exception) { + override fun handleJobFailed(job: Job, dispatcherName: String, error: Exception) { // Canceled val storage = MessagingModuleConfiguration.shared.storage if (storage.isJobCanceled(job)) { - return Log.i("Loki", "${job::class.simpleName} canceled.") + return Log.i("Loki", "${job::class.simpleName} canceled (id: ${job.id}).") } // Message send jobs waiting for the attachment to upload if (job is MessageSendJob && error is MessageSendJob.AwaitingAttachmentUploadException) { - Log.i("Loki", "Message send job waiting for attachment upload to finish.") + Log.i("Loki", "Message send job waiting for attachment upload to finish (id: ${job.id}).") return } @@ -255,21 +262,22 @@ class JobQueue : JobDelegate { job.failureCount += 1 if (job.failureCount >= job.maxFailureCount) { - handleJobFailedPermanently(job, error) + handleJobFailedPermanently(job, dispatcherName, error) } else { storage.persistJob(job) val retryInterval = getRetryInterval(job) - Log.i("Loki", "${job::class.simpleName} failed; scheduling retry (failure count is ${job.failureCount}).") + Log.i("Loki", "${job::class.simpleName} failed (id: ${job.id}); scheduling retry (failure count is ${job.failureCount}).") timer.schedule(delay = retryInterval) { - Log.i("Loki", "Retrying ${job::class.simpleName}.") + Log.i("Loki", "Retrying ${job::class.simpleName} (id: ${job.id}).") queue.trySend(job) } } } - override fun handleJobFailedPermanently(job: Job, error: Exception) { + override fun handleJobFailedPermanently(job: Job, dispatcherName: String, error: Exception) { val jobId = job.id ?: return handleJobFailedPermanently(jobId) + Log.d(dispatcherName, "permanentlyFailedJob: ${javaClass.simpleName} (id: ${job.id})") } private fun handleJobFailedPermanently(jobId: String) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt index 439fbb7a3..2ba33b563 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageReceiveJob.kt @@ -25,11 +25,11 @@ class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val private val OPEN_GROUP_ID_KEY = "open_group_id" } - override fun execute() { - executeAsync().get() + override fun execute(dispatcherName: String) { + executeAsync(dispatcherName).get() } - fun executeAsync(): Promise { + fun executeAsync(dispatcherName: String): Promise { val deferred = deferred() try { val isRetry: Boolean = failureCount != 0 @@ -39,32 +39,32 @@ class MessageReceiveJob(val data: ByteArray, val serverHash: String? = null, val val (message, proto) = MessageReceiver.parse(this.data, this.openGroupMessageServerID, openGroupPublicKey = serverPublicKey) message.serverHash = serverHash MessageReceiver.handle(message, proto, this.openGroupID) - this.handleSuccess() + this.handleSuccess(dispatcherName) deferred.resolve(Unit) } catch (e: Exception) { Log.e(TAG, "Couldn't receive message.", e) if (e is MessageReceiver.Error && !e.isRetryable) { Log.e("Loki", "Message receive job permanently failed.", e) - this.handlePermanentFailure(e) + this.handlePermanentFailure(dispatcherName, e) } else { Log.e("Loki", "Couldn't receive message.", e) - this.handleFailure(e) + this.handleFailure(dispatcherName, e) } deferred.resolve(Unit) // The promise is just used to keep track of when we're done } return deferred.promise } - private fun handleSuccess() { - delegate?.handleJobSucceeded(this) + private fun handleSuccess(dispatcherName: String) { + delegate?.handleJobSucceeded(this, dispatcherName) } - private fun handlePermanentFailure(e: Exception) { - delegate?.handleJobFailedPermanently(this, e) + private fun handlePermanentFailure(dispatcherName: String, e: Exception) { + delegate?.handleJobFailedPermanently(this, dispatcherName, e) } - private fun handleFailure(e: Exception) { - delegate?.handleJobFailed(this, e) + private fun handleFailure(dispatcherName: String, e: Exception) { + delegate?.handleJobFailed(this, dispatcherName, e) } override fun serialize(): Data { 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 8ce1adf48..524338592 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 @@ -33,7 +33,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { private val DESTINATION_KEY = "destination" } - override fun execute() { + override fun execute(dispatcherName: String) { val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val message = message as? VisibleMessage val storage = MessagingModuleConfiguration.shared.storage @@ -61,12 +61,12 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { } } if (attachmentsToUpload.isNotEmpty()) { - this.handleFailure(AwaitingAttachmentUploadException) + this.handleFailure(dispatcherName, AwaitingAttachmentUploadException) return } // Wait for all attachments to upload before continuing } val promise = MessageSender.send(this.message, this.destination).success { - this.handleSuccess() + this.handleSuccess(dispatcherName) }.fail { exception -> var logStacktrace = true @@ -75,14 +75,14 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { is HTTP.HTTPRequestFailedException -> { logStacktrace = false - if (exception.statusCode == 429) { this.handlePermanentFailure(exception) } - else { this.handleFailure(exception) } + if (exception.statusCode == 429) { this.handlePermanentFailure(dispatcherName, exception) } + else { this.handleFailure(dispatcherName, exception) } } is MessageSender.Error -> { - if (!exception.isRetryable) { this.handlePermanentFailure(exception) } - else { this.handleFailure(exception) } + if (!exception.isRetryable) { this.handlePermanentFailure(dispatcherName, exception) } + else { this.handleFailure(dispatcherName, exception) } } - else -> this.handleFailure(exception) + else -> this.handleFailure(dispatcherName, exception) } if (logStacktrace) { Log.e(TAG, "Couldn't send message due to error", exception) } @@ -95,15 +95,15 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { } } - private fun handleSuccess() { - delegate?.handleJobSucceeded(this) + private fun handleSuccess(dispatcherName: String) { + delegate?.handleJobSucceeded(this, dispatcherName) } - private fun handlePermanentFailure(error: Exception) { - delegate?.handleJobFailedPermanently(this, error) + private fun handlePermanentFailure(dispatcherName: String, error: Exception) { + delegate?.handleJobFailedPermanently(this, dispatcherName, error) } - private fun handleFailure(error: Exception) { + private fun handleFailure(dispatcherName: String, error: Exception) { Log.w(TAG, "Failed to send $message::class.simpleName.") val message = message as? VisibleMessage if (message != null) { @@ -111,7 +111,7 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { return // The message has been deleted } } - delegate?.handleJobFailed(this, error) + delegate?.handleJobFailed(this, dispatcherName, error) } override fun serialize(): Data { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 5c393c97b..25fb2194c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -32,7 +32,7 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { private val MESSAGE_KEY = "message" } - override fun execute() { + override fun execute(dispatcherName: String) { val server = PushNotificationAPI.server val parameters = mapOf( "data" to message.data, "send_to" to message.recipient ) val url = "${server}/notify" @@ -48,18 +48,18 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { Log.d("Loki", "Couldn't notify PN server due to error: $exception.") } }.success { - handleSuccess() + handleSuccess(dispatcherName) }. fail { - handleFailure(it) + handleFailure(dispatcherName, it) } } - private fun handleSuccess() { - delegate?.handleJobSucceeded(this) + private fun handleSuccess(dispatcherName: String) { + delegate?.handleJobSucceeded(this, dispatcherName) } - private fun handleFailure(error: Exception) { - delegate?.handleJobFailed(this, error) + private fun handleFailure(dispatcherName: String, error: Exception) { + delegate?.handleJobFailed(this, dispatcherName, error) } override fun serialize(): Data { 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 1fb2d0df2..4c76f8763 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 @@ -19,7 +19,7 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th override var failureCount: Int = 0 override val maxFailureCount: Int = 1 - override fun execute() { + override fun execute(dispatcherName: String) { val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val numberToDelete = messageServerIds.size Log.d(TAG, "Deleting $numberToDelete messages") @@ -39,10 +39,10 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th } Log.d(TAG, "Deleted ${messageIds.first.size + messageIds.second.size} messages successfully") - delegate?.handleJobSucceeded(this) + delegate?.handleJobSucceeded(this, dispatcherName) } catch (e: Exception) { - delegate?.handleJobFailed(this, e) + delegate?.handleJobFailed(this, dispatcherName, e) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt index e02a2f00e..d082ac708 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt @@ -20,7 +20,7 @@ class TrimThreadJob(val threadId: Long, val openGroupId: String?) : Job { const val THREAD_LENGTH_TRIGGER_SIZE = 2000 } - override fun execute() { + override fun execute(dispatcherName: String) { val context = MessagingModuleConfiguration.shared.context val trimmingEnabled = TextSecurePreferences.isThreadLengthTrimmingEnabled(context) val storage = MessagingModuleConfiguration.shared.storage @@ -29,7 +29,7 @@ class TrimThreadJob(val threadId: Long, val openGroupId: String?) : Job { val oldestMessageTime = System.currentTimeMillis() - TRIM_TIME_LIMIT storage.trimThreadBefore(threadId, oldestMessageTime) } - delegate?.handleJobSucceeded(this) + delegate?.handleJobSucceeded(this, dispatcherName) } override fun serialize(): Data { From 50989cb2eeea76433e6583fc232d372676bca5a9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 6 Feb 2023 14:22:26 +1100 Subject: [PATCH 005/244] Increased file upload limits to 10Mb --- .../securesms/mms/PushMediaConstraints.java | 10 +++++----- .../libsession/messaging/file_server/FileServerApi.kt | 9 --------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java index 179c28bc3..22af450aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/PushMediaConstraints.java @@ -21,26 +21,26 @@ public class PushMediaConstraints extends MediaConstraints { @Override public int getImageMaxSize(Context context) { - return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier); + return FileServerApi.maxFileSize; } @Override public int getGifMaxSize(Context context) { - return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier); + return FileServerApi.maxFileSize; } @Override public int getVideoMaxSize(Context context) { - return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier); + return FileServerApi.maxFileSize; } @Override public int getAudioMaxSize(Context context) { - return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier); + return FileServerApi.maxFileSize; } @Override public int getDocumentMaxSize(Context context) { - return (int) (((double) FileServerApi.maxFileSize) / FileServerApi.fileSizeORMultiplier); + return FileServerApi.maxFileSize; } } 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 01fae1f50..0e8768d53 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 @@ -16,15 +16,6 @@ object FileServerApi { private const val serverPublicKey = "da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59" const val server = "http://filev2.getsession.org" const val maxFileSize = 10_000_000 // 10 MB - /** - * The file server has a file size limit of `maxFileSize`, which the Service Nodes try to enforce as well. However, the limit applied by the Service Nodes - * is on the **HTTP request** and not the actual file size. Because the file server expects the file data to be base 64 encoded, the size of the HTTP - * request for a given file will be at least `ceil(n / 3) * 4` bytes, where n is the file size in bytes. This is the minimum size because there might also - * be other parameters in the request. On average the multiplier appears to be about 1.5, so when checking whether the file will exceed the file size limit when - * uploading a file we just divide the size of the file by this number. The alternative would be to actually check the size of the HTTP request but that's only - * possible after proof of work has been calculated and the onion request encryption has happened, which takes several seconds. - */ - const val fileSizeORMultiplier = 2 // TODO: It should be possible to set this to 1.5? sealed class Error(message: String) : Exception(message) { object ParsingFailed : Error("Invalid response.") From 395ada62fffa7031244cd0a6f144a20e7e824562 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 6 Feb 2023 16:01:16 +1100 Subject: [PATCH 006/244] Update French translations (#1103) * chore: update french translations file from crowdin * chore: update french translations libsession file from crowdin * chore: update french non-region translations file from crowdin * chore: update french non-region libsession translations file from crowdin --- app/src/main/res/values-fr-rFR/strings.xml | 213 +++++++++++++++--- app/src/main/res/values-fr/strings.xml | 213 +++++++++++++++--- .../src/main/res/values-fr-rFR/strings.xml | 54 +++++ libsession/src/main/res/values-fr/strings.xml | 54 +++++ 4 files changed, 474 insertions(+), 60 deletions(-) create mode 100644 libsession/src/main/res/values-fr-rFR/strings.xml create mode 100644 libsession/src/main/res/values-fr/strings.xml diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 9d58ab917..3aeeba055 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -1,10 +1,10 @@ - Session Oui Non Supprimer Bannir + Veuillez patienter… Enregistrer Note à mon intention Version %s @@ -19,7 +19,7 @@ Supprimer tous les anciens messages maintenant ? - Cela va immédiatement réduire toutes les conversations pour qu’il ne reste que le message le plus récent. + Cela réduira immédiatement toutes les conversations au message le plus récent. Cela réduira immédiatement toutes les conversations aux %d messages les plus récents. Supprimer @@ -63,6 +63,7 @@ Son désactivé jusqu\'à %1$s En sourdine %1$d membres + %1$d membres actifs Règles de la communauté Le destinataire est invalide ! Ajouté à l’écran d’accueil @@ -82,7 +83,9 @@ Session a besoin d\'un accès au stockage pour envoyer des photos et des vidéos. Session a besoin de l’autorisation Appareil photo afin de prendre des photos ou des vidéos, mais elle a été refusée définitivement. Veuillez accéder au menu des paramètres des applis, sélectionner Autorisations et activer Appareil photo. Session a besoin de l’autorisation Appareil photo pour prendre des photos ou des vidéos - %1$d de %2$d + %1$d sur %2$d + Autorisations d\'appel requises + Vous pouvez activer la permission \"Appels vocaux et vidéo\" dans les paramètres de confidentialité. Supprimer le message sélectionné ? @@ -99,13 +102,17 @@ L’enregistrement des %1$d médias dans la mémoire permettra à n’importe quelles autres applis de votre appareil d’y accéder.\n\nContinuer ? - Erreur d’enregistrement de la pièce jointe dans la mémoire ! + Erreur lors de l’enregistrement de la pièce jointe dans la mémoire ! Erreur d’enregistrement des pièces jointes dans la mémoire ! Enregistrement de la pièce jointe Enregistrement de %1$d pièces jointes + + Enregistrement de la pièce jointe dans la mémoire… + Enregistrement de %1$d pièces jointes dans la mémoire… + Photo de profil @@ -156,7 +163,7 @@ Envoyer à %s - Ajouter un légende… + Ajouter une légende... Un élément a été supprimé, car il dépassait la taille limite L’appareil photo n’est pas disponible Message à %s @@ -191,6 +198,7 @@ Vidéo Vous avez reçu un message d’échange de clés corrompu ! + Le message d\'échange de clé reçu est pour une version du protocole invalide. Vous avez reçu un message avec un nouveau numéro de sécurité. Touchez pour le traiter et l’afficher. Vous avez réinitialisé la session sécurisée. %s a réinitialisé la session sécurisée. @@ -201,7 +209,7 @@ La session sécurisée a été réinitialisée. Brouillon : Vous avez appelé - Vous a appelé + Vous a appelé·e Appel manqué Message multimédia %s est sur Session ! @@ -304,7 +312,7 @@ Envoyer Rédaction d’un message Afficher, masquer le clavier des émojis - Imagette de pièces jointes + Vignette de pièce jointe Afficher, masquer le tiroir permettant de lancer l’appareil photo à basse résolution Enregistrer et envoyer une pièce jointe audio Verrouiller l’enregistrement de pièces jointes audio @@ -378,9 +386,9 @@ Valeur par défaut Activé Désactivé - Nom et message - Nom seulement - Aucun nom ni message + Nom et Contenu + Nom uniquement + Aucun nom ni contenu Images Son Vidéo @@ -398,9 +406,14 @@ %d heures - La touche Entrée envoie - Envoyer des aperçus de liens - Les aperçus sont pris en charge pour les liens Imgur, Instagram, Pinterest, Reddit et YouTube + Envoyer avec bouton Entrée + Appuyer sur la touche Entrée enverra un message au lieu de commencer une nouvelle ligne. + Envoyer les aperçus des liens + Aperçus des liens + Générer les aperçus des liens pour les URLs supportés. + Messages audio + Lire automatiquement les messages audios + Lire automatiquement les messages audio consécutifs. Sécurité de l’écran Bloquer les captures d’écran dans la liste des récents et dans l’appli Notifications @@ -408,6 +421,7 @@ Inconnue Rythme de clignotement de la DEL Son + Son à l\'ouverture de l\'application Silencieux Répéter les alertes Jamais @@ -436,18 +450,27 @@ Valeur par défaut Clavier incognito Accusés de lecture + Envoyer des accusés de lecture dans les conversations individuelles. Indicateurs de saisie - Si les indicateurs de saisie sont désactivés, vous ne serez pas en mesure de voir les indicateurs de saisie des autres. + Voir et envoyer les indicateurs de saisie dans les conversations un à un. Demander au clavier de désactiver l’apprentissage personnalisé Clair Sombre Élagage des messages + Raccourcir les communautés + Supprimer des messages de plus de 6 mois dans des communautés qui ont plus de 2 000 messages. Utiliser les émojis du système Désactiver la prise en charge des émojis intégrés à Session + Sécurité d\'écran Conversations Messages Sons des conversations + Contenu de la notification + Afficher : + Informations affichées dans les notifications. Priorité + Notifications de capture d\'écran + Recevoir une notification lorsqu\'un contact prend une capture d\'écran d\'une conversation individuelle. @@ -460,7 +483,10 @@ Bannir l\'utilisateur Bannir et supprimer tout Renvoyer le message + Répondre Répondre au message + Appeler + Sélectionner Enregistrer la pièce jointe @@ -513,8 +539,8 @@ Création de la sauvegarde… %d messages pour l’instant Jamais - Verrouillage de l’écran - Verrouiller l’accès à Session avec le verrouillage de l’écran d’Android ou une empreinte + Verrouiller Session + Nécessite une empreinte digitale, un code PIN, un schéma ou un mot de passe pour déverrouiller Session. Délai d’inactivité avant verrouillage de l’écran Aucune @@ -522,6 +548,7 @@ Continuer Copier + Fermer URL non valide Copié dans le presse-papier Suivant @@ -573,12 +600,12 @@ Contact en cours… Nouvelle Session Saisir un Session ID - Scanner un Code QR - Scannez le code QR d\'un utilisateur pour démarrer une session. Les codes QR peuvent se trouver en touchant l\'icône du code QR dans les paramètres du compte. + Scanner un QR Code + Scannez le QR code d\'un utilisateur pour démarrer une session. Les QR codes peuvent se trouver en touchant l\'icône du QR code dans les paramètres du compte. Entrer un Session ID ou un nom ONS Les utilisateurs peuvent partager leur Session ID depuis les paramètres du compte ou en utilisant le code QR. Veuillez vérifier le Session ID ou le nom ONS et réessayer. - Session a besoin d\'accéder à l\'appareil photo pour scanner les codes QR + Session a besoin d\'accéder à l\'appareil photo pour scanner les QR codes Autoriser l\'accès Nouveau groupe privé Saisissez un nom de groupe @@ -591,7 +618,7 @@ Joindre un groupe public Impossible de rejoindre le groupe URL du groupe public - Scannez le code QR + Scanner le QR Code Scannez le code QR du groupe public que vous souhaitez rejoindre Saisissez une URL de groupe public Paramètres @@ -600,6 +627,7 @@ Veuillez choisir un nom d\'utilisateur plus court Confidentialité Notifications + Demandes de message Conversations Appareils reliés Inviter un ami @@ -612,25 +640,39 @@ Style de notification Contenu de notification Confidentialité + Conversations + Aide + Signaler un bug + Exportez vos logs, puis télécharger le fichier au service d\'aide de Session. + Traduire Session + Nous aimerions avoir votre avis + FAQ + Assistance + Exporter les journaux Stratégie de notification Utiliser le Mode Rapide Vous serez averti de nouveaux messages de manière fiable et immédiate en utilisant les serveurs de notification de Google. Modifier le nom Déconnecter l\'appareil Votre phrase de récupération - Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil. + Vous pouvez utiliser votre phrase de récupération pour restaurer votre compte ou relier un appareil. Effacer toutes les données Cela supprimera définitivement vos messages, vos sessions et vos contacts. Souhaitez-vous effacer seulement cet appareil ou supprimer l\'ensemble de votre compte ? + Cela supprimera définitivement vos messages, sessions et contacts. Voulez-vous uniquement effacer cet appareil ou supprimer l\'intégralité de votre compte ? + Effacer l\'appareil uniquement + Effacer l\'appareil et le réseau + Êtes-vous sûr de vouloir supprimer vos données du réseau ? Si vous continuez, vous ne pourrez pas restaurer vos messages ou vos contacts. + Effacer Effacer seulement Compte complet - Code QR - Afficher mon code QR - Scanner le code QR - Scannez le code QR d\'un autre utilisateur pour démarrer une session + QR Code + Afficher mon QR code + Scanner le QR Code + Scannez le QR code d\'un autre utilisateur pour démarrer une session Scannez-moi - Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous. - Partager le code QR + Ceci est votre QR code. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous. + Partager le QR code Contacts Groupes privés Groupes publics @@ -664,8 +706,8 @@ Cela prend un certain temps, voulez-vous passer ? Relier un appareil Phrase de récupération - Scannez le code QR - Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre code QR. + Scanner le QR Code + Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre QR code. Ou rejoignez l\'un(e) de ceux-ci… Notifications de message Session peut vous avertir de la présence de nouveaux messages de deux façons. @@ -694,6 +736,7 @@ Êtes-vous sûr de vouloir télécharger le média envoyé par %s ? Télécharger %s est bloqué. Débloquer ? + Bloquer l\'utilisateur La préparation de la pièce jointe pour l\'envoi a échoué. Médias Touchez pour télécharger %s @@ -711,6 +754,116 @@ Journal de débogage Partager les logs Voulez-vous exporter les logs de votre application pour pouvoir partager pour le dépannage ? - Code pin + Épingler Désépingler + Tout marquer comme lu + Contacts et Groupes + Messages + Demandes de message + Envoyer un message à cet utilisateur acceptera automatiquement sa demande de message et révélera votre ID de session. + Accepter + Refuser + Effacer tout + Êtes-vous sûr de vouloir refuser cette demande de message ? + Êtes-vous sûr de vouloir supprimer cette demande de message ? + Demande de message supprimée + Êtes-vous sûr de vouloir supprimer toutes les demandes de message ? + Demandes de message supprimées + Votre demande de message a été acceptée. + Votre demande de message est en attente. + Aucune demande de message en attente + Message privé + Groupes privés + Groupe public + Vous avez une nouvelle demande de message + Connexion… + Appel entrant + Refuser l’appel + Répondre à l’appel + Appel en cours + Annuler l’appel + Établissement de l\'appel + Raccrocher + Accepter l\'appel + Refuser l\'appel + Appels vocaux et vidéos + Appels (Bêta) + Active les appels vocaux et vidéo vers et depuis d\'autres utilisateurs. + Appels vocaux / vidéo + La version actuelle des appels vocaux/vidéo exposera votre adresse IP aux serveurs de la Fondation Oxen et aux utilisateurs appelés + Appel Manqué + Vous avez manqué un appel car vous devez activer la permission « Appels vocaux et vidéo » dans les paramètres de confidentialité. + Appel Session + Reconnexion… + Notifications + Les notifications désactivées vous empêcheront de recevoir des appels, aller dans les paramètres de notification de session? + Rejeter + Conversations + Apparence + Aide + Thèmes + Océan sombre + Sombre classique + Océan lumineux + Clair classique + Couleur principale + Ce message + Fréquemment Utilisés + Émoticônes et personnes + Nature + Nourriture + Activités + Voyage + Objets + Symboles + Drapeaux + Emoticônes + Aucun résultat trouvé + + Tous · %1$d + + +%1$d + + Vous + %1$s a réagi à un message %2$s + Masquer les détails + Rechercher un émoticône + Retour à l\'émoticône + Effacer la recherche + Thème sombre automatique + Faire correspondre aux paramètres systèmes + Accédez aux paramètres de notifications de l\'appareil + Contacts bloqués + Vous n\'avez aucun contact bloqué + Débloquer %s + Débloquer les utilisateurs + Êtes-vous sûr·e de vouloir débloquer %s ? + + et %d autre + et %d autres + + + Et %1$d autre a réagi %2$s à ce message + Et %1$d autres ont réagi %2$s à ce message + + Nouvelle conversation + Nouveau message + Créer un groupe + Rejoindre la communauté + Contacts + Inconnu·e + Commencez une nouvelle conversation en entrant l\'ID Session de quelqu\'un ou en lui partageant votre ID Session. + Créer + Rechercher parmi les contacts + URL de la communauté + Entrez l\'URL de la communauté + Rejoindre + Revenir en arrière + Fermer la fenêtre + Échec de la mise à jour de la base de données + Veuillez contacter le support pour signaler l\'erreur. + Envoi + Lu + Envoyé + Échec d’envoi diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9d58ab917..3aeeba055 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,10 +1,10 @@ - Session Oui Non Supprimer Bannir + Veuillez patienter… Enregistrer Note à mon intention Version %s @@ -19,7 +19,7 @@ Supprimer tous les anciens messages maintenant ? - Cela va immédiatement réduire toutes les conversations pour qu’il ne reste que le message le plus récent. + Cela réduira immédiatement toutes les conversations au message le plus récent. Cela réduira immédiatement toutes les conversations aux %d messages les plus récents. Supprimer @@ -63,6 +63,7 @@ Son désactivé jusqu\'à %1$s En sourdine %1$d membres + %1$d membres actifs Règles de la communauté Le destinataire est invalide ! Ajouté à l’écran d’accueil @@ -82,7 +83,9 @@ Session a besoin d\'un accès au stockage pour envoyer des photos et des vidéos. Session a besoin de l’autorisation Appareil photo afin de prendre des photos ou des vidéos, mais elle a été refusée définitivement. Veuillez accéder au menu des paramètres des applis, sélectionner Autorisations et activer Appareil photo. Session a besoin de l’autorisation Appareil photo pour prendre des photos ou des vidéos - %1$d de %2$d + %1$d sur %2$d + Autorisations d\'appel requises + Vous pouvez activer la permission \"Appels vocaux et vidéo\" dans les paramètres de confidentialité. Supprimer le message sélectionné ? @@ -99,13 +102,17 @@ L’enregistrement des %1$d médias dans la mémoire permettra à n’importe quelles autres applis de votre appareil d’y accéder.\n\nContinuer ? - Erreur d’enregistrement de la pièce jointe dans la mémoire ! + Erreur lors de l’enregistrement de la pièce jointe dans la mémoire ! Erreur d’enregistrement des pièces jointes dans la mémoire ! Enregistrement de la pièce jointe Enregistrement de %1$d pièces jointes + + Enregistrement de la pièce jointe dans la mémoire… + Enregistrement de %1$d pièces jointes dans la mémoire… + Photo de profil @@ -156,7 +163,7 @@ Envoyer à %s - Ajouter un légende… + Ajouter une légende... Un élément a été supprimé, car il dépassait la taille limite L’appareil photo n’est pas disponible Message à %s @@ -191,6 +198,7 @@ Vidéo Vous avez reçu un message d’échange de clés corrompu ! + Le message d\'échange de clé reçu est pour une version du protocole invalide. Vous avez reçu un message avec un nouveau numéro de sécurité. Touchez pour le traiter et l’afficher. Vous avez réinitialisé la session sécurisée. %s a réinitialisé la session sécurisée. @@ -201,7 +209,7 @@ La session sécurisée a été réinitialisée. Brouillon : Vous avez appelé - Vous a appelé + Vous a appelé·e Appel manqué Message multimédia %s est sur Session ! @@ -304,7 +312,7 @@ Envoyer Rédaction d’un message Afficher, masquer le clavier des émojis - Imagette de pièces jointes + Vignette de pièce jointe Afficher, masquer le tiroir permettant de lancer l’appareil photo à basse résolution Enregistrer et envoyer une pièce jointe audio Verrouiller l’enregistrement de pièces jointes audio @@ -378,9 +386,9 @@ Valeur par défaut Activé Désactivé - Nom et message - Nom seulement - Aucun nom ni message + Nom et Contenu + Nom uniquement + Aucun nom ni contenu Images Son Vidéo @@ -398,9 +406,14 @@ %d heures - La touche Entrée envoie - Envoyer des aperçus de liens - Les aperçus sont pris en charge pour les liens Imgur, Instagram, Pinterest, Reddit et YouTube + Envoyer avec bouton Entrée + Appuyer sur la touche Entrée enverra un message au lieu de commencer une nouvelle ligne. + Envoyer les aperçus des liens + Aperçus des liens + Générer les aperçus des liens pour les URLs supportés. + Messages audio + Lire automatiquement les messages audios + Lire automatiquement les messages audio consécutifs. Sécurité de l’écran Bloquer les captures d’écran dans la liste des récents et dans l’appli Notifications @@ -408,6 +421,7 @@ Inconnue Rythme de clignotement de la DEL Son + Son à l\'ouverture de l\'application Silencieux Répéter les alertes Jamais @@ -436,18 +450,27 @@ Valeur par défaut Clavier incognito Accusés de lecture + Envoyer des accusés de lecture dans les conversations individuelles. Indicateurs de saisie - Si les indicateurs de saisie sont désactivés, vous ne serez pas en mesure de voir les indicateurs de saisie des autres. + Voir et envoyer les indicateurs de saisie dans les conversations un à un. Demander au clavier de désactiver l’apprentissage personnalisé Clair Sombre Élagage des messages + Raccourcir les communautés + Supprimer des messages de plus de 6 mois dans des communautés qui ont plus de 2 000 messages. Utiliser les émojis du système Désactiver la prise en charge des émojis intégrés à Session + Sécurité d\'écran Conversations Messages Sons des conversations + Contenu de la notification + Afficher : + Informations affichées dans les notifications. Priorité + Notifications de capture d\'écran + Recevoir une notification lorsqu\'un contact prend une capture d\'écran d\'une conversation individuelle. @@ -460,7 +483,10 @@ Bannir l\'utilisateur Bannir et supprimer tout Renvoyer le message + Répondre Répondre au message + Appeler + Sélectionner Enregistrer la pièce jointe @@ -513,8 +539,8 @@ Création de la sauvegarde… %d messages pour l’instant Jamais - Verrouillage de l’écran - Verrouiller l’accès à Session avec le verrouillage de l’écran d’Android ou une empreinte + Verrouiller Session + Nécessite une empreinte digitale, un code PIN, un schéma ou un mot de passe pour déverrouiller Session. Délai d’inactivité avant verrouillage de l’écran Aucune @@ -522,6 +548,7 @@ Continuer Copier + Fermer URL non valide Copié dans le presse-papier Suivant @@ -573,12 +600,12 @@ Contact en cours… Nouvelle Session Saisir un Session ID - Scanner un Code QR - Scannez le code QR d\'un utilisateur pour démarrer une session. Les codes QR peuvent se trouver en touchant l\'icône du code QR dans les paramètres du compte. + Scanner un QR Code + Scannez le QR code d\'un utilisateur pour démarrer une session. Les QR codes peuvent se trouver en touchant l\'icône du QR code dans les paramètres du compte. Entrer un Session ID ou un nom ONS Les utilisateurs peuvent partager leur Session ID depuis les paramètres du compte ou en utilisant le code QR. Veuillez vérifier le Session ID ou le nom ONS et réessayer. - Session a besoin d\'accéder à l\'appareil photo pour scanner les codes QR + Session a besoin d\'accéder à l\'appareil photo pour scanner les QR codes Autoriser l\'accès Nouveau groupe privé Saisissez un nom de groupe @@ -591,7 +618,7 @@ Joindre un groupe public Impossible de rejoindre le groupe URL du groupe public - Scannez le code QR + Scanner le QR Code Scannez le code QR du groupe public que vous souhaitez rejoindre Saisissez une URL de groupe public Paramètres @@ -600,6 +627,7 @@ Veuillez choisir un nom d\'utilisateur plus court Confidentialité Notifications + Demandes de message Conversations Appareils reliés Inviter un ami @@ -612,25 +640,39 @@ Style de notification Contenu de notification Confidentialité + Conversations + Aide + Signaler un bug + Exportez vos logs, puis télécharger le fichier au service d\'aide de Session. + Traduire Session + Nous aimerions avoir votre avis + FAQ + Assistance + Exporter les journaux Stratégie de notification Utiliser le Mode Rapide Vous serez averti de nouveaux messages de manière fiable et immédiate en utilisant les serveurs de notification de Google. Modifier le nom Déconnecter l\'appareil Votre phrase de récupération - Ceci est votre phrase de récupération. Elle vous permet de restaurer ou migrer votre Session ID vers un nouvel appareil. + Vous pouvez utiliser votre phrase de récupération pour restaurer votre compte ou relier un appareil. Effacer toutes les données Cela supprimera définitivement vos messages, vos sessions et vos contacts. Souhaitez-vous effacer seulement cet appareil ou supprimer l\'ensemble de votre compte ? + Cela supprimera définitivement vos messages, sessions et contacts. Voulez-vous uniquement effacer cet appareil ou supprimer l\'intégralité de votre compte ? + Effacer l\'appareil uniquement + Effacer l\'appareil et le réseau + Êtes-vous sûr de vouloir supprimer vos données du réseau ? Si vous continuez, vous ne pourrez pas restaurer vos messages ou vos contacts. + Effacer Effacer seulement Compte complet - Code QR - Afficher mon code QR - Scanner le code QR - Scannez le code QR d\'un autre utilisateur pour démarrer une session + QR Code + Afficher mon QR code + Scanner le QR Code + Scannez le QR code d\'un autre utilisateur pour démarrer une session Scannez-moi - Ceci est votre code QR. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous. - Partager le code QR + Ceci est votre QR code. Les autres utilisateurs peuvent le scanner pour démarrer une session avec vous. + Partager le QR code Contacts Groupes privés Groupes publics @@ -664,8 +706,8 @@ Cela prend un certain temps, voulez-vous passer ? Relier un appareil Phrase de récupération - Scannez le code QR - Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre code QR. + Scanner le QR Code + Allez dans Paramètres → Phrase de récupération sur votre autre appareil pour afficher votre QR code. Ou rejoignez l\'un(e) de ceux-ci… Notifications de message Session peut vous avertir de la présence de nouveaux messages de deux façons. @@ -694,6 +736,7 @@ Êtes-vous sûr de vouloir télécharger le média envoyé par %s ? Télécharger %s est bloqué. Débloquer ? + Bloquer l\'utilisateur La préparation de la pièce jointe pour l\'envoi a échoué. Médias Touchez pour télécharger %s @@ -711,6 +754,116 @@ Journal de débogage Partager les logs Voulez-vous exporter les logs de votre application pour pouvoir partager pour le dépannage ? - Code pin + Épingler Désépingler + Tout marquer comme lu + Contacts et Groupes + Messages + Demandes de message + Envoyer un message à cet utilisateur acceptera automatiquement sa demande de message et révélera votre ID de session. + Accepter + Refuser + Effacer tout + Êtes-vous sûr de vouloir refuser cette demande de message ? + Êtes-vous sûr de vouloir supprimer cette demande de message ? + Demande de message supprimée + Êtes-vous sûr de vouloir supprimer toutes les demandes de message ? + Demandes de message supprimées + Votre demande de message a été acceptée. + Votre demande de message est en attente. + Aucune demande de message en attente + Message privé + Groupes privés + Groupe public + Vous avez une nouvelle demande de message + Connexion… + Appel entrant + Refuser l’appel + Répondre à l’appel + Appel en cours + Annuler l’appel + Établissement de l\'appel + Raccrocher + Accepter l\'appel + Refuser l\'appel + Appels vocaux et vidéos + Appels (Bêta) + Active les appels vocaux et vidéo vers et depuis d\'autres utilisateurs. + Appels vocaux / vidéo + La version actuelle des appels vocaux/vidéo exposera votre adresse IP aux serveurs de la Fondation Oxen et aux utilisateurs appelés + Appel Manqué + Vous avez manqué un appel car vous devez activer la permission « Appels vocaux et vidéo » dans les paramètres de confidentialité. + Appel Session + Reconnexion… + Notifications + Les notifications désactivées vous empêcheront de recevoir des appels, aller dans les paramètres de notification de session? + Rejeter + Conversations + Apparence + Aide + Thèmes + Océan sombre + Sombre classique + Océan lumineux + Clair classique + Couleur principale + Ce message + Fréquemment Utilisés + Émoticônes et personnes + Nature + Nourriture + Activités + Voyage + Objets + Symboles + Drapeaux + Emoticônes + Aucun résultat trouvé + + Tous · %1$d + + +%1$d + + Vous + %1$s a réagi à un message %2$s + Masquer les détails + Rechercher un émoticône + Retour à l\'émoticône + Effacer la recherche + Thème sombre automatique + Faire correspondre aux paramètres systèmes + Accédez aux paramètres de notifications de l\'appareil + Contacts bloqués + Vous n\'avez aucun contact bloqué + Débloquer %s + Débloquer les utilisateurs + Êtes-vous sûr·e de vouloir débloquer %s ? + + et %d autre + et %d autres + + + Et %1$d autre a réagi %2$s à ce message + Et %1$d autres ont réagi %2$s à ce message + + Nouvelle conversation + Nouveau message + Créer un groupe + Rejoindre la communauté + Contacts + Inconnu·e + Commencez une nouvelle conversation en entrant l\'ID Session de quelqu\'un ou en lui partageant votre ID Session. + Créer + Rechercher parmi les contacts + URL de la communauté + Entrez l\'URL de la communauté + Rejoindre + Revenir en arrière + Fermer la fenêtre + Échec de la mise à jour de la base de données + Veuillez contacter le support pour signaler l\'erreur. + Envoi + Lu + Envoyé + Échec d’envoi diff --git a/libsession/src/main/res/values-fr-rFR/strings.xml b/libsession/src/main/res/values-fr-rFR/strings.xml new file mode 100644 index 000000000..c8324290f --- /dev/null +++ b/libsession/src/main/res/values-fr-rFR/strings.xml @@ -0,0 +1,54 @@ + + + + Vous avez quitté le groupe. + Vous avez créé un nouveau groupe. + %1$s vous a ajouté·e dans le groupe. + Vous avez renommé le groupe en %1$s + %1$s a renommé le groupe en : %2$s + Vous avez ajouté %1$s au groupe. + %1$s a ajouté %2$s au groupe. + Vous avez retiré %1$s du groupe. + %1$s a supprimé %2$s du groupe. + Vous avez été retiré·e du groupe. + Vous + %s vous a appelé·e + Vous avez appelé %s + Appel manqué de %s + Vous avez désactivé les messages éphémères. + %1$s a désactivé les messages éphémères. + Vous avez défini l’expiration des messages éphémères à %1$s + %1$s a défini l’expiration des messages éphémères à %2$s + %1$s a pris une capture d\'écran. + %1$s a enregistré le média. + + Désactivé + + %d seconde + %d secondes + + %d s + + %d minute + %d minutes + + %d min + + %d heure + %d heures + + %d h + + %d jour + %d jours + + %d j + + %d semaine + %d semaines + + %d sem + %1$s a quitté le groupe. + + Groupe sans nom + diff --git a/libsession/src/main/res/values-fr/strings.xml b/libsession/src/main/res/values-fr/strings.xml new file mode 100644 index 000000000..c8324290f --- /dev/null +++ b/libsession/src/main/res/values-fr/strings.xml @@ -0,0 +1,54 @@ + + + + Vous avez quitté le groupe. + Vous avez créé un nouveau groupe. + %1$s vous a ajouté·e dans le groupe. + Vous avez renommé le groupe en %1$s + %1$s a renommé le groupe en : %2$s + Vous avez ajouté %1$s au groupe. + %1$s a ajouté %2$s au groupe. + Vous avez retiré %1$s du groupe. + %1$s a supprimé %2$s du groupe. + Vous avez été retiré·e du groupe. + Vous + %s vous a appelé·e + Vous avez appelé %s + Appel manqué de %s + Vous avez désactivé les messages éphémères. + %1$s a désactivé les messages éphémères. + Vous avez défini l’expiration des messages éphémères à %1$s + %1$s a défini l’expiration des messages éphémères à %2$s + %1$s a pris une capture d\'écran. + %1$s a enregistré le média. + + Désactivé + + %d seconde + %d secondes + + %d s + + %d minute + %d minutes + + %d min + + %d heure + %d heures + + %d h + + %d jour + %d jours + + %d j + + %d semaine + %d semaines + + %d sem + %1$s a quitté le groupe. + + Groupe sans nom + From 48799db21cc40893a0134ff9dc4e04391c089aca Mon Sep 17 00:00:00 2001 From: hjubb Date: Mon, 6 Feb 2023 16:03:49 +1100 Subject: [PATCH 007/244] build: update build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fdb064f30..4c6bb9648 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,8 +159,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 331 -def canonicalVersionName = "1.16.4" +def canonicalVersionCode = 333 +def canonicalVersionName = "1.16.5" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From cd3b8f357199fcc47ef90111e244a7ccce010cd0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 8 Feb 2023 13:42:35 +1100 Subject: [PATCH 008/244] Fixed a few issues related to the GroupAvatarDownloadJob Added logic to avoid scheduling a GroupAvatarDownloadJob if there is already an existing one with the same parameters Added logic to avoid trying to download an old avatar for a group if there is a new one Added logic to prevent an old GroupAvatarDownloadJob from overriding a valid avatar with an old one --- .../securesms/database/SessionJobDatabase.kt | 4 +-- .../securesms/database/Storage.kt | 4 +-- .../libsession/database/StorageProtocol.kt | 2 +- .../messaging/jobs/BackgroundGroupAddJob.kt | 4 +-- .../messaging/jobs/GroupAvatarDownloadJob.kt | 29 +++++++++++++++++-- .../pollers/OpenGroupPoller.kt | 8 +++-- 6 files changed, 38 insertions(+), 13 deletions(-) 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 4425e3d85..66497d9da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt @@ -83,11 +83,11 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa } } - fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? { + fun getGroupAvatarDownloadJob(server: String, room: String, imageId: String?): GroupAvatarDownloadJob? { val database = databaseHelper.readableDatabase return database.getAll(sessionJobTable, "$jobType = ?", arrayOf(GroupAvatarDownloadJob.KEY)) { jobFromCursor(it) as GroupAvatarDownloadJob? - }.filterNotNull().find { it.server == server && it.room == room } + }.filterNotNull().find { it.server == server && it.room == room && (imageId == null || it.imageId == imageId) } } fun cancelPendingMessageSendJobs(threadID: Long) { 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 f7e2d9d06..33aad17d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -226,8 +226,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseComponent.get(context).sessionJobDatabase().getMessageReceiveJob(messageReceiveJobID) } - override fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? { - return DatabaseComponent.get(context).sessionJobDatabase().getGroupAvatarDownloadJob(server, room) + override fun getGroupAvatarDownloadJob(server: String, room: String, imageId: String?): GroupAvatarDownloadJob? { + return DatabaseComponent.get(context).sessionJobDatabase().getGroupAvatarDownloadJob(server, room, imageId) } override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) { 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 c80ef2464..ee124aa0e 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -49,7 +49,7 @@ interface StorageProtocol { fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob? fun getMessageSendJob(messageSendJobID: String): MessageSendJob? fun getMessageReceiveJob(messageReceiveJobID: String): Job? - fun getGroupAvatarDownloadJob(server: String, room: String): Job? + fun getGroupAvatarDownloadJob(server: String, room: String, imageId: String?): Job? fun resumeMessageSendJobIfNeeded(messageSendJobID: String) fun isJobCanceled(job: Job): Boolean 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 ef67408fb..515410132 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 @@ -43,8 +43,8 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) val info = storage.addOpenGroup(openGroup.joinUrl()) val imageId = info?.imageId - if (imageId != null) { - JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.room, openGroup.server)) + if (imageId != null && storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId) == null) { + JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId)) } Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") storage.onOpenGroupAdded(openGroup.server) 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 6429d760a..e17604636 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 @@ -5,7 +5,7 @@ import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.utilities.Data import org.session.libsession.utilities.GroupUtil -class GroupAvatarDownloadJob(val room: String, val server: String) : Job { +class GroupAvatarDownloadJob(val server: String, val room: String, val imageId: String?) : Job { override var delegate: JobDelegate? = null override var id: String? = null @@ -13,10 +13,30 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { override val maxFailureCount: Int = 10 override fun execute(dispatcherName: String) { + if (imageId == null) { + delegate?.handleJobFailedPermanently(this, dispatcherName, Exception("GroupAvatarDownloadJob now requires imageId")) + return + } + val storage = MessagingModuleConfiguration.shared.storage - val imageId = storage.getOpenGroup(room, server)?.imageId ?: return + val storedImageId = storage.getOpenGroup(room, server)?.imageId + + if (storedImageId == null || storedImageId != imageId) { + delegate?.handleJobFailedPermanently(this, dispatcherName, Exception("GroupAvatarDownloadJob imageId does not match the OpenGroup")) + return + } + try { val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, room, imageId).get() + + // Once the download is complete the imageId might no longer match, so we need to fetch it again just in case + val postDownloadStoredImageId = storage.getOpenGroup(room, server)?.imageId + + if (postDownloadStoredImageId == null || postDownloadStoredImageId != imageId) { + delegate?.handleJobFailedPermanently(this, dispatcherName, Exception("GroupAvatarDownloadJob imageId no longer matches the OpenGroup")) + return + } + val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) storage.updateProfilePicture(groupId, bytes) storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) @@ -30,6 +50,7 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { return Data.Builder() .putString(ROOM, room) .putString(SERVER, server) + .putString(IMAGE_ID, imageId) .build() } @@ -40,6 +61,7 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { private const val ROOM = "room" private const val SERVER = "server" + private const val IMAGE_ID = "imageId" } class Factory : Job.Factory { @@ -47,7 +69,8 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { override fun create(data: Data): GroupAvatarDownloadJob { return GroupAvatarDownloadJob( data.getString(ROOM), - data.getString(SERVER) + data.getString(SERVER), + if (data.hasString(IMAGE_ID)) { data.getString(IMAGE_ID) } else { null } ) } } 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 353b4e1bf..4f270206b 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 @@ -165,14 +165,16 @@ class OpenGroupPoller(private val server: String, private val executorService: S pollInfo.details.imageId != null && ( pollInfo.details.imageId != existingOpenGroup.imageId || !storage.hasDownloadedProfilePicture(dbGroupId) - ) + ) && + storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null ) || ( pollInfo.details == null && existingOpenGroup.imageId != null && - !storage.hasDownloadedProfilePicture(dbGroupId) + !storage.hasDownloadedProfilePicture(dbGroupId) && + storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null ) ) { - JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server)) + JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server, existingOpenGroup.imageId)) } } From 97458a4baac70a5ba4fce974da423d46f69b1c9f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 8 Feb 2023 14:04:02 +1100 Subject: [PATCH 009/244] Added a missing constructor --- .../src/main/java/org/session/libsession/snode/SnodeMessage.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt index 31ced1620..a141de30c 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeMessage.kt @@ -20,6 +20,7 @@ data class SnodeMessage( */ val timestamp: Long ) { + internal constructor(): this("", "", -1, -1) internal fun toJSON(): Map { return mapOf( From 9fd68d27f83e0a8daa98c7e0c29d7d4d7f50276f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 10 Feb 2023 10:05:28 +1100 Subject: [PATCH 010/244] Fixed a few issues with the GroupAvatarDownloadJob Added a method to remove the Group avatar Fixed an issue where the recipient diffing on the HomeActivity wasn't working properly Fixed an issue where the GroupAvatarDownloadJob could be scheduled even when there was already one scheduled --- .../securesms/database/GroupDatabase.java | 19 +++++++++++++++++++ .../securesms/database/Storage.kt | 4 ++++ .../database/model/ThreadRecord.java | 6 ++++++ .../securesms/home/HomeDiffUtil.kt | 7 +++++-- .../libsession/database/StorageProtocol.kt | 1 + .../messaging/jobs/GroupAvatarDownloadJob.kt | 2 +- .../messaging/open_groups/OpenGroup.kt | 2 +- .../pollers/OpenGroupPoller.kt | 10 +++++++++- .../database/LokiOpenGroupDatabaseProtocol.kt | 1 + 9 files changed, 47 insertions(+), 5 deletions(-) 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 584bf3a71..79adead57 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -318,6 +318,25 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt notifyConversationListListeners(); } + @Override + public void removeProfilePicture(String groupID) { + databaseHelper.getWritableDatabase() + .execSQL("UPDATE " + TABLE_NAME + + " SET " + AVATAR + " = NULL, " + + AVATAR_ID + " = NULL, " + + AVATAR_KEY + " = NULL, " + + AVATAR_CONTENT_TYPE + " = NULL, " + + AVATAR_RELAY + " = NULL, " + + AVATAR_DIGEST + " = NULL, " + + AVATAR_URL + " = NULL" + + " WHERE " + + GROUP_ID + " = ?", + new String[] {groupID}); + + Recipient.applyCached(Address.fromSerialized(groupID), recipient -> recipient.setGroupAvatarId(null)); + notifyConversationListListeners(); + } + public boolean hasDownloadedProfilePicture(String groupId) { try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{AVATAR}, GROUP_ID + " = ?", new String[] {groupId}, 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 33aad17d1..076fa57af 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -324,6 +324,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue) } + override fun removeProfilePicture(groupID: String) { + DatabaseComponent.get(context).groupDatabase().removeProfilePicture(groupID) + } + override fun hasDownloadedProfilePicture(groupID: String): Boolean { return DatabaseComponent.get(context).groupDatabase().hasDownloadedProfilePicture(groupID) } 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 dfc4c1bc8..f3e72a874 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 @@ -51,6 +51,7 @@ public class ThreadRecord extends DisplayRecord { private final long expiresIn; private final long lastSeen; private final boolean pinned; + private final int initialRecipientHash; public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, @NonNull Recipient recipient, long date, long count, int unreadCount, @@ -68,6 +69,7 @@ public class ThreadRecord extends DisplayRecord { this.expiresIn = expiresIn; this.lastSeen = lastSeen; this.pinned = pinned; + this.initialRecipientHash = recipient.hashCode(); } public @Nullable Uri getSnippetUri() { @@ -176,4 +178,8 @@ public class ThreadRecord extends DisplayRecord { public boolean isPinned() { return pinned; } + + public int getInitialRecipientHash() { + return initialRecipientHash; + } } 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 1baec2085..b883709c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -28,8 +28,11 @@ class HomeDiffUtil( 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()) } + // The recipient is passed as a reference and changes to recipients update the reference so we + // need to cache the hashCode for the recipient and use that for diffing - unfortunately + // recipient data is also loaded asyncronously which means every thread will refresh at least + // once when the initial recipient data is loaded + if (isSameItem) { isSameItem = (oldItem.initialRecipientHash == newItem.initialRecipientHash) } // Note: Two instances of 'SpannableString' may not equate even though their content matches if (isSameItem) { isSameItem = (oldItem.getDisplayBody(context).toString() == newItem.getDisplayBody(context).toString()) } 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 ee124aa0e..d75e209d1 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -80,6 +80,7 @@ interface StorageProtocol { // Open Group Metadata fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) + fun removeProfilePicture(groupID: String) fun hasDownloadedProfilePicture(groupID: String): Boolean fun setUserCount(room: String, server: String, newValue: Int) 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 e17604636..5861229e5 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 @@ -68,8 +68,8 @@ class GroupAvatarDownloadJob(val server: String, val room: String, val imageId: override fun create(data: Data): GroupAvatarDownloadJob { return GroupAvatarDownloadJob( - data.getString(ROOM), data.getString(SERVER), + data.getString(ROOM), if (data.hasString(IMAGE_ID)) { data.getString(IMAGE_ID) } else { null } ) } 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 6a2a771e0..80a9a1e50 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 @@ -36,7 +36,7 @@ data class OpenGroup( 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 imageId = if (json.hasNonNull("imageId")) { json.get("imageId")?.asText() } else { null } val canWrite = json.get("canWrite")?.asText()?.toBoolean() ?: true val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0 OpenGroup(server = server, room = room, name = displayName, publicKey = publicKey, imageId = imageId, canWrite = canWrite, infoUpdates = infoUpdates) 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 4f270206b..562ddda69 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 @@ -159,6 +159,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S }) } + // Update the group avatar if ( ( pollInfo.details != null && @@ -174,7 +175,14 @@ class OpenGroupPoller(private val server: String, private val executorService: S storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null ) ) { - JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server, existingOpenGroup.imageId)) + JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, existingOpenGroup.imageId)) + } + else if ( + pollInfo.details != null && + pollInfo.details.imageId == null && + existingOpenGroup.imageId != null + ) { + storage.removeProfilePicture(dbGroupId) } } diff --git a/libsignal/src/main/java/org/session/libsignal/database/LokiOpenGroupDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/database/LokiOpenGroupDatabaseProtocol.kt index 28bcecdbd..6ccb1ab61 100644 --- a/libsignal/src/main/java/org/session/libsignal/database/LokiOpenGroupDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/database/LokiOpenGroupDatabaseProtocol.kt @@ -4,4 +4,5 @@ interface LokiOpenGroupDatabaseProtocol { fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) + fun removeProfilePicture(groupID: String) } From bbedad5ebba2bbbbca6247c70b49aee478fe23ee Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 10 Feb 2023 14:33:19 +1100 Subject: [PATCH 011/244] Fixed a few layout issues with the LinkPreviewView --- .../components/GlideBitmapListeningTarget.java | 16 +++++++++++++++- .../components/GlideDrawableListeningTarget.java | 16 +++++++++++++++- .../conversation/v2/messages/LinkPreviewView.kt | 8 ++++++-- .../v2/messages/VisibleMessageContentView.kt | 4 +++- .../conversation/v2/utilities/ThumbnailView.kt | 7 ++++--- .../res/layout/view_visible_message_content.xml | 2 +- 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java b/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java index 61094fb7d..76b7dc21a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java @@ -4,30 +4,44 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import android.view.View; import android.widget.ImageView; import com.bumptech.glide.request.target.BitmapImageViewTarget; import org.session.libsignal.utilities.SettableFuture; +import java.lang.ref.WeakReference; + public class GlideBitmapListeningTarget extends BitmapImageViewTarget { private final SettableFuture loaded; + private final WeakReference loadingView; - public GlideBitmapListeningTarget(@NonNull ImageView view, @NonNull SettableFuture loaded) { + public GlideBitmapListeningTarget(@NonNull ImageView view, @Nullable View loadingView, @NonNull SettableFuture loaded) { super(view); this.loaded = loaded; + this.loadingView = new WeakReference(loadingView); } @Override protected void setResource(@Nullable Bitmap resource) { super.setResource(resource); loaded.set(true); + + if (loadingView.get() != null) { + loadingView.get().setVisibility(View.GONE); + } } @Override public void onLoadFailed(@Nullable Drawable errorDrawable) { super.onLoadFailed(errorDrawable); loaded.set(true); + + if (loadingView.get() != null) { + loadingView.get().setVisibility(View.GONE); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java b/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java index d17790012..16c3bb0c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java @@ -3,30 +3,44 @@ package org.thoughtcrime.securesms.components; import android.graphics.drawable.Drawable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + +import android.view.View; import android.widget.ImageView; import com.bumptech.glide.request.target.DrawableImageViewTarget; import org.session.libsignal.utilities.SettableFuture; +import java.lang.ref.WeakReference; + public class GlideDrawableListeningTarget extends DrawableImageViewTarget { private final SettableFuture loaded; + private final WeakReference loadingView; - public GlideDrawableListeningTarget(@NonNull ImageView view, @NonNull SettableFuture loaded) { + public GlideDrawableListeningTarget(@NonNull ImageView view, @Nullable View loadingView, @NonNull SettableFuture loaded) { super(view); this.loaded = loaded; + this.loadingView = new WeakReference(loadingView); } @Override protected void setResource(@Nullable Drawable resource) { super.setResource(resource); loaded.set(true); + + if (loadingView.get() != null) { + loadingView.get().setVisibility(View.GONE); + } } @Override public void onLoadFailed(@Nullable Drawable errorDrawable) { super.onLoadFailed(errorDrawable); loaded.set(true); + + if (loadingView.get() != null) { + loadingView.get().setVisibility(View.GONE); + } } } 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 45d353cc3..967722389 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 @@ -57,8 +57,12 @@ class LinkPreviewView : LinearLayout { val cornerRadii = MessageBubbleUtilities.calculateRadii(context, isStartOfMessageCluster, isEndOfMessageCluster, message.isOutgoing) cornerMask.setTopLeftRadius(cornerRadii[0]) cornerMask.setTopRightRadius(cornerRadii[1]) - cornerMask.setBottomRightRadius(cornerRadii[2]) - cornerMask.setBottomLeftRadius(cornerRadii[3]) + + // Only round the bottom corners if there is no body text + if (message.body.isEmpty()) { + cornerMask.setBottomRightRadius(cornerRadii[2]) + cornerMask.setBottomLeftRadius(cornerRadii[3]) + } } override fun dispatchDraw(canvas: Canvas) { 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 f8e655e70..8c33e4473 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 @@ -158,7 +158,9 @@ class VisibleMessageContentView : ConstraintLayout { message is MmsMessageRecord && message.linkPreviews.isNotEmpty() -> { 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 + + // When in a link preview ensure the bodyTextView can expand to the full width + binding.bodyTextView.maxWidth = binding.linkPreviewView.root.layoutParams.width } message is MmsMessageRecord && message.slideDeck.audioSlide != null -> { hideBody = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index e15855667..e9a085dad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -123,12 +123,13 @@ open class ThumbnailView: FrameLayout { when { slide.thumbnailUri != null -> { - buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, result)) + buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) } slide.hasPlaceholder() -> { - buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, result)) + buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) } else -> { + binding.thumbnailLoadIndicator.isVisible = false glide.clear(binding.thumbnailImage) result.set(false) } @@ -190,7 +191,7 @@ open class ThumbnailView: FrameLayout { request.transforms(CenterCrop()) } - request.into(GlideDrawableListeningTarget(binding.thumbnailImage, future)) + request.into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, future)) return future } 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 7b09faf83..9c617bd57 100644 --- a/app/src/main/res/layout/view_visible_message_content.xml +++ b/app/src/main/res/layout/view_visible_message_content.xml @@ -80,7 +80,7 @@ app:layout_constraintStart_toStartOf="parent" android:visibility="gone" android:id="@+id/linkPreviewView" - android:layout_width="wrap_content" + android:layout_width="300dp" android:layout_height="wrap_content"/> Date: Mon, 13 Feb 2023 18:26:55 +1100 Subject: [PATCH 012/244] fix: Increase contrast between selected messages and window background. Fixes #1110 (Plan B) (#1116) --- app/src/main/res/values/themes.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 95fb8b797..2874d4297 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -366,7 +366,7 @@ @color/classic_dark_0 - @color/classic_dark_2 + @color/classic_dark_3 @color/classic_dark_6 ?colorAccent @color/classic_dark_0 @@ -386,7 +386,7 @@ @color/classic_dark_1 @color/classic_dark_3 @color/classic_dark_4 - @color/classic_dark_1 + @color/classic_dark_2 @@ -640,7 +640,7 @@ ?colorCellBackground @color/ocean_light_6 ?android:textColorSecondary - @color/ocean_light_6 + @color/ocean_light_5 From 7e7c016bc45a9bf46003e9bd935d895ae63603bd Mon Sep 17 00:00:00 2001 From: Qian Hong Date: Tue, 14 Feb 2023 14:05:28 +1100 Subject: [PATCH 013/244] Update APK url to Oxen repo (#1114) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 341fd4284..c509dd956 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Add the [F-Droid repo](https://fdroid.getsession.org/) -[Download the APK from here](https://github.com/loki-project/session-android/releases/latest) +[Download the APK from here](https://github.com/oxen-io/session-android/releases/latest) ## Summary From 4df530d341df35dc57d9c78bd7d756ef1f561281 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 14 Feb 2023 14:10:47 +1100 Subject: [PATCH 014/244] Code review changes --- .../components/GlideBitmapListeningTarget.java | 12 ++++++++---- .../components/GlideDrawableListeningTarget.java | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java b/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java index 76b7dc21a..157bc215e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/GlideBitmapListeningTarget.java @@ -30,8 +30,10 @@ public class GlideBitmapListeningTarget extends BitmapImageViewTarget { super.setResource(resource); loaded.set(true); - if (loadingView.get() != null) { - loadingView.get().setVisibility(View.GONE); + View loadingViewInstance = loadingView.get(); + + if (loadingViewInstance != null) { + loadingViewInstance.setVisibility(View.GONE); } } @@ -40,8 +42,10 @@ public class GlideBitmapListeningTarget extends BitmapImageViewTarget { super.onLoadFailed(errorDrawable); loaded.set(true); - if (loadingView.get() != null) { - loadingView.get().setVisibility(View.GONE); + View loadingViewInstance = loadingView.get(); + + if (loadingViewInstance != null) { + loadingViewInstance.setVisibility(View.GONE); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java b/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java index 16c3bb0c3..406c878ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/GlideDrawableListeningTarget.java @@ -29,8 +29,10 @@ public class GlideDrawableListeningTarget extends DrawableImageViewTarget { super.setResource(resource); loaded.set(true); - if (loadingView.get() != null) { - loadingView.get().setVisibility(View.GONE); + View loadingViewInstance = loadingView.get(); + + if (loadingViewInstance != null) { + loadingViewInstance.setVisibility(View.GONE); } } @@ -39,8 +41,10 @@ public class GlideDrawableListeningTarget extends DrawableImageViewTarget { super.onLoadFailed(errorDrawable); loaded.set(true); - if (loadingView.get() != null) { - loadingView.get().setVisibility(View.GONE); + View loadingViewInstance = loadingView.get(); + + if (loadingViewInstance != null) { + loadingViewInstance.setVisibility(View.GONE); } } } From da0d38f3897c88c2d4cb83f26292e40ec60df0cf Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 14 Feb 2023 16:17:25 +1100 Subject: [PATCH 015/244] Updated the copy for the Giphy warning prompt --- .../conversation/v2/ConversationActivityV2.kt | 12 ++++++------ app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) 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 8ba8f1068..a1a3dc253 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 @@ -1439,16 +1439,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val hasSeenGIFMetaDataWarning: Boolean = textSecurePreferences.hasSeenGIFMetaDataWarning() if (!hasSeenGIFMetaDataWarning) { val builder = AlertDialog.Builder(this) - builder.setTitle("Search GIFs?") - builder.setMessage("You will not have full metadata protection when sending GIFs.") - builder.setPositiveButton("OK") { dialog: DialogInterface, _: Int -> + builder.setTitle(R.string.giphy_permission_title) + builder.setMessage(R.string.giphy_permission_message) + builder.setPositiveButton(R.string.continue_2) { dialog: DialogInterface, _: Int -> textSecurePreferences.setHasSeenGIFMetaDataWarning() AttachmentManager.selectGif(this, PICK_GIF) dialog.dismiss() } - builder.setNegativeButton( - "Cancel" - ) { dialog: DialogInterface, _: Int -> dialog.dismiss() } + builder.setNegativeButton(R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + } builder.create().show() } else { AttachmentManager.selectGif(this, PICK_GIF) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c39714c0..ebacb766d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -871,4 +871,6 @@ Read Sent Failed to send + Search GIFs? + Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs. From cc7a80900f798cdb3b3dd95fabc3671a2ae87e75 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 14 Feb 2023 16:43:55 +1100 Subject: [PATCH 016/244] Added copy session id and community url to the conversation long press menu --- .../home/ConversationOptionsBottomSheet.kt | 7 +++++++ .../securesms/home/HomeActivity.kt | 20 +++++++++++++++++++ .../fragment_conversation_bottom_sheet.xml | 16 +++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index 0b3c44a54..fc85f544f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -20,6 +20,7 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto lateinit var thread: ThreadRecord var onViewDetailsTapped: (() -> Unit?)? = null + var onCopyConversationId: (() -> Unit?)? = null var onPinTapped: (() -> Unit)? = null var onUnpinTapped: (() -> Unit)? = null var onBlockTapped: (() -> Unit)? = null @@ -37,6 +38,8 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto override fun onClick(v: View?) { when (v) { binding.detailsTextView -> onViewDetailsTapped?.invoke() + binding.copyConversationId -> onCopyConversationId?.invoke() + binding.copyCommunityUrl -> onCopyConversationId?.invoke() binding.pinTextView -> onPinTapped?.invoke() binding.unpinTextView -> onUnpinTapped?.invoke() binding.blockTextView -> onBlockTapped?.invoke() @@ -63,6 +66,10 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto } else { binding.detailsTextView.visibility = View.GONE } + binding.copyConversationId.visibility = if (!recipient.isGroupRecipient && !recipient.isLocalNumber) View.VISIBLE else View.GONE + binding.copyConversationId.setOnClickListener(this) + binding.copyCommunityUrl.visibility = if (recipient.isOpenGroupRecipient) View.VISIBLE else View.GONE + binding.copyCommunityUrl.setOnClickListener(this) binding.unMuteNotificationsTextView.isVisible = recipient.isMuted && !recipient.isLocalNumber binding.muteNotificationsTextView.isVisible = !recipient.isMuted && !recipient.isLocalNumber binding.unMuteNotificationsTextView.setOnClickListener(this) 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 f1a9c8ed9..9b1e01a54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -4,6 +4,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.ClipData +import android.content.ClipboardManager import android.os.Bundle import android.text.SpannableString import android.widget.Toast @@ -426,6 +428,24 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), userDetailsBottomSheet.arguments = bundle userDetailsBottomSheet.show(supportFragmentManager, userDetailsBottomSheet.tag) } + bottomSheet.onCopyConversationId = onCopyConversationId@{ + bottomSheet.dismiss() + if (!thread.recipient.isGroupRecipient && !thread.recipient.isLocalNumber) { + val clip = ClipData.newPlainText("Session ID", thread.recipient.address.toString()) + val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager + manager.setPrimaryClip(clip) + Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + } + else if (thread.recipient.isOpenGroupRecipient) { + val threadId = threadDb.getThreadIdIfExistsFor(thread.recipient) ?: return@onCopyConversationId Unit + val openGroup = DatabaseComponent.get(this@HomeActivity).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return@onCopyConversationId Unit + + val clip = ClipData.newPlainText("Community URL", openGroup.joinURL) + val manager = getSystemService(PassphraseRequiredActionBarActivity.CLIPBOARD_SERVICE) as ClipboardManager + manager.setPrimaryClip(clip) + Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + } + } bottomSheet.onBlockTapped = { bottomSheet.dismiss() if (!thread.recipient.isBlocked) { diff --git a/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml b/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml index 81f62c4a7..a4b93801b 100644 --- a/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml @@ -16,6 +16,22 @@ android:drawableTint="?attr/colorControlNormal" android:text="@string/details" /> + + + + Date: Thu, 16 Mar 2023 07:18:18 +0800 Subject: [PATCH 017/244] Update zh-rCN strings.xml (#1129) --- app/src/main/res/values-zh-rCN/strings.xml | 100 ++++++++++----------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 86963c540..6452efb83 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -18,7 +18,7 @@ 删除所有旧消息? - 这将会立刻整理所有会话到 %d 最近的信息。 + 这将会立刻整理所有会话到 %d 最近的信息 删除 开启 @@ -29,7 +29,7 @@ (视频) (回复) - 找不到用于选择媒体的应用。 + 找不到用于选择媒体的应用 Session 需要\"存储空间\"权限才能发送图片、视频以及音频,但是该权限已被永久禁用。请转到系统的应用设置菜单,找到权限设置,选择 Session 应用,并启用\"存储空间\"权限。 Session 需要\"相机\"权限才能拍照并发送相片,但是该权限已被永久禁用。请转到系统的应用设置菜单,找到权限设置,选择 Session 应用,并启用\"相机\"权限。 @@ -40,14 +40,14 @@ 本周 本月 - 未找到网页浏览器。 + 未找到网页浏览器 群组 发送失败,点击查看详情 - 收到密钥交换消息,点击进行处理。 + 收到密钥交换消息,点击进行处理 发送失败,点击使用不安全的方式发送 - 无法找到能打开该媒体的应用。 + 无法找到能打开该媒体的应用 已复制 %s 阅读更多   下载更多 @@ -55,7 +55,7 @@ 添加附件 选择联系信息 - 抱歉,设置附件时出错。 + 抱歉,设置附件时出错 消息 撰写 禁言至 %1$s @@ -86,7 +86,7 @@ 删除选择的信息? - 将会永久删除所有已选择的 %1$d 条消息。 + 将会永久删除所有已选择的 %1$d 条消息 是否封禁此用户? 保存到存储? @@ -148,19 +148,19 @@ 发送给%s 添加注释... - 一个附件已被移除,因为它超过了文件大小限制。 - 摄像头不可用。 + 一个附件已被移除,因为它超过了文件大小限制 + 摄像头不可用 发送给 %s 的消息 - 您最多分享 %d 项。 + 您最多分享 %d 项 所有媒体 - 已收到的信息使用了旧版本的 Signal 进行加密,并且已经不再被支持。请联系发送者升级 Session 到最新版本然后再次发送该信息。 - 您已经离开了此群组。 + 已收到的信息使用了旧版本的 Signal 进行加密,并且已经不再被支持。请联系发送者升级 Session 到最新版本然后再次发送该信息 + 您已经离开了此群组 您更新了此群组 - %s 更新了此群组。 + %s 更新了此群组 阅后即焚 您的信息将不会过期。 @@ -169,10 +169,10 @@ 输入密码 屏蔽此联系人? - 您将不再收到来自此联系人的消息和通话。 + 您将不再收到来自此联系人的消息和通话 屏蔽 解除此联系人的屏蔽? - 您将可以再次收到来自此联系人的信息和呼叫。 + 您将可以再次收到来自此联系人的信息和呼叫 解除屏蔽 通知设置 @@ -182,14 +182,14 @@ 收到损坏的密钥 交换消息! - 接受到了使用新的安全码的信息。点击以处理和显示。 + 接受到了使用新的安全码的信息。点击以处理和显示 您重置了安全会话。 %s 重置了安全会话 重复的信息。 群组已更新 离开此群组 - 安全会话已重设。 + 安全会话已重设 草稿: 您呼叫的 呼叫您的 @@ -198,8 +198,8 @@ %s 在 Session 上! 阅后即焚已禁用 阅后即焚时间设置为 %s - %s 进行了截图。 - %s 保存了媒体内容。 + %s 进行了截图 + %s 保存了媒体内容 安全代码已改变 您与 %s 的安全码已经改变 你被标识为已验证 @@ -218,24 +218,24 @@ 静音通知 - 轻触以开启。 + 轻触以开启  Session 已解锁 锁定 Session - 不支持的媒体类型。 + 不支持的媒体类型 草稿 Session 需要存储权限以写入外部存储,但是该权限已经被永久拒绝。请进入应用程序设置,点击权限,并启用“存储”。 无法在没有写入到外部存储的权限时保存到外部存储 删除消息吗? - 这将会永久删除消息。 + 这将会永久删除消息 %1$d 新信息是在 %2$d 中的会话 最新的邮件来自:%1$s 锁定的消息 信息发送失败。 信息发送失败 - 发送信息错误。 + 发送信息错误 全部标记为已读 标记为已读 回复 @@ -344,7 +344,7 @@ 屏蔽 - 有些问题需要您注意。 + 有些问题需要您注意 发送 已接收 销毁 @@ -426,7 +426,7 @@ 隐身键盘 已读回执 “正在输入”提示 - 如果“正在输入”提示功能被禁用,您将无法看到别人正在输入的提示。 + 如果“正在输入”提示功能被禁用,您将无法看到别人正在输入的提示 关闭键盘自动学习功能 明亮 黑暗 @@ -482,19 +482,19 @@ 正在删除 正在删除旧消息… - 旧消息删除成功。 + 旧消息删除成功 需要相应权限 继续 不是现在 备份将会保存到外部存储并使用下面的密码加密。您必须使用此密码才能恢复备份。 - 我已记下此密码。如果没有该密码,我将无法恢复备份。 + 我已记下此密码。如果没有该密码,我将无法恢复备份 略过 - 不能导入来自新版本的 Session 备份。 + 不能导入来自新版本的 Session 备份 备份密码错误 启用本地备份吗? 启用备份 - 请确定您已理解对话框中的文字并勾选最下方的复选框。 + 请确定您已理解对话框中的文字并勾选最下方的复选框 删除备份吗? 关闭并且删除所有本地备份吗? 删除备份 @@ -522,17 +522,17 @@ 创建Session ID 继续使用您的Session ID 什么是Session? - Session是一个去中心化的加密消息应用。 + Session是一个去中心化的加密消息应用 所以Session不会收集我的个人信息或者对话元数据?怎么做到的? 通过结合高效的匿名路由和端到端的加密技术。 好朋友之间就要使用能够保证信息安全的聊天工具,不用谢啦! 向您的Session ID打个招呼吧 - 您的Session ID是其他用户在与您聊天时使用的独一无二的地址。Session ID与您的真实身份无关,它在设计上完全是匿名且私密的。 + 您的Session ID是其他用户在与您聊天时使用的独一无二的地址。Session ID与您的真实身份无关,它在设计上完全是匿名且私密的 恢复您的帐号 - 在您重新登陆并需要恢复账户时,请输入您注册帐号时的恢复口令。 + 在您重新登陆并需要恢复账户时,请输入您注册帐号时的恢复口令 输入您的恢复口令 选择您想显示的名称 - 这就是您在使用Session时的名字。它可以是您的真实姓名,别名或您喜欢的其他任何名称。 + 这就是您在使用Session时的名字。它可以是您的真实姓名,别名或您喜欢的其他任何名称 输入您想显示的名称 请设定一个名称 请设定一个较短的名称 @@ -550,7 +550,7 @@ 长按显示内容 就快完成了!80% 保存恢复口令以保护您的帐号安全 - 点击并按住遮盖住的单词以显示您的恢复口令,请将它安全地存储以保护您的Session ID。 + 点击并按住遮盖住的单词以显示您的恢复口令,请将它安全地存储以保护您的Session ID 请确保将恢复口令存储在安全的地方 路径 Session会通过其去中心化网络中的多个服务节点跳转消息以隐藏IP。以下国家是您目前的消息连接跳转服务节点所在地: @@ -563,10 +563,10 @@ 新建私人聊天 输入Session ID 扫描二维码 - 扫描其他用户的二维码来发起对话。您可以在帐号设置中点击二维码图标找到二维码。 + 扫描其他用户的二维码来发起对话。您可以在帐号设置中点击二维码图标找到二维码 输入Session ID或ONS名称 - 用户可以通过进入帐号设置并点击“共享Session ID”来分享自己的Session ID,或通过共享其二维码来分享其Session ID。 - 请检查会话 ID 或 ONS 名称,然后重试。 + 用户可以通过进入帐号设置并点击“共享Session ID”来分享自己的Session ID,或通过共享其二维码来分享其Session ID + 请检查会话 ID 或 ONS 名称,然后重试 Session需要摄像头访问权限才能扫描二维码 授予摄像头访问权限 创建私密群组 @@ -603,13 +603,13 @@ 隐私 通知选项 使用快速模式 - 新消息将通过 Google 通知服务器即时可靠地发送。 + 新消息将通过 Google 通知服务器即时可靠地发送 更换名称 断开设备关联 您的恢复口令 - 这是您的恢复口令。您可以通过该口令将Session ID还原或迁移到新设备上。 + 这是您的恢复口令。您可以通过该口令将Session ID还原或迁移到新设备上 清除所有数据 - 这将永久删除您的消息、对话和联系人。 + 这将永久删除您的消息、对话和联系人 你想只清除这个设备,还是删除你的整个账户? 仅删除 整个账户 @@ -618,7 +618,7 @@ 扫描二维码 扫描对方的二维码以发起对话 扫描我的二维码 - 这是您的二维码。其他用户可以对其进行扫描以发起与您的对话。 + 这是您的二维码。其他用户可以对其进行扫描以发起与您的对话 分享二维码 联系人 私密群组 @@ -645,23 +645,23 @@ 附件 语音消息 详细信息 - 无法激活备份,请稍后重试或联系客服。 + 无法激活备份,请稍后重试或联系客服 恢复备份 选择文件 - 选择一个备份文件,并输入创建该文件时使用的密码。 + 选择一个备份文件,并输入创建该文件时使用的密码 30位数的密码 这需要一点时间,您想要跳过吗? 关联设备 恢复口令 扫描二维码 - 在您的其他设备上导航到设置 -> 恢复口令以显示您的 QR 代码。 + 在您的其他设备上导航到设置 -> 恢复口令以显示您的 QR 代码 或加入下列群组… 消息通知 - 我们有两种方式来向您发送消息通知。 + 我们有两种方式来向您发送消息通知 快速模式 慢速模式 - 新消息将通过 Google 通知服务器即时可靠地发送。 - Session 将不时在后台获取新消息。 + 新消息将通过 Google 通知服务器即时可靠地发送 + Session 将不时在后台获取新消息 恢复口令 Session 已锁定 点击解锁 @@ -677,18 +677,18 @@ 打开 复制链接 是否启用链接预览? - 链接预览将为您发送或收到的URL生成预览内容。 该功能非常实用,但Session需要访问该链接指向的网站以生成预览。您可以随后在会话设置中禁用该功能。 + 链接预览将为您发送或收到的URL生成预览内容。 该功能非常实用,但Session需要访问该链接指向的网站以生成预览。您可以随后在会话设置中禁用该功能 启用 是否信任 %s? 您确定要下载%s发送的媒体消息吗? 下载 %s 已被屏蔽。是否解除屏蔽? - 准备发送附件失败。 + 准备发送附件失败 媒体文件 点击下载 %s 错误 警告 - 这是您的恢复口令。如果您将其发送给某人,他们将完全有权访问您的帐户。 + 这是您的恢复口令。如果您将其发送给某人,他们将完全有权访问您的帐户 发送 所有的 提到我的 From 405b8c7d287c6b0d4fb5bd649f630ef751eaeb9c Mon Sep 17 00:00:00 2001 From: Ian Macdonald Date: Thu, 16 Mar 2023 00:38:49 +0100 Subject: [PATCH 018/244] Allow user display names of up to 64 bytes. (#981) --- .../java/org/session/libsession/utilities/SSKEnvironment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt index 0374b9c00..b750b3940 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/SSKEnvironment.kt @@ -26,7 +26,7 @@ class SSKEnvironment( interface ProfileManagerProtocol { companion object { - const val NAME_PADDED_LENGTH = 26 + const val NAME_PADDED_LENGTH = 64 } fun setNickname(context: Context, recipient: Recipient, nickname: String?) @@ -54,4 +54,4 @@ class SSKEnvironment( shared = SSKEnvironment(typingIndicators, readReceiptManager, profileManager, notificationManager, messageExpirationManager) } } -} \ No newline at end of file +} From 5e28af2be4f7f8e41d61fd466a25be036a326c51 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 31 Mar 2023 10:11:30 +1100 Subject: [PATCH 019/244] Updated the code to use the network offset time everywhere relevant (#1111) * Updated the code to use the network offset time everywhere relevant * Updated the SnodeAPI.nowWithOffset to use @JvmStatic --- .../securesms/MediaOverviewActivity.java | 3 ++- .../securesms/MediaPreviewActivity.java | 5 +++-- .../components/ConversationItemFooter.java | 3 ++- .../conversation/v2/ConversationActivityV2.kt | 13 +++++++------ .../conversation/v2/MessageDetailActivity.kt | 3 ++- .../v2/messages/VisibleMessageView.kt | 3 ++- .../securesms/database/MmsDatabase.kt | 9 +++++---- .../securesms/database/SmsDatabase.java | 9 +++++---- .../securesms/database/ThreadDatabase.java | 7 ++++--- .../notifications/AndroidAutoReplyReceiver.java | 5 +++-- .../notifications/DefaultMessageNotifier.java | 3 ++- .../notifications/MarkReadReceiver.java | 3 ++- .../repository/ConversationRepository.kt | 2 +- .../securesms/service/QuickResponseService.java | 3 ++- .../securesms/webrtc/CallManager.kt | 3 ++- .../messaging/jobs/GroupAvatarDownloadJob.kt | 3 ++- .../messaging/open_groups/OpenGroupApi.kt | 3 ++- .../sending_receiving/MessageReceiver.kt | 3 ++- .../sending_receiving/MessageSender.kt | 6 +++--- .../MessageSenderClosedGroupHandler.kt | 17 +++++++++-------- .../org/session/libsession/snode/SnodeAPI.kt | 5 +++-- .../messages/SignalServiceDataMessage.java | 4 ++-- 22 files changed, 67 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 53a909c5a..b36fdb3ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -54,6 +54,7 @@ import com.google.android.material.tabs.TabLayout; import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.sending_receiving.MessageSender; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MediaDatabase; @@ -366,7 +367,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { private void sendMediaSavedNotificationIfNeeded() { if (recipient.isGroupRecipient()) return; - DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); + DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset())); MessageSender.send(message, recipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index b21c7dac8..6544c2ab8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -60,6 +60,7 @@ import androidx.viewpager.widget.ViewPager; import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; @@ -423,7 +424,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im .onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) .onAllGranted(() -> { SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this); - long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis(); + long saveDate = (mediaItem.date > 0) ? mediaItem.date : SnodeAPI.getNowWithOffset(); saveTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null)); @@ -437,7 +438,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im private void sendMediaSavedNotificationIfNeeded() { if (conversationRecipient.isGroupRecipient()) return; - DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); + DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(SnodeAPI.getNowWithOffset())); MessageSender.send(message, conversationRecipient.getAddress()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java index 195c066d4..1ac4f8442 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -13,6 +13,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.session.libsession.snode.SnodeAPI; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView; import org.thoughtcrime.securesms.database.model.MessageRecord; @@ -106,7 +107,7 @@ public class ConversationItemFooter extends LinearLayout { messageRecord.getExpiresIn()); this.timerView.startAnimation(); - if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= System.currentTimeMillis()) { + if (messageRecord.getExpireStarted() + messageRecord.getExpiresIn() <= SnodeAPI.getNowWithOffset()) { ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule(); } } else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) { 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 a1a3dc253..2da61feee 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 @@ -57,6 +57,7 @@ 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.snode.SnodeAPI import org.session.libsession.utilities.* import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.concurrent.SimpleTask @@ -984,7 +985,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe recipientDb.setExpireMessages(thread, expirationTime) val message = ExpirationTimerUpdate(expirationTime) message.recipient = thread.address.serialize() - message.sentTimestamp = System.currentTimeMillis() + message.sentTimestamp = SnodeAPI.nowWithOffset val expiringMessageManager = ApplicationContext.getInstance(this).expiringMessageManager expiringMessageManager.setExpirationTimer(message) MessageSender.send(message, thread.address) @@ -1113,7 +1114,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // Create the message val recipient = viewModel.recipient ?: return val reactionMessage = VisibleMessage() - val emojiTimestamp = System.currentTimeMillis() + val emojiTimestamp = SnodeAPI.nowWithOffset reactionMessage.sentTimestamp = emojiTimestamp val author = textSecurePreferences.getLocalNumber()!! // Put the message in the database @@ -1146,7 +1147,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun sendEmojiRemoval(emoji: String, originalMessage: MessageRecord) { val recipient = viewModel.recipient ?: return val message = VisibleMessage() - val emojiTimestamp = System.currentTimeMillis() + val emojiTimestamp = SnodeAPI.nowWithOffset message.sentTimestamp = emojiTimestamp val author = textSecurePreferences.getLocalNumber()!! reactionDb.deleteReaction(emoji, MessageId(originalMessage.id, originalMessage.isMms), author, false) @@ -1375,7 +1376,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } // Create the message val message = VisibleMessage() - message.sentTimestamp = System.currentTimeMillis() + message.sentTimestamp = SnodeAPI.nowWithOffset message.text = text val outgoingTextMessage = OutgoingTextMessage.from(message, recipient) // Clear the input bar @@ -1399,7 +1400,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe processMessageRequestApproval() // Create the message val message = VisibleMessage() - message.sentTimestamp = System.currentTimeMillis() + message.sentTimestamp = SnodeAPI.nowWithOffset message.text = body val quote = quotedMessage?.let { val quotedAttachments = (it as? MmsMessageRecord)?.slideDeck?.asAttachments() ?: listOf() @@ -1796,7 +1797,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun sendMediaSavedNotification() { val recipient = viewModel.recipient ?: return if (recipient.isGroupRecipient) { return } - val timestamp = System.currentTimeMillis() + val timestamp = SnodeAPI.nowWithOffset val kind = DataExtractionNotification.Kind.MediaSaved(timestamp) val message = DataExtractionNotification(kind) MessageSender.send(message, recipient.address) 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 104c9851b..0938b21dd 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 @@ -10,6 +10,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.TextSecurePreferences @@ -88,7 +89,7 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { binding.expiresContainer.visibility = View.GONE } else { binding.expiresContainer.visibility = View.VISIBLE - val elapsed = System.currentTimeMillis() - messageRecord!!.expireStarted + val elapsed = SnodeAPI.nowWithOffset - messageRecord!!.expireStarted val remaining = messageRecord!!.expiresIn - elapsed val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1)) 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 580516462..1890e604a 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 @@ -26,6 +26,7 @@ import network.loki.messenger.databinding.ViewVisibleMessageBinding import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.getColorFromAttr @@ -315,7 +316,7 @@ class VisibleMessageView : LinearLayout { if (message.expireStarted > 0) { binding.expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) binding.expirationTimerView.startAnimation() - if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) { + if (message.expireStarted + message.expiresIn <= SnodeAPI.nowWithOffset) { ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule() } } else if (!message.isMediaPending) { 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 f0b849ef0..90d3cad45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -35,6 +35,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.UNKNOWN import org.session.libsession.utilities.Address.Companion.fromExternal @@ -380,7 +381,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } override fun markExpireStarted(messageId: Long) { - markExpireStarted(messageId, System.currentTimeMillis()) + markExpireStarted(messageId, SnodeAPI.nowWithOffset) } override fun markExpireStarted(messageId: Long, startedTimestamp: Long) { @@ -798,7 +799,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa // In open groups messages should be sorted by their server timestamp var receivedTimestamp = serverTimestamp if (serverTimestamp == 0L) { - receivedTimestamp = System.currentTimeMillis() + receivedTimestamp = SnodeAPI.nowWithOffset } contentValues.put(DATE_RECEIVED, receivedTimestamp) contentValues.put(SUBSCRIPTION_ID, message.subscriptionId) @@ -1277,7 +1278,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val slideDeck = SlideDeck(context, message!!.attachments) return MediaMmsMessageRecord( id, message.recipient, message.recipient, - 1, System.currentTimeMillis(), System.currentTimeMillis(), + 1, SnodeAPI.nowWithOffset, SnodeAPI.nowWithOffset, 0, threadId, message.body, slideDeck, slideDeck.slides.size, if (message.isSecure) MmsSmsColumns.Types.getOutgoingEncryptedMessageType() else MmsSmsColumns.Types.getOutgoingSmsMessageType(), @@ -1285,7 +1286,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa LinkedList(), message.subscriptionId, message.expiresIn, - System.currentTimeMillis(), 0, + SnodeAPI.nowWithOffset, 0, if (message.outgoingQuote != null) Quote( message.outgoingQuote!!.id, message.outgoingQuote!!.author, 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 a5014aacb..0d61b2679 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -36,6 +36,7 @@ import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.IdentityKeyMismatchList; @@ -227,7 +228,7 @@ public class SmsDatabase extends MessagingDatabase { @Override public void markExpireStarted(long id) { - markExpireStarted(id, System.currentTimeMillis()); + markExpireStarted(id, SnodeAPI.getNowWithOffset()); } @Override @@ -530,7 +531,7 @@ public class SmsDatabase extends MessagingDatabase { contentValues.put(ADDRESS, address.serialize()); contentValues.put(THREAD_ID, threadId); contentValues.put(BODY, message.getMessageBody()); - contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); + contentValues.put(DATE_RECEIVED, SnodeAPI.getNowWithOffset()); contentValues.put(DATE_SENT, message.getSentTimestampMillis()); contentValues.put(READ, 1); contentValues.put(TYPE, type); @@ -765,11 +766,11 @@ public class SmsDatabase extends MessagingDatabase { public MessageRecord getCurrent() { return new SmsMessageRecord(id, message.getMessageBody(), message.getRecipient(), message.getRecipient(), - System.currentTimeMillis(), System.currentTimeMillis(), + SnodeAPI.getNowWithOffset(), SnodeAPI.getNowWithOffset(), 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), threadId, 0, new LinkedList(), message.getExpiresIn(), - System.currentTimeMillis(), 0, false, Collections.emptyList(), false); + SnodeAPI.getNowWithOffset(), 0, false, Collections.emptyList(), false); } } 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 976a21595..52d914af0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -35,6 +35,7 @@ import com.annimon.stream.Stream; import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.jetbrains.annotations.NotNull; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.DelimiterUtil; @@ -146,7 +147,7 @@ public class ThreadDatabase extends Database { private long createThreadForRecipient(Address address, boolean group, int distributionType) { ContentValues contentValues = new ContentValues(4); - long date = System.currentTimeMillis(); + long date = SnodeAPI.getNowWithOffset(); contentValues.put(DATE, date - date % 1000); contentValues.put(ADDRESS, address.serialize()); @@ -301,7 +302,7 @@ public class ThreadDatabase extends Database { contentValues.put(UNREAD_MENTION_COUNT, 0); if (lastSeen) { - contentValues.put(LAST_SEEN, System.currentTimeMillis()); + contentValues.put(LAST_SEEN, SnodeAPI.getNowWithOffset()); } SQLiteDatabase db = databaseHelper.getWritableDatabase(); @@ -520,7 +521,7 @@ public class ThreadDatabase extends Database { SQLiteDatabase db = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(1); if (timestamp == -1) { - contentValues.put(LAST_SEEN, System.currentTimeMillis()); + contentValues.put(LAST_SEEN, SnodeAPI.getNowWithOffset()); } else { contentValues.put(LAST_SEEN, timestamp); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java index 884d6d7be..ddff9f52b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java @@ -30,6 +30,7 @@ import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; import org.session.libsession.messaging.messages.visible.VisibleMessage; import org.session.libsession.messaging.sending_receiving.MessageSender; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; @@ -82,7 +83,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { VisibleMessage message = new VisibleMessage(); message.setText(responseText.toString()); - message.setSentTimestamp(System.currentTimeMillis()); + message.setSentTimestamp(SnodeAPI.getNowWithOffset()); MessageSender.send(message, recipient.getAddress()); if (recipient.isGroupRecipient()) { @@ -96,7 +97,7 @@ public class AndroidAutoReplyReceiver extends BroadcastReceiver { } else { Log.w("AndroidAutoReplyReceiver", "Sending regular message "); OutgoingTextMessage reply = OutgoingTextMessage.from(message, recipient); - DatabaseComponent.get(context).smsDatabase().insertMessageOutbox(replyThreadId, reply, false, System.currentTimeMillis(), null, true); + DatabaseComponent.get(context).smsDatabase().insertMessageOutbox(replyThreadId, reply, false, SnodeAPI.getNowWithOffset(), null, true); } List messageIds = DatabaseComponent.get(context).threadDatabase().setRead(replyThreadId, true); 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 728462903..327492e95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -44,6 +44,7 @@ import org.session.libsession.messaging.open_groups.OpenGroup; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.utilities.SessionId; import org.session.libsession.messaging.utilities.SodiumUtilities; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.ServiceUtil; @@ -137,7 +138,7 @@ public class DefaultMessageNotifier implements MessageNotifier { Intent intent = new Intent(context, ConversationActivityV2.class); intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); - intent.setData((Uri.parse("custom://" + System.currentTimeMillis()))); + intent.setData((Uri.parse("custom://" + SnodeAPI.getNowWithOffset()))); FailedNotificationBuilder builder = new FailedNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context), intent); ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index f1ec6d188..6075be65e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -14,6 +14,7 @@ import com.annimon.stream.Stream; import org.session.libsession.messaging.messages.control.ReadReceipt; import org.session.libsession.messaging.sending_receiving.MessageSender; +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; @@ -86,7 +87,7 @@ public class MarkReadReceiver extends BroadcastReceiver { List timestamps = Stream.of(addressMap.get(address)).map(SyncMessageId::getTimetamp).toList(); if (!SessionMetaProtocol.shouldSendReadReceipt(Recipient.from(context, address, false))) { continue; } ReadReceipt readReceipt = new ReadReceipt(timestamps); - readReceipt.setSentTimestamp(System.currentTimeMillis()); + readReceipt.setSentTimestamp(SnodeAPI.getNowWithOffset()); MessageSender.send(readReceipt, address); } } 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 2d6789401..7c1e036da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -110,7 +110,7 @@ class DefaultConversationRepository @Inject constructor( val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return for (contact in contacts) { val message = VisibleMessage() - message.sentTimestamp = System.currentTimeMillis() + message.sentTimestamp = SnodeAPI.nowWithOffset val openGroupInvitation = OpenGroupInvitation() openGroupInvitation.name = openGroup.name openGroupInvitation.url = openGroup.joinURL diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java index 0f5a3f17a..a56bc8c0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/QuickResponseService.java @@ -9,6 +9,7 @@ import android.widget.Toast; import network.loki.messenger.R; import org.session.libsession.messaging.messages.visible.VisibleMessage; +import org.session.libsession.snode.SnodeAPI; import org.session.libsession.utilities.Address; import org.session.libsignal.utilities.Log; import org.session.libsession.messaging.sending_receiving.MessageSender; @@ -50,7 +51,7 @@ public class QuickResponseService extends IntentService { if (!TextUtils.isEmpty(content)) { VisibleMessage message = new VisibleMessage(); message.setText(content); - message.setSentTimestamp(System.currentTimeMillis()); + message.setSentTimestamp(SnodeAPI.getNowWithOffset()); MessageSender.send(message, Address.fromExternal(this, number)); } } catch (URISyntaxException e) { 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 b7a9b6fd6..07e73bedd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -18,6 +18,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.sending_receiving.MessageSender +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Debouncer import org.session.libsession.utilities.Util @@ -574,7 +575,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va } } - fun insertCallMessage(threadPublicKey: String, callMessageType: CallMessageType, signal: Boolean = false, sentTimestamp: Long = System.currentTimeMillis()) { + fun insertCallMessage(threadPublicKey: String, callMessageType: CallMessageType, signal: Boolean = false, sentTimestamp: Long = SnodeAPI.nowWithOffset) { storage.insertCallMessage(threadPublicKey, callMessageType, sentTimestamp) } 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 5861229e5..07fd6254d 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 @@ -3,6 +3,7 @@ package org.session.libsession.messaging.jobs import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.utilities.Data +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.GroupUtil class GroupAvatarDownloadJob(val server: String, val room: String, val imageId: String?) : Job { @@ -39,7 +40,7 @@ class GroupAvatarDownloadJob(val server: String, val room: String, val imageId: val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) storage.updateProfilePicture(groupId, bytes) - storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) + storage.updateTimestampUpdated(groupId, SnodeAPI.nowWithOffset) delegate?.handleJobSucceeded(this, dispatcherName) } catch (e: Exception) { delegate?.handleJobFailed(this, dispatcherName, e) 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 46eff4b03..ca60fd3cb 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 @@ -23,6 +23,7 @@ import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.OnionResponse +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64.decode import org.session.libsignal.utilities.Base64.encodeBytes @@ -303,7 +304,7 @@ object OpenGroupApi { val headers = request.headers.toMutableMap() if (request.isAuthRequired) { val nonce = sodium.nonce(16) - val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + val timestamp = TimeUnit.MILLISECONDS.toSeconds(SnodeAPI.nowWithOffset) var pubKey = "" var signature = ByteArray(Sign.BYTES) var bodyHash = ByteArray(0) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt index 6a38a551f..f5965d5f2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageReceiver.kt @@ -14,6 +14,7 @@ import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities +import org.session.libsession.snode.SnodeAPI import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.IdPrefix @@ -154,7 +155,7 @@ object MessageReceiver { message.sender = sender message.recipient = userPublicKey message.sentTimestamp = envelope.timestamp - message.receivedTimestamp = if (envelope.hasServerTimestamp()) envelope.serverTimestamp else System.currentTimeMillis() + message.receivedTimestamp = if (envelope.hasServerTimestamp()) envelope.serverTimestamp else SnodeAPI.nowWithOffset message.groupPublicKey = groupPublicKey message.openGroupServerMessageID = openGroupServerID // Validate 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 3cc23da1e..11fd9ac5a 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 @@ -77,10 +77,10 @@ object MessageSender { val userPublicKey = storage.getUserPublicKey() // Set the timestamp, sender and recipient if (message.sentTimestamp == null) { - message.sentTimestamp = System.currentTimeMillis() // Visible messages will already have their sent timestamp set + message.sentTimestamp = SnodeAPI.nowWithOffset // Visible messages will already have their sent timestamp set } - val messageSendTime = System.currentTimeMillis() + val messageSendTime = SnodeAPI.nowWithOffset message.sender = userPublicKey val isSelfSend = (message.recipient == userPublicKey) @@ -210,7 +210,7 @@ object MessageSender { val deferred = deferred() val storage = MessagingModuleConfiguration.shared.storage if (message.sentTimestamp == null) { - message.sentTimestamp = System.currentTimeMillis() + message.sentTimestamp = SnodeAPI.nowWithOffset } val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!! var serverCapabilities = listOf() diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index 906e201a8..b8a903539 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -10,6 +10,7 @@ import org.session.libsession.messaging.messages.control.ClosedGroupControlMessa import org.session.libsession.messaging.sending_receiving.MessageSender.Error import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupUtil @@ -49,11 +50,11 @@ fun MessageSender.create(name: String, members: Collection): Promise) val name = group.title // Send the update to the group val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersAdded(newMembersAsData) - val sentTime = System.currentTimeMillis() + val sentTime = SnodeAPI.nowWithOffset val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind) closedGroupControlMessage.sentTimestamp = sentTime send(closedGroupControlMessage, Address.fromSerialized(groupID)) @@ -167,7 +168,7 @@ fun MessageSender.addMembers(groupPublicKey: String, membersToAdd: List) // updates from before that timestamp. By setting the timestamp of the message below to a value // greater than that of the `MembersAdded` message, we ensure that newly added members ignore // the `MembersAdded` message. - closedGroupControlMessage.sentTimestamp = System.currentTimeMillis() + closedGroupControlMessage.sentTimestamp = SnodeAPI.nowWithOffset send(closedGroupControlMessage, Address.fromSerialized(member)) } // Notify the user @@ -208,7 +209,7 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List @@ -375,7 +376,7 @@ object SnodeAPI { val parameters = message.toJSON().toMutableMap() // Construct signature if (requiresAuth) { - val sigTimestamp = System.currentTimeMillis() + SnodeAPI.clockOffset + val sigTimestamp = nowWithOffset val ed25519PublicKey = userED25519KeyPair.publicKey.asHexString val signature = ByteArray(Sign.BYTES) // assume namespace here is non-zero, as zero namespace doesn't require auth diff --git a/libsignal/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java b/libsignal/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java index de9d8d9a3..8f908284e 100644 --- a/libsignal/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java +++ b/libsignal/src/main/java/org/session/libsignal/messages/SignalServiceDataMessage.java @@ -319,8 +319,8 @@ public class SignalServiceDataMessage { return this; } - public SignalServiceDataMessage build() { - if (timestamp == 0) timestamp = System.currentTimeMillis(); + public SignalServiceDataMessage build(long fallbackTimestamp) { + if (timestamp == 0) timestamp = fallbackTimestamp; // closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob) return new SignalServiceDataMessage(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews, null, syncTarget); From eb739bdc9bdd49ea9eef5e8cf565b623a6fc9dfc Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 31 Mar 2023 13:24:36 +1100 Subject: [PATCH 020/244] ANR Defensive Coding (#1132) * Made a number of changes to try and improve background ANRs Added some more logs to the BatchMessageReceiveJob (to make it easier to track a specific job) Shifted the ConversationActivity adapter initialisation to run on a background thread to reduce the hang when opening a conversation Updated the ConversationViewModel to cache the recipient and openGroup values to avoid accessing the database unnecessarily Updated the code to just stop all current closed group pollers instead of fetching a list to stop Updated the PN registration to be triggered in an AsyncTask Updated the call code to unregister a couple of additional receivers Updated the background poller so it waits for 15 mins before running and doesn't replace the existing scheduler (allows for PNs to trigger explicit background polling) Fixed an issue where we were sending push notifications which were too large and likely to fail as a result (non-pre-offer call messages) Fixed an issue where a failing Open Group poller could prevent the background poller from receiving and processing DMs * Updated to a more coroutine-y convention --- app/build.gradle | 1 + .../securesms/ApplicationContext.java | 16 ++- .../conversation/v2/ConversationActivityV2.kt | 40 ++++-- .../conversation/v2/ConversationViewModel.kt | 30 ++++- .../notifications/BackgroundPollWorker.kt | 122 +++++++++++++++--- .../securesms/service/WebRtcCallService.kt | 50 +++++-- .../securesms/webrtc/CallMessageProcessor.kt | 26 +++- .../messaging/jobs/BatchMessageReceiveJob.kt | 18 +-- .../sending_receiving/MessageSender.kt | 15 ++- .../pollers/ClosedGroupPollerV2.kt | 7 +- .../libsession/snode/OnionRequestAPI.kt | 17 ++- 11 files changed, 269 insertions(+), 73 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3dcc19726..8f432bd08 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,6 +43,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" implementation 'androidx.activity:activity-ktx:1.5.1' implementation 'androidx.fragment:fragment-ktx:1.5.3' diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index a95b6c28c..a605a8492 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -272,7 +272,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO if (poller != null) { poller.stopIfNeeded(); } - ClosedGroupPollerV2.getShared().stop(); + ClosedGroupPollerV2.getShared().stopAll(); } @Override @@ -452,11 +452,15 @@ public class ApplicationContext extends Application implements DefaultLifecycleO String token = task.getResult().getToken(); String userPublicKey = TextSecurePreferences.getLocalNumber(this); if (userPublicKey == null) return Unit.INSTANCE; - if (TextSecurePreferences.isUsingFCM(this)) { - LokiPushNotificationManager.register(token, userPublicKey, this, force); - } else { - LokiPushNotificationManager.unregister(token, this); - } + + AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { + if (TextSecurePreferences.isUsingFCM(this)) { + LokiPushNotificationManager.register(token, userPublicKey, this, force); + } else { + LokiPushNotificationManager.unregister(token, this); + } + }); + return Unit.INSTANCE; }); } 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 2da61feee..f4068ad6b 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 @@ -34,6 +34,7 @@ import com.annimon.stream.Stream import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import network.loki.messenger.R import network.loki.messenger.databinding.ActivityConversationV2Binding import network.loki.messenger.databinding.ViewVisibleMessageBinding @@ -113,6 +114,7 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment import org.thoughtcrime.securesms.util.* +import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.ExecutionException import java.util.concurrent.atomic.AtomicLong @@ -304,12 +306,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // messageIdToScroll messageToScrollTimestamp.set(intent.getLongExtra(SCROLL_MESSAGE_ID, -1)) messageToScrollAuthor.set(intent.getParcelableExtra(SCROLL_MESSAGE_AUTHOR)) - val thread = threadDb.getRecipientForThreadId(viewModel.threadId) - if (thread == null) { + val recipient = viewModel.recipient + val openGroup = recipient.let { viewModel.openGroup } + if (recipient == null || (recipient.isOpenGroupRecipient && openGroup == null)) { Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show() return finish() } - setUpRecyclerView() + setUpToolBar() setUpInputBar() setUpLinkPreviewObserver() @@ -336,22 +339,31 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } } - unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId) + updateUnreadCountIndicator() - setUpTypingObserver() - setUpRecipientObserver() updateSubtitle() - getLatestOpenGroupInfoIfNeeded() setUpBlockedBanner() binding!!.searchBottomBar.setEventListener(this) - setUpSearchResultObserver() - scrollToFirstUnreadMessageIfNeeded() showOrHideInputIfNeeded() setUpMessageRequestsBar() - viewModel.recipient?.let { recipient -> - if (recipient.isOpenGroupRecipient && viewModel.openGroup == null) { - Toast.makeText(this, "This thread has been deleted.", Toast.LENGTH_LONG).show() - return finish() + + val weakActivity = WeakReference(this) + + lifecycleScope.launch(Dispatchers.IO) { + unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId) + + // Note: We are accessing the `adapter` property because we want it to be loaded on + // the background thread to avoid blocking the UI thread and potentially hanging when + // transitioning to the activity + weakActivity.get()?.adapter ?: return@launch + + withContext(Dispatchers.Main) { + setUpRecyclerView() + setUpTypingObserver() + setUpRecipientObserver() + getLatestOpenGroupInfoIfNeeded() + setUpSearchResultObserver() + scrollToFirstUnreadMessageIfNeeded() } } @@ -631,6 +643,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // region Animation & Updating override fun onModified(recipient: Recipient) { + viewModel.updateRecipient() + runOnUiThread { val threadRecipient = viewModel.recipient ?: return@runOnUiThread if (threadRecipient.isContactRecipient) { 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 9a5610896..7f6768ee9 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 @@ -34,11 +34,17 @@ class ConversationViewModel( private val _uiState = MutableStateFlow(ConversationUiState()) val uiState: StateFlow = _uiState + private var _recipient: RetrieveOnce = RetrieveOnce { + repository.maybeGetRecipientForThreadId(threadId) + } val recipient: Recipient? - get() = repository.maybeGetRecipientForThreadId(threadId) + get() = _recipient.value + private var _openGroup: RetrieveOnce = RetrieveOnce { + storage.getOpenGroup(threadId) + } val openGroup: OpenGroup? - get() = storage.getOpenGroup(threadId) + get() = _openGroup.value val serverCapabilities: List get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf() @@ -170,6 +176,10 @@ class ConversationViewModel( return repository.hasReceived(threadId) } + fun updateRecipient() { + _recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId)) + } + @dagger.assisted.AssistedFactory interface AssistedFactory { fun create(threadId: Long, edKeyPair: KeyPair?): Factory @@ -195,3 +205,19 @@ data class ConversationUiState( val uiMessages: List = emptyList(), val isMessageRequestAccepted: Boolean? = null ) + +data class RetrieveOnce(val retrieval: () -> T?) { + private var triedToRetrieve: Boolean = false + private var _value: T? = null + + val value: T? + get() { + if (triedToRetrieve) { return _value } + + triedToRetrieve = true + _value = retrieval() + return _value + } + + fun updateTo(value: T?) { _value = value } +} 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 9b8ea5824..e583fb0ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt @@ -4,9 +4,11 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.work.Constraints +import androidx.work.Data import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.NetworkType import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.Worker import androidx.work.WorkerParameters @@ -21,19 +23,35 @@ import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPolle import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.recover import org.thoughtcrime.securesms.dependencies.DatabaseComponent import java.util.concurrent.TimeUnit class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Worker(context, params) { + enum class Targets { + DMS, CLOSED_GROUPS, OPEN_GROUPS + } companion object { const val TAG = "BackgroundPollWorker" + const val INITIAL_SCHEDULE_TIME = "INITIAL_SCHEDULE_TIME" + const val REQUEST_TARGETS = "REQUEST_TARGETS" @JvmStatic - fun schedulePeriodic(context: Context) { + fun schedulePeriodic(context: Context) = schedulePeriodic(context, targets = Targets.values()) + + @JvmStatic + fun schedulePeriodic(context: Context, targets: Array) { Log.v(TAG, "Scheduling periodic work.") - val builder = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES) + val durationMinutes: Long = 15 + val builder = PeriodicWorkRequestBuilder(durationMinutes, TimeUnit.MINUTES) builder.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) + + val dataBuilder = Data.Builder() + dataBuilder.putLong(INITIAL_SCHEDULE_TIME, System.currentTimeMillis() + (durationMinutes * 60 * 1000)) + dataBuilder.putStringArray(REQUEST_TARGETS, targets.map { it.name }.toTypedArray()) + builder.setInputData(dataBuilder.build()) + val workRequest = builder.build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( TAG, @@ -41,6 +59,20 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor workRequest ) } + + @JvmStatic + fun scheduleOnce(context: Context, targets: Array = Targets.values()) { + Log.v(TAG, "Scheduling single run.") + val builder = OneTimeWorkRequestBuilder() + builder.setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) + + val dataBuilder = Data.Builder() + dataBuilder.putStringArray(REQUEST_TARGETS, targets.map { it.name }.toTypedArray()) + builder.setInputData(dataBuilder.build()) + + val workRequest = builder.build() + WorkManager.getInstance(context).enqueue(workRequest) + } } override fun doWork(): Result { @@ -49,41 +81,89 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor return Result.failure() } + // If this is a scheduled run and it is happening before the initial scheduled time (as + // periodic background tasks run immediately when scheduled) then don't actually do anything + // because this might slow requests on initial startup or triggered by PNs + val initialScheduleTime = inputData.getLong(INITIAL_SCHEDULE_TIME, -1) + + if (initialScheduleTime != -1L && System.currentTimeMillis() < (initialScheduleTime - (60 * 1000))) { + Log.v(TAG, "Skipping initial run.") + return Result.success() + } + + // Retrieve the desired targets (defaulting to all if not provided or empty) + val requestTargets: List = (inputData.getStringArray(REQUEST_TARGETS) ?: emptyArray()) + .map { + try { Targets.valueOf(it) } + catch(e: Exception) { null } + } + .filterNotNull() + .ifEmpty { Targets.values().toList() } + try { - Log.v(TAG, "Performing background poll.") + Log.v(TAG, "Performing background poll for ${requestTargets.joinToString { it.name }}.") val promises = mutableListOf>() // DMs - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - val dmsPromise = SnodeAPI.getMessages(userPublicKey).bind { envelopes -> - val params = envelopes.map { (envelope, serverHash) -> - // FIXME: Using a job here seems like a bad idea... - MessageReceiveParameters(envelope.toByteArray(), serverHash, null) + var dmsPromise: Promise = Promise.ofSuccess(Unit) + + if (requestTargets.contains(Targets.DMS)) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! + dmsPromise = SnodeAPI.getMessages(userPublicKey).bind { envelopes -> + val params = envelopes.map { (envelope, serverHash) -> + // FIXME: Using a job here seems like a bad idea... + MessageReceiveParameters(envelope.toByteArray(), serverHash, null) + } + BatchMessageReceiveJob(params).executeAsync("background") } - BatchMessageReceiveJob(params).executeAsync("background") + promises.add(dmsPromise) } - promises.add(dmsPromise) // Closed groups - val closedGroupPoller = ClosedGroupPollerV2() // Intentionally don't use shared - val storage = MessagingModuleConfiguration.shared.storage - val allGroupPublicKeys = storage.getAllClosedGroupPublicKeys() - allGroupPublicKeys.iterator().forEach { closedGroupPoller.poll(it) } + if (requestTargets.contains(Targets.CLOSED_GROUPS)) { + val closedGroupPoller = ClosedGroupPollerV2() // Intentionally don't use shared + val storage = MessagingModuleConfiguration.shared.storage + val allGroupPublicKeys = storage.getAllClosedGroupPublicKeys() + allGroupPublicKeys.iterator().forEach { closedGroupPoller.poll(it) } + } // Open Groups - val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() - val openGroups = threadDB.getAllOpenGroups() - val openGroupServers = openGroups.map { it.value.server }.toSet() + var ogPollError: Exception? = null - for (server in openGroupServers) { - val poller = OpenGroupPoller(server, null) - poller.hasStarted = true - promises.add(poller.poll()) + if (requestTargets.contains(Targets.OPEN_GROUPS)) { + val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() + val openGroups = threadDB.getAllOpenGroups() + val openGroupServers = openGroups.map { it.value.server }.toSet() + + for (server in openGroupServers) { + val poller = OpenGroupPoller(server, null) + poller.hasStarted = true + + // If one of the open group pollers fails we don't want it to cancel the DM + // poller so just hold on to the error for later + promises.add( + poller.poll().recover { + if (dmsPromise.isDone()) { + throw it + } + + ogPollError = it + } + ) + } } // Wait until all the promises are resolved all(promises).get() + // If the Open Group pollers threw an exception then re-throw it here (now that + // the DM promise has completed) + val localOgPollException = ogPollError + + if (localOgPollException != null) { + throw localOgPollException + } + return Result.success() } catch (exception: Exception) { Log.e(TAG, "Background poll failed due to error: ${exception.message}.", exception) 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 d09933ab8..b5e9b78a7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -1,6 +1,6 @@ package org.thoughtcrime.securesms.service -import android.app.Service +import android.app.ForegroundServiceStartNotAllowedException import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -17,6 +17,8 @@ import android.telephony.PhoneStateListener.LISTEN_NONE import android.telephony.TelephonyManager import androidx.core.content.ContextCompat import androidx.core.os.bundleOf +import androidx.lifecycle.LifecycleService +import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.calls.CallMessageType @@ -25,6 +27,7 @@ import org.session.libsession.utilities.FutureTaskListener import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.calls.WebRtcCallActivity +import org.thoughtcrime.securesms.notifications.BackgroundPollWorker import org.thoughtcrime.securesms.util.CallNotificationBuilder import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING @@ -46,7 +49,7 @@ import javax.inject.Inject import org.thoughtcrime.securesms.webrtc.data.State as CallState @AndroidEntryPoint -class WebRtcCallService : Service(), CallManager.WebRtcListener { +class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { companion object { @@ -238,7 +241,10 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { scheduledReconnect?.cancel(false) scheduledTimeout = null scheduledReconnect = null - stopForeground(true) + + lifecycleScope.launchWhenCreated { + stopForeground(true) + } } private fun isSameCall(intent: Intent): Boolean { @@ -253,7 +259,9 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { private fun isIdle() = callManager.isIdle() - override fun onBind(intent: Intent?): IBinder? = null + override fun onBind(intent: Intent): IBinder? { + return super.onBind(intent) + } override fun onHangup() { serviceExecutor.execute { @@ -272,7 +280,8 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { if (intent == null || intent.action == null) return START_NOT_STICKY serviceExecutor.execute { val action = intent.action - Log.i("Loki", "Handling ${intent.action}") + val callId = ((intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID)?.toString() ?: "No callId") + Log.i("Loki", "Handling ${intent.action} for call: ${callId}") when { action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer( intent @@ -361,7 +370,9 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { insertMissedCall(recipient, false) if (callState == CallState.Idle) { - stopForeground(true) + lifecycleScope.launchWhenCreated { + stopForeground(true) + } } } @@ -409,6 +420,11 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { callManager.initializeAudioForCall() callManager.startIncomingRinger() callManager.setAudioEnabled(true) + + BackgroundPollWorker.scheduleOnce( + this, + arrayOf(BackgroundPollWorker.Targets.DMS) + ) } } @@ -573,7 +589,9 @@ 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) + lifecycleScope.launchWhenCreated { + stopForeground(true) + } return } @@ -717,10 +735,16 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { } private fun setCallInProgressNotification(type: Int, recipient: Recipient?) { - startForeground( - CallNotificationBuilder.WEBRTC_NOTIFICATION, - CallNotificationBuilder.getCallInProgressNotification(this, type, recipient) - ) + try { + startForeground( + CallNotificationBuilder.WEBRTC_NOTIFICATION, + CallNotificationBuilder.getCallInProgressNotification(this, type, recipient) + ) + } + catch(e: ForegroundServiceStartNotAllowedException) { + Log.e(TAG, "Failed to setCallInProgressNotification as a foreground service for type: ${type}, trying to update instead") + } + if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) { // start an intent for the fullscreen val foregroundIntent = Intent(this, WebRtcCallActivity::class.java) @@ -769,10 +793,14 @@ class WebRtcCallService : Service(), CallManager.WebRtcListener { callReceiver?.let { receiver -> unregisterReceiver(receiver) } + wiredHeadsetStateReceiver?.let { unregisterReceiver(it) } + powerButtonReceiver?.let { unregisterReceiver(it) } networkChangedReceiver?.unregister(this) wantsToAnswerReceiver?.let { receiver -> LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } + powerButtonReceiver = null + wiredHeadsetStateReceiver = null networkChangedReceiver = null callReceiver = null uncaughtExceptionHandlerManager?.unregister() 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 bb41c7c97..3d40b5f74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.webrtc import android.app.NotificationManager import android.content.Context +import android.content.Intent import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope @@ -32,6 +33,20 @@ class CallMessageProcessor(private val context: Context, private val textSecureP companion object { private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L + + fun safeStartService(context: Context, intent: Intent) { + // If the foreground service crashes then it's possible for one of these intents to + // be started in the background (in which case 'startService' will throw a + // 'BackgroundServiceStartNotAllowedException' exception) so catch that case and try + // to re-start the service in the foreground + try { context.startService(intent) } + catch(e: Exception) { + try { ContextCompat.startForegroundService(context, intent) } + catch (e2: Exception) { + Log.e("Loki", "Unable to start CallMessage intent: ${e2.message}") + } + } + } } init { @@ -90,7 +105,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) - context.startService(hangupIntent) + safeStartService(context, hangupIntent) } private fun incomingAnswer(callMessage: CallMessage) { @@ -103,7 +118,8 @@ class CallMessageProcessor(private val context: Context, private val textSecureP sdp = sdp, callId = callId ) - context.startService(answerIntent) + + safeStartService(context, answerIntent) } private fun handleIceCandidates(callMessage: CallMessage) { @@ -119,7 +135,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, address = Address.fromSerialized(sender) ) - context.startService(iceIntent) + safeStartService(context, iceIntent) } private fun incomingPreOffer(callMessage: CallMessage) { @@ -132,7 +148,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, callTime = callMessage.sentTimestamp!! ) - context.startService(incomingIntent) + safeStartService(context, incomingIntent) } private fun incomingCall(callMessage: CallMessage) { @@ -146,7 +162,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, callTime = callMessage.sentTimestamp!! ) - context.startService(incomingIntent) + safeStartService(context, incomingIntent) } private fun CallMessage.iceCandidates(): List { 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 54a6551dc..fa07a7d9c 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 @@ -94,19 +94,19 @@ class BatchMessageReceiveJob( } catch (e: Exception) { when (e) { is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> { - Log.i(TAG, "Couldn't receive message, failed with error: ${e.message}") + Log.i(TAG, "Couldn't receive message, failed with error: ${e.message} (id: $id)") } is MessageReceiver.Error -> { if (!e.isRetryable) { - Log.e(TAG, "Couldn't receive message, failed permanently", e) + Log.e(TAG, "Couldn't receive message, failed permanently (id: $id)", e) } else { - Log.e(TAG, "Couldn't receive message, failed", e) + Log.e(TAG, "Couldn't receive message, failed (id: $id)", e) failures += messageParameters } } else -> { - Log.e(TAG, "Couldn't receive message, failed", e) + Log.e(TAG, "Couldn't receive message, failed (id: $id)", e) failures += messageParameters } } @@ -155,11 +155,11 @@ class BatchMessageReceiveJob( else -> MessageReceiver.handle(message, proto, openGroupID) } } catch (e: Exception) { - Log.e(TAG, "Couldn't process message.", e) + Log.e(TAG, "Couldn't process message (id: $id)", e) if (e is MessageReceiver.Error && !e.isRetryable) { - Log.e(TAG, "Message failed permanently",e) + Log.e(TAG, "Message failed permanently (id: $id)", e) } else { - Log.e(TAG, "Message failed",e) + Log.e(TAG, "Message failed (id: $id)", e) failures += parameters } } @@ -196,12 +196,12 @@ class BatchMessageReceiveJob( } private fun handleSuccess(dispatcherName: String) { - Log.i(TAG, "Completed processing of ${messages.size} messages") + Log.i(TAG, "Completed processing of ${messages.size} messages (id: $id)") this.delegate?.handleJobSucceeded(this, dispatcherName) } private fun handleFailure(dispatcherName: String) { - Log.i(TAG, "Handling failure of ${failures.size} messages (${messages.size - failures.size} processed successfully)") + Log.i(TAG, "Handling failure of ${failures.size} messages (${messages.size - failures.size} processed successfully) (id: $id)") this.delegate?.handleJobFailed(this, dispatcherName, Exception("One or more jobs resulted in failure")) } 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 11fd9ac5a..eb0d15739 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 @@ -180,7 +180,20 @@ object MessageSender { val hash = it["hash"] as? String message.serverHash = hash handleSuccessfulMessageSend(message, destination, isSyncMessage) - val shouldNotify = ((message is VisibleMessage || message is UnsendRequest || message is CallMessage) && !isSyncMessage) + + val shouldNotify: Boolean = when (message) { + is VisibleMessage, is UnsendRequest -> !isSyncMessage + is CallMessage -> { + // Note: Other 'CallMessage' types are too big to send as push notifications + // so only send the 'preOffer' message as a notification + when (message.type) { + SignalServiceProtos.CallMessage.Type.PRE_OFFER -> true + else -> false + } + } + else -> false + } + /* if (message is ClosedGroupControlMessage && message.kind is ClosedGroupControlMessage.Kind.New) { shouldNotify = true diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt index 140bf6b9e..963373959 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPollerV2.kt @@ -54,10 +54,9 @@ class ClosedGroupPollerV2 { setUpPolling(groupPublicKey) } - fun stop() { - val storage = MessagingModuleConfiguration.shared.storage - val allGroupPublicKeys = storage.getAllClosedGroupPublicKeys() - allGroupPublicKeys.iterator().forEach { stopPolling(it) } + fun stopAll() { + futures.forEach { it.value.cancel(false) } + isPolling.forEach { isPolling[it.key] = false } } fun stopPolling(groupPublicKey: String) { 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 bcce887a5..087c8e29d 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -26,6 +26,7 @@ import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.recover import org.session.libsignal.utilities.toHexString import java.util.Date +import java.util.concurrent.atomic.AtomicReference import kotlin.collections.set private typealias Path = List @@ -43,13 +44,27 @@ object OnionRequestAPI { private val snodeFailureCount = mutableMapOf() var guardSnodes = setOf() + var _paths: AtomicReference?> = AtomicReference(null) var paths: List // Not a set to ensure we consistently show the same path to the user - get() = database.getOnionRequestPaths() + get() { + val paths = _paths.get() + + if (paths != null) { return paths } + + // Storing this in an atomic variable as it was causing a number of background + // ANRs when this value was accessed via the main thread after tapping on + // a notification) + val result = database.getOnionRequestPaths() + _paths.set(result) + return result + } set(newValue) { if (newValue.isEmpty()) { database.clearOnionRequestPaths() + _paths.set(null) } else { database.setOnionRequestPaths(newValue) + _paths.set(newValue) } } From cfdc3dc24d37bb020e64af167bb704f25f588040 Mon Sep 17 00:00:00 2001 From: hjubb Date: Mon, 3 Apr 2023 17:12:41 +1000 Subject: [PATCH 021/244] build: update build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 757271588..cc05319ac 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,8 +160,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 333 -def canonicalVersionName = "1.16.5" +def canonicalVersionCode = 334 +def canonicalVersionName = "1.16.6" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 9ad73139b678155ae2ede1b9ce13926afc7c9d3e Mon Sep 17 00:00:00 2001 From: wafflesvsfrankie <92288602+burtonemily@users.noreply.github.com> Date: Tue, 4 Apr 2023 11:33:17 +1000 Subject: [PATCH 022/244] Add accessibility tags (#1054) * adding accessibility id to new conversation button * adding accessibility ids and strings for testing * updating id tags for new conversation buttons * accessibility tags for create contact test * adding ids for message requests, config message, requests banner and conversation view * adding more tags to settings page * adding tags to different resolutions for landing page * updating display name in settings to include accessibility id * found some stashed changes which i forgot about * more stashed changes * adding tags to layout sw400dp * closed group testing, delete/unsend message testing and selecting contacts tag * adding tags for message body, selecting contacts for group creation, deleted message config, unsend message modal, and trash icon * added tags for disappearing messages menu option, time selector, clock icon, confirm of change (ok)and control message * add test for block user * docs: Adding in accessibility ID's for Appium testing * accessibility tags for conversation options, profile picture/settings and block options * Add content descriptions for better accessibility * Add more content descriptions * Add timer icon content description * Update profile picture content description * Adding accessibility ids to new conversation creation screen and to message notification settings * fix: content descriptions in their correct places and prevent a crash * build: update build number * build: update build number * Adding back in FrameLayout, making changes as request * Fixed viewPager move * Fixing changes as requested by Jubb * Adding content descriptions to mentions list, voice message settings, link preview permissions, closed group menu and slow mode notifications option * Adds content descriptions to blocked contacts heading in conversations settings, individual contacts in blocked contacts page and to empty message requests folder * Adds content descriptions to empty message request folder and text input box for username on settings page --------- Co-authored-by: charles Co-authored-by: hjubb --- .../securesms/components/menu/ActionItem.kt | 5 +- .../components/menu/ContextMenuList.kt | 1 + .../conversation/v2/ConversationActivityV2.kt | 8 +- .../v2/ConversationReactionOverlay.java | 22 ++- .../conversation/v2/input_bar/InputBar.kt | 6 +- .../mentions/MentionCandidatesView.kt | 5 +- .../v2/menus/ConversationMenuHelper.kt | 8 +- .../v2/messages/ControlMessageView.kt | 2 + .../v2/messages/VisibleMessageView.kt | 44 +++++- .../securesms/permissions/Permissions.java | 24 ++- .../PrivacySettingsPreferenceFragment.java | 8 +- .../layout-sw400dp/activity_display_name.xml | 2 + .../res/layout-sw400dp/activity_landing.xml | 3 + .../res/layout-sw400dp/activity_pn_mode.xml | 7 +- .../activity_recovery_phrase_restore.xml | 2 + .../res/layout-sw400dp/activity_register.xml | 2 + .../main/res/layout-sw400dp/activity_seed.xml | 2 + .../fragment_recovery_phrase.xml | 2 + .../res/layout-sw400dp/view_seed_reminder.xml | 1 + .../res/layout/activity_conversation_v2.xml | 15 +- .../activity_conversation_v2_action_bar.xml | 1 + .../main/res/layout/activity_display_name.xml | 2 + .../res/layout/activity_edit_closed_group.xml | 8 +- app/src/main/res/layout/activity_home.xml | 5 +- app/src/main/res/layout/activity_landing.xml | 6 +- .../main/res/layout/activity_link_device.xml | 6 +- .../res/layout/activity_message_requests.xml | 2 + app/src/main/res/layout/activity_pn_mode.xml | 4 + .../activity_recovery_phrase_restore.xml | 10 +- app/src/main/res/layout/activity_register.xml | 3 + app/src/main/res/layout/activity_seed.xml | 2 + app/src/main/res/layout/activity_settings.xml | 38 +++-- .../res/layout/blocked_contact_layout.xml | 1 + .../layout/blocked_contacts_preference.xml | 1 + .../res/layout/conversation_item_footer.xml | 1 + app/src/main/res/layout/dialog_blocked.xml | 1 + app/src/main/res/layout/dialog_download.xml | 2 + .../main/res/layout/dialog_link_preview.xml | 8 +- app/src/main/res/layout/dialog_seed.xml | 3 + app/src/main/res/layout/expiration_dialog.xml | 1 + .../fragment_conversation_bottom_sheet.xml | 5 + .../main/res/layout/fragment_create_group.xml | 2 + .../fragment_delete_message_bottom_sheet.xml | 3 + .../res/layout/fragment_enter_public_key.xml | 2 + .../layout/fragment_new_conversation_home.xml | 3 + .../res/layout/fragment_recovery_phrase.xml | 8 +- .../fragment_user_details_bottom_sheet.xml | 15 +- .../main/res/layout/view_control_message.xml | 1 + app/src/main/res/layout/view_conversation.xml | 5 +- .../main/res/layout/view_deleted_message.xml | 1 + app/src/main/res/layout/view_document.xml | 3 +- app/src/main/res/layout/view_input_bar.xml | 2 + .../res/layout/view_mention_candidate_v2.xml | 4 +- .../main/res/layout/view_message_request.xml | 1 + .../layout/view_message_request_banner.xml | 1 + .../main/res/layout/view_profile_picture.xml | 1 + .../main/res/layout/view_seed_reminder.xml | 2 + .../res/layout/view_untrusted_attachment.xml | 3 +- app/src/main/res/layout/view_user.xml | 3 + .../main/res/layout/view_visible_message.xml | 1 + .../layout/view_visible_message_content.xml | 1 + .../main/res/layout/view_voice_message.xml | 1 + app/src/main/res/menu/menu_conversation.xml | 4 +- .../main/res/menu/menu_conversation_block.xml | 1 + .../main/res/menu/menu_conversation_call.xml | 1 + .../menu/menu_conversation_closed_group.xml | 2 + .../menu_conversation_copy_session_id.xml | 3 +- .../menu/menu_conversation_expiration_off.xml | 1 + .../menu/menu_conversation_expiration_on.xml | 1 + ...enu_conversation_notification_settings.xml | 3 +- .../res/menu/menu_conversation_unmuted.xml | 3 +- app/src/main/res/menu/menu_done.xml | 1 + .../main/res/menu/menu_edit_closed_group.xml | 5 +- app/src/main/res/values/strings.xml | 139 ++++++++++++++++++ 74 files changed, 423 insertions(+), 83 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt index 358a9d326..d0b101a9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ActionItem.kt @@ -5,8 +5,9 @@ import androidx.annotation.AttrRes /** * Represents an action to be rendered */ -data class ActionItem( +data class ActionItem @JvmOverloads constructor( @AttrRes val iconRes: Int, val title: CharSequence, - val action: Runnable + val action: Runnable, + val contentDescription: String? = null ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt index 65fb1ddbb..c86b40dfa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/menu/ContextMenuList.kt @@ -77,6 +77,7 @@ class ContextMenuList(recyclerView: RecyclerView, onItemClick: () -> Unit) { context.theme.resolveAttribute(model.item.iconRes, typedValue, true) icon.setImageDrawable(ContextCompat.getDrawable(context, typedValue.resourceId)) } + itemView.contentDescription = model.item.contentDescription title.text = model.item.title itemView.setOnClickListener { model.item.action.run() 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 f4068ad6b..b5f1b96fd 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 @@ -781,7 +781,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val recipient = viewModel.recipient ?: return if (!isShowingMentionCandidatesView) { additionalContentContainer.removeAllViews() - val view = MentionCandidatesView(this) + val view = MentionCandidatesView(this).apply { + contentDescription = context.getString(R.string.AccessibilityId_mentions_list) + } view.glide = glide view.onCandidateSelected = { handleMentionSelected(it) } additionalContentContainer.addView(view) @@ -958,7 +960,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun block(deleteThread: Boolean) { val title = R.string.RecipientPreferenceActivity_block_this_contact_question val message = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact - AlertDialog.Builder(this) + val dialog = AlertDialog.Builder(this) .setTitle(title) .setMessage(message) .setNegativeButton(android.R.string.cancel, null) @@ -969,6 +971,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe finish() } }.show() + val button = dialog.getButton(DialogInterface.BUTTON_POSITIVE) + button.setContentDescription("Confirm block") } override fun copySessionID(sessionId: String) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java index 995dcda2f..b3c151690 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java @@ -660,10 +660,14 @@ public final class ConversationReactionOverlay extends FrameLayout { String userPublicKey = TextSecurePreferences.getLocalNumber(getContext()); // Select message - items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT))); + items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT), + getContext().getResources().getString(R.string.AccessibilityId_select))); // Reply if (!message.isPending() && !message.isFailed()) { - items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY))); + items.add( + new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY), + getContext().getResources().getString(R.string.AccessibilityId_reply_message)) + ); } // Copy message text if (!containsControlMessage && hasText) { @@ -671,11 +675,17 @@ public final class ConversationReactionOverlay extends FrameLayout { } // Copy Session ID if (recipient.isGroupRecipient() && !recipient.isOpenGroupRecipient() && !message.getRecipient().getAddress().toString().equals(userPublicKey)) { - items.add(new ActionItem(R.attr.menu_copy_icon, getContext().getResources().getString(R.string.activity_conversation_menu_copy_session_id), () -> handleActionItemClicked(Action.COPY_SESSION_ID))); + items.add(new ActionItem( + R.attr.menu_copy_icon, getContext().getResources().getString(R.string.activity_conversation_menu_copy_session_id), () -> handleActionItemClicked(Action.COPY_SESSION_ID)) + ); } // Delete message if (ConversationMenuItemHelper.userCanDeleteSelectedItems(getContext(), message, openGroup, userPublicKey, blindedPublicKey)) { - items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.delete), () -> handleActionItemClicked(Action.DELETE))); + items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.delete), + () -> handleActionItemClicked(Action.DELETE), + getContext().getResources().getString(R.string.AccessibilityId_delete_message) + ) + ); } // Ban user if (ConversationMenuItemHelper.userCanBanSelectedUsers(getContext(), message, openGroup, userPublicKey, blindedPublicKey)) { @@ -695,7 +705,9 @@ public final class ConversationReactionOverlay extends FrameLayout { } // Save media if (message.isMms() && ((MediaMmsMessageRecord)message).containsMediaSlide()) { - items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD))); + items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD), + getContext().getResources().getString(R.string.AccessibilityId_save_attachment)) + ); } backgroundView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt index 7ac70b843..73e2d571c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/InputBar.kt @@ -57,9 +57,9 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li val attachmentButtonsContainerHeight: Int get() = binding.attachmentsButtonContainer.height - private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) } - private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone) } - private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) } + private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24).apply { contentDescription = context.getString(R.string.AccessibilityId_attachments_button)} } + private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone).apply { contentDescription = context.getString(R.string.AccessibilityId_microphone_button)} } + private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true).apply { contentDescription = context.getString(R.string.AccessibilityId_send_message_button)} } // region Lifecycle constructor(context: Context) : super(context) { initialize() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt index 401ccaa3c..e62f7f8f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/input_bar/mentions/MentionCandidatesView.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.ListView import dagger.hilt.android.AndroidEntryPoint +import network.loki.messenger.R import org.session.libsession.messaging.mentions.Mention import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.mms.GlideRequests @@ -41,7 +42,9 @@ class MentionCandidatesView(context: Context, attrs: AttributeSet?, defStyleAttr override fun getItem(position: Int): Mention { return candidates[position] } override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View { - val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context) + val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context).apply { + contentDescription = context.getString(R.string.AccessibilityId_contact) + } val mentionCandidate = getItem(position) cell.glide = glide cell.candidate = mentionCandidate diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index 8a6c84aec..ce29efa3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.menus import android.annotation.SuppressLint import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.graphics.BitmapFactory import android.graphics.PorterDuff @@ -185,7 +186,7 @@ object ConversationMenuHelper { private fun call(context: Context, thread: Recipient) { if (!TextSecurePreferences.isCallNotificationsEnabled(context)) { - AlertDialog.Builder(context) + val dialog = AlertDialog.Builder(context) .setTitle(R.string.ConversationActivity_call_title) .setMessage(R.string.ConversationActivity_call_prompt) .setPositiveButton(R.string.activity_settings_title) { _, _ -> @@ -194,7 +195,10 @@ object ConversationMenuHelper { } .setNeutralButton(R.string.cancel) { d, _ -> d.dismiss() - }.show() + }.create() + dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.contentDescription = context.getString(R.string.AccessibilityId_settings) + dialog.getButton(DialogInterface.BUTTON_NEGATIVE)?.contentDescription = context.getString(R.string.AccessibilityId_cancel_button) + dialog.show() return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index a4e4a52d5..3e370104e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -31,6 +31,7 @@ class ControlMessageView : LinearLayout { binding.dateBreakTextView.showDateBreak(message, previous) binding.iconImageView.visibility = View.GONE var messageBody: CharSequence = message.getDisplayBody(context) + binding.root.contentDescription= null when { message.isExpirationTimerUpdate -> { binding.iconImageView.setImageDrawable( @@ -46,6 +47,7 @@ class ControlMessageView : LinearLayout { } message.isMessageRequestResponse -> { messageBody = context.getString(R.string.message_requests_accepted) + binding.root.contentDescription=context.getString(R.string.AccessibilityId_message_request_config_message) } message.isCallLog -> { val drawable = when { 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 1890e604a..2c04bcfce 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 @@ -13,6 +13,9 @@ import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View import android.widget.LinearLayout +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat @@ -194,7 +197,7 @@ class VisibleMessageView : LinearLayout { binding.dateBreakTextView.isVisible = showDateBreak // Message status indicator if (message.isOutgoing) { - val (iconID, iconColor, textId) = getMessageStatusImage(message) + val (iconID, iconColor, textId, contentDescription) = getMessageStatusImage(message) if (textId != null) { binding.messageStatusTextView.setText(textId) @@ -209,6 +212,7 @@ class VisibleMessageView : LinearLayout { } binding.messageStatusImageView.setImageDrawable(drawable) } + binding.messageStatusImageView.contentDescription = contentDescription val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId) binding.messageStatusTextView.isVisible = ( @@ -283,17 +287,43 @@ class VisibleMessageView : LinearLayout { } } - private fun getMessageStatusImage(message: MessageRecord): Triple { + data class MessageStatusInfo(@DrawableRes val iconId: Int?, + @ColorInt val iconTint: Int?, + @StringRes val messageText: Int?, + val contentDescription: String?) + + private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo { return when { - !message.isOutgoing -> Triple(null, null, null) + !message.isOutgoing -> MessageStatusInfo(null, + null, + null, + null) message.isFailed -> - Triple(R.drawable.ic_delivery_status_failed, resources.getColor(R.color.destructive, context.theme), R.string.delivery_status_failed) + MessageStatusInfo( + R.drawable.ic_delivery_status_failed, + resources.getColor(R.color.destructive, context.theme), + R.string.delivery_status_failed, + null + ) message.isPending -> - Triple(R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending) + MessageStatusInfo( + R.drawable.ic_delivery_status_sending, + context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending, + context.getString(R.string.AccessibilityId_message_sent_status_pending) + ) message.isRead -> - Triple(R.drawable.ic_delivery_status_read, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read) + MessageStatusInfo( + R.drawable.ic_delivery_status_read, + context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read, + null + ) else -> - Triple(R.drawable.ic_delivery_status_sent, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sent) + MessageStatusInfo( + R.drawable.ic_delivery_status_sent, + context.getColorFromAttr(R.attr.message_status_color), + R.string.delivery_status_sent, + context.getString(R.string.AccessibilityId_message_sent_status_tick) + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java b/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java index 999dad001..2d7e6dae5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/permissions/Permissions.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.permissions; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -12,6 +13,7 @@ import android.util.DisplayMetrics; import android.view.Display; import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.Button; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; @@ -162,12 +164,13 @@ public class Permissions { @SuppressWarnings("ConstantConditions") private void executePermissionsRequestWithRationale(PermissionsRequest request) { - RationaleDialog.createFor(permissionObject.getContext(), rationaleDialogMessage, rationalDialogHeader) - .setPositiveButton(R.string.Permissions_continue, (dialog, which) -> executePermissionsRequest(request)) - .setNegativeButton(R.string.Permissions_not_now, (dialog, which) -> executeNoPermissionsRequest(request)) - .show() - .getWindow() - .setLayout((int)(permissionObject.getWindowWidth() * .75), ViewGroup.LayoutParams.WRAP_CONTENT); + AlertDialog dialog = RationaleDialog.createFor(permissionObject.getContext(), rationaleDialogMessage, rationalDialogHeader) + .setPositiveButton(R.string.Permissions_continue, (d, which) -> executePermissionsRequest(request)) + .setNegativeButton(R.string.Permissions_not_now, (d, which) -> executeNoPermissionsRequest(request)) + .show(); + dialog.getWindow().setLayout((int)(permissionObject.getWindowWidth() * .75), ViewGroup.LayoutParams.WRAP_CONTENT); + Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + positiveButton.setContentDescription("Continue"); } private void executePermissionsRequest(PermissionsRequest request) { @@ -353,12 +356,17 @@ public class Permissions { Context context = this.context.get(); if (context != null) { - new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog) + AlertDialog alertDialog = new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog) .setTitle(R.string.Permissions_permission_required) .setMessage(message) .setPositiveButton(R.string.Permissions_continue, (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context))) .setNegativeButton(android.R.string.cancel, null) - .show(); + .create(); + Button positiveButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE); + if (positiveButton != null) { + positiveButton.setContentDescription(context.getString(R.string.AccessibilityId_continue)); + } + alertDialog.show(); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java index b5774447e..ac03efa36 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/PrivacySettingsPreferenceFragment.java @@ -5,10 +5,12 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.KeyguardManager; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; +import android.widget.Button; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -179,7 +181,7 @@ public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFrag boolean val = (boolean) newValue; if (val) { // check if we've shown the info dialog and check for microphone permissions - new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog)) + AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog)) .setTitle(R.string.dialog_voice_video_title) .setMessage(R.string.dialog_voice_video_message) .setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> { @@ -189,7 +191,9 @@ public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFrag }) .show(); - return false; + Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + positiveButton.setContentDescription("Enable"); + return false; } else { return true; } diff --git a/app/src/main/res/layout-sw400dp/activity_display_name.xml b/app/src/main/res/layout-sw400dp/activity_display_name.xml index 50986e7dd..4d4ff3040 100644 --- a/app/src/main/res/layout-sw400dp/activity_display_name.xml +++ b/app/src/main/res/layout-sw400dp/activity_display_name.xml @@ -33,6 +33,7 @@ + android:text="@string/activity_pn_mode_slow_mode" + /> @@ -176,6 +180,7 @@ + android:text="@string/activity_conversation_block_user"/> @@ -72,6 +75,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:contentDescription="@string/AccessibilityId_group_name" android:textColor="?android:textColorPrimary" android:textSize="@dimen/very_large_font_size" android:textStyle="bold" @@ -113,10 +117,10 @@ style="@style/Widget.Session.Button.Common.ProminentOutline" android:layout_width="wrap_content" android:layout_height="@dimen/small_button_height" - android:layout_marginTop="@dimen/small_spacing" + android:layout_marginVertical="@dimen/small_spacing" android:layout_marginEnd="@dimen/medium_spacing" android:layout_marginStart="@dimen/small_spacing" - android:layout_marginBottom="@dimen/small_spacing" + android:contentDescription="@string/AccessibilityId_add_members" android:paddingStart="@dimen/medium_spacing" android:paddingEnd="@dimen/medium_spacing" android:text="@string/activity_edit_closed_group_add_members" /> diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 935848565..3fff3890c 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -33,7 +33,8 @@ android:layout_height="@dimen/small_profile_picture_size" android:layout_alignParentLeft="true" android:layout_centerVertical="true" - android:layout_marginLeft="9dp" /> + android:layout_marginLeft="9dp" + android:contentDescription="@string/AccessibilityId_user_settings" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_conversation_call.xml b/app/src/main/res/menu/menu_conversation_call.xml index 8ebfeb8c8..1fa6955af 100644 --- a/app/src/main/res/menu/menu_conversation_call.xml +++ b/app/src/main/res/menu/menu_conversation_call.xml @@ -3,6 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/app/src/main/res/menu/menu_conversation_closed_group.xml b/app/src/main/res/menu/menu_conversation_closed_group.xml index 7ab02e07d..979d2afbf 100644 --- a/app/src/main/res/menu/menu_conversation_closed_group.xml +++ b/app/src/main/res/menu/menu_conversation_closed_group.xml @@ -5,11 +5,13 @@ diff --git a/app/src/main/res/menu/menu_conversation_copy_session_id.xml b/app/src/main/res/menu/menu_conversation_copy_session_id.xml index 4ca49666c..68a546ba9 100644 --- a/app/src/main/res/menu/menu_conversation_copy_session_id.xml +++ b/app/src/main/res/menu/menu_conversation_copy_session_id.xml @@ -5,6 +5,7 @@ + android:icon="@drawable/ic_content_copy_white_24dp" + android:contentDescription="@string/AccessibilityId_copy_session_id"/> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_conversation_expiration_off.xml b/app/src/main/res/menu/menu_conversation_expiration_off.xml index 8e062dc64..939220009 100644 --- a/app/src/main/res/menu/menu_conversation_expiration_off.xml +++ b/app/src/main/res/menu/menu_conversation_expiration_off.xml @@ -4,6 +4,7 @@ diff --git a/app/src/main/res/menu/menu_conversation_expiration_on.xml b/app/src/main/res/menu/menu_conversation_expiration_on.xml index 2abb49cef..88775cf71 100644 --- a/app/src/main/res/menu/menu_conversation_expiration_on.xml +++ b/app/src/main/res/menu/menu_conversation_expiration_on.xml @@ -5,6 +5,7 @@ diff --git a/app/src/main/res/menu/menu_conversation_notification_settings.xml b/app/src/main/res/menu/menu_conversation_notification_settings.xml index 2275db01f..3ea3037f5 100644 --- a/app/src/main/res/menu/menu_conversation_notification_settings.xml +++ b/app/src/main/res/menu/menu_conversation_notification_settings.xml @@ -2,5 +2,6 @@ + android:id="@+id/menu_notification_settings" + android:contentDescription="@string/AccessibilityId_notification_settings"/> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_conversation_unmuted.xml b/app/src/main/res/menu/menu_conversation_unmuted.xml index 85b9d16e8..7cc5c094b 100644 --- a/app/src/main/res/menu/menu_conversation_unmuted.xml +++ b/app/src/main/res/menu/menu_conversation_unmuted.xml @@ -4,6 +4,7 @@ + android:id="@+id/menu_mute_notifications" + android:contentDescription="@string/AccessibilityId_mute_notifications" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_done.xml b/app/src/main/res/menu/menu_done.xml index 9ee181de1..091979172 100644 --- a/app/src/main/res/menu/menu_done.xml +++ b/app/src/main/res/menu/menu_done.xml @@ -6,6 +6,7 @@ android:title="@string/menu_done_button" android:id="@+id/doneButton" android:icon="?menu_accept_icon" + android:contentDescription="@string/AccessibilityId_done" app:showAsAction="always" /> diff --git a/app/src/main/res/menu/menu_edit_closed_group.xml b/app/src/main/res/menu/menu_edit_closed_group.xml index 0c6b915d0..bcf5c784e 100644 --- a/app/src/main/res/menu/menu_edit_closed_group.xml +++ b/app/src/main/res/menu/menu_edit_closed_group.xml @@ -4,8 +4,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + android:title="@string/menu_apply_button" + android:contentDescription="@string/AccessibilityId_apply_changes" + app:showAsAction="always|withText" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ebacb766d..d7c5dddbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,145 @@ Save Note to Self Version %s + + + Create session ID + Restore Your Session + Link a device + Link Device + + Session ID + Recovery phrase reminder + Continue + + + Copy Session ID + + Enter your recovery phrase + + Enter display name + + Message Notifications + Fast mode notifications option + Slow mode notifications option + Continue with settings + + Recovery Phrase + Copy Recovery Phrase + + User settings + Search icon + Conversation list item + Details + Pin + + Blocked contacts + + + Message requests banner + Message request + No pending message requests + Clear all + + New conversation button + New direct message + Create group + Join community + + All media + Search + Add to home screen + Disappearing messages + Block + Confirm block + Notification settings + Mute notifications + Delete + Enable + + Edit group + Leave group + Group name + Accept name change + Cancel name change + Apply changes + Add members + Done + Mentions list + Contact mentions + + Call button + Settings + Disappearing messages timer + Time selector + Accept message request + Decline message request + Block message request + Timer icon + + Configuration message + Blocked banner + Blocked banner text + + Session id input box + Next + + Group name input + Continue group creation + Contact + Select contact + + + Message input box + New voice message + Send message button + Attachments button + Select camera button + Images folder + Documents folder + GIF button + Untrusted attachment message + Download media + Don\'t download media + + Message sent status: Sent + Message sent status pending + Message request has been accepted + Message Body + Voice message + Document + Deleted message + Delete message + Reply to message + Select + Save attachment + + Delete just for me + Delete for everyone + Cancel deletion + + Username input + Username text + User settings + Username + Privacy + Show recovery phrase + Edit user nickname + Apply + Cancel + Message user + Notifications + Conversations + Message requests + Appearance + Invite a friend + Help + Clear data + + Cancel + + Enable + Cancel New message From b25eb9af8efdd36b97261455701808c5bf091afc Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:58:56 +1000 Subject: [PATCH 023/244] Use new seed node certificates (#1144) * fix: use new seed node certificates * refactor: don't need to include subdomains actually --- app/src/main/res/raw/lf_session_cert.pem | 24 ------------------ app/src/main/res/raw/seed1.pem | 24 ++++++++++++++++++ app/src/main/res/raw/seed1cert.pem | 25 ------------------- app/src/main/res/raw/seed2.pem | 24 ++++++++++++++++++ app/src/main/res/raw/seed3.pem | 24 ++++++++++++++++++ app/src/main/res/raw/seed3cert.pem | 25 ------------------- .../xml/network_security_configuration.xml | 12 ++++----- .../org/session/libsession/snode/SnodeAPI.kt | 8 ++++-- 8 files changed, 84 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/res/raw/lf_session_cert.pem create mode 100644 app/src/main/res/raw/seed1.pem delete mode 100644 app/src/main/res/raw/seed1cert.pem create mode 100644 app/src/main/res/raw/seed2.pem create mode 100644 app/src/main/res/raw/seed3.pem delete mode 100644 app/src/main/res/raw/seed3cert.pem diff --git a/app/src/main/res/raw/lf_session_cert.pem b/app/src/main/res/raw/lf_session_cert.pem deleted file mode 100644 index 344a05543..000000000 --- a/app/src/main/res/raw/lf_session_cert.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN -ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x -HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx -WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y -aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl -Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e -RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+ -K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh -GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ -0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY -PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC -AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j -BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV -HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF -BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI -zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv -ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz -Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ -lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9 -OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I ------END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed1.pem b/app/src/main/res/raw/seed1.pem new file mode 100644 index 000000000..57199d80b --- /dev/null +++ b/app/src/main/res/raw/seed1.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIUWk96HLAovn4uFSI057KhnMxqosowDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HTAbBgNVBAMMFHNlZWQxLmdldHNlc3Npb24ub3JnMB4XDTIzMDQwNTAxMjQzNVoX +DTMzMDQwNTAxMjQzNVowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh +MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo +IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQxLmdldHNlc3Npb24ub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2wlGkR2aDOHoizik4mqvWEwDPOQG +o/Afd/6VqKzo4BpNerVZQNgdMgdLTedZE4FRfetubonYu6iSYALK2iKoGsIlru1u +Q9dUl0abA9v+yg6duh1aHw8oS16JPL0zdq8QevJaTxd0MeDnx4eXfFjtv8L0xO4r +CRFH+H6ATcJy+zhVBcWLjiNPe6mGSHM4trx3hwJY6OuuWX5FkO0tMqj9aKJtJ+l0 +NArra0BZ9MaMwAFE7AxWwyD0jWIcSvwK06eap+6jBcZIr+cr7fPO5mAlT+CoGB68 +yUFwh1wglcVdNPoa1mbFQssCsCRa3MWgpzbMq+KregVzjVEtilwLFjx7FQIDAQAB +o4GKMIGHMB0GA1UdDgQWBBQ1XAjGKhyIU22mYdUEIlzlktogNzAfBgNVHSMEGDAW +gBQ1XAjGKhyIU22mYdUEIlzlktogNzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY +MBaCFHNlZWQxLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4IBAQC4PRiu4LyxK71Gk+f3dDvjinuE9F0XtAamKfRlLMEo +KxK8dtLrT8p62rME7QiigSv15AmSNyqAp751N/j0th1prOnxBoG38BXKLBDDClri +u91MR4h034G6LIYCiM99ldc8Q5a5WCKu9/9z6CtVxZcNlfe477d6lKHSwb3mQ581 +1Ui3RnpkkU1n4XULI+TW2n/Hb8gN6IyTHFB9y2jb4kdg7N7PZIN8FS3n3XGiup9r +b/Rujkuy7rFW78Q1BuHWrQPbJ3RU2CKh1j5o6mtcJFRqP1PfqWmbuaomam48s5hU +4JEiR9tyxP+ewl/bToFcet+5Lp9wRLxn0afm/3V00WyP +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed1cert.pem b/app/src/main/res/raw/seed1cert.pem deleted file mode 100644 index 7360d6fca..000000000 --- a/app/src/main/res/raw/seed1cert.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL -BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ -TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u -MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw -MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI -VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2 -YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp -Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd -kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k -TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM -OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP -F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw -+axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP -OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K -zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF -MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs -gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj -lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP -hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln -N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT -/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV -gxJsm+g= ------END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed2.pem b/app/src/main/res/raw/seed2.pem new file mode 100644 index 000000000..bf14073c2 --- /dev/null +++ b/app/src/main/res/raw/seed2.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIUXkVaUNO/G727mNeaiso9MjvBEm4wDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMB4XDTIzMDQwNTAxMjI0MloX +DTMzMDQwNTAxMjI0MlowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh +MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo +IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQyLmdldHNlc3Npb24ub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvT493tt1EWdyIa++X59ffrQt+ghK ++3Hv/guCPmR0FxPUeVnayoLbeKgbe8dduThh7nlmlYnpwbulvDnMF/rRpX51AZiT +A8UGktBzGXi17/D/X71EXGqlM41QZfVm5MCdQcghvbwO8MP0nWmbV4DdiNYAwSNh +fpGMEiblCvKtGN71clTkOW+8Moq4eOxT9tKIlOv97uvkUS21NgmSzsj453hrb6oj +XR3rtW264zn99+Gv83rDE1jk0qfDjxCkaUb0BvRDREc+1q3p8GZ6euEFBM3AcXe7 +Yl0qbJgIXd5I+W5nMJJCyJHPTxQNvS+uJqL4kLvdwQRFAkwEM+t9GCH1PQIDAQAB +o4GKMIGHMB0GA1UdDgQWBBQOdqxllTHj+fmGjmdgIXBl+k0PRDAfBgNVHSMEGDAW +gBQOdqxllTHj+fmGjmdgIXBl+k0PRDAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY +MBaCFHNlZWQyLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4IBAQBkmmX+mopdnhzQC5b5rgbU7wVhlDaG7eJCRgUvqkYm +Pbv6XFfvtshykhw2BjSyQetofJaBh5KOR7g0MGRSn3AqRPBeEpXfkBI9urhqFwBF +F5atmp1rTCeHuAS6w4mL6rmj7wHl2CRSom7czRdUCNM+Tu1iK6xOrtOLwQ1H1ps1 +KK3siJb3W0eKykHnheQPn77RulVBNLz1yedEUTVkkuVhzSUj5yc8tiwrcagwWX6m +BlfVCJgsBbrJ754rg0AJ0k59wriRamimcUIBvKIo3g3UhJHDI8bt4+SvsRYkSmbi +rzVthAlJjSlRA28X/OLnknWcgEdkGhu0F1tkBtVjIQXd +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed3.pem b/app/src/main/res/raw/seed3.pem new file mode 100644 index 000000000..6939129f8 --- /dev/null +++ b/app/src/main/res/raw/seed3.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIUTz5rHKUe+VA9IM6vY6QACc0ORFkwDQYJKoZIhvcNAQEL +BQAwejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN +ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x +HTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMB4XDTIzMDQwNTAxMjYzMVoX +DTMzMDQwNTAxMjYzMVowejELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3Rvcmlh +MRIwEAYDVQQHDAlNZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNo +IEZvdW5kYXRpb24xHTAbBgNVBAMMFHNlZWQzLmdldHNlc3Npb24ub3JnMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6FgxIk9KmYISL5fk7BLaGAW6lBx8 +b4VL3DjlyrFMz7ZhSbcUcavWyyYB+iJxBRhfQGJ7vbwJZ1AwVJisjDFdiLcWzTF8 +gzZ7LVXH8qlVnqcx0gksrWYFnG3Y2WJrxEBFdD29lP7LVN3xLQdplMitOciqg5jN +oRjtwGo+wzaMW6WNPzgTvxLzPce9Rl3oN4tSK7qlA9VtsyHwOWBMcogv9LC9IUFZ +2yu0RdcxPdlwLwywYtSRt/W87KbAWTcYY1DfN2VA68p9Cip7/dPOokRduMh1peux +swmIybpC/wz/Ql6J6scSOjDUp/2UsIdYIvyP/Dibi4nHRmD+oz9kb+J3AQIDAQAB +o4GKMIGHMB0GA1UdDgQWBBSQAFetDPIzVg9rfgOI7bfaeEHd8TAfBgNVHSMEGDAW +gBSQAFetDPIzVg9rfgOI7bfaeEHd8TAPBgNVHRMBAf8EBTADAQH/MB8GA1UdEQQY +MBaCFHNlZWQzLmdldHNlc3Npb24ub3JnMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0G +CSqGSIb3DQEBCwUAA4IBAQCiBNdbKNSHyCZJKvC/V+pHy9E/igwvih2GQ5bNZJFA +daOiKBgaADxaxB4lhtzasr2LdgZdLrn0oONw+wYaui9Z12Yfdr9oWuOgktn8HKLY +oKkJc5EcMYFsd00FnnFcO2U8lQoL6PB/tdcEmpOfqtvShpNhp8SbadSNiqlttvtV +1dqvqSBiRdQm1kz2b8hA6GR6SPzSKlSuwI0J+ZcXEi232EJFbgJ3ESHFVHrhUZro +8A16/WDvZOMWCjOqJsFBw15WzosW9kyNwBtZinXVO3LW/7tVl08PDcarpH4IWjd0 +LDpU7zGjcD/A19tfdfMFTOmETuq40I8xxtlR2NENFOAL +-----END CERTIFICATE----- diff --git a/app/src/main/res/raw/seed3cert.pem b/app/src/main/res/raw/seed3cert.pem deleted file mode 100644 index 92574b769..000000000 --- a/app/src/main/res/raw/seed3cert.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEITCCAwmgAwIBAgIUc486Dy9Y00bUFfDeYmJIgSS5xREwDQYJKoZIhvcNAQEL -BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ -TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u -MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQzLmxva2kubmV0d29yazAeFw0yMTA0MDcw -MTIwNTJaFw0yMzA0MDcwMTIwNTJaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI -VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2 -YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMy5sb2tp -Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtokMlsFzf -piYeD0EVNikMyvjltpF6fUEde9NOVrTtNTQT6kkDk+/0HF5LYgPaatv6v7fpUQHi -kIwd6F0LTRGeWDFdsaWMdtlR1n/GxLPrOROsE8dcLt6GLavPf9rDabgva93m/JD6 -XW+Ne+MPEwqS8dAmFGhZd0gju6AtKFoSHnIf5pSQN6fSZUF/JQtHLVprAKKWKDiS -ZwmWbmrZR2aofLD/VRpetabajnZlv9EeWloQwvUsw1C1hkAmmtFeeXtg7ePwrOzo -6CnmcUJwOmi+LWqQV4A+58RZPFKaZoC5pzaKd0OYB8eZ8HB1F41UjGJgheX5Cyl4 -+amfF3l8dSq1AgMBAAGjgZAwgY0wHQYDVR0OBBYEFM9VSq4pGydjtX92Beul4+ml -jBKtMB8GA1UdIwQYMBaAFM9VSq4pGydjtX92Beul4+mljBKtMA8GA1UdEwEB/wQF -MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMy5sb2tpLm5ldHdvcmswEwYD -VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAAYxmhhkcKE1n6g1 -JqOa3UCBo4EfbqY5+FDZ0FVqv/cwemwVpKLbe6luRIS8poomdPCyMOS45V7wN3H9 -cFpfJ1TW19ydPVKmCXrl29ngmnY1q7YDwE/4qi3VK/UiqDkTHMKWjVPkenOyi8u6 -VVQANXSnKrn6GtigNFjGyD38O+j7AUSXBtXOJczaoF6r6BWgwQZ2WmgjuwvKTWSN -4r8uObERoAQYVaeXfgdr4e9X/JdskBDaLFfoW/rrSozHB4FqVNFW96k+aIUgRa5p -9kv115QcBPCSh9qOyTHij4tswS6SyOFaiKrNC4hgHQXP4QgioKmtsR/2Y+qJ6ddH -6oo+4QU= ------END CERTIFICATE----- diff --git a/app/src/main/res/xml/network_security_configuration.xml b/app/src/main/res/xml/network_security_configuration.xml index e0a3502bc..f3a7419b5 100644 --- a/app/src/main/res/xml/network_security_configuration.xml +++ b/app/src/main/res/xml/network_security_configuration.xml @@ -4,21 +4,21 @@ 127.0.0.1 - public.loki.foundation + seed1.getsession.org - + - storage.seed1.loki.network + seed2.getsession.org - + - storage.seed3.loki.network + seed3.getsession.org - + \ No newline at end of file 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 e8a53dfc2..ebd66d3a3 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -73,12 +73,16 @@ object SnodeAPI { private val minimumSnodePoolCount = 12 private val minimumSwarmSnodeCount = 3 // Use port 4433 if the API level can handle the network security configuration and enforce pinned certificates - private val seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4433 + private val seedNodePort = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) 443 else 4443 private val seedNodePool by lazy { if (useTestnet) { setOf( "http://public.loki.foundation:38157" ) } else { - setOf( "https://storage.seed1.loki.network:$seedNodePort", "https://storage.seed3.loki.network:$seedNodePort", "https://public.loki.foundation:$seedNodePort" ) + setOf( + "https://seed1.getsession.org:$seedNodePort", + "https://seed2.getsession.org:$seedNodePort", + "https://seed3.getsession.org:$seedNodePort", + ) } } private const val snodeFailureThreshold = 3 From e1ff2bf98855a0dc25b01ed9e92f18e2f1bd5606 Mon Sep 17 00:00:00 2001 From: hjubb Date: Wed, 5 Apr 2023 16:32:16 +1000 Subject: [PATCH 024/244] build: update build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index cc05319ac..b86122033 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,8 +160,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 334 -def canonicalVersionName = "1.16.6" +def canonicalVersionCode = 335 +def canonicalVersionName = "1.16.7" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 7b0c0147917655c88782e21eb0321ab16671ad78 Mon Sep 17 00:00:00 2001 From: SzBenedek2006 <95222597+SzBenedek2006@users.noreply.github.com> Date: Thu, 13 Apr 2023 02:40:32 +0200 Subject: [PATCH 025/244] Fix hungarian translations (#1140) --- app/src/main/res/values-hu-rHU/strings.xml | 31 +++++++++++----------- app/src/main/res/values-hu/strings.xml | 31 +++++++++++----------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index 3c8b4a54c..517efa560 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -92,7 +92,8 @@ Ez végelegesen törölni fogja a kiválasztott üzenetet. Ez véglegesen törölni fogja mind a(z) %1$d db kiválasztott üzenetet. - Tiltja ezt a felhasználót? + Hívás engedély szükséges + Tiltod ezt a felhasználót? Mentés tárolóra? A média mentése a tárolóra lehetővé teszi bármelyik másik alkalmazásnak a készülékeden, hogy hozzáférjen.\n\nFolytatod? @@ -206,7 +207,7 @@ Hívott téged Nem fogadott hívás Média üzenet - %s a Session-on van! + %s elérhető a Session-on! Eltűnő üzenetek letiltva Eltűnő üzenet ideje beállítva erre: %s %s készített egy képernyőképet. @@ -236,7 +237,7 @@ Te Nem támogatott médiatípus Piszkozat - A Session- nek szüksége van fájlhozzáférési engedélyekhez, hogy menteni tudjon külső tárhelyre, de ezt az engedélyt megtagadták. Kérjük, hogy a készüléke beállításaiban engedélyezze a \"fájlok és média\" opciót a Session számára. + A Session- nek szüksége van fájlhozzáférési engedélyekhez, hogy menteni tudjon külső tárhelyre. Kérlek, hogy a rendszerbeállításokban engedélyezd a \"fájlok és média\" opciót a Session számára. Nem lehet engedély nélkül menteni a külső tárolóra Törlöd az üzenetet? Ez véglegesen törölni fogja ezt az üzenetet. @@ -250,8 +251,8 @@ Összes megjelölése olvasottként Olvasottnak jelöl Válasz - Függő Session üzenetek - Függő Session üzeneteid vannak, koppints a megnyitáshoz és letöltéshez + Függőben lévő Session üzenetek + Függőben lévő Session üzeneteid vannak, koppints a megnyitáshoz és letöltéshez %1$s %2$s Névjegy @@ -275,7 +276,7 @@ Érvénytelen parancsikon - Ülés + Session Új üzenet @@ -530,17 +531,17 @@ Megosztás Érvénytelen Session azonosító Mégse - Az ön Session azonosítója - Az ön Session-ja itt kezdődik... + A session azonosítód + A Session itt kezdődik... Session azonosító létrehozása - Folytassa az ülését + Session azonosító helyreállítása Mi az a Session? Ez egy decentralizált, titkosított üzenetküldő alkalmazás Tehát nem gyűjti a személyes adataimat vagy a beszélgetés metaadatait? Hogyan működik? - Fejlett névtelen útválasztási és end-to-end titkosítási technológiák kombinációjának használata. - A barátok nem engedik, hogy a barátok kompromittált hírnököket használjanak. Szívesen. + Fejlett anonim útválasztási és végponttól-végpontig titkosítási technológiák használatával. + A barátként nem engedhetem, hogy megbízhatatlan üzenetküldő appokat használj. Szívesen. Ismerd meg a Session ID-d - Az üles azonosító az az egyedi cím, amelyet az emberek használhatnak, hogy kapcsolatba lépjenek Önnel az Ülés során. Mivel nincs kapcsolat a valódi személyazonosságával, az Ülés azonosító teljesen névtelen, és privát. + A Session azonosító az az egyedi cím, amelyet az emberek használhatnak, hogy kapcsolatba lépjenek Önnel a Sessionon. Mivel nincs kapcsolat a valódi személyazonosságával, az Session azonosító teljesen névtelen, és privát. Fiók visszaállítása Írja be azt a helyreállítási kifejezést, amelyet a fiók visszaállításához regisztrálásakor kapott. Írja be a helyreállítási kifejezést @@ -552,7 +553,7 @@ Ajánlott Kérjük, válasszon egy lehetőséget Még nincsenek névjegyei - Indítson el egy ülést + Indítson el egy beszélgetést Biztosan elhagyja ezt a csoportot? "Nem sikerült kilépni a csoportból" Biztosan törli ezt a beszélgetést? @@ -573,8 +574,8 @@ Célállomás Tudj meg többet Feloldás... - Új Ülés - Adja meg az Ülés azonosítóját + Új Session + Adja meg a Session azonosítóját QR kód beolvasása A beszélgetés elindításához olvassa be a felhasználó QR kódját. A QR kód a fiókbeállításokban található a QR kód ikonra koppintva. Írja be Session azonosítóját vagy ONS nevét diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 3c8b4a54c..517efa560 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -92,7 +92,8 @@ Ez végelegesen törölni fogja a kiválasztott üzenetet. Ez véglegesen törölni fogja mind a(z) %1$d db kiválasztott üzenetet. - Tiltja ezt a felhasználót? + Hívás engedély szükséges + Tiltod ezt a felhasználót? Mentés tárolóra? A média mentése a tárolóra lehetővé teszi bármelyik másik alkalmazásnak a készülékeden, hogy hozzáférjen.\n\nFolytatod? @@ -206,7 +207,7 @@ Hívott téged Nem fogadott hívás Média üzenet - %s a Session-on van! + %s elérhető a Session-on! Eltűnő üzenetek letiltva Eltűnő üzenet ideje beállítva erre: %s %s készített egy képernyőképet. @@ -236,7 +237,7 @@ Te Nem támogatott médiatípus Piszkozat - A Session- nek szüksége van fájlhozzáférési engedélyekhez, hogy menteni tudjon külső tárhelyre, de ezt az engedélyt megtagadták. Kérjük, hogy a készüléke beállításaiban engedélyezze a \"fájlok és média\" opciót a Session számára. + A Session- nek szüksége van fájlhozzáférési engedélyekhez, hogy menteni tudjon külső tárhelyre. Kérlek, hogy a rendszerbeállításokban engedélyezd a \"fájlok és média\" opciót a Session számára. Nem lehet engedély nélkül menteni a külső tárolóra Törlöd az üzenetet? Ez véglegesen törölni fogja ezt az üzenetet. @@ -250,8 +251,8 @@ Összes megjelölése olvasottként Olvasottnak jelöl Válasz - Függő Session üzenetek - Függő Session üzeneteid vannak, koppints a megnyitáshoz és letöltéshez + Függőben lévő Session üzenetek + Függőben lévő Session üzeneteid vannak, koppints a megnyitáshoz és letöltéshez %1$s %2$s Névjegy @@ -275,7 +276,7 @@ Érvénytelen parancsikon - Ülés + Session Új üzenet @@ -530,17 +531,17 @@ Megosztás Érvénytelen Session azonosító Mégse - Az ön Session azonosítója - Az ön Session-ja itt kezdődik... + A session azonosítód + A Session itt kezdődik... Session azonosító létrehozása - Folytassa az ülését + Session azonosító helyreállítása Mi az a Session? Ez egy decentralizált, titkosított üzenetküldő alkalmazás Tehát nem gyűjti a személyes adataimat vagy a beszélgetés metaadatait? Hogyan működik? - Fejlett névtelen útválasztási és end-to-end titkosítási technológiák kombinációjának használata. - A barátok nem engedik, hogy a barátok kompromittált hírnököket használjanak. Szívesen. + Fejlett anonim útválasztási és végponttól-végpontig titkosítási technológiák használatával. + A barátként nem engedhetem, hogy megbízhatatlan üzenetküldő appokat használj. Szívesen. Ismerd meg a Session ID-d - Az üles azonosító az az egyedi cím, amelyet az emberek használhatnak, hogy kapcsolatba lépjenek Önnel az Ülés során. Mivel nincs kapcsolat a valódi személyazonosságával, az Ülés azonosító teljesen névtelen, és privát. + A Session azonosító az az egyedi cím, amelyet az emberek használhatnak, hogy kapcsolatba lépjenek Önnel a Sessionon. Mivel nincs kapcsolat a valódi személyazonosságával, az Session azonosító teljesen névtelen, és privát. Fiók visszaállítása Írja be azt a helyreállítási kifejezést, amelyet a fiók visszaállításához regisztrálásakor kapott. Írja be a helyreállítási kifejezést @@ -552,7 +553,7 @@ Ajánlott Kérjük, válasszon egy lehetőséget Még nincsenek névjegyei - Indítson el egy ülést + Indítson el egy beszélgetést Biztosan elhagyja ezt a csoportot? "Nem sikerült kilépni a csoportból" Biztosan törli ezt a beszélgetést? @@ -573,8 +574,8 @@ Célállomás Tudj meg többet Feloldás... - Új Ülés - Adja meg az Ülés azonosítóját + Új Session + Adja meg a Session azonosítóját QR kód beolvasása A beszélgetés elindításához olvassa be a felhasználó QR kódját. A QR kód a fiókbeállításokban található a QR kód ikonra koppintva. Írja be Session azonosítóját vagy ONS nevét From 63d442584c1bf0c8919793f1f20dae0cacde690d Mon Sep 17 00:00:00 2001 From: Andrew Gallasch Date: Thu, 13 Apr 2023 16:24:03 +0930 Subject: [PATCH 026/244] Fix empty message behind media message (#1150) --- .../conversation/v2/messages/VisibleMessageContentView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8c33e4473..75a3c5875 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 @@ -10,10 +10,8 @@ import android.text.style.ForegroundColorSpan import android.text.style.URLSpan import android.text.util.Linkify import android.util.AttributeSet -import android.view.LayoutInflater import android.view.MotionEvent import android.view.View -import android.widget.LinearLayout import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout @@ -22,6 +20,7 @@ import androidx.core.graphics.BlendModeColorFilterCompat import androidx.core.graphics.BlendModeCompat import androidx.core.text.getSpans import androidx.core.text.toSpannable +import androidx.core.view.children import androidx.core.view.isVisible import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding @@ -223,6 +222,7 @@ class VisibleMessageContentView : ConstraintLayout { } binding.bodyTextView.isVisible = message.body.isNotEmpty() && !hideBody + binding.contentParent.apply { isVisible = children.any { it.isVisible } } if (message.body.isNotEmpty() && !hideBody) { val color = getTextColor(context, message) From 2246a5d9ce01f52fefda7e51457f221f7d18782d Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 19 Apr 2023 23:07:36 +1000 Subject: [PATCH 027/244] feat: introduce new pns models and wire kotlinx serialization to apply in libsession --- app/build.gradle | 15 ------ .../securesms/ApplicationContext.java | 8 +-- ...nManager.kt => PushNotificationManager.kt} | 2 +- .../notifications/PushNotificationService.kt | 4 +- build.gradle | 6 ++- libsession/build.gradle | 2 + .../sending_receiving/notifications/Models.kt | 52 +++++++++++++++++++ .../notifications/PushNotificationAPI.kt | 4 +- 8 files changed, 67 insertions(+), 26 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/notifications/{LokiPushNotificationManager.kt => PushNotificationManager.kt} (99%) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt diff --git a/app/build.gradle b/app/build.gradle index b86122033..a2bb94092 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,18 +1,3 @@ -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - 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:$googleServicesVersion" - classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" - } -} - apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'witness' diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index a605a8492..2fc53dc24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -76,7 +76,7 @@ import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.notifications.BackgroundPollWorker; import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.FcmUtils; -import org.thoughtcrime.securesms.notifications.LokiPushNotificationManager; +import org.thoughtcrime.securesms.notifications.PushNotificationManager; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; import org.thoughtcrime.securesms.providers.BlobProvider; @@ -455,9 +455,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { if (TextSecurePreferences.isUsingFCM(this)) { - LokiPushNotificationManager.register(token, userPublicKey, this, force); + PushNotificationManager.register(token, userPublicKey, this, force); } else { - LokiPushNotificationManager.unregister(token, this); + PushNotificationManager.unregister(token, this); } }); @@ -536,7 +536,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO public void clearAllData(boolean isMigratingToV2KeyPair) { String token = TextSecurePreferences.getFCMToken(this); if (token != null && !token.isEmpty()) { - LokiPushNotificationManager.unregister(token, this); + PushNotificationManager.unregister(token, this); } if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt similarity index 99% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt rename to app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt index adaec0e17..5a53f7af2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/LokiPushNotificationManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt @@ -15,7 +15,7 @@ import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.dependencies.DatabaseComponent -object LokiPushNotificationManager { +object PushNotificationManager { private val maxRetryCount = 4 private val tokenExpirationInterval = 12 * 60 * 60 * 1000 diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index fc399d293..b7a92e3b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -18,7 +18,7 @@ class PushNotificationService : FirebaseMessagingService() { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - LokiPushNotificationManager.register(token, userPublicKey, this, false) + PushNotificationManager.register(token, userPublicKey, this, false) } override fun onMessageReceived(message: RemoteMessage) { @@ -53,6 +53,6 @@ class PushNotificationService : FirebaseMessagingService() { super.onDeletedMessages() val token = TextSecurePreferences.getFCMToken(this)!! val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - LokiPushNotificationManager.register(token, userPublicKey, this, true) + PushNotificationManager.register(token, userPublicKey, this, true) } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7e7e14f00..64a1fd4d0 100644 --- a/build.gradle +++ b/build.gradle @@ -5,9 +5,11 @@ buildscript { } dependencies { classpath "com.android.tools.build:gradle:$gradlePluginVersion" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath "com.google.gms:google-services:$googleServicesVersion" 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:$googleServicesVersion" + classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" } } diff --git a/libsession/build.gradle b/libsession/build.gradle index dd8959958..2241cfb3c 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.library' id 'kotlin-android' + id 'kotlinx-serialization' } android { @@ -41,6 +42,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxJsonVersion" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" testImplementation "junit:junit:$junitVersion" testImplementation 'org.assertj:assertj-core:3.11.1' diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt new file mode 100644 index 000000000..e37b5b0b6 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -0,0 +1,52 @@ +package org.session.libsession.messaging.sending_receiving.notifications + +import kotlinx.serialization.Serializable + +@Serializable +data class SubscriptionRequest( + /** the 33-byte account being subscribed to; typically a session ID */ + val pubkey: String, + /** when the pubkey starts with 05 (i.e. a session ID) this is the ed25519 32-byte pubkey associated with the session ID */ + val session_ed25519: String?, + /** 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth (new closed groups) */ + val subkey_tag: String?, + /** array of integer namespaces to subscribe to, **must be sorted in ascending order** */ + val namespaces: List, + /** if provided and true then notifications will include the body of the message (as long as it isn't too large) */ + val data: Boolean, + /** the signature unix timestamp in seconds, not ms */ + val sig_ts: Long, + /** the 64-byte ed25519 signature */ + val signature: String, + /** the string identifying the notification service, "firebase" for android (currently) */ + val service: String, + /** dict of service-specific data, currently just "token" field with device-specific token but different services might have other requirements */ + val service_info: Map, + /** 32-byte encryption key; notification payloads sent to the device will be encrypted with XChaCha20-Poly1305 via libsodium using this key. + * persist it on device */ + val enc_key: String +) + +@Serializable +data class SubscriptionResponse( + val error: Int?, + val message: String?, + val success: Boolean?, + val added: Boolean?, + val updated: Boolean?, +) { + companion object { + /** invalid values, missing reuqired arguments etc, details in message */ + const val UNPARSEABLE_ERROR = 1 + /** the "service" value is not active / valid */ + const val SERVICE_NOT_AVAILABLE = 2 + /** something getting wrong internally talking to the backend */ + const val SERVICE_TIMEOUT = 3 + /** other error processing the subscription (details in the message) */ + const val GENERIC_ERROR = 4 + } + fun isSuccess() = success == true && error == null + fun errorInfo() = if (success == false && error != null) { + true to message + } else false to null +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index f793cd6e4..e49375bab 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -16,8 +16,8 @@ import org.session.libsignal.utilities.Log @SuppressLint("StaticFieldLeak") object PushNotificationAPI { val context = MessagingModuleConfiguration.shared.context - val server = "https://live.apns.getsession.org" - val serverPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" + val server = "https://push.getsession.org" + val serverPublicKey: String = TODO("get the new server pubkey here") private val maxRetryCount = 4 private val tokenExpirationInterval = 12 * 60 * 60 * 1000 From 8d4f2445f28f1f9042f28c4e8fe87b3761e58486 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:12:38 +1000 Subject: [PATCH 028/244] feat: add support for firebase and split out google services as a dependency for only the play version of the app. Add support for requests in new pn server --- .gitignore | 2 +- app/build.gradle | 268 ++++++++--------- app/src/main/AndroidManifest.xml | 8 - .../securesms/ApplicationContext.java | 36 +-- .../securesms/crypto/IdentityKeyUtil.java | 1 + .../dependencies/InjectableType.java | 4 - .../securesms/dependencies/PushComponent.kt | 12 + .../securesms/home/HomeActivity.kt | 6 +- .../securesms/jobs/JobManagerFactories.java | 1 - .../securesms/jobs/UpdateApkJob.java | 271 ------------------ .../securesms/notifications/PushManager.kt | 6 + .../notifications/PushNotificationService.kt | 9 +- .../securesms/onboarding/PNModeActivity.kt | 2 +- .../NotificationsPreferenceFragment.java | 2 +- .../service/UpdateApkRefreshListener.java | 47 --- app/src/play/AndroidManifest.xml | 16 ++ .../securesms/notifications/FcmUtils.kt | 4 +- .../notifications/FirebasePushManager.kt | 142 +++++++++ .../notifications/FirebasePushModule.kt | 21 ++ .../sending_receiving/notifications/Models.kt | 27 +- .../notifications/PushNotificationAPI.kt | 6 +- 21 files changed, 381 insertions(+), 510 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java create mode 100644 app/src/play/AndroidManifest.xml rename app/src/{main/java => play/kotlin}/org/thoughtcrime/securesms/notifications/FcmUtils.kt (90%) create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt diff --git a/.gitignore b/.gitignore index 01ec4c41c..1fe35a0e7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,4 @@ signing.properties ffpr *.sh pkcs11.password -play +app/play diff --git a/app/build.gradle b/app/build.gradle index a2bb94092..a8a869fc5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,6 @@ apply plugin: 'kotlin-android' apply plugin: 'witness' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' -apply plugin: 'com.google.gms.google-services' apply plugin: 'kotlinx-serialization' apply plugin: 'dagger.hilt.android.plugin' @@ -11,6 +10,140 @@ configurations.all { exclude module: "commons-logging" } +def canonicalVersionCode = 335 +def canonicalVersionName = "1.16.7" + +def postFixSize = 10 +def abiPostFix = ['armeabi-v7a' : 1, + 'arm64-v8a' : 2, + 'x86' : 3, + 'x86_64' : 4, + 'universal' : 5] + +android { + compileSdkVersion androidCompileSdkVersion + namespace 'network.loki.messenger' + useLibrary 'org.apache.http.legacy' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + packagingOptions { + exclude 'LICENSE.txt' + exclude 'LICENSE' + exclude 'NOTICE' + exclude 'asm-license.txt' + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + exclude 'META-INF/proguard/androidx-annotations.pro' + } + + splits { + abi { + enable true + reset() + include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + universalApk true + } + } + + defaultConfig { + versionCode canonicalVersionCode * postFixSize + versionName canonicalVersionName + + minSdkVersion androidMinimumSdkVersion + targetSdkVersion androidTargetSdkVersion + + multiDexEnabled = true + + vectorDrawables.useSupportLibrary = true + project.ext.set("archivesBaseName", "session") + + buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" + buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" + buildConfigField "int", "CONTENT_PROXY_PORT", "443" + buildConfigField "String", "USER_AGENT", "\"OWA\"" + buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' + buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" + + resConfigs autoResConfig() + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + // The following argument makes the Android Test Orchestrator run its + // "pm clear" command after each test invocation. This command ensures + // that the app's state is completely cleared between tests. + testInstrumentationRunnerArguments clearPackageData: 'true' + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' + } + } + + sourceSets { + String sharedTestDir = 'src/sharedTest/java' + test.java.srcDirs += sharedTestDir + androidTest.java.srcDirs += sharedTestDir + } + + buildTypes { + release { + minifyEnabled false + } + debug { + minifyEnabled false + } + } + + flavorDimensions "distribution" + productFlavors { + play { + dimension "distribution" + apply plugin: 'com.google.gms.google-services' + ext.websiteUpdateUrl = "null" + buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" + buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" + } + + website { + dimension "distribution" + ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" + buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" + buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" + } + } + + applicationVariants.all { variant -> + variant.outputs.each { output -> + def abiName = output.getFilter("ABI") ?: 'universal' + def postFix = abiPostFix.get(abiName, 0) + + if (postFix >= postFixSize) throw new AssertionError("postFix is too large") + output.outputFileName = output.outputFileName = "session-${variant.versionName}-${abiName}.apk" + output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix + } + } + + lintOptions { + abortOnError true + baseline file("lint-baseline.xml") + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } + + buildFeatures { + dataBinding true + viewBinding true + } +} + dependencies { implementation "androidx.appcompat:appcompat:$appcompatVersion" implementation 'androidx.recyclerview:recyclerview:1.2.1' @@ -34,7 +167,7 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.5.3' implementation "androidx.core:core-ktx:$coreVersion" implementation "androidx.work:work-runtime-ktx:2.7.1" - implementation ("com.google.firebase:firebase-messaging:18.0.0") { + playImplementation ("com.google.firebase:firebase-messaging:18.0.0") { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -145,137 +278,6 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 335 -def canonicalVersionName = "1.16.7" - -def postFixSize = 10 -def abiPostFix = ['armeabi-v7a' : 1, - 'arm64-v8a' : 2, - 'x86' : 3, - 'x86_64' : 4, - 'universal' : 5] - -android { - compileSdkVersion androidCompileSdkVersion - namespace 'network.loki.messenger' - useLibrary 'org.apache.http.legacy' - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - packagingOptions { - exclude 'LICENSE.txt' - exclude 'LICENSE' - exclude 'NOTICE' - exclude 'asm-license.txt' - exclude 'META-INF/LICENSE' - exclude 'META-INF/NOTICE' - exclude 'META-INF/proguard/androidx-annotations.pro' - } - - splits { - abi { - enable true - reset() - include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' - universalApk true - } - } - - defaultConfig { - versionCode canonicalVersionCode * postFixSize - versionName canonicalVersionName - - minSdkVersion androidMinimumSdkVersion - targetSdkVersion androidTargetSdkVersion - - multiDexEnabled = true - - vectorDrawables.useSupportLibrary = true - project.ext.set("archivesBaseName", "session") - - buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" - buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" - buildConfigField "int", "CONTENT_PROXY_PORT", "443" - buildConfigField "String", "USER_AGENT", "\"OWA\"" - buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' - buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" - - resConfigs autoResConfig() - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - // The following argument makes the Android Test Orchestrator run its - // "pm clear" command after each test invocation. This command ensures - // that the app's state is completely cleared between tests. - testInstrumentationRunnerArguments clearPackageData: 'true' - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - } - - sourceSets { - String sharedTestDir = 'src/sharedTest/java' - test.java.srcDirs += sharedTestDir - androidTest.java.srcDirs += sharedTestDir - } - - buildTypes { - release { - minifyEnabled false - } - debug { - minifyEnabled false - } - } - - flavorDimensions "distribution" - productFlavors { - play { - ext.websiteUpdateUrl = "null" - buildConfigField "boolean", "PLAY_STORE_DISABLED", "false" - buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl" - } - - website { - ext.websiteUpdateUrl = "https://github.com/oxen-io/session-android/releases" - buildConfigField "boolean", "PLAY_STORE_DISABLED", "true" - buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\"" - } - } - - applicationVariants.all { variant -> - variant.outputs.each { output -> - def abiName = output.getFilter("ABI") ?: 'universal' - def postFix = abiPostFix.get(abiName, 0) - - if (postFix >= postFixSize) throw new AssertionError("postFix is too large") - output.outputFileName = output.outputFileName = "session-${variant.versionName}-${abiName}.apk" - output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix - } - } - - lintOptions { - abortOnError true - baseline file("lint-baseline.xml") - } - - testOptions { - unitTests { - includeAndroidResources = true - } - } - - buildFeatures { - dataBinding true - viewBinding true - } -} - static def getLastCommitTimestamp() { new ByteArrayOutputStream().withStream { os -> return os.toString() + "000" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6755addc0..7912ab973 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -306,14 +306,6 @@ android:name="android.support.PARENT_ACTIVITY" android:value="org.thoughtcrime.securesms.home.HomeActivity" /> - - - - - { - if (!task.isSuccessful()) { - Log.w("Loki", "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.getException()); - return Unit.INSTANCE; - } - String token = task.getResult().getToken(); - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (userPublicKey == null) return Unit.INSTANCE; - - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - if (TextSecurePreferences.isUsingFCM(this)) { - PushNotificationManager.register(token, userPublicKey, this, force); - } else { - PushNotificationManager.unregister(token, this); - } - }); - - return Unit.INSTANCE; - }); + public void registerForPnIfNeeded(final Boolean force) { + pushManager.register(force); } private void setUpPollingIfNeeded() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java index a5333ef5d..62aaf58f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java @@ -52,6 +52,7 @@ public class IdentityKeyUtil { public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3"; public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key"; public static final String ED25519_SECRET_KEY = "pref_ed25519_secret_key"; + public static final String NOTIFICATION_KEY = "pref_notification_key"; public static final String LOKI_SEED = "loki_seed"; public static final String HAS_MIGRATED_KEY = "has_migrated_keys"; diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java deleted file mode 100644 index 033b3ef45..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/InjectableType.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.thoughtcrime.securesms.dependencies; - -public interface InjectableType { -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt new file mode 100644 index 000000000..cd4e00416 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PushComponent.kt @@ -0,0 +1,12 @@ +package org.thoughtcrime.securesms.dependencies + +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.thoughtcrime.securesms.notifications.PushManager + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface PushComponent { + fun providePushManager(): PushManager +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 9b1e01a54..72df05798 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -1,11 +1,11 @@ package org.thoughtcrime.securesms.home import android.content.BroadcastReceiver +import android.content.ClipData +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.ClipData -import android.content.ClipboardManager import android.os.Bundle import android.text.SpannableString import android.widget.Toast @@ -199,7 +199,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // update things based on TextSecurePrefs (profile info etc) // Set up remaining components if needed val application = ApplicationContext.getInstance(this@HomeActivity) - application.registerForFCMIfNeeded(false) + application.registerForPnIfNeeded(false) if (textSecurePreferences.getLocalNumber() != null) { OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index ef73325f3..e660a774b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -31,7 +31,6 @@ public final class JobManagerFactories { put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application)); - put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory()); }}; factoryKeys.addAll(factoryHashMap.keySet()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java deleted file mode 100644 index 5b4ce8d13..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java +++ /dev/null @@ -1,271 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - - -import android.app.DownloadManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.session.libsession.messaging.utilities.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.service.UpdateApkReadyListener; -import org.session.libsession.utilities.FileUtils; -import org.session.libsignal.utilities.Hex; -import org.session.libsignal.utilities.JsonUtil; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.io.FileInputStream; -import java.io.IOException; -import java.security.MessageDigest; - -import network.loki.messenger.BuildConfig; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public class UpdateApkJob extends BaseJob { - - public static final String KEY = "UpdateApkJob"; - - private static final String TAG = UpdateApkJob.class.getSimpleName(); - - public UpdateApkJob() { - this(new Job.Parameters.Builder() - .setQueue("UpdateApkJob") - .addConstraint(NetworkConstraint.KEY) - .setMaxAttempts(3) - .build()); - } - - private UpdateApkJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull - Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException, PackageManager.NameNotFoundException { - if (!BuildConfig.PLAY_STORE_DISABLED) return; - - Log.i(TAG, "Checking for APK update..."); - - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build(); - - Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) { - throw new IOException("Bad response: " + response.message()); - } - - UpdateDescriptor updateDescriptor = JsonUtil.fromJson(response.body().string(), UpdateDescriptor.class); - byte[] digest = Hex.fromStringCondensed(updateDescriptor.getDigest()); - - Log.i(TAG, "Got descriptor: " + updateDescriptor); - - if (updateDescriptor.getVersionCode() > getVersionCode()) { - DownloadStatus downloadStatus = getDownloadStatus(updateDescriptor.getUrl(), digest); - - Log.i(TAG, "Download status: " + downloadStatus.getStatus()); - - if (downloadStatus.getStatus() == DownloadStatus.Status.COMPLETE) { - Log.i(TAG, "Download status complete, notifying..."); - handleDownloadNotify(downloadStatus.getDownloadId()); - } else if (downloadStatus.getStatus() == DownloadStatus.Status.MISSING) { - Log.i(TAG, "Download status missing, starting download..."); - handleDownloadStart(updateDescriptor.getUrl(), updateDescriptor.getVersionName(), digest); - } - } - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - return e instanceof IOException; - } - - @Override - public void onCanceled() { - Log.w(TAG, "Update check failed"); - } - - private int getVersionCode() throws PackageManager.NameNotFoundException { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - - return packageInfo.versionCode; - } - - private DownloadStatus getDownloadStatus(String uri, byte[] theirDigest) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Query query = new DownloadManager.Query(); - - query.setFilterByStatus(DownloadManager.STATUS_PAUSED | DownloadManager.STATUS_PENDING | DownloadManager.STATUS_RUNNING | DownloadManager.STATUS_SUCCESSFUL); - - long pendingDownloadId = TextSecurePreferences.getUpdateApkDownloadId(context); - byte[] pendingDigest = getPendingDigest(context); - Cursor cursor = downloadManager.query(query); - - try { - DownloadStatus status = new DownloadStatus(DownloadStatus.Status.MISSING, -1); - - while (cursor != null && cursor.moveToNext()) { - int jobStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); - String jobRemoteUri = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI)); - long downloadId = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)); - byte[] digest = getDigestForDownloadId(downloadId); - - if (jobRemoteUri != null && jobRemoteUri.equals(uri) && downloadId == pendingDownloadId) { - - if (jobStatus == DownloadManager.STATUS_SUCCESSFUL && - digest != null && pendingDigest != null && - MessageDigest.isEqual(pendingDigest, theirDigest) && - MessageDigest.isEqual(digest, theirDigest)) - { - return new DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId); - } else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) { - status = new DownloadStatus(DownloadStatus.Status.PENDING, downloadId); - } - } - } - - return status; - } finally { - if (cursor != null) cursor.close(); - } - } - - private void handleDownloadStart(String uri, String versionName, byte[] digest) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(uri)); - - downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); - downloadRequest.setTitle("Downloading Signal update"); - downloadRequest.setDescription("Downloading Signal " + versionName); - downloadRequest.setVisibleInDownloadsUi(false); - downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk"); - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); - - long downloadId = downloadManager.enqueue(downloadRequest); - TextSecurePreferences.setUpdateApkDownloadId(context, downloadId); - TextSecurePreferences.setUpdateApkDigest(context, Hex.toStringCondensed(digest)); - } - - private void handleDownloadNotify(long downloadId) { - Intent intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE); - intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId); - - new UpdateApkReadyListener().onReceive(context, intent); - } - - private @Nullable byte[] getDigestForDownloadId(long downloadId) { - try { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - FileInputStream fin = new FileInputStream(downloadManager.openDownloadedFile(downloadId).getFileDescriptor()); - byte[] digest = FileUtils.getFileDigest(fin); - - fin.close(); - - return digest; - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - private @Nullable byte[] getPendingDigest(Context context) { - try { - String encodedDigest = TextSecurePreferences.getUpdateApkDigest(context); - - if (encodedDigest == null) return null; - - return Hex.fromStringCondensed(encodedDigest); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - private static class UpdateDescriptor { - @JsonProperty - private int versionCode; - - @JsonProperty - private String versionName; - - @JsonProperty - private String url; - - @JsonProperty - private String sha256sum; - - - public int getVersionCode() { - return versionCode; - } - - public String getVersionName() { - return versionName; - } - - public String getUrl() { - return url; - } - - public @NonNull String toString() { - return "[" + versionCode + ", " + versionName + ", " + url + "]"; - } - - public String getDigest() { - return sha256sum; - } - } - - private static class DownloadStatus { - enum Status { - PENDING, - COMPLETE, - MISSING - } - - private final Status status; - private final long downloadId; - - DownloadStatus(Status status, long downloadId) { - this.status = status; - this.downloadId = downloadId; - } - - public Status getStatus() { - return status; - } - - public long getDownloadId() { - return downloadId; - } - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull UpdateApkJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new UpdateApkJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt new file mode 100644 index 000000000..36aca1c91 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.notifications + +interface PushManager { + fun register(force: Boolean) + fun unregister(token: String) +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index b7a92e3b7..6e4f8d104 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -4,6 +4,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage +import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters @@ -11,14 +12,18 @@ import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log +import javax.inject.Inject +@AndroidEntryPoint class PushNotificationService : FirebaseMessagingService() { + @Inject lateinit var pushManager: PushManager + override fun onNewToken(token: String) { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") - val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - PushNotificationManager.register(token, userPublicKey, this, false) + TextSecurePreferences.getLocalNumber(this) ?: return + pushManager.register(true) } override fun onMessageReceived(message: RemoteMessage) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index 9cf9c3d04..e440ba21d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -160,7 +160,7 @@ class PNModeActivity : BaseActionBarActivity() { TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == binding.fcmOptionView)) val application = ApplicationContext.getInstance(this) application.startPollingIfNeeded() - application.registerForFCMIfNeeded(true) + application.registerForPnIfNeeded(true) val intent = Intent(this, HomeActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK show(intent) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java index 9ae78fc5c..efc20aca4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java @@ -39,7 +39,7 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme this.findPreference(fcmKey) .setOnPreferenceChangeListener((preference, newValue) -> { TextSecurePreferences.setIsUsingFCM(getContext(), (boolean) newValue); - ApplicationContext.getInstance(getContext()).registerForFCMIfNeeded(true); + ApplicationContext.getInstance(getContext()).registerForPnIfNeeded(true); return true; }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java deleted file mode 100644 index 187713df9..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.thoughtcrime.securesms.service; - - -import android.content.Context; -import android.content.Intent; -import org.session.libsignal.utilities.Log; - -import org.thoughtcrime.securesms.ApplicationContext; -import network.loki.messenger.BuildConfig; -import org.thoughtcrime.securesms.jobs.UpdateApkJob; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.util.concurrent.TimeUnit; - -public class UpdateApkRefreshListener extends PersistentAlarmManagerListener { - - private static final String TAG = UpdateApkRefreshListener.class.getSimpleName(); - - private static final long INTERVAL = TimeUnit.HOURS.toMillis(6); - - @Override - protected long getNextScheduledExecutionTime(Context context) { - return TextSecurePreferences.getUpdateApkRefreshTime(context); - } - - @Override - protected long onAlarm(Context context, long scheduledTime) { - Log.i(TAG, "onAlarm..."); - - if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) { - Log.i(TAG, "Queueing APK update job..."); - ApplicationContext.getInstance(context) - .getJobManager() - .add(new UpdateApkJob()); - } - - long newTime = System.currentTimeMillis() + INTERVAL; - TextSecurePreferences.setUpdateApkRefreshTime(context, newTime); - - return newTime; - } - - public static void schedule(Context context) { - new UpdateApkRefreshListener().onReceive(context, new Intent()); - } - -} diff --git a/app/src/play/AndroidManifest.xml b/app/src/play/AndroidManifest.xml new file mode 100644 index 000000000..f5b54fd47 --- /dev/null +++ b/app/src/play/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt similarity index 90% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt index 87a9efc0d..de3d14d43 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmUtils.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt @@ -13,7 +13,5 @@ fun getFcmInstanceId(body: (Task)->Unit): Job = MainScope().la // wait for task to complete while we are active } if (!isActive) return@launch // don't 'complete' task if we were canceled - withContext(Dispatchers.Main) { - body(task) - } + body(task) } \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt new file mode 100644 index 000000000..0865c25c9 --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -0,0 +1,142 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import com.goterl.lazysodium.LazySodiumAndroid +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.interfaces.Sign +import com.goterl.lazysodium.utils.Key +import com.goterl.lazysodium.utils.KeyPair +import kotlinx.coroutines.Job +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.functional.map +import okhttp3.MediaType +import okhttp3.Request +import okhttp3.RequestBody +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest +import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.snode.OnionRequestAPI +import org.session.libsession.snode.SnodeAPI +import org.session.libsession.snode.Version +import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber +import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.Namespace +import org.session.libsignal.utilities.retryIfNeeded +import org.thoughtcrime.securesms.crypto.IdentityKeyUtil +import org.thoughtcrime.securesms.crypto.KeyPairUtilities + +class FirebasePushManager(private val context: Context, private val prefs: TextSecurePreferences): PushManager { + + companion object { + private const val maxRetryCount = 4 + private const val tokenExpirationInterval = 12 * 60 * 60 * 1000 + } + + private var firebaseInstanceIdJob: Job? = null + private val sodium = LazySodiumAndroid(SodiumAndroid()) + + private fun getOrCreateNotificationKey(): Key { + if (IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY) == null) { + // generate the key and store it + val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) + IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) + } + return Key.fromHexString( + IdentityKeyUtil.retrieve( + context, + IdentityKeyUtil.NOTIFICATION_KEY + ) + ) + } + + override fun register(force: Boolean) { + val currentInstanceIdJob = firebaseInstanceIdJob + if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return + + if (force && currentInstanceIdJob != null) { + currentInstanceIdJob.cancel(null) + } + + firebaseInstanceIdJob = getFcmInstanceId { task -> + // context in here is Dispatchers.IO + if (!task.isSuccessful) { + Log.w( + "Loki", + "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception + ) + return@getFcmInstanceId + } + val token: String = task.result?.token ?: return@getFcmInstanceId + val userPublicKey = getLocalNumber(context) ?: return@getFcmInstanceId + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@getFcmInstanceId + if (prefs.isUsingFCM()) { + register(token, userPublicKey, userEdKey, force) + } else { + unregister(token) + } + } + } + + override fun unregister(token: String) { + TODO("Not yet implemented") + } + + fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { + val oldToken = TextSecurePreferences.getFCMToken(context) + val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) + if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } + + val pnKey = getOrCreateNotificationKey() + + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes) + val requestParameters = SubscriptionRequest ( + pubkey = publicKey, + session_ed25519 = userEd25519Key.publicKey.asHexString, + namespaces = listOf(Namespace.DEFAULT), + data = true, // only permit data subscription for now (?) + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + enc_key = pnKey.asHexString, + ) + + val url = "${PushNotificationAPI.server}/subscribe" + val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) + val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { + getResponseBody(request.build()).map { response -> + if (response.isSuccess()) { + TextSecurePreferences.setIsUsingFCM(context, true) + TextSecurePreferences.setFCMToken(context, token) + TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) + } else { + val (_, message) = response.errorInfo() + Log.d("Loki", "Couldn't register for FCM due to error: $message.") + } + }.fail { exception -> + Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") + } + } + } + + private fun getResponseBody(request: Request): Promise { + return OnionRequestAPI.sendOnionRequest(request, + PushNotificationAPI.server, + PushNotificationAPI.serverPublicKey, Version.V4).map { response -> + Json.decodeFromStream(response.body!!.inputStream()) + } + } + + +} \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt new file mode 100644 index 000000000..983973d96 --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import org.session.libsession.utilities.TextSecurePreferences +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object FirebasePushModule { + @Provides + @Singleton + fun provideFirebasePushManager( + @ApplicationContext context: Context, + prefs: TextSecurePreferences, + ): PushManager = FirebasePushManager(context, prefs) +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index e37b5b0b6..78b6cd4a1 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -1,7 +1,15 @@ package org.session.libsession.messaging.sending_receiving.notifications +import com.goterl.lazysodium.utils.Key +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * N.B. all of these variable names will be named the same as the actual JSON utf-8 request/responses expected from the server. + * Changing the variable names will break how data is serialized/deserialized. + * If it's less than ideally named we can use [SerialName] + */ + @Serializable data class SubscriptionRequest( /** the 33-byte account being subscribed to; typically a session ID */ @@ -9,7 +17,7 @@ data class SubscriptionRequest( /** when the pubkey starts with 05 (i.e. a session ID) this is the ed25519 32-byte pubkey associated with the session ID */ val session_ed25519: String?, /** 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth (new closed groups) */ - val subkey_tag: String?, + val subkey_tag: String? = null, /** array of integer namespaces to subscribe to, **must be sorted in ascending order** */ val namespaces: List, /** if provided and true then notifications will include the body of the message (as long as it isn't too large) */ @@ -46,7 +54,18 @@ data class SubscriptionResponse( const val GENERIC_ERROR = 4 } fun isSuccess() = success == true && error == null - fun errorInfo() = if (success == false && error != null) { - true to message - } else false to null + fun errorInfo() = if (success != true && error != null) { + error to message + } else null to null +} + +@Serializable +data class PushNotificationServerObject( + val enc_payload: String, + val spns: Int, +) { + fun decryptPayload(key: Key): Any { + + TODO() + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index e49375bab..cf037b019 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -9,15 +9,17 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.retryIfNeeded @SuppressLint("StaticFieldLeak") object PushNotificationAPI { val context = MessagingModuleConfiguration.shared.context val server = "https://push.getsession.org" val serverPublicKey: String = TODO("get the new server pubkey here") + private val legacyServer = "https://live.apns.getsession.org" + private val legacyServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private val maxRetryCount = 4 private val tokenExpirationInterval = 12 * 60 * 60 * 1000 @@ -94,7 +96,7 @@ object PushNotificationAPI { val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2).map { response -> val code = response.info["code"] as? Int if (code == null || code == 0) { Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"] as? String ?: "null"}.") From 7762d534bbc8b54a13f3bdb0867e3f6cce73ba82 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Thu, 20 Apr 2023 17:25:03 +1000 Subject: [PATCH 029/244] feat: add no op push manager for de-googled --- .../linkpreview/LinkPreviewRepository.java | 6 +++--- .../notifications/PushNotificationService.kt | 7 +++++++ .../securesms/notifications/NoOpPushManager.kt | 14 ++++++++++++++ .../securesms/notifications/NoOpPushModule.kt | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) rename app/src/{main/java => play/kotlin}/org/thoughtcrime/securesms/notifications/PushNotificationService.kt (88%) create mode 100644 app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt create mode 100644 app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 697f6718c..6dcc928c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.linkpreview; +import static org.session.libsession.utilities.Util.readFully; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -8,8 +10,6 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.android.gms.common.util.IOUtils; - import org.session.libsession.messaging.sending_receiving.attachments.Attachment; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; import org.session.libsession.messaging.sending_receiving.attachments.UriAttachment; @@ -148,7 +148,7 @@ public class LinkPreviewRepository { InputStream bodyStream = response.body().byteStream(); controller.setStream(bodyStream); - byte[] data = IOUtils.readInputStreamFully(bodyStream); + byte[] data = readFully(bodyStream); Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); Optional thumbnail = bitmapToAttachment(bitmap, Bitmap.CompressFormat.JPEG, MediaTypes.IMAGE_JPEG); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt similarity index 88% rename from app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt rename to app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 6e4f8d104..6bf18131e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -28,6 +28,13 @@ class PushNotificationService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { Log.d("Loki", "Received a push notification.") + if (message.data.containsKey("spns")) { + // assume this is the new push notification content + // deal with the enc payload (probably decrypting through the PushManager? + Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}") + pushManager.decrypt(message.data) + return + } val base64EncodedData = message.data?.get("ENCRYPTED_DATA") val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt new file mode 100644 index 000000000..edbf7d710 --- /dev/null +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt @@ -0,0 +1,14 @@ +package org.thoughtcrime.securesms.notifications + +import org.session.libsignal.utilities.Log + +class NoOpPushManager: PushManager { + + override fun register(force: Boolean) { + Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") + } + + override fun unregister(token: String) { + Log.d("NoOpPushManager", "Push notifications not supported, not unregistering for push notifications") + } +} \ No newline at end of file diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt new file mode 100644 index 000000000..1c8f2f936 --- /dev/null +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushModule.kt @@ -0,0 +1,15 @@ +package org.thoughtcrime.securesms.notifications + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class NoOpPushModule { + @Provides + @Singleton + fun provideNoOpManager(): PushManager = NoOpPushManager() +} \ No newline at end of file From d2e80c3157c368d1cd9c7bc330a6bb21f295ec6b Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Fri, 28 Oct 2022 15:17:18 +1100 Subject: [PATCH 030/244] feat: re-add bencode utility and fix tests to use bytearray instead of assuming utf-8 encoding for strings --- .../notifications/FirebasePushManager.kt | 36 ++++ .../notifications/FirebasePushModule.kt | 10 +- .../notifications/PushNotificationService.kt | 4 +- .../libsession/utilities/bencode/Bencode.kt | 169 ++++++++++++++++++ .../libsession/utilities/BencoderTest.kt | 107 +++++++++++ 5 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt create mode 100644 libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 0865c25c9..39705c75d 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -19,11 +19,16 @@ import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber +import org.session.libsession.utilities.bencode.Bencode +import org.session.libsession.utilities.bencode.BencodeDict +import org.session.libsession.utilities.bencode.BencodeList +import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace @@ -55,6 +60,37 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS ) } + fun decrypt(encPayload: ByteArray) { + val encKey = getOrCreateNotificationKey() + val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() + val decrypted = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) + ?: return Log.e("Loki", "Failed to decrypt push notification") + val bencoded = Bencode.Decoder(decrypted) + val expectedList = (bencoded.decode() as? BencodeList) + ?: return Log.e("Loki", "Failed to decode bencoded list from payload") + + val (metadata, content) = expectedList.values + val metadataDict = (metadata as? BencodeDict)?.values + ?: return Log.e("Loki", "Failed to decode metadata dict") + + val push = """ + Push metadata received was: + @: ${metadataDict["@"]} + #: ${metadataDict["#"]} + n: ${metadataDict["n"]} + l: ${metadataDict["l"]} + B: ${metadataDict["B"]} + """.trimIndent() + + Log.d("Loki", "push") + + val contentBytes = (content as? BencodeString)?.value + ?: return Log.e("Loki", "Failed to decode content string") + + // TODO: something with contentBytes + } + override fun register(force: Boolean) { val currentInstanceIdJob = firebaseInstanceIdJob if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt index 983973d96..30045d6c2 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,5 +18,12 @@ object FirebasePushModule { fun provideFirebasePushManager( @ApplicationContext context: Context, prefs: TextSecurePreferences, - ): PushManager = FirebasePushManager(context, prefs) + ) = FirebasePushManager(context, prefs) +} + +@Module +@InstallIn(SingletonComponent::class) +abstract class FirebaseBindingModule { + @Binds + abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager } \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 6bf18131e..4c268ce18 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -17,7 +17,7 @@ import javax.inject.Inject @AndroidEntryPoint class PushNotificationService : FirebaseMessagingService() { - @Inject lateinit var pushManager: PushManager + @Inject lateinit var pushManager: FirebasePushManager override fun onNewToken(token: String) { super.onNewToken(token) @@ -32,7 +32,7 @@ class PushNotificationService : FirebaseMessagingService() { // assume this is the new push notification content // deal with the enc payload (probably decrypting through the PushManager? Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}") - pushManager.decrypt(message.data) + pushManager.decrypt(Base64.decode(message.data["enc_payload"])) return } val base64EncodedData = message.data?.get("ENCRYPTED_DATA") diff --git a/libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt b/libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt new file mode 100644 index 000000000..427e80691 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/utilities/bencode/Bencode.kt @@ -0,0 +1,169 @@ +package org.session.libsession.utilities.bencode + +import java.util.LinkedList + +object Bencode { + class Decoder(source: ByteArray) { + + private val iterator = LinkedList().apply { + addAll(source.asIterable()) + } + + /** + * Decode an element based on next marker assumed to be string/int/list/dict or return null + */ + fun decode(): BencodeElement? { + val result = when (iterator.peek()?.toInt()?.toChar()) { + in NUMBERS -> decodeString() + INT_INDICATOR -> decodeInt() + LIST_INDICATOR -> decodeList() + DICT_INDICATOR -> decodeDict() + else -> { + null + } + } + return result + } + + /** + * Decode a string element from iterator assumed to have structure `{length}:{data}` + */ + private fun decodeString(): BencodeString? { + val lengthStrings = buildString { + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != SEPARATOR) { + append(iterator.pop().toInt().toChar()) + } + } + iterator.pop() // drop `:` + val length = lengthStrings.toIntOrNull(10) ?: return null + val remaining = (0 until length).map { iterator.pop() }.toByteArray() + return BencodeString(remaining) + } + + /** + * Decode an int element from iterator assumed to have structure `i{int}e` + */ + private fun decodeInt(): BencodeElement? { + iterator.pop() // drop `i` + val intString = buildString { + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) { + append(iterator.pop().toInt().toChar()) + } + } + val asInt = intString.toIntOrNull(10) ?: return null + iterator.pop() // drop `e` + return BencodeInteger(asInt) + } + + /** + * Decode a list element from iterator assumed to have structure `l{data}e` + */ + private fun decodeList(): BencodeElement { + iterator.pop() // drop `l` + val listElements = mutableListOf() + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) { + decode()?.let { nextElement -> + listElements += nextElement + } + } + iterator.pop() // drop `e` + return BencodeList(listElements) + } + + /** + * Decode a dict element from iterator assumed to have structure `d{data}e` + */ + private fun decodeDict(): BencodeElement? { + iterator.pop() // drop `d` + val dictElements = mutableMapOf() + while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) { + val key = decodeString() ?: return null + val value = decode() ?: return null + dictElements += key.value.decodeToString() to value + } + iterator.pop() // drop `e` + return BencodeDict(dictElements) + } + + companion object { + private val NUMBERS = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') + private const val INT_INDICATOR = 'i' + private const val LIST_INDICATOR = 'l' + private const val DICT_INDICATOR = 'd' + private const val END_INDICATOR = 'e' + private const val SEPARATOR = ':' + } + + } + +} + +sealed class BencodeElement { + abstract fun encode(): ByteArray +} + +fun String.bencode() = BencodeString(this.encodeToByteArray()) +fun Int.bencode() = BencodeInteger(this) + +data class BencodeString(val value: ByteArray): BencodeElement() { + override fun encode(): ByteArray = buildString { + append(value.size.toString()) + append(':') + }.toByteArray() + value + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BencodeString + + if (!value.contentEquals(other.value)) return false + + return true + } + + override fun hashCode(): Int { + return value.contentHashCode() + } +} +data class BencodeInteger(val value: Int): BencodeElement() { + override fun encode(): ByteArray = buildString { + append('i') + append(value.toString()) + append('e') + }.toByteArray() +} +data class BencodeList(val values: List): BencodeElement() { + + constructor(vararg values: BencodeElement) : this(values.toList()) + + override fun encode(): ByteArray = "l".toByteArray() + + values.fold(byteArrayOf()) { array, element -> array + element.encode() } + + "e".toByteArray() +} +data class BencodeDict(val values: Map): BencodeElement() { + + constructor(vararg values: Pair) : this(values.toMap()) + + override fun encode(): ByteArray = "d".toByteArray() + + values.entries.fold(byteArrayOf()) { array, (key, value) -> + array + key.bencode().encode() + value.encode() + } + "e".toByteArray() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as BencodeDict + + if (values != other.values) return false + + return true + } + + override fun hashCode(): Int { + return values.hashCode() + } + + +} \ No newline at end of file diff --git a/libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt b/libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt new file mode 100644 index 000000000..d96fa6658 --- /dev/null +++ b/libsession/src/test/java/org/session/libsession/utilities/BencoderTest.kt @@ -0,0 +1,107 @@ +package org.session.libsession.utilities + +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Test +import org.session.libsession.utilities.bencode.Bencode +import org.session.libsession.utilities.bencode.BencodeDict +import org.session.libsession.utilities.bencode.BencodeInteger +import org.session.libsession.utilities.bencode.BencodeList +import org.session.libsession.utilities.bencode.bencode + +class BencoderTest { + + @Test + fun `it should decode a basic string`() { + val basicString = "5:howdy".toByteArray() + val bencoder = Bencode.Decoder(basicString) + val result = bencoder.decode() + assertEquals("howdy".bencode(), result) + } + + @Test + fun `it should decode a basic integer`() { + val basicInteger = "i3e".toByteArray() + val bencoder = Bencode.Decoder(basicInteger) + val result = bencoder.decode() + assertEquals(BencodeInteger(3), result) + } + + @Test + fun `it should decode a list of integers`() { + val basicIntList = "li1ei2ee".toByteArray() + val bencoder = Bencode.Decoder(basicIntList) + val result = bencoder.decode() + assertEquals( + BencodeList( + 1.bencode(), + 2.bencode() + ), + result + ) + } + + @Test + fun `it should decode a basic dict`() { + val basicDict = "d4:spaml1:a1:bee".toByteArray() + val bencoder = Bencode.Decoder(basicDict) + val result = bencoder.decode() + assertEquals( + BencodeDict( + "spam" to BencodeList( + "a".bencode(), + "b".bencode() + ) + ), + result + ) + } + + @Test + fun `it should encode a basic string`() { + val basicString = "5:howdy".toByteArray() + val element = "howdy".bencode() + assertArrayEquals(basicString, element.encode()) + } + + @Test + fun `it should encode a basic int`() { + val basicInt = "i3e".toByteArray() + val element = 3.bencode() + assertArrayEquals(basicInt, element.encode()) + } + + @Test + fun `it should encode a basic list`() { + val basicList = "li1ei2ee".toByteArray() + val element = BencodeList(1.bencode(),2.bencode()) + assertArrayEquals(basicList, element.encode()) + } + + @Test + fun `it should encode a basic dict`() { + val basicDict = "d4:spaml1:a1:bee".toByteArray() + val element = BencodeDict( + "spam" to BencodeList( + "a".bencode(), + "b".bencode() + ) + ) + assertArrayEquals(basicDict, element.encode()) + } + + @Test + fun `it should encode a more complex real world case`() { + val source = "d15:lastReadMessaged66:031122334455667788990011223344556677889900112233445566778899001122i1234568790e66:051122334455667788990011223344556677889900112233445566778899001122i1234568790ee5:seqNoi1ee".toByteArray() + val result = Bencode.Decoder(source).decode() + val expected = BencodeDict( + "lastReadMessage" to BencodeDict( + "051122334455667788990011223344556677889900112233445566778899001122" to 1234568790.bencode(), + "031122334455667788990011223344556677889900112233445566778899001122" to 1234568790.bencode() + ), + "seqNo" to BencodeInteger(1) + ) + assertEquals(expected, result) + } + +} \ No newline at end of file From 51856138e3ae23606c7f5ad04756a6db89ee5390 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 24 Apr 2023 15:38:06 +1000 Subject: [PATCH 031/244] refactor on SignalAudioManager --- .../securesms/service/WebRtcCallService.kt | 1 + .../securesms/webrtc/CallManager.kt | 4 + .../webrtc/audio/SignalAudioManager.kt | 99 +++++++++---------- .../libsignal/utilities/ThreadUtils.kt | 9 +- 4 files changed, 53 insertions(+), 60 deletions(-) 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 b5e9b78a7..3ae3d30f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -799,6 +799,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { wantsToAnswerReceiver?.let { receiver -> LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver) } + callManager.shutDownAudioManager() powerButtonReceiver = null wiredHeadsetStateReceiver = null networkChangedReceiver = null 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 07e73bedd..d96de5eed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -93,6 +93,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va peerConnectionObservers.remove(listener) } + fun shutDownAudioManager() { + signalAudioManager.shutdown() + } + private val _audioEvents = MutableStateFlow(AudioEnabled(false)) val audioEvents = _audioEvents.asSharedFlow() private val _videoEvents = MutableStateFlow(VideoEnabled(false)) 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 67514c58b..6eb130443 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 @@ -9,6 +9,7 @@ import android.media.SoundPool import android.os.HandlerThread import network.loki.messenger.R import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.audio.SignalBluetoothManager.State as BState @@ -32,10 +33,10 @@ class SignalAudioManager(private val context: Context, private val eventListener: EventListener?, private val androidAudioManager: AudioManagerCompat) { - private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio").apply { start() } - private var handler: SignalAudioHandler? = null + private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio", ThreadUtils.PRIORITY_IMPORTANT_BACKGROUND_THREAD).apply { start() } + private var handler: SignalAudioHandler? = SignalAudioHandler(commandAndControlThread!!.looper) - private var signalBluetoothManager: SignalBluetoothManager? = null + private var signalBluetoothManager: SignalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler!!) private var state: State = State.UNINITIALIZED @@ -62,12 +63,9 @@ class SignalAudioManager(private val context: Context, private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null fun handleCommand(command: AudioManagerCommand) { - if (command == AudioManagerCommand.Initialize) { - initialize() - return - } handler?.post { when (command) { + is AudioManagerCommand.Initialize -> initialize() is AudioManagerCommand.UpdateAudioDeviceState -> updateAudioDeviceState() is AudioManagerCommand.Start -> start() is AudioManagerCommand.Stop -> stop(command.playDisconnect) @@ -84,34 +82,37 @@ class SignalAudioManager(private val context: Context, Log.i(TAG, "Initializing audio manager state: $state") if (state == State.UNINITIALIZED) { - commandAndControlThread = HandlerThread("call-audio").apply { start() } - handler = SignalAudioHandler(commandAndControlThread!!.looper) + savedAudioMode = androidAudioManager.mode + savedIsSpeakerPhoneOn = androidAudioManager.isSpeakerphoneOn + savedIsMicrophoneMute = androidAudioManager.isMicrophoneMute + hasWiredHeadset = androidAudioManager.isWiredHeadsetOn - signalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler!!) + androidAudioManager.requestCallAudioFocus() - handler!!.post { + setMicrophoneMute(false) - savedAudioMode = androidAudioManager.mode - savedIsSpeakerPhoneOn = androidAudioManager.isSpeakerphoneOn - savedIsMicrophoneMute = androidAudioManager.isMicrophoneMute - hasWiredHeadset = androidAudioManager.isWiredHeadsetOn + audioDevices.clear() - androidAudioManager.requestCallAudioFocus() + signalBluetoothManager.start() - setMicrophoneMute(false) + updateAudioDeviceState() - audioDevices.clear() + wiredHeadsetReceiver = WiredHeadsetReceiver() + context.registerReceiver(wiredHeadsetReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG)) - signalBluetoothManager!!.start() + state = State.PREINITIALIZED - updateAudioDeviceState() + Log.d(TAG, "Initialized") + } + } - wiredHeadsetReceiver = WiredHeadsetReceiver() - context.registerReceiver(wiredHeadsetReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG)) - - state = State.PREINITIALIZED - - Log.d(TAG, "Initialized") + fun shutdown() { + handler!!.post { + stop(false) + if (commandAndControlThread != null) { + Log.i(TAG, "Shutting down command and control") + commandAndControlThread?.quitSafely() + commandAndControlThread = null } } } @@ -138,23 +139,11 @@ class SignalAudioManager(private val context: Context, private fun stop(playDisconnect: Boolean) { Log.d(TAG, "Stopping. state: $state") - if (state == State.UNINITIALIZED) { - Log.i(TAG, "Trying to stop AudioManager in incorrect state: $state") - return - } - handler?.post { - incomingRinger.stop() - outgoingRinger.stop() - stop(false) - if (commandAndControlThread != null) { - Log.i(TAG, "Shutting down command and control") - commandAndControlThread?.quitSafely() - commandAndControlThread = null - } - } + incomingRinger.stop() + outgoingRinger.stop() - if (playDisconnect) { + if (playDisconnect && state != State.UNINITIALIZED) { val volume: Float = androidAudioManager.ringVolumeWithMinimum() soundPool.play(disconnectedSoundId, volume, volume, 0, 0, 1.0f) } @@ -170,7 +159,7 @@ class SignalAudioManager(private val context: Context, } wiredHeadsetReceiver = null - signalBluetoothManager?.stop() + signalBluetoothManager.stop() setSpeakerphoneOn(savedIsSpeakerPhoneOn) setMicrophoneMute(savedIsMicrophoneMute) @@ -189,19 +178,19 @@ class SignalAudioManager(private val context: Context, TAG, "updateAudioDeviceState(): " + "wired: $hasWiredHeadset " + - "bt: ${signalBluetoothManager!!.state} " + + "bt: ${signalBluetoothManager.state} " + "available: $audioDevices " + "selected: $selectedAudioDevice " + "userSelected: $userSelectedAudioDevice" ) - if (signalBluetoothManager!!.state.shouldUpdate()) { - signalBluetoothManager!!.updateDevice() + if (signalBluetoothManager.state.shouldUpdate()) { + signalBluetoothManager.updateDevice() } val newAudioDevices = mutableSetOf(AudioDevice.SPEAKER_PHONE) - if (signalBluetoothManager!!.state.hasDevice()) { + if (signalBluetoothManager.state.hasDevice()) { newAudioDevices += AudioDevice.BLUETOOTH } @@ -217,7 +206,7 @@ class SignalAudioManager(private val context: Context, var audioDeviceSetUpdated = audioDevices != newAudioDevices audioDevices = newAudioDevices - if (signalBluetoothManager!!.state == BState.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { + if (signalBluetoothManager.state == BState.UNAVAILABLE && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { userSelectedAudioDevice = AudioDevice.NONE } @@ -230,7 +219,7 @@ class SignalAudioManager(private val context: Context, userSelectedAudioDevice = AudioDevice.NONE } - val btState = signalBluetoothManager!!.state + val btState = signalBluetoothManager.state val needBluetoothAudioStart = btState == BState.AVAILABLE && (userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth) @@ -238,27 +227,27 @@ class SignalAudioManager(private val context: Context, (userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH) if (btState.hasDevice()) { - Log.i(TAG, "Need bluetooth audio: state: ${signalBluetoothManager!!.state} start: $needBluetoothAudioStart stop: $needBluetoothAudioStop") + Log.i(TAG, "Need bluetooth audio: state: ${signalBluetoothManager.state} start: $needBluetoothAudioStart stop: $needBluetoothAudioStop") } if (needBluetoothAudioStop) { - signalBluetoothManager!!.stopScoAudio() - signalBluetoothManager!!.updateDevice() + signalBluetoothManager.stopScoAudio() + signalBluetoothManager.updateDevice() } - if (!autoSwitchToBluetooth && signalBluetoothManager!!.state == BState.UNAVAILABLE) { + if (!autoSwitchToBluetooth && signalBluetoothManager.state == BState.UNAVAILABLE) { autoSwitchToBluetooth = true } - if (needBluetoothAudioStart && !needBluetoothAudioStop) { - if (!signalBluetoothManager!!.startScoAudio()) { + if (!needBluetoothAudioStop && needBluetoothAudioStart) { + if (!signalBluetoothManager.startScoAudio()) { Log.e(TAG,"Failed to start sco audio") audioDevices.remove(AudioDevice.BLUETOOTH) audioDeviceSetUpdated = true } } - if (autoSwitchToBluetooth && signalBluetoothManager!!.state == BState.CONNECTED) { + if (autoSwitchToBluetooth && signalBluetoothManager.state == BState.CONNECTED) { userSelectedAudioDevice = AudioDevice.BLUETOOTH autoSwitchToBluetooth = false } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt b/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt index 0700d01f6..ac81564bd 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/ThreadUtils.kt @@ -1,13 +1,12 @@ package org.session.libsignal.utilities -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.ThreadPoolExecutor -import java.util.concurrent.TimeUnit +import android.os.Process +import java.util.concurrent.* object ThreadUtils { + const val PRIORITY_IMPORTANT_BACKGROUND_THREAD = Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + val executorPool: ExecutorService = Executors.newCachedThreadPool() @JvmStatic From c7bbdb778b93fb97bad93dad020240055c6507c8 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 24 Apr 2023 15:50:53 +1000 Subject: [PATCH 032/244] minor refactor --- .../securesms/webrtc/audio/SignalAudioHandler.kt | 2 +- .../securesms/webrtc/audio/SignalAudioManager.kt | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt index 89eba2a3a..7ca44f46d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioHandler.kt @@ -15,7 +15,7 @@ class SignalAudioHandler(looper: Looper) : Handler(looper) { } } - fun isOnHandler(): Boolean { + private fun isOnHandler(): Boolean { return Looper.myLooper() == looper } } 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 6eb130443..229cbd13d 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 @@ -34,9 +34,9 @@ class SignalAudioManager(private val context: Context, private val androidAudioManager: AudioManagerCompat) { private var commandAndControlThread: HandlerThread? = HandlerThread("call-audio", ThreadUtils.PRIORITY_IMPORTANT_BACKGROUND_THREAD).apply { start() } - private var handler: SignalAudioHandler? = SignalAudioHandler(commandAndControlThread!!.looper) + private var handler: SignalAudioHandler = SignalAudioHandler(commandAndControlThread!!.looper) - private var signalBluetoothManager: SignalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler!!) + private var signalBluetoothManager: SignalBluetoothManager = SignalBluetoothManager(context, this, androidAudioManager, handler) private var state: State = State.UNINITIALIZED @@ -63,7 +63,7 @@ class SignalAudioManager(private val context: Context, private var wiredHeadsetReceiver: WiredHeadsetReceiver? = null fun handleCommand(command: AudioManagerCommand) { - handler?.post { + handler.post { when (command) { is AudioManagerCommand.Initialize -> initialize() is AudioManagerCommand.UpdateAudioDeviceState -> updateAudioDeviceState() @@ -107,7 +107,7 @@ class SignalAudioManager(private val context: Context, } fun shutdown() { - handler!!.post { + handler.post { stop(false) if (commandAndControlThread != null) { Log.i(TAG, "Shutting down command and control") @@ -172,7 +172,7 @@ class SignalAudioManager(private val context: Context, } private fun updateAudioDeviceState() { - handler!!.assertHandlerThread() + handler.assertHandlerThread() Log.i( TAG, @@ -362,7 +362,7 @@ class SignalAudioManager(private val context: Context, val pluggedIn = intent.getIntExtra("state", 0) == 1 val hasMic = intent.getIntExtra("microphone", 0) == 1 - handler?.post { onWiredHeadsetChange(pluggedIn, hasMic) } + handler.post { onWiredHeadsetChange(pluggedIn, hasMic) } } } From 7d33177e065099d1709045b97d820c624a92707b Mon Sep 17 00:00:00 2001 From: Andrew Gallasch Date: Wed, 26 Apr 2023 14:18:34 +0930 Subject: [PATCH 033/244] Fix status and nav bar colors (#1165) --- .../org/thoughtcrime/securesms/BaseActionBarActivity.java | 7 +++++++ app/src/main/res/values-v27/colors.xml | 5 +++++ app/src/main/res/values/colors.xml | 3 +++ app/src/main/res/values/themes.xml | 8 ++++---- 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/values-v27/colors.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java index 7d82c760c..51f66ec32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BaseActionBarActivity.java @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms; +import static android.os.Build.VERSION.SDK_INT; import static org.session.libsession.utilities.TextSecurePreferences.SELECTED_ACCENT_COLOR; import android.app.ActivityManager; @@ -18,6 +19,7 @@ import androidx.appcompat.app.AppCompatActivity; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityHelper; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; +import org.thoughtcrime.securesms.conversation.v2.WindowUtil; import org.thoughtcrime.securesms.util.ActivityUtilitiesKt; import org.thoughtcrime.securesms.util.ThemeState; import org.thoughtcrime.securesms.util.UiModeUtilities; @@ -92,6 +94,11 @@ public abstract class BaseActionBarActivity extends AppCompatActivity { if (!currentThemeState.equals(ActivityUtilitiesKt.themeState(getPreferences()))) { recreate(); } + + // apply lightStatusBar manually as API 26 does not update properly via applyTheme + // https://issuetracker.google.com/issues/65883460?pli=1 + if (SDK_INT >= 26 && SDK_INT <= 27) WindowUtil.setLightStatusBarFromTheme(this); + if (SDK_INT == 27) WindowUtil.setLightNavigationBarFromTheme(this); } @Override diff --git a/app/src/main/res/values-v27/colors.xml b/app/src/main/res/values-v27/colors.xml new file mode 100644 index 000000000..5c5e9494f --- /dev/null +++ b/app/src/main/res/values-v27/colors.xml @@ -0,0 +1,5 @@ + + + @color/classic_light_6 + @color/ocean_light_7 + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d3b9d9b25..196e587cd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -60,6 +60,9 @@ #40ffffff #aaffffff + @color/compose_view_background + @color/navigation_bar + @color/navigation_bar @color/gray65 #22000000 diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2874d4297..43d06ee7a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -326,7 +326,7 @@ ?android:textColorPrimary @color/gray27 ?colorPrimary - @color/compose_view_background + @color/navigation_bar @color/classic_dark_1 @style/Classic.Dark.BottomSheet ?android:textColorPrimary @@ -404,7 +404,7 @@ ?android:textColorPrimary @color/gray27 ?colorPrimary - ?colorPrimary + @color/classic_light_navigation_bar @color/classic_light_6 @color/classic_light_5 @color/classic_light_3 @@ -490,7 +490,7 @@ @color/ocean_dark_5 ?colorPrimary @color/default_background_start - @color/compose_view_background + @color/navigation_bar ?colorPrimary ?colorPrimaryDark @color/ocean_dark_3 @@ -570,7 +570,7 @@ @color/ocean_light_2 ?android:textColorPrimary @color/ocean_light_6 - @color/ocean_light_7 + @color/ocean_light_navigation_bar ?colorPrimary @color/default_background_start @color/ocean_light_7 From 716fc1f4fa840efee4e4dfb3c6596273c083aa62 Mon Sep 17 00:00:00 2001 From: Andrew Gallasch Date: Wed, 26 Apr 2023 14:24:54 +0930 Subject: [PATCH 034/244] Add SafeViewPager to fix touch exception in MediaPreviewActivity (#1166) --- .../securesms/components/SafeViewPager.kt | 30 +++++++++++++++++++ .../res/layout/media_preview_activity.xml | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt b/app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt new file mode 100644 index 000000000..674847873 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/SafeViewPager.kt @@ -0,0 +1,30 @@ +package org.thoughtcrime.securesms.components + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.viewpager.widget.ViewPager + +/** + * An extension of ViewPager to swallow erroneous multi-touch exceptions. + * + * @see https://stackoverflow.com/questions/6919292/pointerindex-out-of-range-android-multitouch + */ +class SafeViewPager @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : ViewPager(context, attrs) { + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean = try { + super.onTouchEvent(event) + } catch (e: IllegalArgumentException) { + false + } + + override fun onInterceptTouchEvent(event: MotionEvent?): Boolean = try { + super.onInterceptTouchEvent(event) + } catch (e: IllegalArgumentException) { + false + } +} diff --git a/app/src/main/res/layout/media_preview_activity.xml b/app/src/main/res/layout/media_preview_activity.xml index 2f6aaff45..78327b6cc 100644 --- a/app/src/main/res/layout/media_preview_activity.xml +++ b/app/src/main/res/layout/media_preview_activity.xml @@ -22,7 +22,7 @@ - Date: Wed, 26 Apr 2023 14:26:00 +0930 Subject: [PATCH 035/244] Remove unnecessary api version checks in WindowUtil (#1163) --- .../securesms/conversation/v2/WindowUtil.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java index 4bff4e76a..6083bb267 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/WindowUtil.java @@ -38,14 +38,10 @@ public final class WindowUtil { } public static void setNavigationBarColor(@NonNull Window window, @ColorInt int color) { - if (Build.VERSION.SDK_INT < 21) return; - window.setNavigationBarColor(color); } public static void setLightStatusBarFromTheme(@NonNull Activity activity) { - if (Build.VERSION.SDK_INT < 23) return; - final boolean isLightStatusBar = ThemeUtil.getThemedBoolean(activity, android.R.attr.windowLightStatusBar); if (isLightStatusBar) setLightStatusBar(activity.getWindow()); @@ -53,20 +49,14 @@ public final class WindowUtil { } public static void clearLightStatusBar(@NonNull Window window) { - if (Build.VERSION.SDK_INT < 23) return; - clearSystemUiFlags(window, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } public static void setLightStatusBar(@NonNull Window window) { - if (Build.VERSION.SDK_INT < 23) return; - setSystemUiFlags(window, View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } public static void setStatusBarColor(@NonNull Window window, @ColorInt int color) { - if (Build.VERSION.SDK_INT < 21) return; - window.setStatusBarColor(color); } From ffef98ecc93780e367ebe08cb8a621e51cd86cf8 Mon Sep 17 00:00:00 2001 From: Andrew Gallasch Date: Wed, 26 Apr 2023 14:29:32 +0930 Subject: [PATCH 036/244] Fix LandingActivity textColor (#1159) --- app/src/main/res/values/styles.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5f9ee0316..1cf88c0c0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -228,11 +228,13 @@ From 58e532f0ec0be0f34434113ce782b91958e803a9 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 26 Apr 2023 16:31:28 +0930 Subject: [PATCH 037/244] Swallow screenshot exceptions --- .../attachments/ScreenshotObserver.kt | 78 +++++++++++-------- 1 file changed, 45 insertions(+), 33 deletions(-) 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 84a9b6cfc..9c7ca21e8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt @@ -7,6 +7,10 @@ import android.os.Build import android.os.Handler import android.provider.MediaStore import androidx.annotation.RequiresApi +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer + +private const val TAG = "ScreenshotObserver" class ScreenshotObserver(private val context: Context, handler: Handler, private val screenshotTriggered: ()->Unit): ContentObserver(handler) { @@ -31,22 +35,26 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private val projection = arrayOf( MediaStore.Images.Media.DATA ) - context.contentResolver.query( - uri, - projection, - null, - null, - null - )?.use { cursor -> - val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA) - while (cursor.moveToNext()) { - val path = cursor.getString(dataColumn) - if (path.contains("screenshot", true)) { - if (cache.add(uri.hashCode())) { - screenshotTriggered() + try { + context.contentResolver.query( + uri, + projection, + null, + null, + null + )?.use { cursor -> + val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA) + while (cursor.moveToNext()) { + val path = cursor.getString(dataColumn) + if (path.contains("screenshot", true)) { + if (cache.add(uri.hashCode())) { + screenshotTriggered() + } } } } + } catch (e: SecurityException) { + Log.e(TAG, e) } } @@ -56,28 +64,32 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.RELATIVE_PATH ) - context.contentResolver.query( - uri, - projection, - null, - null, - null - )?.use { cursor -> - val relativePathColumn = - cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH) - val displayNameColumn = - cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) - while (cursor.moveToNext()) { - val name = cursor.getString(displayNameColumn) - val relativePath = cursor.getString(relativePathColumn) - if (name.contains("screenshot", true) or - relativePath.contains("screenshot", true)) { - if (cache.add(uri.hashCode())) { - screenshotTriggered() + + try { + context.contentResolver.query( + uri, + projection, + null, + null, + null + )?.use { cursor -> + val relativePathColumn = + cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH) + val displayNameColumn = + cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) + while (cursor.moveToNext()) { + val name = cursor.getString(displayNameColumn) + val relativePath = cursor.getString(relativePathColumn) + if (name.contains("screenshot", true) or + relativePath.contains("screenshot", true)) { + if (cache.add(uri.hashCode())) { + screenshotTriggered() + } } } } + } catch (e: IllegalStateException) { + Log.e(TAG, e) } } - -} \ No newline at end of file +} From 2630c97a4efe34ffad3d43b5bd72a39ee2357bc9 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 1 May 2023 17:01:57 +1000 Subject: [PATCH 038/244] add where clause for marking all jobs pending to reduce the time for writing to db --- .../org/thoughtcrime/securesms/database/JobDatabase.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 ef4746923..21719df20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -138,7 +138,10 @@ public class JobDatabase extends Database { ContentValues contentValues = new ContentValues(); contentValues.put(Jobs.IS_RUNNING, 0); - databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, null, null); + String query = Jobs.IS_RUNNING + " = ?"; + String[] args = new String[] { "1" }; + + databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args); } public synchronized void deleteJobs(@NonNull List jobIds) { From 70aab2994b51896472e08de0d892f64f00b946a4 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:46:48 +0930 Subject: [PATCH 039/244] Refactor MmsDatabase (#1169) --- .../securesms/database/MmsDatabase.kt | 155 +++++------------- .../thoughtcrime/securesms/util/CursorUtil.kt | 6 + 2 files changed, 43 insertions(+), 118 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt 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 90d3cad45..3a4b35ad1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.SlideDeck +import org.thoughtcrime.securesms.util.asSequence import java.io.Closeable import java.io.IOException import java.security.SecureRandom @@ -91,54 +92,22 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa return 0 } - fun addFailures(messageId: Long, failure: List) { - try { - addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java) - } catch (e: IOException) { - Log.w(TAG, e) + fun isOutgoingMessage(timestamp: Long): Boolean = + databaseHelper.writableDatabase.query( + TABLE_NAME, + arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS), + DATE_SENT + " = ?", + arrayOf(timestamp.toString()), + null, + null, + null, + null + ).use { cursor -> + cursor.asSequence() + .map { cursor.getColumnIndexOrThrow(MESSAGE_BOX) } + .map(cursor::getLong) + .any { MmsSmsColumns.Types.isOutgoingMessageType(it) } } - } - - fun removeFailure(messageId: Long, failure: NetworkFailure?) { - try { - removeFromDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java) - } catch (e: IOException) { - Log.w(TAG, e) - } - } - - fun isOutgoingMessage(timestamp: Long): Boolean { - val database = databaseHelper.writableDatabase - var cursor: Cursor? = null - var isOutgoing = false - try { - cursor = database.query( - TABLE_NAME, - arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS), - DATE_SENT + " = ?", - arrayOf(timestamp.toString()), - null, - null, - null, - null - ) - while (cursor.moveToNext()) { - if (MmsSmsColumns.Types.isOutgoingMessageType( - cursor.getLong( - cursor.getColumnIndexOrThrow( - MESSAGE_BOX - ) - ) - ) - ) { - isOutgoing = true - } - } - } finally { - cursor?.close() - } - return isOutgoing - } fun incrementReceiptCount( messageId: SyncMessageId, @@ -254,15 +223,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } } - private fun getThreadIdFor(notification: NotificationInd): Long { - val fromString = - if (notification.from != null && notification.from.textString != null) toIsoString( - notification.from.textString - ) else "" - val recipient = Recipient.from(context, fromExternal(context, fromString), false) - return get(context).threadDatabase().getOrCreateThreadIdFor(recipient) - } - private fun rawQuery(where: String, arguments: Array?): Cursor { val database = databaseHelper.readableDatabase return database.rawQuery( @@ -273,10 +233,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) } - fun getMessages(idsAsString: String): Cursor { - return rawQuery(idsAsString, null) - } - fun getMessage(messageId: Long): Cursor { val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString())) setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId)) @@ -306,48 +262,30 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } } - fun markAsPendingInsecureSmsFallback(messageId: Long) { - val threadId = getThreadIdForMessage(messageId) + private fun markAs( + messageId: Long, + baseType: Long, + threadId: Long = getThreadIdForMessage(messageId) + ) { updateMailboxBitmask( messageId, MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_PENDING_INSECURE_SMS_FALLBACK, + baseType, Optional.of(threadId) ) notifyConversationListeners(threadId) } fun markAsSending(messageId: Long) { - val threadId = getThreadIdForMessage(messageId) - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_SENDING_TYPE, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE) } fun markAsSentFailed(messageId: Long) { - val threadId = getThreadIdForMessage(messageId) - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE) } override fun markAsSent(messageId: Long, secure: Boolean) { - val threadId = getThreadIdForMessage(messageId) - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0) } override fun markUnidentified(messageId: Long, unidentified: Boolean) { @@ -371,13 +309,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val mentionChange = if (hasMention) { 1 } else { 0 } get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange) } - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_DELETED_TYPE, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId) } override fun markExpireStarted(messageId: Long) { @@ -407,10 +339,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) } - fun setAllMessagesRead(): List { - return setMessagesRead(READ + " = 0", null) - } - private fun setMessagesRead(where: String, arguments: Array?): List { val database = databaseHelper.writableDatabase val result: MutableList = LinkedList() @@ -419,7 +347,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa try { cursor = database.query( TABLE_NAME, - arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED), + arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED), where, arguments, null, @@ -1400,25 +1328,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val attachments = get(context).attachmentDatabase().getAttachment( cursor ) - val contacts: List = getSharedContacts( - cursor, attachments - ) - val contactAttachments = - contacts.map { obj: Contact? -> obj!!.avatarAttachment } - .filter { a: Attachment? -> a != null } - .toSet() - val previews: List = getLinkPreviews( - cursor, attachments - ) - val previewAttachments = - previews.filter { lp: LinkPreview? -> lp!!.getThumbnail().isPresent } - .map { lp: LinkPreview? -> lp!!.getThumbnail().get() } - .toSet() + val contacts: List = getSharedContacts(cursor, attachments) + val contactAttachments: Set = + contacts.mapNotNull { it?.avatarAttachment }.toSet() + val previews: List = getLinkPreviews(cursor, attachments) + val previewAttachments: Set = + previews.mapNotNull { it?.getThumbnail()?.orNull() }.toSet() val slideDeck = getSlideDeck( - Stream.of(attachments) - .filterNot { o: DatabaseAttachment? -> contactAttachments.contains(o) } - .filterNot { o: DatabaseAttachment? -> previewAttachments.contains(o) } - .toList() + attachments + .filterNot { o: DatabaseAttachment? -> o in contactAttachments } + .filterNot { o: DatabaseAttachment? -> o in previewAttachments } ) val quote = getQuote(cursor) val reactions = get(context).reactionDatabase().getReactions(cursor) @@ -1623,4 +1542,4 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa 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/util/CursorUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt new file mode 100644 index 000000000..9cff8c77b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.util + +import android.database.Cursor + +fun Cursor.asSequence(): Sequence = + generateSequence { if (moveToNext()) this else null } From a934c5c2e2e73ad6fb245b3dc88adbcdd15264db Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:49:01 +0930 Subject: [PATCH 040/244] Fix missing media progress (#1151) --- .../securesms/conversation/v2/utilities/ThumbnailView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index e9a085dad..4a9986d6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -126,10 +126,9 @@ open class ThumbnailView: FrameLayout { buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) } slide.hasPlaceholder() -> { - buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) + buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, null, result)) } else -> { - binding.thumbnailLoadIndicator.isVisible = false glide.clear(binding.thumbnailImage) result.set(false) } From 83b6002a270bca19b98826be0ec0b9d98885c890 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:49:13 +0930 Subject: [PATCH 041/244] Fix emoji misalignment (#1155) --- .../v2/ConversationReactionOverlay.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java index b3c151690..1d81325e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java @@ -203,10 +203,11 @@ public final class ConversationReactionOverlay extends FrameLayout { boolean isMessageOnLeft) { contextMenu = new ConversationContextMenu(dropdownAnchor, getMenuActionItems(messageRecord)); - float itemX = isMessageOnLeft ? scrubberHorizontalMargin : + float endX = isMessageOnLeft ? scrubberHorizontalMargin : selectedConversationModel.getBubbleX() - conversationItem.getWidth() + selectedConversationModel.getBubbleWidth(); - conversationItem.setX(itemX); - conversationItem.setY(selectedConversationModel.getBubbleY() - statusBarHeight); + float endY = selectedConversationModel.getBubbleY() - statusBarHeight; + conversationItem.setX(endX); + conversationItem.setY(endY); Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap(); boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth(); @@ -214,8 +215,6 @@ public final class ConversationReactionOverlay extends FrameLayout { int overlayHeight = getHeight(); int bubbleWidth = selectedConversationModel.getBubbleWidth(); - float endX = itemX; - float endY = conversationItem.getY(); float endApparentTop = endY; float endScale = 1f; @@ -265,9 +264,7 @@ public final class ConversationReactionOverlay extends FrameLayout { } } else { endY = overlayHeight - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight(); - - float contextMenuTop = endY + conversationItemSnapshot.getHeight(); - reactionBarBackgroundY = getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop, menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY); + reactionBarBackgroundY = endY - reactionBarHeight - menuPadding; } endApparentTop = endY; From 99cb10f5befe288a61e56804207808b9972db200 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:49:19 +0930 Subject: [PATCH 042/244] Add send approval message (#1157) --- .../conversation/v2/ConversationActivityV2.kt | 8 ++++++++ .../main/res/layout/activity_conversation_v2.xml | 14 +++++++++++++- app/src/main/res/values/strings.xml | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) 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 b5f1b96fd..cabb58308 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 @@ -22,6 +22,7 @@ import androidx.activity.viewModels import androidx.annotation.DimenRes import androidx.appcompat.app.AlertDialog import androidx.core.view.drawToBitmap +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -344,6 +345,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe updateSubtitle() setUpBlockedBanner() binding!!.searchBottomBar.setEventListener(this) + updateSendAfterApprovalText() showOrHideInputIfNeeded() setUpMessageRequestsBar() @@ -653,7 +655,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe setUpMessageRequestsBar() invalidateOptionsMenu() updateSubtitle() + updateSendAfterApprovalText() showOrHideInputIfNeeded() + binding?.toolbarContent?.profilePictureView?.root?.update(threadRecipient) binding?.toolbarContent?.conversationTitleView?.text = when { threadRecipient.isLocalNumber -> getString(R.string.note_to_self) @@ -662,6 +666,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } + private fun updateSendAfterApprovalText() { + binding?.textSendAfterApproval?.isGone = viewModel.recipient?.hasApprovedMe() ?: true + } + private fun showOrHideInputIfNeeded() { val recipient = viewModel.recipient if (recipient != null && recipient.isClosedGroupRecipient) { diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 4ec4666d7..d2696a45e 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -35,7 +35,7 @@ android:layout_width="match_parent" android:layout_height="36dp" android:visibility="gone" - android:layout_above="@+id/messageRequestBar" + android:layout_above="@+id/textSendAfterApproval" /> + + %1$d of %2$d Call Permissions Required You can enable the \'Voice and video calls\' permission in the Privacy Settings. + You will be able to send voice messages and attachments once the recipient has approved this message request Delete selected message? From d868021f0ac5f3f1770b6c0aad2f841f7df9eb3b Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 4 May 2023 15:30:09 +1000 Subject: [PATCH 043/244] move job running status from database to memory --- .../securesms/database/JobDatabase.java | 33 +++++++++---------- .../securesms/database/SessionJobDatabase.kt | 2 +- .../securesms/database/Storage.kt | 2 +- .../securesms/jobs/FastJobStorageTest.java | 10 ------ 4 files changed, 17 insertions(+), 30 deletions(-) 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 21719df20..5e84d5e23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec; import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -22,6 +23,8 @@ public class JobDatabase extends Database { Constraints.CREATE_TABLE, Dependencies.CREATE_TABLE }; + private static final ArrayList runningJobs = new ArrayList<>(); + public static final class Jobs { public static final String TABLE_NAME = "job_spec"; private static final String ID = "_id"; @@ -113,18 +116,17 @@ public class JobDatabase extends Database { } public synchronized void updateJobRunningState(@NonNull String id, boolean isRunning) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Jobs.IS_RUNNING, isRunning ? 1 : 0); - - String query = Jobs.JOB_SPEC_ID + " = ?"; - String[] args = new String[]{ id }; - - databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args); + if (!isRunning) { + JobDatabase.runningJobs.remove(id); + } else if (!JobDatabase.runningJobs.contains(id)) { + JobDatabase.runningJobs.add(id); + } } public synchronized void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime) { + updateJobRunningState(id, isRunning); + ContentValues contentValues = new ContentValues(); - contentValues.put(Jobs.IS_RUNNING, isRunning ? 1 : 0); contentValues.put(Jobs.RUN_ATTEMPT, runAttempt); contentValues.put(Jobs.NEXT_RUN_ATTEMPT_TIME, nextRunAttemptTime); @@ -135,13 +137,7 @@ public class JobDatabase extends Database { } public synchronized void updateAllJobsToBePending() { - ContentValues contentValues = new ContentValues(); - contentValues.put(Jobs.IS_RUNNING, 0); - - String query = Jobs.IS_RUNNING + " = ?"; - String[] args = new String[] { "1" }; - - databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args); + JobDatabase.runningJobs.clear(); } public synchronized void deleteJobs(@NonNull List jobIds) { @@ -202,7 +198,7 @@ public class JobDatabase extends Database { contentValues.put(Jobs.MAX_INSTANCES, job.getMaxInstances()); contentValues.put(Jobs.LIFESPAN, job.getLifespan()); contentValues.put(Jobs.SERIALIZED_DATA, job.getSerializedData()); - contentValues.put(Jobs.IS_RUNNING, job.isRunning() ? 1 : 0); + updateJobRunningState(job.getId(), job.isRunning()); db.insertWithOnConflict(Jobs.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); } @@ -226,7 +222,8 @@ public class JobDatabase extends Database { } private @NonNull JobSpec jobSpecFromCursor(@NonNull Cursor cursor) { - return new JobSpec(cursor.getString(cursor.getColumnIndexOrThrow(Jobs.JOB_SPEC_ID)), + String jobId = cursor.getString(cursor.getColumnIndexOrThrow(Jobs.JOB_SPEC_ID)); + return new JobSpec(jobId, cursor.getString(cursor.getColumnIndexOrThrow(Jobs.FACTORY_KEY)), cursor.getString(cursor.getColumnIndexOrThrow(Jobs.QUEUE_KEY)), cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.CREATE_TIME)), @@ -237,7 +234,7 @@ public class JobDatabase extends Database { cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.LIFESPAN)), cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_INSTANCES)), cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_DATA)), - cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.IS_RUNNING)) == 1); + JobDatabase.runningJobs.contains(jobId)); } private @NonNull ConstraintSpec constraintSpecFromCursor(@NonNull Cursor cursor) { 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 66497d9da..b081fb007 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt @@ -46,7 +46,7 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa databaseHelper.writableDatabase.delete(sessionJobTable, "${Companion.jobID} = ?", arrayOf( jobID )) } - fun getAllPendingJobs(type: String): Map { + fun getAllJobs(type: String): Map { val database = databaseHelper.readableDatabase return database.getAll(sessionJobTable, "$jobType = ?", arrayOf( type )) { cursor -> val jobID = cursor.getString(jobID) 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 076fa57af..0d7ea3352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -211,7 +211,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun getAllPendingJobs(type: String): Map { - return DatabaseComponent.get(context).sessionJobDatabase().getAllPendingJobs(type) + return DatabaseComponent.get(context).sessionJobDatabase().getAllJobs(type) } override fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob? { diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java index ee1f232b5..a6624189d 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java @@ -74,16 +74,6 @@ public class FastJobStorageTest { assertEquals(DataSet1.JOB_2, subject.getJobSpec(DataSet1.JOB_2.getId())); } - @Test - public void updateAllJobsToBePending_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.updateAllJobsToBePending(); - - verify(database).updateAllJobsToBePending(); - } - @Test public void updateAllJobsToBePending_allArePending() { FullSpec fullSpec1 = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, true), From 45eb3549f6ba66838ef80b56beb83058510adf72 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 4 May 2023 14:50:51 +0930 Subject: [PATCH 044/244] Add sync status message --- .../conversation/v2/ConversationActivityV2.kt | 8 ++ .../v2/ConversationReactionOverlay.java | 5 ++ .../menus/ConversationActionModeCallback.kt | 4 + .../v2/messages/VisibleMessageView.kt | 73 ++++++++++--------- .../v2/utilities/ResendMessageUtilities.kt | 16 +++- .../securesms/database/MessagingDatabase.java | 8 +- .../securesms/database/MmsDatabase.kt | 10 +++ .../securesms/database/MmsSmsColumns.java | 17 +++++ .../securesms/database/SmsDatabase.java | 15 ++++ .../securesms/database/Storage.kt | 56 +++++++++++++- .../database/model/DisplayRecord.java | 12 +++ .../menu/menu_conversation_item_action.xml | 5 ++ app/src/main/res/values/strings.xml | 4 + .../libsession/database/StorageProtocol.kt | 5 +- .../messaging/jobs/MessageSendJob.kt | 2 + .../messaging/messages/Destination.kt | 6 +- .../messages/visible/VisibleMessage.kt | 28 +++---- .../sending_receiving/MessageSender.kt | 37 ++++++++-- 18 files changed, 248 insertions(+), 63 deletions(-) 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 cabb58308..18c62a74c 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 @@ -1755,6 +1755,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } + override fun resyncMessage(messages: Set) { + messages.iterator().forEach { messageRecord -> + ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey, isResync = true) + } + endActionMode() + } + override fun resendMessage(messages: Set) { messages.iterator().forEach { messageRecord -> ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey) @@ -1915,6 +1922,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe val selectedItems = setOf(message) when (action) { ConversationReactionOverlay.Action.REPLY -> reply(selectedItems) + ConversationReactionOverlay.Action.RESYNC -> resyncMessage(selectedItems) ConversationReactionOverlay.Action.RESEND -> resendMessage(selectedItems) ConversationReactionOverlay.Action.DOWNLOAD -> saveAttachment(selectedItems) ConversationReactionOverlay.Action.COPY_MESSAGE -> copyMessages(selectedItems) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java index 1d81325e0..8f1fb6a0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java @@ -700,6 +700,10 @@ public final class ConversationReactionOverlay extends FrameLayout { if (message.isFailed()) { items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resend_message), () -> handleActionItemClicked(Action.RESEND))); } + // Resync + if (message.isSyncFailed()) { + items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_resync_message), () -> handleActionItemClicked(Action.RESYNC))); + } // Save media if (message.isMms() && ((MediaMmsMessageRecord)message).containsMediaSlide()) { items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD), @@ -885,6 +889,7 @@ public final class ConversationReactionOverlay extends FrameLayout { public enum Action { REPLY, RESEND, + RESYNC, DOWNLOAD, COPY_MESSAGE, COPY_SESSION_ID, 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 d475a6444..f86920f90 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 @@ -70,6 +70,8 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p 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) + // Resync + menu.findItem(R.id.menu_context_resync).isVisible = (selectedItems.size == 1 && firstMessage.isSyncFailed) // Save media menu.findItem(R.id.menu_context_save_attachment).isVisible = (selectedItems.size == 1 && firstMessage.isMms && (firstMessage as MediaMmsMessageRecord).containsMediaSlide()) @@ -90,6 +92,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p R.id.menu_context_ban_and_delete_all -> delegate?.banAndDeleteAll(selectedItems) R.id.menu_context_copy -> delegate?.copyMessages(selectedItems) R.id.menu_context_copy_public_key -> delegate?.copySessionID(selectedItems) + R.id.menu_context_resync -> delegate?.resyncMessage(selectedItems) R.id.menu_context_resend -> delegate?.resendMessage(selectedItems) R.id.menu_message_details -> delegate?.showMessageDetail(selectedItems) R.id.menu_context_save_attachment -> delegate?.saveAttachment(selectedItems) @@ -113,6 +116,7 @@ interface ConversationActionModeCallbackDelegate { fun banAndDeleteAll(messages: Set) fun copyMessages(messages: Set) fun copySessionID(messages: Set) + fun resyncMessage(messages: Set) fun resendMessage(messages: Set) fun showMessageDetail(messages: Set) fun saveAttachment(messages: Set) 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 2c04bcfce..745c91e91 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 @@ -292,39 +292,46 @@ class VisibleMessageView : LinearLayout { @StringRes val messageText: Int?, val contentDescription: String?) - private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo { - return when { - !message.isOutgoing -> MessageStatusInfo(null, - null, - null, - null) - message.isFailed -> - MessageStatusInfo( - R.drawable.ic_delivery_status_failed, - resources.getColor(R.color.destructive, context.theme), - R.string.delivery_status_failed, - null - ) - message.isPending -> - MessageStatusInfo( - R.drawable.ic_delivery_status_sending, - context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending, - context.getString(R.string.AccessibilityId_message_sent_status_pending) - ) - message.isRead -> - MessageStatusInfo( - R.drawable.ic_delivery_status_read, - context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read, - null - ) - else -> - MessageStatusInfo( - R.drawable.ic_delivery_status_sent, - context.getColorFromAttr(R.attr.message_status_color), - R.string.delivery_status_sent, - context.getString(R.string.AccessibilityId_message_sent_status_tick) - ) - } + private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo = when { + message.isFailed -> + MessageStatusInfo( + R.drawable.ic_delivery_status_failed, + resources.getColor(R.color.destructive, context.theme), + R.string.delivery_status_failed, + null + ) + message.isSyncFailed -> + MessageStatusInfo( + R.drawable.ic_delivery_status_failed, + resources.getColor(R.color.destructive, context.theme), + R.string.delivery_status_sync_failed, + null + ) + message.isPending -> + MessageStatusInfo( + R.drawable.ic_delivery_status_sending, + context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending, + context.getString(R.string.AccessibilityId_message_sent_status_pending) + ) + message.isResyncing -> + MessageStatusInfo( + R.drawable.ic_delivery_status_sending, + context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_syncing, + context.getString(R.string.AccessibilityId_message_sent_status_syncing) + ) + message.isRead -> + MessageStatusInfo( + R.drawable.ic_delivery_status_read, + context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read, + null + ) + else -> + MessageStatusInfo( + R.drawable.ic_delivery_status_sent, + context.getColorFromAttr(R.attr.message_status_color), + R.string.delivery_status_sent, + context.getString(R.string.AccessibilityId_message_sent_status_tick) + ) } private fun updateExpirationTimer(message: MessageRecord) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt index 80f4cc0bf..5e6283e1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.utilities import android.content.Context import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.visible.LinkPreview import org.session.libsession.messaging.messages.visible.OpenGroupInvitation import org.session.libsession.messaging.messages.visible.Quote @@ -10,12 +11,16 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord +private val TAG = ResendMessageUtilities.javaClass.simpleName + object ResendMessageUtilities { - fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?) { + fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?, isResync: Boolean = false) { + Log.d(TAG, "resend() called with: context = $context, messageRecord = $messageRecord, userBlindedKey = $userBlindedKey, isResync = $isResync") val recipient: Recipient = messageRecord.recipient val message = VisibleMessage() message.id = messageRecord.getId() @@ -55,8 +60,13 @@ object ResendMessageUtilities { val sentTimestamp = message.sentTimestamp val sender = MessagingModuleConfiguration.shared.storage.getUserPublicKey() if (sentTimestamp != null && sender != null) { - MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender) + if (isResync) { + MessagingModuleConfiguration.shared.storage.markAsResyncing(sentTimestamp, sender) + MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = true) + } else { + MessagingModuleConfiguration.shared.storage.markAsSending(sentTimestamp, sender) + MessageSender.send(message, recipient.address) + } } - MessageSender.send(message, recipient.address) } } \ No newline at end of file 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 d3ba31747..edc6bc1a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -37,6 +37,13 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markExpireStarted(long messageId, long startTime); public abstract void markAsSent(long messageId, boolean secure); + + public abstract void markAsSyncing(long id); + + public abstract void markAsResyncing(long id); + + public abstract void markAsSyncFailed(long id); + public abstract void markUnidentified(long messageId, boolean unidentified); public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention); @@ -199,7 +206,6 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn contentValues.put(THREAD_ID, newThreadId); db.update(getTableName(), contentValues, where, args); } - public static class SyncMessageId { private final Address address; 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 3a4b35ad1..9e854698f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -276,6 +276,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa notifyConversationListeners(threadId) } + override fun markAsSyncing(messageId: Long) { + markAs(messageId, MmsSmsColumns.Types.BASE_SYNCING_TYPE) + } + override fun markAsResyncing(messageId: Long) { + markAs(messageId, MmsSmsColumns.Types.BASE_RESYNCING_TYPE) + } + override fun markAsSyncFailed(messageId: Long) { + markAs(messageId, MmsSmsColumns.Types.BASE_SYNC_FAILED_TYPE) + } + fun markAsSending(messageId: Long) { markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE) } 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 f3110a5c7..1e1cc5089 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -47,8 +47,13 @@ public interface MmsSmsColumns { protected static final long BASE_PENDING_INSECURE_SMS_FALLBACK = 26; public static final long BASE_DRAFT_TYPE = 27; protected static final long BASE_DELETED_TYPE = 28; + protected static final long BASE_SYNCING_TYPE = 29; + protected static final long BASE_RESYNCING_TYPE = 30; + protected static final long BASE_SYNC_FAILED_TYPE = 31; protected static final long[] OUTGOING_MESSAGE_TYPES = {BASE_OUTBOX_TYPE, BASE_SENT_TYPE, + BASE_SYNCING_TYPE, BASE_RESYNCING_TYPE, + BASE_SYNC_FAILED_TYPE, BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE, BASE_PENDING_SECURE_SMS_FALLBACK, BASE_PENDING_INSECURE_SMS_FALLBACK, @@ -109,6 +114,18 @@ public interface MmsSmsColumns { return (type & BASE_TYPE_MASK) == BASE_DRAFT_TYPE; } + public static boolean isResyncingType(long type) { + return (type & BASE_TYPE_MASK) == BASE_RESYNCING_TYPE; + } + + public static boolean isSyncingType(long type) { + return (type & BASE_TYPE_MASK) == BASE_SYNCING_TYPE; + } + + public static boolean isSyncFailedMessageType(long type) { + return (type & BASE_TYPE_MASK) == BASE_SYNC_FAILED_TYPE; + } + public static boolean isFailedMessageType(long type) { return (type & BASE_TYPE_MASK) == BASE_SENT_FAILED_TYPE; } 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 0d61b2679..42a00ccbb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -202,6 +202,21 @@ public class SmsDatabase extends MessagingDatabase { updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE); } + @Override + public void markAsSyncing(long id) { + updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SYNCING_TYPE); + } + + @Override + public void markAsResyncing(long id) { + updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_RESYNCING_TYPE); + } + + @Override + public void markAsSyncFailed(long id) { + updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SYNC_FAILED_TYPE); + } + @Override public void markUnidentified(long id, boolean unidentified) { ContentValues contentValues = new ContentValues(1); 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 076fa57af..77f616026 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -41,6 +41,7 @@ import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.KeyHelper +import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -53,6 +54,8 @@ import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol import java.security.MessageDigest +private val TAG = Storage::class.java.simpleName + class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { @@ -356,6 +359,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, openGroupSentTimestamp: Long, threadId: Long ) { + Log.d(TAG, "updateSentTimestamp() called with: messageID = $messageID, isMms = $isMms, openGroupSentTimestamp = $openGroupSentTimestamp, threadId = $threadId") + if (isMms) { val mmsDb = DatabaseComponent.get(context).mmsDatabase() mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) @@ -366,6 +371,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun markAsSent(timestamp: Long, author: String) { + Log.d(TAG, "markAsSent() called with: timestamp = $timestamp, author = $author") + val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { @@ -377,7 +384,29 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun markAsSyncing(timestamp: Long, author: String) { + Log.d(TAG, "markAsSyncing() called with: timestamp = $timestamp, author = $author") + + DatabaseComponent.get(context).mmsSmsDatabase() + .getMessageFor(timestamp, author) + ?.run { getMmsDatabaseElseSms(isMms).markAsSyncing(id) } + } + + private fun getMmsDatabaseElseSms(isMms: Boolean) = + if (isMms) DatabaseComponent.get(context).mmsDatabase() + else DatabaseComponent.get(context).smsDatabase() + + override fun markAsResyncing(timestamp: Long, author: String) { + Log.d(TAG, "markAsResyncing() called with: timestamp = $timestamp, author = $author") + + DatabaseComponent.get(context).mmsSmsDatabase() + .getMessageFor(timestamp, author) + ?.run { getMmsDatabaseElseSms(isMms).markAsResyncing(id) } + } + override fun markAsSending(timestamp: Long, author: String) { + Log.d(TAG, "markAsSending() called with: timestamp = $timestamp, author = $author") + val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { @@ -402,7 +431,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } - override fun setErrorMessage(timestamp: Long, author: String, error: Exception) { + override fun markAsSentFailed(timestamp: Long, author: String, error: Exception) { + Log.d(TAG, "markAsSentFailed() called with: timestamp = $timestamp, author = $author, error = $error") + val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { @@ -425,6 +456,28 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun markAsSyncFailed(timestamp: Long, author: String, error: Exception) { + Log.d(TAG, "markAsSyncFailed() called with: timestamp = $timestamp, author = $author, error = $error") + + val database = DatabaseComponent.get(context).mmsSmsDatabase() + val messageRecord = database.getMessageFor(timestamp, author) ?: return + + database.getMessageFor(timestamp, author) + ?.run { getMmsDatabaseElseSms(isMms).markAsSyncFailed(id) } + + if (error.localizedMessage != null) { + val message: String + if (error is OnionRequestAPI.HTTPRequestFailedAtDestinationException && error.statusCode == 429) { + message = "429: Rate limited." + } else { + message = error.localizedMessage!! + } + DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), message) + } else { + DatabaseComponent.get(context).lokiMessageDatabase().setErrorMessage(messageRecord.getId(), error.javaClass.simpleName) + } + } + override fun clearErrorMessage(messageID: Long) { val db = DatabaseComponent.get(context).lokiMessageDatabase() db.clearErrorMessage(messageID) @@ -983,5 +1036,4 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val recipientDb = DatabaseComponent.get(context).recipientDatabase() return recipientDb.blockedContacts } - } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index ef0f4b54f..39fba182a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -80,6 +80,18 @@ public abstract class DisplayRecord { return !isFailed() && !isPending(); } + public boolean isSyncing() { + return MmsSmsColumns.Types.isSyncingType(type); + } + + public boolean isResyncing() { + return MmsSmsColumns.Types.isResyncingType(type); + } + + public boolean isSyncFailed() { + return MmsSmsColumns.Types.isSyncFailedMessageType(type); + } + public boolean isFailed() { return MmsSmsColumns.Types.isFailedMessageType(type) || MmsSmsColumns.Types.isPendingSecureSmsFallbackType(type) diff --git a/app/src/main/res/menu/menu_conversation_item_action.xml b/app/src/main/res/menu/menu_conversation_item_action.xml index 8e2093922..ea8168561 100644 --- a/app/src/main/res/menu/menu_conversation_item_action.xml +++ b/app/src/main/res/menu/menu_conversation_item_action.xml @@ -32,6 +32,11 @@ android:icon="?menu_copy_icon" app:showAsAction="always" /> + + Message sent status: Sent Message sent status pending + Message sent status syncing Message request has been accepted Message Body Voice message @@ -627,6 +628,7 @@ Delete message Ban user Ban and delete all + Resync message Resend message Reply Reply to message @@ -1007,9 +1009,11 @@ Close Dialog Database Upgrade Failed Please contact support to report the error. + Syncing Sending Read Sent + Failed to sync Failed to send Search GIFs? Session will connect to Giphy to provide search results. You will not have full metadata protection when sending GIFs. 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 d75e209d1..48d15a18a 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -106,10 +106,13 @@ interface StorageProtocol { fun getAttachmentsForMessage(messageID: Long): List fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name fun updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long) + fun markAsResyncing(timestamp: Long, author: String) + fun markAsSyncing(timestamp: Long, author: String) fun markAsSending(timestamp: Long, author: String) fun markAsSent(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String) - fun setErrorMessage(timestamp: Long, author: String, error: Exception) + fun markAsSyncFailed(timestamp: Long, author: String, error: Exception) + fun markAsSentFailed(timestamp: Long, author: String, error: Exception) fun clearErrorMessage(messageID: Long) fun setMessageServerHash(messageID: Long, serverHash: String) 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 524338592..4300fe4b6 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 @@ -34,6 +34,8 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { } override fun execute(dispatcherName: String) { + Log.d(TAG, "MessageSendJob#execute() called with: dispatcherName = $dispatcherName") + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val message = message as? VisibleMessage val storage = MessagingModuleConfiguration.shared.storage diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt index 3abf0ed3e..f30c1b916 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/Destination.kt @@ -7,13 +7,13 @@ import org.session.libsignal.utilities.toHexString sealed class Destination { - class Contact(var publicKey: String) : Destination() { + data class Contact(var publicKey: String) : Destination() { internal constructor(): this("") } - class ClosedGroup(var groupPublicKey: String) : Destination() { + data class ClosedGroup(var groupPublicKey: String) : Destination() { internal constructor(): this("") } - class LegacyOpenGroup(var roomToken: String, var server: String) : Destination() { + data class LegacyOpenGroup(var roomToken: String, var server: String) : Destination() { internal constructor(): this("", "") } 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 e66147da1..705fc7ce4 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 @@ -11,20 +11,22 @@ import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Log import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment -class VisibleMessage : Message() { - /** In the case of a sync message, the public key of the person the message was targeted at. - * - * **Note:** `nil` if this isn't a sync message. - */ - var syncTarget: String? = null - var text: String? = null - val attachmentIDs: MutableList = mutableListOf() - var quote: Quote? = null - var linkPreview: LinkPreview? = null - var profile: Profile? = null - var openGroupInvitation: OpenGroupInvitation? = null - var reaction: Reaction? = null +/** + * @param syncTarget In the case of a sync message, the public key of the person the message was targeted at. + * + * **Note:** `nil` if this isn't a sync message. + */ +class VisibleMessage( + var syncTarget: String? = null, + var text: String? = null, + val attachmentIDs: MutableList = mutableListOf(), + var quote: Quote? = null, + var linkPreview: LinkPreview? = null, + var profile: Profile? = null, + var openGroupInvitation: OpenGroupInvitation? = null, + var reaction: Reaction? = null, var hasMention: Boolean = false +) : Message() { override val isSelfSendValid: Boolean = true 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 eb0d15739..9da6cd8b1 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 @@ -39,6 +39,8 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote +private val TAG = MessageSender::class.java.simpleName + object MessageSender { // Error @@ -61,16 +63,20 @@ object MessageSender { } // Convenience - fun send(message: Message, destination: Destination): Promise { + fun send(message: Message, destination: Destination, isSyncMessage: Boolean = false): Promise { + Log.d(TAG, "send() called with: message = $message, destination = $destination, isSyncMessage = $isSyncMessage") + return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { sendToOpenGroupDestination(destination, message) } else { - sendToSnodeDestination(destination, message) + sendToSnodeDestination(destination, message, isSyncMessage) } } // One-on-One Chats & Closed Groups private fun sendToSnodeDestination(destination: Destination, message: Message, isSyncMessage: Boolean = false): Promise { + Log.d(TAG, "sendToSnodeDestination() called with: destination = $destination, message = $message, isSyncMessage = $isSyncMessage") + val deferred = deferred() val promise = deferred.promise val storage = MessagingModuleConfiguration.shared.storage @@ -86,7 +92,7 @@ object MessageSender { val isSelfSend = (message.recipient == userPublicKey) // Set the failure handler (need it here already for precondition failure handling) fun handleFailure(error: Exception) { - handleFailedMessageSend(message, error) + handleFailedMessageSend(message, error, isSyncMessage) if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeModule.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!) } @@ -220,6 +226,8 @@ object MessageSender { // Open Groups private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise { + Log.d(TAG, "sendToOpenGroupDestination() called with: destination = $destination, message = $message") + val deferred = deferred() val storage = MessagingModuleConfiguration.shared.storage if (message.sentTimestamp == null) { @@ -318,6 +326,8 @@ object MessageSender { // Result Handling fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { + Log.d(TAG, "handleSuccessfulMessageSend() called with: message = $message, destination = $destination, isSyncMessage = $isSyncMessage, openGroupSentTimestamp = $openGroupSentTimestamp") + val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! // Ignore future self-sends @@ -374,21 +384,32 @@ object MessageSender { // • the destination was a contact // • we didn't sync it already if (destination is Destination.Contact && !isSyncMessage) { - if (message is VisibleMessage) { message.syncTarget = destination.publicKey } - if (message is ExpirationTimerUpdate) { message.syncTarget = destination.publicKey } + if (message is VisibleMessage) message.syncTarget = destination.publicKey + if (message is ExpirationTimerUpdate) message.syncTarget = destination.publicKey + + storage.markAsSyncing(message.sentTimestamp!!, userPublicKey) sendToSnodeDestination(Destination.Contact(userPublicKey), message, true) } } - fun handleFailedMessageSend(message: Message, error: Exception) { + fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) { + Log.d(TAG, "handleFailedMessageSend() called with: message = $message, error = $error, isSyncMessage = $isSyncMessage") + val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! - storage.setErrorMessage(message.sentTimestamp!!, message.sender?:userPublicKey, error) + + val timestamp = message.sentTimestamp!! + val author = message.sender ?: userPublicKey + + if (isSyncMessage) storage.markAsSyncFailed(timestamp, author, error) + else storage.markAsSentFailed(timestamp, author, error) } // Convenience @JvmStatic fun send(message: VisibleMessage, address: Address, attachments: List, quote: SignalQuote?, linkPreview: SignalLinkPreview?) { + Log.d(TAG, "send() called with: message = $message, address = $address, attachments = $attachments, quote = $quote, linkPreview = $linkPreview") + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val attachmentIDs = messageDataProvider.getAttachmentIDsFor(message.id!!) message.attachmentIDs.addAll(attachmentIDs) @@ -407,6 +428,8 @@ object MessageSender { @JvmStatic fun send(message: Message, address: Address) { + Log.d(TAG, "send() called with: message = $message, address = $address") + val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address) message.threadID = threadID val destination = Destination.from(address) From d3ce899a8056c9b9d307f9993845603b0016c058 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 12:07:19 +0930 Subject: [PATCH 045/244] Synchronize all Cipher#doFinal --- .../securesms/backup/FullBackupExporter.kt | 35 +++++++++------ .../session/libsession/utilities/AESGCM.kt | 17 ++++--- .../session/libsignal/crypto/CipherUtil.java | 5 +++ .../session/libsignal/crypto/DiffieHellman.kt | 45 ------------------- .../session/libsignal/crypto/kdf/HKDF.java | 8 +--- .../streams/AttachmentCipherOutputStream.java | 15 ++++--- .../streams/ProfileCipherInputStream.java | 30 +++++++------ .../streams/ProfileCipherOutputStream.java | 16 ++++--- 8 files changed, 75 insertions(+), 96 deletions(-) create mode 100644 libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java delete mode 100644 libsignal/src/main/java/org/session/libsignal/crypto/DiffieHellman.kt 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 6b5d47a2e..783468eee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt @@ -14,6 +14,7 @@ import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.utilities.Conversions import org.session.libsession.utilities.Util +import org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK import org.session.libsignal.crypto.kdf.HKDFv3 import org.session.libsignal.utilities.ByteUtil import org.session.libsignal.utilities.Log @@ -289,7 +290,7 @@ object FullBackupExporter { private var counter: Int = 0 - constructor(outputStream: OutputStream, passphrase: String) : super() { + private constructor(outputStream: OutputStream, passphrase: String) : super() { try { val salt = Util.getSecretBytes(32) val key = BackupUtil.computeBackupKey(passphrase, salt) @@ -381,18 +382,24 @@ object FullBackupExporter { private fun writeStream(inputStream: InputStream) { try { Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - mac.update(iv) - val buffer = ByteArray(8192) - var read: Int - while (inputStream.read(buffer).also { read = it } != -1) { - val ciphertext = cipher.update(buffer, 0, read) - if (ciphertext != null) { - outputStream.write(ciphertext) - mac.update(ciphertext) + val remainder = synchronized(CIPHER_LOCK) { + cipher.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(cipherKey, "AES"), + IvParameterSpec(iv) + ) + mac.update(iv) + val buffer = ByteArray(8192) + var read: Int + while (inputStream.read(buffer).also { read = it } != -1) { + val ciphertext = cipher.update(buffer, 0, read) + if (ciphertext != null) { + outputStream.write(ciphertext) + mac.update(ciphertext) + } } + cipher.doFinal() } - val remainder = cipher.doFinal() outputStream.write(remainder) mac.update(remainder) val attachmentDigest = mac.doFinal() @@ -414,8 +421,10 @@ object FullBackupExporter { private fun write(out: OutputStream, frame: BackupFrame) { try { Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - val frameCiphertext = cipher.doFinal(frame.toByteArray()) + val frameCiphertext = synchronized(CIPHER_LOCK) { + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) + cipher.doFinal(frame.toByteArray()) + } val frameMac = mac.doFinal(frameCiphertext) val length = Conversions.intToByteArray(frameCiphertext.size + 10) out.write(length) diff --git a/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt b/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt index 225900b09..4a6a588dc 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/AESGCM.kt @@ -1,6 +1,7 @@ package org.session.libsession.utilities import androidx.annotation.WorkerThread +import org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK import org.session.libsignal.utilities.ByteUtil import org.session.libsignal.utilities.Util import org.session.libsignal.utilities.Hex @@ -27,9 +28,11 @@ internal object AESGCM { internal fun decrypt(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray { val iv = ivAndCiphertext.sliceArray(0 until ivSize) val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.count()) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) - return cipher.doFinal(ciphertext) + synchronized(CIPHER_LOCK) { + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) + return cipher.doFinal(ciphertext) + } } /** @@ -47,9 +50,11 @@ internal object AESGCM { */ internal fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray { val iv = Util.getSecretBytes(ivSize) - val cipher = Cipher.getInstance("AES/GCM/NoPadding") - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) - return ByteUtil.combine(iv, cipher.doFinal(plaintext)) + synchronized(CIPHER_LOCK) { + val cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) + return ByteUtil.combine(iv, cipher.doFinal(plaintext)) + } } /** diff --git a/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java b/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java new file mode 100644 index 000000000..d3423a0bd --- /dev/null +++ b/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java @@ -0,0 +1,5 @@ +package org.session.libsignal.crypto; + +public class CipherUtil { + public static final Object CIPHER_LOCK = new Object(); +} diff --git a/libsignal/src/main/java/org/session/libsignal/crypto/DiffieHellman.kt b/libsignal/src/main/java/org/session/libsignal/crypto/DiffieHellman.kt deleted file mode 100644 index 2b613247b..000000000 --- a/libsignal/src/main/java/org/session/libsignal/crypto/DiffieHellman.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.session.libsignal.crypto - -import org.whispersystems.curve25519.Curve25519 -import org.session.libsignal.utilities.Util -import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -object DiffieHellman { - private val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") - private val curve = Curve25519.getInstance(Curve25519.BEST) - private val ivSize = 16 - - @JvmStatic @Throws - fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray { - val iv = Util.getSecretBytes(ivSize) - val ivSpec = IvParameterSpec(iv) - val secretKeySpec = SecretKeySpec(symmetricKey, "AES") - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec) - val ciphertext = cipher.doFinal(plaintext) - return iv + ciphertext - } - - @JvmStatic @Throws - fun encrypt(plaintext: ByteArray, publicKey: ByteArray, privateKey: ByteArray): ByteArray { - val symmetricKey = curve.calculateAgreement(publicKey, privateKey) - return encrypt(plaintext, symmetricKey) - } - - @JvmStatic @Throws - fun decrypt(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray { - val iv = ivAndCiphertext.sliceArray(0 until ivSize) - val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.size) - val ivSpec = IvParameterSpec(iv) - val secretKeySpec = SecretKeySpec(symmetricKey, "AES") - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec) - return cipher.doFinal(ciphertext) - } - - @JvmStatic @Throws - fun decrypt(ivAndCiphertext: ByteArray, publicKey: ByteArray, privateKey: ByteArray): ByteArray { - val symmetricKey = curve.calculateAgreement(publicKey, privateKey) - return decrypt(ivAndCiphertext, symmetricKey) - } -} diff --git a/libsignal/src/main/java/org/session/libsignal/crypto/kdf/HKDF.java b/libsignal/src/main/java/org/session/libsignal/crypto/kdf/HKDF.java index 3295f06e5..73c87c075 100644 --- a/libsignal/src/main/java/org/session/libsignal/crypto/kdf/HKDF.java +++ b/libsignal/src/main/java/org/session/libsignal/crypto/kdf/HKDF.java @@ -39,9 +39,7 @@ public abstract class HKDF { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(salt, "HmacSHA256")); return mac.doFinal(inputKeyMaterial); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new AssertionError(e); } } @@ -73,9 +71,7 @@ public abstract class HKDF { } return results.toByteArray(); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new AssertionError(e); } } diff --git a/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherOutputStream.java b/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherOutputStream.java index 91c356370..2f58c84c7 100644 --- a/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherOutputStream.java +++ b/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherOutputStream.java @@ -6,6 +6,8 @@ package org.session.libsignal.streams; +import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK; + import org.session.libsignal.utilities.Util; import java.io.IOException; @@ -68,16 +70,17 @@ public class AttachmentCipherOutputStream extends DigestingOutputStream { @Override public void flush() throws IOException { try { - byte[] ciphertext = cipher.doFinal(); + byte[] ciphertext; + synchronized (CIPHER_LOCK) { + ciphertext = cipher.doFinal(); + } byte[] auth = mac.doFinal(ciphertext); super.write(ciphertext); super.write(auth); super.flush(); - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } catch (BadPaddingException e) { + } catch (IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } } @@ -97,9 +100,7 @@ public class AttachmentCipherOutputStream extends DigestingOutputStream { private Cipher initializeCipher() { try { return Cipher.getInstance("AES/CBC/PKCS5Padding"); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (NoSuchPaddingException e) { + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new AssertionError(e); } } diff --git a/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java b/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java index aa15eb00c..19996c17c 100644 --- a/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java +++ b/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherInputStream.java @@ -1,5 +1,7 @@ package org.session.libsignal.streams; +import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK; + import org.session.libsignal.utilities.Util; import java.io.FilterInputStream; @@ -62,23 +64,23 @@ public class ProfileCipherInputStream extends FilterInputStream { byte[] ciphertext = new byte[outputLength / 2]; int read = in.read(ciphertext, 0, ciphertext.length); - if (read == -1) { - if (cipher.getOutputSize(0) > outputLength) { - throw new AssertionError("Need: " + cipher.getOutputSize(0) + " but only have: " + outputLength); - } + synchronized (CIPHER_LOCK) { + if (read == -1) { + if (cipher.getOutputSize(0) > outputLength) { + throw new AssertionError("Need: " + cipher.getOutputSize(0) + " but only have: " + outputLength); + } - finished = true; - return cipher.doFinal(output, outputOffset); - } else { - if (cipher.getOutputSize(read) > outputLength) { - throw new AssertionError("Need: " + cipher.getOutputSize(read) + " but only have: " + outputLength); - } + finished = true; + return cipher.doFinal(output, outputOffset); + } else { + if (cipher.getOutputSize(read) > outputLength) { + throw new AssertionError("Need: " + cipher.getOutputSize(read) + " but only have: " + outputLength); + } - return cipher.update(ciphertext, 0, read, output, outputOffset); + return cipher.update(ciphertext, 0, read, output, outputOffset); + } } - } catch (IllegalBlockSizeException e) { - throw new AssertionError(e); - } catch(ShortBufferException e) { + } catch (IllegalBlockSizeException | ShortBufferException e) { throw new AssertionError(e); } catch (BadPaddingException e) { throw new IOException(e); diff --git a/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherOutputStream.java b/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherOutputStream.java index 9d4e13a0c..f47a5f72b 100644 --- a/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherOutputStream.java +++ b/libsignal/src/main/java/org/session/libsignal/streams/ProfileCipherOutputStream.java @@ -1,5 +1,7 @@ package org.session.libsignal.streams; +import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK; + import java.io.IOException; import java.io.OutputStream; import java.security.InvalidAlgorithmParameterException; @@ -54,20 +56,24 @@ public class ProfileCipherOutputStream extends DigestingOutputStream { byte[] input = new byte[1]; input[0] = (byte)b; - byte[] output = cipher.update(input); + byte[] output; + synchronized (CIPHER_LOCK) { + output = cipher.update(input); + } super.write(output); } @Override public void flush() throws IOException { try { - byte[] output = cipher.doFinal(); + byte[] output; + synchronized (CIPHER_LOCK) { + output = cipher.doFinal(); + } super.write(output); super.flush(); - } catch (BadPaddingException e) { - throw new AssertionError(e); - } catch (IllegalBlockSizeException e) { + } catch (BadPaddingException | IllegalBlockSizeException e) { throw new AssertionError(e); } } From 24741fcc22dabaef7757d723be355654bc70e1b9 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 12:10:46 +0930 Subject: [PATCH 046/244] Synchronize Cipher in KeystoreHelper --- .../securesms/crypto/KeyStoreHelper.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java index 43e986559..45ec9e207 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java @@ -45,44 +45,46 @@ public final class KeyStoreHelper { private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private static final String KEY_ALIAS = "SignalSecret"; - @RequiresApi(Build.VERSION_CODES.M) + private static final Object lock = new Object(); + public static SealedData seal(@NonNull byte[] input) { SecretKey secretKey = getOrCreateKeyStoreEntry(); try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); + synchronized (lock) { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] iv = cipher.getIV(); - byte[] data = cipher.doFinal(input); + byte[] iv = cipher.getIV(); + byte[] data = cipher.doFinal(input); - return new SealedData(iv, data); + return new SealedData(iv, data); + } } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } } - @RequiresApi(Build.VERSION_CODES.M) public static byte[] unseal(@NonNull SealedData sealedData) { SecretKey secretKey = getKeyStoreEntry(); try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); + synchronized (lock) { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); - return cipher.doFinal(sealedData.data); + return cipher.doFinal(sealedData.data); + } } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } } - @RequiresApi(Build.VERSION_CODES.M) private static SecretKey getOrCreateKeyStoreEntry() { if (hasKeyStoreEntry()) return getKeyStoreEntry(); else return createKeyStoreEntry(); } - @RequiresApi(Build.VERSION_CODES.M) private static SecretKey createKeyStoreEntry() { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); @@ -99,7 +101,6 @@ public final class KeyStoreHelper { } } - @RequiresApi(Build.VERSION_CODES.M) private static SecretKey getKeyStoreEntry() { KeyStore keyStore = getKeyStore(); @@ -137,7 +138,6 @@ public final class KeyStoreHelper { } } - @RequiresApi(Build.VERSION_CODES.M) private static boolean hasKeyStoreEntry() { try { KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE); From a152250a6007c423bda465f8c8824f1be5b73780 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 12:18:39 +0930 Subject: [PATCH 047/244] Add comments --- .../org/thoughtcrime/securesms/crypto/KeyStoreHelper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java index 45ec9e207..001b4fa1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java @@ -51,6 +51,9 @@ public final class KeyStoreHelper { SecretKey secretKey = getOrCreateKeyStoreEntry(); try { + // Cipher operations are not thread-safe so we synchronize over them through doFinal to + // prevent crashes with quickly repeated encrypt/decrypt operations + // https://github.com/mozilla-mobile/android-components/issues/5342 synchronized (lock) { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); @@ -69,6 +72,9 @@ public final class KeyStoreHelper { SecretKey secretKey = getKeyStoreEntry(); try { + // Cipher operations are not thread-safe so we synchronize over them through doFinal to + // prevent crashes with quickly repeated encrypt/decrypt operations + // https://github.com/mozilla-mobile/android-components/issues/5342 synchronized (lock) { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); From a9078c8d08939604087b21c73446ba66bfe3248d Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 12:32:54 +0930 Subject: [PATCH 048/244] ...and the rest --- .../securesms/backup/FullBackupImporter.kt | 39 ++++++++++++------- .../securesms/crypto/KeyStoreHelper.java | 30 +++++++------- .../securesms/logging/LogFile.java | 27 +++++++------ .../streams/AttachmentCipherInputStream.java | 35 +++++++---------- 4 files changed, 68 insertions(+), 63 deletions(-) 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 b40c049bc..7d2daa910 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt @@ -12,6 +12,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.utilities.Address import org.session.libsession.utilities.Conversions import org.session.libsession.utilities.Util +import org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK import org.session.libsignal.crypto.kdf.HKDFv3 import org.session.libsignal.utilities.ByteUtil import org.session.libsignal.utilities.Log @@ -243,7 +244,7 @@ object FullBackupImporter { val split = ByteUtil.split(derived, 32, 32) cipherKey = split[0] macKey = split[1] - cipher = Cipher.getInstance("AES/CTR/NoPadding") + cipher = synchronized(CIPHER_LOCK) { Cipher.getInstance("AES/CTR/NoPadding") } mac = Mac.getInstance("HmacSHA256") mac.init(SecretKeySpec(macKey, "HmacSHA256")) counter = Conversions.byteArrayToInt(iv) @@ -269,20 +270,26 @@ object FullBackupImporter { var length = length try { Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - mac.update(iv) - val buffer = ByteArray(8192) - while (length > 0) { - val read = inputStream.read(buffer, 0, Math.min(buffer.size, length)) - if (read == -1) throw IOException("File ended early!") - mac.update(buffer, 0, read) - val plaintext = cipher.update(buffer, 0, read) - if (plaintext != null) { - out.write(plaintext, 0, plaintext.size) + val plaintext = synchronized(CIPHER_LOCK) { + cipher.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(cipherKey, "AES"), + IvParameterSpec(iv) + ) + mac.update(iv) + val buffer = ByteArray(8192) + while (length > 0) { + val read = inputStream.read(buffer, 0, Math.min(buffer.size, length)) + if (read == -1) throw IOException("File ended early!") + mac.update(buffer, 0, read) + val plaintext = cipher.update(buffer, 0, read) + if (plaintext != null) { + out.write(plaintext, 0, plaintext.size) + } + length -= read } - length -= read + cipher.doFinal() } - val plaintext = cipher.doFinal() if (plaintext != null) { out.write(plaintext, 0, plaintext.size) } @@ -325,8 +332,10 @@ object FullBackupImporter { throw IOException("Bad MAC") } Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - val plaintext = cipher.doFinal(frame, 0, frame.size - 10) + val plaintext = synchronized(CIPHER_LOCK) { + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) + cipher.doFinal(frame, 0, frame.size - 10) + } BackupFrame.parseFrom(plaintext) } catch (e: Exception) { when (e) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java index 43e986559..c0372cc7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/KeyStoreHelper.java @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.crypto; +import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK; + import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; @@ -45,44 +47,44 @@ public final class KeyStoreHelper { private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private static final String KEY_ALIAS = "SignalSecret"; - @RequiresApi(Build.VERSION_CODES.M) public static SealedData seal(@NonNull byte[] input) { SecretKey secretKey = getOrCreateKeyStoreEntry(); try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); + synchronized (CIPHER_LOCK) { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] iv = cipher.getIV(); - byte[] data = cipher.doFinal(input); + byte[] iv = cipher.getIV(); + byte[] data = cipher.doFinal(input); - return new SealedData(iv, data); + return new SealedData(iv, data); + } } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } } - @RequiresApi(Build.VERSION_CODES.M) public static byte[] unseal(@NonNull SealedData sealedData) { SecretKey secretKey = getKeyStoreEntry(); try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); + synchronized (CIPHER_LOCK) { + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); - return cipher.doFinal(sealedData.data); + return cipher.doFinal(sealedData.data); + } } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } } - @RequiresApi(Build.VERSION_CODES.M) private static SecretKey getOrCreateKeyStoreEntry() { if (hasKeyStoreEntry()) return getKeyStoreEntry(); else return createKeyStoreEntry(); } - @RequiresApi(Build.VERSION_CODES.M) private static SecretKey createKeyStoreEntry() { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); @@ -99,7 +101,6 @@ public final class KeyStoreHelper { } } - @RequiresApi(Build.VERSION_CODES.M) private static SecretKey getKeyStoreEntry() { KeyStore keyStore = getKeyStore(); @@ -137,7 +138,6 @@ public final class KeyStoreHelper { } } - @RequiresApi(Build.VERSION_CODES.M) private static boolean hasKeyStoreEntry() { try { KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE); @@ -202,7 +202,5 @@ public final class KeyStoreHelper { return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING); } } - } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java b/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java index f0c083ca1..909f19e08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.logging; +import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK; + import androidx.annotation.NonNull; import org.session.libsession.utilities.Conversions; @@ -66,15 +68,17 @@ class LogFile { byte[] plaintext = entry.getBytes(); try { - cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); + synchronized (CIPHER_LOCK) { + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); - int cipherLength = cipher.getOutputSize(plaintext.length); - byte[] ciphertext = ciphertextBuffer.get(cipherLength); - cipherLength = cipher.doFinal(plaintext, 0, plaintext.length, ciphertext); + int cipherLength = cipher.getOutputSize(plaintext.length); + byte[] ciphertext = ciphertextBuffer.get(cipherLength); + cipherLength = cipher.doFinal(plaintext, 0, plaintext.length, ciphertext); - outputStream.write(ivBuffer); - outputStream.write(Conversions.intToByteArray(cipherLength)); - outputStream.write(ciphertext, 0, cipherLength); + outputStream.write(ivBuffer); + outputStream.write(Conversions.intToByteArray(cipherLength)); + outputStream.write(ciphertext, 0, cipherLength); + } outputStream.flush(); } catch (ShortBufferException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { @@ -134,10 +138,11 @@ class LogFile { Util.readFully(inputStream, ciphertext, length); try { - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); - byte[] plaintext = cipher.doFinal(ciphertext, 0, length); - - return new String(plaintext); + synchronized (CIPHER_LOCK) { + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); + byte[] plaintext = cipher.doFinal(ciphertext, 0, length); + return new String(plaintext); + } } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { throw new AssertionError(e); } diff --git a/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherInputStream.java b/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherInputStream.java index 3158d35f7..fd3c8123d 100644 --- a/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherInputStream.java +++ b/libsignal/src/main/java/org/session/libsignal/streams/AttachmentCipherInputStream.java @@ -6,6 +6,8 @@ package org.session.libsignal.streams; +import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK; + import org.session.libsignal.exceptions.InvalidMacException; import org.session.libsignal.exceptions.InvalidMessageException; import org.session.libsignal.utilities.Util; @@ -92,19 +94,15 @@ public class AttachmentCipherInputStream extends FilterInputStream { byte[] iv = new byte[BLOCK_SIZE]; readFully(iv); - this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv)); + synchronized (CIPHER_LOCK) { + this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv)); + } this.done = false; this.totalRead = 0; this.totalDataSize = totalDataSize; - } catch (NoSuchAlgorithmException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } catch (NoSuchPaddingException e) { - throw new AssertionError(e); - } catch (InvalidAlgorithmParameterException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException e) { throw new AssertionError(e); } } @@ -141,15 +139,12 @@ public class AttachmentCipherInputStream extends FilterInputStream { private int readFinal(byte[] buffer, int offset, int length) throws IOException { try { - int flourish = cipher.doFinal(buffer, offset); - - done = true; - return flourish; - } catch (IllegalBlockSizeException e) { - throw new IOException(e); - } catch (BadPaddingException e) { - throw new IOException(e); - } catch (ShortBufferException e) { + synchronized (CIPHER_LOCK) { + int flourish = cipher.doFinal(buffer, offset); + done = true; + return flourish; + } + } catch (IllegalBlockSizeException | ShortBufferException | BadPaddingException e) { throw new IOException(e); } } @@ -234,9 +229,7 @@ public class AttachmentCipherInputStream extends FilterInputStream { throw new InvalidMacException("Digest doesn't match!"); } - } catch (IOException e) { - throw new InvalidMacException(e); - } catch (ArithmeticException e) { + } catch (IOException | ArithmeticException e) { throw new InvalidMacException(e); } catch (NoSuchAlgorithmException e) { throw new AssertionError(e); From 9e6d1e27fc4657170e8dd7c87b64f618f3bd16ff Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 12:33:50 +0930 Subject: [PATCH 049/244] Add comment --- .../src/main/java/org/session/libsignal/crypto/CipherUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java b/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java index d3423a0bd..a6a3808bb 100644 --- a/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java +++ b/libsignal/src/main/java/org/session/libsignal/crypto/CipherUtil.java @@ -1,5 +1,8 @@ package org.session.libsignal.crypto; public class CipherUtil { + // Cipher operations are not thread-safe so we synchronize over them through doFinal to + // prevent crashes with quickly repeated encrypt/decrypt operations + // https://github.com/mozilla-mobile/android-components/issues/5342 public static final Object CIPHER_LOCK = new Object(); } From 6a5d97a0f063f7385f61d18202352e98ad968cf0 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 12:37:46 +0930 Subject: [PATCH 050/244] Fix something --- .../org/thoughtcrime/securesms/backup/FullBackupExporter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 783468eee..5eba9b994 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt @@ -279,7 +279,7 @@ object FullBackupExporter { return false } - private class BackupFrameOutputStream : Closeable, Flushable { + private class BackupFrameOutputStream(outputStream: OutputStream, passphrase: String) : Closeable, Flushable { private val outputStream: OutputStream private var cipher: Cipher @@ -290,7 +290,7 @@ object FullBackupExporter { private var counter: Int = 0 - private constructor(outputStream: OutputStream, passphrase: String) : super() { + init { try { val salt = Util.getSecretBytes(32) val key = BackupUtil.computeBackupKey(passphrase, salt) From 375815c7190444f0a4d49b304095551866ce0cd5 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 5 May 2023 16:51:44 +1000 Subject: [PATCH 051/244] WIP: refactor on jobs using old job table --- .../securesms/jobmanager/JobManager.java | 39 --- .../securesms/jobs/JobManagerFactories.java | 4 +- .../securesms/jobs/LocalBackupJob.java | 83 ------ .../securesms/jobs/LocalBackupJob.kt | 61 ++++ .../securesms/jobs/UpdateApkJob.java | 271 ------------------ .../securesms/jobs/UpdateApkJob.kt | 200 +++++++++++++ .../service/UpdateApkRefreshListener.java | 2 + .../sskenvironment/ProfileManager.kt | 1 + 8 files changed, 266 insertions(+), 395 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java index 5906afd29..abb15d9f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java @@ -78,35 +78,6 @@ public class JobManager implements ConstraintObserver.Notifier { new Chain(this, Collections.singletonList(job)).enqueue(); } - /** - * Begins the creation of a job chain with a single job. - * @see Chain - */ - public Chain startChain(@NonNull Job job) { - return new Chain(this, Collections.singletonList(job)); - } - - /** - * Begins the creation of a job chain with a set of jobs that can be run in parallel. - * @see Chain - */ - public Chain startChain(@NonNull List jobs) { - return new Chain(this, jobs); - } - - /** - * Retrieves a string representing the state of the job queue. Intended for debugging. - */ - public @NonNull String getDebugInfo() { - Future result = executor.submit(jobController::getDebugInfo); - try { - return result.get(); - } catch (ExecutionException | InterruptedException e) { - Log.w(TAG, "Failed to retrieve Job info.", e); - return "Failed to retrieve Job info."; - } - } - /** * Adds a listener to that will be notified when the job queue has been drained. */ @@ -261,16 +232,6 @@ public class JobManager implements ConstraintObserver.Notifier { private Data.Serializer dataSerializer = new JsonDataSerializer(); private JobStorage jobStorage = null; - public @NonNull Builder setJobThreadCount(int jobThreadCount) { - this.jobThreadCount = jobThreadCount; - return this; - } - - public @NonNull Builder setExecutorFactory(@NonNull ExecutorFactory executorFactory) { - this.executorFactory = executorFactory; - return this; - } - public @NonNull Builder setJobFactories(@NonNull Map jobFactories) { this.jobFactories = jobFactories; return this; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index ef73325f3..468bf5836 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -29,9 +29,9 @@ public final class JobManagerFactories { public static Map getJobFactories(@NonNull Application application) { HashMap factoryHashMap = new HashMap() {{ put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); - put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); + put(LocalBackupJob.Companion.getKEY(), new LocalBackupJob.Factory()); put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application)); - put(UpdateApkJob.KEY, new UpdateApkJob.Factory()); + put(UpdateApkJob.Companion.getKEY(), new UpdateApkJob.Factory()); put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory()); }}; factoryKeys.addAll(factoryHashMap.keySet()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java deleted file mode 100644 index e5715db26..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; - -import org.session.libsession.messaging.utilities.Data; -import org.session.libsignal.utilities.NoExternalStorageException; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.BackupFileRecord; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.service.GenericForegroundService; -import org.thoughtcrime.securesms.util.BackupUtil; - -import java.io.IOException; -import java.util.Collections; - -import network.loki.messenger.R; - -public class LocalBackupJob extends BaseJob { - - public static final String KEY = "LocalBackupJob"; - - private static final String TAG = LocalBackupJob.class.getSimpleName(); - - public LocalBackupJob() { - this(new Job.Parameters.Builder() - .setQueue("__LOCAL_BACKUP__") - .setMaxInstances(1) - .setMaxAttempts(3) - .build()); - } - - private LocalBackupJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull - Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws NoExternalStorageException, IOException { - Log.i(TAG, "Executing backup job..."); - - GenericForegroundService.startForegroundTask(context, - context.getString(R.string.LocalBackupJob_creating_backup), - NotificationChannels.BACKUPS, - R.drawable.ic_launcher_foreground); - - // TODO: Maybe create a new backup icon like ic_signal_backup? - - try { - BackupFileRecord record = BackupUtil.createBackupFile(context); - BackupUtil.deleteAllBackupFiles(context, Collections.singletonList(record)); - - } finally { - GenericForegroundService.stopForegroundTask(context); - } - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - return false; - } - - @Override - public void onCanceled() { - } - - public static class Factory implements Job.Factory { - @Override - public @NonNull LocalBackupJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new LocalBackupJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt new file mode 100644 index 000000000..8a71760df --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt @@ -0,0 +1,61 @@ +package org.thoughtcrime.securesms.jobs + +import android.content.Context +import org.session.libsession.messaging.jobs.Job +import org.session.libsession.messaging.jobs.JobDelegate +import org.session.libsession.messaging.utilities.Data +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.service.GenericForegroundService +import org.thoughtcrime.securesms.util.BackupUtil.createBackupFile +import org.thoughtcrime.securesms.util.BackupUtil.deleteAllBackupFiles + +import network.loki.messenger.R + + +class LocalBackupJob:Job { + override var delegate: JobDelegate? = null + override var id: String? = null + override var failureCount: Int = 0 + override val maxFailureCount: Int = 0 + + lateinit var context: Context + + companion object { + val TAG = LocalBackupJob::class.simpleName + val KEY: String = "LocalBackupJob" + } + + override fun execute(dispatcherName: String) { + Log.i(TAG, "Executing backup job...") + + GenericForegroundService.startForegroundTask( + context, + context.getString(R.string.LocalBackupJob_creating_backup), + NotificationChannels.BACKUPS, + R.drawable.ic_launcher_foreground + ) + + // TODO: Maybe create a new backup icon like ic_signal_backup? + try { + val record = createBackupFile(context) + deleteAllBackupFiles(context, listOf(record)) + } finally { + GenericForegroundService.stopForegroundTask(context) + } + } + + override fun serialize(): Data { + return Data.EMPTY + } + + override fun getFactoryKey(): String { + return KEY + } + + class Factory: Job.Factory { + override fun create(data: Data): LocalBackupJob { + return LocalBackupJob() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java deleted file mode 100644 index 5b4ce8d13..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.java +++ /dev/null @@ -1,271 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - - -import android.app.DownloadManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import org.session.libsession.messaging.utilities.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.service.UpdateApkReadyListener; -import org.session.libsession.utilities.FileUtils; -import org.session.libsignal.utilities.Hex; -import org.session.libsignal.utilities.JsonUtil; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.io.FileInputStream; -import java.io.IOException; -import java.security.MessageDigest; - -import network.loki.messenger.BuildConfig; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public class UpdateApkJob extends BaseJob { - - public static final String KEY = "UpdateApkJob"; - - private static final String TAG = UpdateApkJob.class.getSimpleName(); - - public UpdateApkJob() { - this(new Job.Parameters.Builder() - .setQueue("UpdateApkJob") - .addConstraint(NetworkConstraint.KEY) - .setMaxAttempts(3) - .build()); - } - - private UpdateApkJob(@NonNull Job.Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull - Data serialize() { - return Data.EMPTY; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException, PackageManager.NameNotFoundException { - if (!BuildConfig.PLAY_STORE_DISABLED) return; - - Log.i(TAG, "Checking for APK update..."); - - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build(); - - Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) { - throw new IOException("Bad response: " + response.message()); - } - - UpdateDescriptor updateDescriptor = JsonUtil.fromJson(response.body().string(), UpdateDescriptor.class); - byte[] digest = Hex.fromStringCondensed(updateDescriptor.getDigest()); - - Log.i(TAG, "Got descriptor: " + updateDescriptor); - - if (updateDescriptor.getVersionCode() > getVersionCode()) { - DownloadStatus downloadStatus = getDownloadStatus(updateDescriptor.getUrl(), digest); - - Log.i(TAG, "Download status: " + downloadStatus.getStatus()); - - if (downloadStatus.getStatus() == DownloadStatus.Status.COMPLETE) { - Log.i(TAG, "Download status complete, notifying..."); - handleDownloadNotify(downloadStatus.getDownloadId()); - } else if (downloadStatus.getStatus() == DownloadStatus.Status.MISSING) { - Log.i(TAG, "Download status missing, starting download..."); - handleDownloadStart(updateDescriptor.getUrl(), updateDescriptor.getVersionName(), digest); - } - } - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - return e instanceof IOException; - } - - @Override - public void onCanceled() { - Log.w(TAG, "Update check failed"); - } - - private int getVersionCode() throws PackageManager.NameNotFoundException { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - - return packageInfo.versionCode; - } - - private DownloadStatus getDownloadStatus(String uri, byte[] theirDigest) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Query query = new DownloadManager.Query(); - - query.setFilterByStatus(DownloadManager.STATUS_PAUSED | DownloadManager.STATUS_PENDING | DownloadManager.STATUS_RUNNING | DownloadManager.STATUS_SUCCESSFUL); - - long pendingDownloadId = TextSecurePreferences.getUpdateApkDownloadId(context); - byte[] pendingDigest = getPendingDigest(context); - Cursor cursor = downloadManager.query(query); - - try { - DownloadStatus status = new DownloadStatus(DownloadStatus.Status.MISSING, -1); - - while (cursor != null && cursor.moveToNext()) { - int jobStatus = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); - String jobRemoteUri = cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI)); - long downloadId = cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)); - byte[] digest = getDigestForDownloadId(downloadId); - - if (jobRemoteUri != null && jobRemoteUri.equals(uri) && downloadId == pendingDownloadId) { - - if (jobStatus == DownloadManager.STATUS_SUCCESSFUL && - digest != null && pendingDigest != null && - MessageDigest.isEqual(pendingDigest, theirDigest) && - MessageDigest.isEqual(digest, theirDigest)) - { - return new DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId); - } else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) { - status = new DownloadStatus(DownloadStatus.Status.PENDING, downloadId); - } - } - } - - return status; - } finally { - if (cursor != null) cursor.close(); - } - } - - private void handleDownloadStart(String uri, String versionName, byte[] digest) { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(uri)); - - downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); - downloadRequest.setTitle("Downloading Signal update"); - downloadRequest.setDescription("Downloading Signal " + versionName); - downloadRequest.setVisibleInDownloadsUi(false); - downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk"); - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); - - long downloadId = downloadManager.enqueue(downloadRequest); - TextSecurePreferences.setUpdateApkDownloadId(context, downloadId); - TextSecurePreferences.setUpdateApkDigest(context, Hex.toStringCondensed(digest)); - } - - private void handleDownloadNotify(long downloadId) { - Intent intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE); - intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId); - - new UpdateApkReadyListener().onReceive(context, intent); - } - - private @Nullable byte[] getDigestForDownloadId(long downloadId) { - try { - DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - FileInputStream fin = new FileInputStream(downloadManager.openDownloadedFile(downloadId).getFileDescriptor()); - byte[] digest = FileUtils.getFileDigest(fin); - - fin.close(); - - return digest; - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - private @Nullable byte[] getPendingDigest(Context context) { - try { - String encodedDigest = TextSecurePreferences.getUpdateApkDigest(context); - - if (encodedDigest == null) return null; - - return Hex.fromStringCondensed(encodedDigest); - } catch (IOException e) { - Log.w(TAG, e); - return null; - } - } - - private static class UpdateDescriptor { - @JsonProperty - private int versionCode; - - @JsonProperty - private String versionName; - - @JsonProperty - private String url; - - @JsonProperty - private String sha256sum; - - - public int getVersionCode() { - return versionCode; - } - - public String getVersionName() { - return versionName; - } - - public String getUrl() { - return url; - } - - public @NonNull String toString() { - return "[" + versionCode + ", " + versionName + ", " + url + "]"; - } - - public String getDigest() { - return sha256sum; - } - } - - private static class DownloadStatus { - enum Status { - PENDING, - COMPLETE, - MISSING - } - - private final Status status; - private final long downloadId; - - DownloadStatus(Status status, long downloadId) { - this.status = status; - this.downloadId = downloadId; - } - - public Status getStatus() { - return status; - } - - public long getDownloadId() { - return downloadId; - } - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull UpdateApkJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new UpdateApkJob(parameters); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt new file mode 100644 index 000000000..411f01fcd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt @@ -0,0 +1,200 @@ +package org.thoughtcrime.securesms.jobs + +import androidx.annotation.Nullable + +import android.app.DownloadManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.net.Uri +import com.fasterxml.jackson.annotation.JsonProperty +import network.loki.messenger.BuildConfig +import okhttp3.OkHttpClient +import okhttp3.Request +import org.session.libsession.messaging.jobs.Job +import org.session.libsession.messaging.jobs.JobDelegate +import org.session.libsession.messaging.utilities.Data +import org.session.libsession.utilities.FileUtils +import org.session.libsession.utilities.TextSecurePreferences.Companion.getUpdateApkDigest +import org.session.libsession.utilities.TextSecurePreferences.Companion.getUpdateApkDownloadId +import org.session.libsession.utilities.TextSecurePreferences.Companion.setUpdateApkDigest +import org.session.libsession.utilities.TextSecurePreferences.Companion.setUpdateApkDownloadId +import org.session.libsignal.utilities.Hex +import org.session.libsignal.utilities.JsonUtil +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.service.UpdateApkReadyListener +import java.io.FileInputStream +import java.io.IOException +import java.security.MessageDigest + +class UpdateApkJob: Job { + override var delegate: JobDelegate? = null + override var id: String? = null + override var failureCount: Int = 0 + override val maxFailureCount: Int = 0 + + lateinit var context: Context + + companion object { + val TAG = UpdateApkJob::class.simpleName + val KEY: String = "UpdateApkJob" + } + + override fun execute(dispatcherName: String) { + if (!BuildConfig.PLAY_STORE_DISABLED) return + + Log.i(TAG, "Checking for APK update...") + + val client = OkHttpClient() + val request = Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build() + val response = client.newCall(request).execute() + + if (!response.isSuccessful) { + throw IOException("Bad response: " + response.message()) + } + + val updateDescriptor: UpdateDescriptor = JsonUtil.fromJson( + response.body()!!.string(), + UpdateDescriptor::class.java + ) + val digest = Hex.fromStringCondensed(updateDescriptor.digest) + + Log.i( + TAG, + "Got descriptor: $updateDescriptor" + ) + + if (updateDescriptor.versionCode > getVersionCode()) { + val downloadStatus: DownloadStatus = getDownloadStatus(updateDescriptor.url, digest) + Log.i(TAG, "Download status: " + downloadStatus.status) + if (downloadStatus.status == DownloadStatus.Status.COMPLETE) { + Log.i(TAG, "Download status complete, notifying...") + handleDownloadNotify(downloadStatus.downloadId) + } else if (downloadStatus.status == DownloadStatus.Status.MISSING) { + Log.i(TAG, "Download status missing, starting download...") + handleDownloadStart( + updateDescriptor.url, + updateDescriptor.versionName, + digest + ) + } + } + } + + @Throws(PackageManager.NameNotFoundException::class) + private fun getVersionCode(): Int { + val packageManager: PackageManager = context.getPackageManager() + val packageInfo: PackageInfo = packageManager.getPackageInfo(context.getPackageName(), 0) + return packageInfo.versionCode + } + + private fun getDownloadStatus(uri: String, theirDigest: ByteArray): DownloadStatus { + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val query = DownloadManager.Query() + query.setFilterByStatus(DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING or DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_SUCCESSFUL) + val pendingDownloadId = getUpdateApkDownloadId(context) + val pendingDigest = getPendingDigest(context) + val cursor = downloadManager.query(query) + return try { + var status = DownloadStatus(DownloadStatus.Status.MISSING, -1) + while (cursor != null && cursor.moveToNext()) { + val jobStatus = + cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) + val jobRemoteUri = + cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI)) + val downloadId = + cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)) + val digest = getDigestForDownloadId(downloadId) + if (jobRemoteUri != null && jobRemoteUri == uri && downloadId == pendingDownloadId) { + if (jobStatus == DownloadManager.STATUS_SUCCESSFUL && digest != null && pendingDigest != null && + MessageDigest.isEqual(pendingDigest, theirDigest) && + MessageDigest.isEqual(digest, theirDigest) + ) { + return DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId) + } else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) { + status = DownloadStatus(DownloadStatus.Status.PENDING, downloadId) + } + } + } + status + } finally { + cursor?.close() + } + } + + private fun handleDownloadStart(uri: String, versionName: String, digest: ByteArray) { + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val downloadRequest = DownloadManager.Request(Uri.parse(uri)) + downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) + downloadRequest.setTitle("Downloading Signal update") + downloadRequest.setDescription("Downloading Signal $versionName") + downloadRequest.setVisibleInDownloadsUi(false) + downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk") + downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) + val downloadId = downloadManager.enqueue(downloadRequest) + setUpdateApkDownloadId(context, downloadId) + setUpdateApkDigest(context, Hex.toStringCondensed(digest)) + } + + private fun handleDownloadNotify(downloadId: Long) { + val intent = Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE) + intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId) + UpdateApkReadyListener().onReceive(context, intent) + } + + private fun getDigestForDownloadId(downloadId: Long): ByteArray? { + return try { + val downloadManager = + context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val fin = FileInputStream(downloadManager.openDownloadedFile(downloadId).fileDescriptor) + val digest = FileUtils.getFileDigest(fin) + fin.close() + digest + } catch (e: IOException) { + Log.w(TAG, e) + null + } + } + + private fun getPendingDigest(context: Context): ByteArray? { + return try { + val encodedDigest = getUpdateApkDigest(context) ?: return null + Hex.fromStringCondensed(encodedDigest) + } catch (e: IOException) { + Log.w(TAG, e) + null + } + } + + override fun serialize(): Data { + return Data.EMPTY + } + + override fun getFactoryKey(): String { + return KEY + } + + private class UpdateDescriptor( + @JsonProperty("versionCode") @Nullable val versionCode: Int, + @JsonProperty("versionName") @Nullable val versionName: String, + @JsonProperty("url") @Nullable val url: String, + @JsonProperty("sha256sum") @Nullable val digest: String) + { + override fun toString(): String { + return "[$versionCode, $versionName, $url]" + } + } + + private class DownloadStatus(val status: Status, val downloadId: Long) { + enum class Status { + PENDING, COMPLETE, MISSING + } + } + + class Factory: Job.Factory { + override fun create(data: Data): UpdateApkJob { + return UpdateApkJob() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java index 187713df9..ef27916ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.service; import android.content.Context; import android.content.Intent; + +import org.session.libsession.messaging.jobs.JobQueue; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ApplicationContext; diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index c46f75bff..2615eff58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.sskenvironment import android.content.Context import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.ApplicationContext From e9a15941aef5971c5058a55162d1a6fc868fa368 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 5 May 2023 17:47:36 +0930 Subject: [PATCH 052/244] Fix colors --- .../securesms/conversation/v2/messages/VisibleMessageView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 745c91e91..ce6019e4c 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 @@ -303,7 +303,7 @@ class VisibleMessageView : LinearLayout { message.isSyncFailed -> MessageStatusInfo( R.drawable.ic_delivery_status_failed, - resources.getColor(R.color.destructive, context.theme), + context.getColor(R.color.accent_orange), R.string.delivery_status_sync_failed, null ) @@ -316,7 +316,7 @@ class VisibleMessageView : LinearLayout { message.isResyncing -> MessageStatusInfo( R.drawable.ic_delivery_status_sending, - context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_syncing, + context.getColor(R.color.accent_orange), R.string.delivery_status_syncing, context.getString(R.string.AccessibilityId_message_sent_status_syncing) ) message.isRead -> From ec2abffdccc395bc119bcb3624a55f84734f9a53 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 8 May 2023 10:33:39 +0930 Subject: [PATCH 053/244] Remove logs --- .../v2/utilities/ResendMessageUtilities.kt | 4 ---- .../thoughtcrime/securesms/database/Storage.kt | 17 ----------------- .../libsession/messaging/jobs/MessageSendJob.kt | 2 -- .../sending_receiving/MessageSender.kt | 16 ---------------- 4 files changed, 39 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt index 5e6283e1a..e01a75b30 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt @@ -11,16 +11,12 @@ import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient -import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord -private val TAG = ResendMessageUtilities.javaClass.simpleName - object ResendMessageUtilities { fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?, isResync: Boolean = false) { - Log.d(TAG, "resend() called with: context = $context, messageRecord = $messageRecord, userBlindedKey = $userBlindedKey, isResync = $isResync") val recipient: Recipient = messageRecord.recipient val message = VisibleMessage() message.id = messageRecord.getId() 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 77f616026..33896803b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -41,7 +41,6 @@ import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.KeyHelper -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -54,8 +53,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol import java.security.MessageDigest -private val TAG = Storage::class.java.simpleName - class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { @@ -359,8 +356,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, openGroupSentTimestamp: Long, threadId: Long ) { - Log.d(TAG, "updateSentTimestamp() called with: messageID = $messageID, isMms = $isMms, openGroupSentTimestamp = $openGroupSentTimestamp, threadId = $threadId") - if (isMms) { val mmsDb = DatabaseComponent.get(context).mmsDatabase() mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) @@ -371,8 +366,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun markAsSent(timestamp: Long, author: String) { - Log.d(TAG, "markAsSent() called with: timestamp = $timestamp, author = $author") - val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { @@ -385,8 +378,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun markAsSyncing(timestamp: Long, author: String) { - Log.d(TAG, "markAsSyncing() called with: timestamp = $timestamp, author = $author") - DatabaseComponent.get(context).mmsSmsDatabase() .getMessageFor(timestamp, author) ?.run { getMmsDatabaseElseSms(isMms).markAsSyncing(id) } @@ -397,16 +388,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, else DatabaseComponent.get(context).smsDatabase() override fun markAsResyncing(timestamp: Long, author: String) { - Log.d(TAG, "markAsResyncing() called with: timestamp = $timestamp, author = $author") - DatabaseComponent.get(context).mmsSmsDatabase() .getMessageFor(timestamp, author) ?.run { getMmsDatabaseElseSms(isMms).markAsResyncing(id) } } override fun markAsSending(timestamp: Long, author: String) { - Log.d(TAG, "markAsSending() called with: timestamp = $timestamp, author = $author") - val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { @@ -432,8 +419,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun markAsSentFailed(timestamp: Long, author: String, error: Exception) { - Log.d(TAG, "markAsSentFailed() called with: timestamp = $timestamp, author = $author, error = $error") - val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { @@ -457,8 +442,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } override fun markAsSyncFailed(timestamp: Long, author: String, error: Exception) { - Log.d(TAG, "markAsSyncFailed() called with: timestamp = $timestamp, author = $author, error = $error") - val database = DatabaseComponent.get(context).mmsSmsDatabase() val messageRecord = database.getMessageFor(timestamp, author) ?: return 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 4300fe4b6..524338592 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 @@ -34,8 +34,6 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { } override fun execute(dispatcherName: String) { - Log.d(TAG, "MessageSendJob#execute() called with: dispatcherName = $dispatcherName") - val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val message = message as? VisibleMessage val storage = MessagingModuleConfiguration.shared.storage 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 9da6cd8b1..fa0a49a64 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 @@ -39,8 +39,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote -private val TAG = MessageSender::class.java.simpleName - object MessageSender { // Error @@ -64,8 +62,6 @@ object MessageSender { // Convenience fun send(message: Message, destination: Destination, isSyncMessage: Boolean = false): Promise { - Log.d(TAG, "send() called with: message = $message, destination = $destination, isSyncMessage = $isSyncMessage") - return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { sendToOpenGroupDestination(destination, message) } else { @@ -75,8 +71,6 @@ object MessageSender { // One-on-One Chats & Closed Groups private fun sendToSnodeDestination(destination: Destination, message: Message, isSyncMessage: Boolean = false): Promise { - Log.d(TAG, "sendToSnodeDestination() called with: destination = $destination, message = $message, isSyncMessage = $isSyncMessage") - val deferred = deferred() val promise = deferred.promise val storage = MessagingModuleConfiguration.shared.storage @@ -226,8 +220,6 @@ object MessageSender { // Open Groups private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise { - Log.d(TAG, "sendToOpenGroupDestination() called with: destination = $destination, message = $message") - val deferred = deferred() val storage = MessagingModuleConfiguration.shared.storage if (message.sentTimestamp == null) { @@ -326,8 +318,6 @@ object MessageSender { // Result Handling fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { - Log.d(TAG, "handleSuccessfulMessageSend() called with: message = $message, destination = $destination, isSyncMessage = $isSyncMessage, openGroupSentTimestamp = $openGroupSentTimestamp") - val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! // Ignore future self-sends @@ -393,8 +383,6 @@ object MessageSender { } fun handleFailedMessageSend(message: Message, error: Exception, isSyncMessage: Boolean = false) { - Log.d(TAG, "handleFailedMessageSend() called with: message = $message, error = $error, isSyncMessage = $isSyncMessage") - val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! @@ -408,8 +396,6 @@ object MessageSender { // Convenience @JvmStatic fun send(message: VisibleMessage, address: Address, attachments: List, quote: SignalQuote?, linkPreview: SignalLinkPreview?) { - Log.d(TAG, "send() called with: message = $message, address = $address, attachments = $attachments, quote = $quote, linkPreview = $linkPreview") - val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val attachmentIDs = messageDataProvider.getAttachmentIDsFor(message.id!!) message.attachmentIDs.addAll(attachmentIDs) @@ -428,8 +414,6 @@ object MessageSender { @JvmStatic fun send(message: Message, address: Address) { - Log.d(TAG, "send() called with: message = $message, address = $address") - val threadID = MessagingModuleConfiguration.shared.storage.getOrCreateThreadIdFor(address) message.threadID = threadID val destination = Destination.from(address) From 2b48b52df0b819f39558d792fc05aee3261f4f2a Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 8 May 2023 11:18:33 +1000 Subject: [PATCH 054/244] remove unused jobs and wrap up old job refactoring --- .../securesms/ApplicationContext.java | 2 +- .../securesms/jobs/AvatarDownloadJob.java | 133 ---------------- .../securesms/jobs/JobManagerFactories.java | 10 +- .../jobs/PrepareAttachmentAudioExtrasJob.kt | 133 ---------------- .../jobs/RetrieveProfileAvatarJob.java | 144 ------------------ .../jobs/RetrieveProfileAvatarJob.kt | 99 ++++++++++++ 6 files changed, 104 insertions(+), 417 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/PrepareAttachmentAudioExtrasJob.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index a605a8492..b398d021c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -355,7 +355,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private void initializeJobManager() { this.jobManager = new JobManager(this, new JobManager.Configuration.Builder() .setDataSerializer(new JsonDataSerializer()) - .setJobFactories(JobManagerFactories.getJobFactories(this)) + .setJobFactories(JobManagerFactories.getJobFactories()) .setConstraintFactories(JobManagerFactories.getConstraintFactories(this)) .setConstraintObservers(JobManagerFactories.getConstraintObservers(this)) .setJobStorage(new FastJobStorage(jobDatabase)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java deleted file mode 100644 index 0bf7ea24e..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.graphics.Bitmap; - -import androidx.annotation.NonNull; - -import org.session.libsession.messaging.utilities.Data; -import org.session.libsession.utilities.DownloadUtilities; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsignal.exceptions.InvalidMessageException; -import org.session.libsignal.exceptions.NonSuccessfulResponseCodeException; -import org.session.libsignal.messages.SignalServiceAttachmentPointer; -import org.session.libsignal.streams.AttachmentCipherInputStream; -import org.session.libsignal.utilities.Hex; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.database.GroupDatabase; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -public class AvatarDownloadJob extends BaseJob { - - public static final String KEY = "AvatarDownloadJob"; - - private static final String TAG = AvatarDownloadJob.class.getSimpleName(); - - private static final int MAX_AVATAR_SIZE = 20 * 1024 * 1024; - - private static final String KEY_GROUP_ID = "group_id"; - - private String groupId; - - public AvatarDownloadJob(@NonNull String groupId) { - this(new Job.Parameters.Builder() - .addConstraint(NetworkConstraint.KEY) - .setMaxAttempts(10) - .build(), - groupId); - } - - private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull String groupId) { - super(parameters); - this.groupId = groupId; - } - - @Override - public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_GROUP_ID, groupId).build(); - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException { - GroupDatabase database = DatabaseComponent.get(context).groupDatabase(); - Optional record = database.getGroup(groupId); - File attachment = null; - - try { - if (record.isPresent()) { - long avatarId = record.get().getAvatarId(); - String contentType = record.get().getAvatarContentType(); - byte[] key = record.get().getAvatarKey(); - String relay = record.get().getRelay(); - Optional digest = Optional.fromNullable(record.get().getAvatarDigest()); - Optional fileName = Optional.absent(); - String url = record.get().getUrl(); - - if (avatarId == -1 || key == null || url.isEmpty()) { - return; - } - - if (digest.isPresent()) { - Log.i(TAG, "Downloading group avatar with digest: " + Hex.toString(digest.get())); - } - - attachment = File.createTempFile("avatar", "tmp", context.getCacheDir()); - attachment.deleteOnExit(); - - SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), url); - - if (pointer.getUrl().isEmpty()) throw new InvalidMessageException("Missing attachment URL."); - DownloadUtilities.downloadFile(attachment, pointer.getUrl()); - - // Assume we're retrieving an attachment for an open group server if the digest is not set - InputStream inputStream; - if (!pointer.getDigest().isPresent()) { - inputStream = new FileInputStream(attachment); - } else { - inputStream = AttachmentCipherInputStream.createForAttachment(attachment, pointer.getSize().or(0), pointer.getKey(), pointer.getDigest().get()); - } - - Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500); - - database.updateProfilePicture(groupId, avatar); - inputStream.close(); - } - } catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) { - Log.w(TAG, e); - } finally { - if (attachment != null) - attachment.delete(); - } - } - - @Override - public void onCanceled() {} - - @Override - public boolean onShouldRetry(@NonNull Exception exception) { - if (exception instanceof IOException) return true; - return false; - } - - public static final class Factory implements Job.Factory { - @Override - public @NonNull AvatarDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new AvatarDownloadJob(parameters, data.getString(KEY_GROUP_ID)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 468bf5836..910a9c09b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -4,9 +4,9 @@ import android.app.Application; import androidx.annotation.NonNull; +import org.session.libsession.messaging.jobs.Job; import org.thoughtcrime.securesms.jobmanager.Constraint; import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; -import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.CellServiceConstraint; import org.thoughtcrime.securesms.jobmanager.impl.CellServiceConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -28,11 +28,9 @@ public final class JobManagerFactories { public static Map getJobFactories(@NonNull Application application) { HashMap factoryHashMap = new HashMap() {{ - put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); - put(LocalBackupJob.Companion.getKEY(), new LocalBackupJob.Factory()); - put(RetrieveProfileAvatarJob.KEY, new RetrieveProfileAvatarJob.Factory(application)); - put(UpdateApkJob.Companion.getKEY(), new UpdateApkJob.Factory()); - put(PrepareAttachmentAudioExtrasJob.KEY, new PrepareAttachmentAudioExtrasJob.Factory()); + put(LocalBackupJob.Companion.getKEY(), new LocalBackupJob.Factory()); + put(RetrieveProfileAvatarJob.Companion.getKEY(), new RetrieveProfileAvatarJob.Factory()); + put(UpdateApkJob.Companion.getKEY(), new UpdateApkJob.Factory()); }}; factoryKeys.addAll(factoryHashMap.keySet()); return factoryHashMap; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PrepareAttachmentAudioExtrasJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/PrepareAttachmentAudioExtrasJob.kt deleted file mode 100644 index 69794d41b..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PrepareAttachmentAudioExtrasJob.kt +++ /dev/null @@ -1,133 +0,0 @@ -package org.thoughtcrime.securesms.jobs - -import android.os.Build -import org.greenrobot.eventbus.EventBus -import org.session.libsession.messaging.sending_receiving.attachments.Attachment -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachmentAudioExtras -import org.session.libsession.messaging.utilities.Data -import org.session.libsession.utilities.DecodedAudio -import org.session.libsession.utilities.InputStreamMediaDataSource -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.jobmanager.Job -import org.thoughtcrime.securesms.jobs.PrepareAttachmentAudioExtrasJob.AudioExtrasUpdatedEvent -import org.thoughtcrime.securesms.mms.PartAuthority -import java.util.* -import java.util.concurrent.TimeUnit - -/** - * Decodes the audio content of the related attachment entry - * and caches the result with [DatabaseAttachmentAudioExtras] data. - * - * It only process attachments with "audio" mime types. - * - * Due to [DecodedAudio] implementation limitations, it only works for API 23+. - * For any lower targets fake data will be generated. - * - * You can subscribe to [AudioExtrasUpdatedEvent] to be notified about the successful result. - */ -//TODO AC: Rewrite to WorkManager API when -// https://github.com/loki-project/session-android/pull/354 is merged. -class PrepareAttachmentAudioExtrasJob : BaseJob { - - companion object { - private const val TAG = "AttachAudioExtrasJob" - - const val KEY = "PrepareAttachmentAudioExtrasJob" - const val DATA_ATTACH_ID = "attachment_id" - - const val VISUAL_RMS_FRAMES = 32 // The amount of values to be computed for the visualization. - } - - private val attachmentId: AttachmentId - - constructor(attachmentId: AttachmentId) : this(Parameters.Builder() - .setQueue(KEY) - .setLifespan(TimeUnit.DAYS.toMillis(1)) - .build(), - attachmentId) - - private constructor(parameters: Parameters, attachmentId: AttachmentId) : super(parameters) { - this.attachmentId = attachmentId - } - - override fun serialize(): Data { - return Data.Builder().putParcelable(DATA_ATTACH_ID, attachmentId).build(); - } - - override fun getFactoryKey(): String { return KEY - } - - override fun onShouldRetry(e: Exception): Boolean { - return false - } - - override fun onCanceled() { } - - override fun onRun() { - Log.v(TAG, "Processing attachment: $attachmentId") - - val attachDb = DatabaseComponent.get(context).attachmentDatabase() - val attachment = attachDb.getAttachment(attachmentId) - - if (attachment == null) { - throw IllegalStateException("Cannot find attachment with the ID $attachmentId") - } - if (!attachment.contentType.startsWith("audio/")) { - throw IllegalStateException("Attachment $attachmentId is not of audio type.") - } - - // Check if the audio extras already exist. - if (attachDb.getAttachmentAudioExtras(attachmentId) != null) return - - fun extractAttachmentRandomSeed(attachment: Attachment): Int { - return when { - attachment.digest != null -> attachment.digest!!.sum() - attachment.fileName != null -> attachment.fileName.hashCode() - else -> attachment.hashCode() - } - } - - fun generateFakeRms(seed: Int, frames: Int = VISUAL_RMS_FRAMES): ByteArray { - return ByteArray(frames).apply { Random(seed.toLong()).nextBytes(this) } - } - - var rmsValues: ByteArray - var totalDurationMs: Long = DatabaseAttachmentAudioExtras.DURATION_UNDEFINED - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Due to API version incompatibility, we just display some random waveform for older API. - rmsValues = generateFakeRms(extractAttachmentRandomSeed(attachment)) - } else { - try { - @Suppress("BlockingMethodInNonBlockingContext") - val decodedAudio = PartAuthority.getAttachmentStream(context, attachment.dataUri!!).use { - DecodedAudio.create(InputStreamMediaDataSource(it)) - } - rmsValues = decodedAudio.calculateRms(VISUAL_RMS_FRAMES) - totalDurationMs = (decodedAudio.totalDuration / 1000.0).toLong() - } catch (e: Exception) { - Log.w(TAG, "Failed to decode sample values for the audio attachment \"${attachment.fileName}\".", e) - rmsValues = generateFakeRms(extractAttachmentRandomSeed(attachment)) - } - } - - attachDb.setAttachmentAudioExtras(DatabaseAttachmentAudioExtras( - attachmentId, - rmsValues, - totalDurationMs - )) - - EventBus.getDefault().post(AudioExtrasUpdatedEvent(attachmentId)) - } - - class Factory : Job.Factory { - override fun create(parameters: Parameters, data: Data): PrepareAttachmentAudioExtrasJob { - return PrepareAttachmentAudioExtrasJob(parameters, data.getParcelable(DATA_ATTACH_ID, AttachmentId.CREATOR)) - } - } - - /** Gets dispatched once the audio extras have been updated. */ - data class AudioExtrasUpdatedEvent(val attachmentId: AttachmentId) -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java deleted file mode 100644 index 39b775303..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.app.Application; -import android.text.TextUtils; - -import androidx.annotation.NonNull; - -import org.session.libsession.avatars.AvatarHelper; -import org.session.libsession.messaging.utilities.Data; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.DownloadUtilities; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsignal.exceptions.PushNetworkException; -import org.session.libsignal.streams.ProfileCipherInputStream; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.SecureRandom; -import java.util.concurrent.TimeUnit; - -public class RetrieveProfileAvatarJob extends BaseJob { - - public static final String KEY = "RetrieveProfileAvatarJob"; - - private static final String TAG = RetrieveProfileAvatarJob.class.getSimpleName(); - - private static final int MAX_PROFILE_SIZE_BYTES = 10 * 1024 * 1024; - - private static final String KEY_PROFILE_AVATAR = "profile_avatar"; - private static final String KEY_ADDRESS = "address"; - - - private String profileAvatar; - private Recipient recipient; - - public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar) { - this(new Job.Parameters.Builder() - .setQueue("RetrieveProfileAvatarJob" + recipient.getAddress().serialize()) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.HOURS.toMillis(1)) - .setMaxAttempts(2) - .setMaxInstances(1) - .build(), - recipient, - profileAvatar); - } - - private RetrieveProfileAvatarJob(@NonNull Job.Parameters parameters, @NonNull Recipient recipient, String profileAvatar) { - super(parameters); - this.recipient = recipient; - this.profileAvatar = profileAvatar; - } - - @Override - public @NonNull - Data serialize() { - return new Data.Builder() - .putString(KEY_PROFILE_AVATAR, profileAvatar) - .putString(KEY_ADDRESS, recipient.getAddress().serialize()) - .build(); - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public void onRun() throws IOException { - RecipientDatabase database = DatabaseComponent.get(context).recipientDatabase(); - byte[] profileKey = recipient.resolve().getProfileKey(); - - if (profileKey == null || (profileKey.length != 32 && profileKey.length != 16)) { - Log.w(TAG, "Recipient profile key is gone!"); - return; - } - - if (AvatarHelper.avatarFileExists(context, recipient.resolve().getAddress()) && Util.equals(profileAvatar, recipient.resolve().getProfileAvatar())) { - Log.w(TAG, "Already retrieved profile avatar: " + profileAvatar); - return; - } - - if (TextUtils.isEmpty(profileAvatar)) { - Log.w(TAG, "Removing profile avatar for: " + recipient.getAddress().serialize()); - AvatarHelper.delete(context, recipient.getAddress()); - database.setProfileAvatar(recipient, profileAvatar); - return; - } - - File downloadDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); - - try { - DownloadUtilities.downloadFile(downloadDestination, profileAvatar); - InputStream avatarStream = new ProfileCipherInputStream(new FileInputStream(downloadDestination), profileKey); - File decryptDestination = File.createTempFile("avatar", ".jpg", context.getCacheDir()); - - Util.copy(avatarStream, new FileOutputStream(decryptDestination)); - decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.getAddress())); - } finally { - if (downloadDestination != null) downloadDestination.delete(); - } - - if (recipient.isLocalNumber()) { - TextSecurePreferences.setProfileAvatarId(context, new SecureRandom().nextInt()); - } - database.setProfileAvatar(recipient, profileAvatar); - } - - @Override - public boolean onShouldRetry(@NonNull Exception e) { - if (e instanceof PushNetworkException) return true; - return false; - } - - @Override - public void onCanceled() { - } - - public static final class Factory implements Job.Factory { - - private final Application application; - - public Factory(Application application) { - this.application = application; - } - - @Override - public @NonNull RetrieveProfileAvatarJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new RetrieveProfileAvatarJob(parameters, - Recipient.from(application, Address.fromSerialized(data.getString(KEY_ADDRESS)), true), - data.getString(KEY_PROFILE_AVATAR)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt new file mode 100644 index 000000000..a5b0fce2d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt @@ -0,0 +1,99 @@ +package org.thoughtcrime.securesms.jobs + +import android.content.Context +import android.text.TextUtils +import org.session.libsession.avatars.AvatarHelper +import org.session.libsession.messaging.jobs.Job +import org.session.libsession.messaging.jobs.JobDelegate +import org.session.libsession.messaging.utilities.Data +import org.session.libsession.utilities.DownloadUtilities.downloadFile +import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId +import org.session.libsession.utilities.Util.copy +import org.session.libsession.utilities.Util.equals +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.streams.ProfileCipherInputStream +import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.InputStream +import java.security.SecureRandom + +class RetrieveProfileAvatarJob(val profileAvatar: String, val recipientAddress: Address): Job { + override var delegate: JobDelegate? = null + override var id: String? = null + override var failureCount: Int = 0 + override val maxFailureCount: Int = 0 + + lateinit var context: Context + + companion object { + val TAG = RetrieveProfileAvatarJob::class.simpleName + val KEY: String = "RetrieveProfileAvatarJob" + + // Keys used for database storage + private val PROFILE_AVATAR_KEY = "profileAvatar" + private val RECEIPIENT_ADDRESS_KEY = "recipient" + } + + override fun execute(dispatcherName: String) { + val recipient = Recipient.from(context, recipientAddress, true) + val database = get(context).recipientDatabase() + val profileKey = recipient.resolve().profileKey + + if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) { + Log.w(TAG, "Recipient profile key is gone!") + return + } + + if (AvatarHelper.avatarFileExists(context, recipient.resolve().address) && equals(profileAvatar, recipient.resolve().profileAvatar)) { + Log.w(TAG, "Already retrieved profile avatar: $profileAvatar") + return + } + + if (TextUtils.isEmpty(profileAvatar)) { + Log.w(TAG, "Removing profile avatar for: " + recipient.address.serialize()) + AvatarHelper.delete(context, recipient.address) + database.setProfileAvatar(recipient, profileAvatar) + return + } + + val downloadDestination = File.createTempFile("avatar", ".jpg", context.cacheDir) + + try { + downloadFile(downloadDestination, profileAvatar) + val avatarStream: InputStream = ProfileCipherInputStream(FileInputStream(downloadDestination), profileKey) + val decryptDestination = File.createTempFile("avatar", ".jpg", context.cacheDir) + copy(avatarStream, FileOutputStream(decryptDestination)) + decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.address)) + } finally { + downloadDestination.delete() + } + + if (recipient.isLocalNumber) { + setProfileAvatarId(context, SecureRandom().nextInt()) + } + database.setProfileAvatar(recipient, profileAvatar) + } + + override fun serialize(): Data { + return Data.Builder() + .putString(PROFILE_AVATAR_KEY, profileAvatar) + .putString(RECEIPIENT_ADDRESS_KEY, recipientAddress.serialize()) + .build() + } + + override fun getFactoryKey(): String { + return KEY + } + + class Factory: Job.Factory { + override fun create(data: Data): RetrieveProfileAvatarJob { + val profileAvatar = data.getString(PROFILE_AVATAR_KEY) + val recipientAddress = Address.fromSerialized(data.getString(RECEIPIENT_ADDRESS_KEY)) + return RetrieveProfileAvatarJob(profileAvatar, recipientAddress) + } + } +} \ No newline at end of file From 2ceb9e2bf448bf37e7f855de43be87637b4d6607 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 8 May 2023 12:29:21 +1000 Subject: [PATCH 055/244] clean up old job manager --- app/src/main/AndroidManifest.xml | 8 - .../securesms/ApplicationContext.java | 6 - .../securesms/database/Storage.kt | 3 +- .../jobmanager/AlarmManagerScheduler.java | 69 ---- .../jobmanager/CompositeScheduler.java | 22 -- .../jobmanager/ConstraintInstantiator.java | 23 -- .../jobmanager/DependencyInjector.java | 24 -- .../securesms/jobmanager/InAppScheduler.java | 49 --- .../securesms/jobmanager/Job.java | 286 -------------- .../securesms/jobmanager/JobController.java | 354 ------------------ .../securesms/jobmanager/JobInstantiator.java | 5 +- .../securesms/jobmanager/JobLogger.java | 24 -- .../securesms/jobmanager/JobManager.java | 167 +-------- .../securesms/jobmanager/JobRunner.java | 110 ------ .../jobmanager/JobSchedulerScheduler.java | 90 ----- .../securesms/jobmanager/Scheduler.java | 9 - .../thoughtcrime/securesms/jobs/BaseJob.java | 41 -- .../securesms/jobs/FastJobStorage.java | 261 ------------- .../securesms/jobs/JobManagerFactories.java | 2 +- .../service/LocalBackupListener.java | 5 +- .../service/UpdateApkRefreshListener.java | 6 +- .../sskenvironment/ProfileManager.kt | 6 +- .../securesms/jobs/FastJobStorageTest.java | 350 ----------------- 23 files changed, 20 insertions(+), 1900 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/CompositeScheduler.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintInstantiator.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/DependencyInjector.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/InAppScheduler.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobLogger.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobRunner.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/Scheduler.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java delete mode 100644 app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6755addc0..da3be785c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -446,17 +446,9 @@ - - diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index b398d021c..0d5e57352 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -66,9 +66,7 @@ import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.groups.OpenGroupManager; 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; import org.thoughtcrime.securesms.logging.PersistentLogger; @@ -354,11 +352,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private void initializeJobManager() { this.jobManager = new JobManager(this, new JobManager.Configuration.Builder() - .setDataSerializer(new JsonDataSerializer()) - .setJobFactories(JobManagerFactories.getJobFactories()) - .setConstraintFactories(JobManagerFactories.getConstraintFactories(this)) .setConstraintObservers(JobManagerFactories.getConstraintObservers(this)) - .setJobStorage(new FastJobStorage(jobDatabase)) .build()); } 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 0d7ea3352..891df6be8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -75,8 +75,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, Recipient.from(context, it, false) } TextSecurePreferences.setProfilePictureURL(context, newValue) - RetrieveProfileAvatarJob(ourRecipient, newValue) - ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newValue)) + JobQueue.shared.add(RetrieveProfileAvatarJob(newValue, ourRecipient.address)) } override fun getOrGenerateRegistrationID(): Int { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java deleted file mode 100644 index dc1d2afcf..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.app.AlarmManager; -import android.app.Application; -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.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}. - * - * Given that this scheduler is only used when {@link KeepAliveService} is also used (which keeps - * all of the {@link ConstraintObserver}s running), this only needs to schedule future runs in - * situations where all constraints are already met. Otherwise, the {@link ConstraintObserver}s will - * trigger future runs when the constraints are met. - * - * For the same reason, this class also doesn't have to schedule jobs that don't have delays. - * - * Important: Only use on API < 26. - */ -public class AlarmManagerScheduler implements Scheduler { - - private static final String TAG = AlarmManagerScheduler.class.getSimpleName(); - - private final Application application; - - AlarmManagerScheduler(@NonNull Application application) { - this.application = application; - } - - @Override - public void schedule(long delay, @NonNull List constraints) { - if (delay > 0 && Stream.of(constraints).allMatch(Constraint::isMet)) { - setUniqueAlarm(application, System.currentTimeMillis() + delay); - } - } - - private void setUniqueAlarm(@NonNull Context context, long time) { - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - 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, PendingIntent.FLAG_IMMUTABLE)); - - Log.i(TAG, "Set an alarm to retry a job in " + (time - System.currentTimeMillis()) + " ms."); - } - - public static class RetryReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "Received an alarm to retry a job."); - ApplicationContext.getInstance(context).getJobManager().wakeUp(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/CompositeScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/CompositeScheduler.java deleted file mode 100644 index 322366f4f..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/CompositeScheduler.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; - -import java.util.Arrays; -import java.util.List; - -class CompositeScheduler implements Scheduler { - - private final List schedulers; - - CompositeScheduler(@NonNull Scheduler... schedulers) { - this.schedulers = Arrays.asList(schedulers); - } - - @Override - public void schedule(long delay, @NonNull List constraints) { - for (Scheduler scheduler : schedulers) { - scheduler.schedule(delay, constraints); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintInstantiator.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintInstantiator.java deleted file mode 100644 index b0a67e3d1..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintInstantiator.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; - -import java.util.HashMap; -import java.util.Map; - -public class ConstraintInstantiator { - - private final Map constraintFactories; - - ConstraintInstantiator(@NonNull Map constraintFactories) { - this.constraintFactories = new HashMap<>(constraintFactories); - } - - public @NonNull Constraint instantiate(@NonNull String constraintFactoryKey) { - if (constraintFactories.containsKey(constraintFactoryKey)) { - return constraintFactories.get(constraintFactoryKey).create(); - } else { - throw new IllegalStateException("Tried to instantiate a constraint with key '" + constraintFactoryKey + "', but no matching factory was found."); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/DependencyInjector.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/DependencyInjector.java deleted file mode 100644 index c8a266bd8..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/DependencyInjector.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (C) 2014 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.jobmanager; - -/** - * Interface responsible for injecting dependencies into Jobs. - */ -public interface DependencyInjector { - void injectDependencies(Object object); -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/InAppScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/InAppScheduler.java deleted file mode 100644 index b0f314eaa..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/InAppScheduler.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.os.Handler; -import android.os.HandlerThread; -import androidx.annotation.NonNull; - -import com.annimon.stream.Stream; - -import org.session.libsignal.utilities.Log; - -import java.util.List; - -/** - * Schedules future runs on an in-app handler. Intended to be used in combination with a persistent - * {@link Scheduler} to improve responsiveness when the app is open. - * - * This should only schedule runs when all constraints are met. Because this only works when the - * app is foregrounded, jobs that don't have their constraints met will be run when the relevant - * {@link ConstraintObserver} is triggered. - * - * Similarly, this does not need to schedule retries with no delay, as this doesn't provide any - * persistence, and other mechanisms will take care of that. - */ -class InAppScheduler implements Scheduler { - - private static final String TAG = InAppScheduler.class.getSimpleName(); - - private final JobManager jobManager; - private final Handler handler; - - InAppScheduler(@NonNull JobManager jobManager) { - HandlerThread handlerThread = new HandlerThread("InAppScheduler"); - handlerThread.start(); - - this.jobManager = jobManager; - this.handler = new Handler(handlerThread.getLooper()); - } - - @Override - public void schedule(long delay, @NonNull List constraints) { - if (delay > 0 && Stream.of(constraints).allMatch(Constraint::isMet)) { - Log.i(TAG, "Scheduling a retry in " + delay + " ms."); - handler.postDelayed(() -> { - Log.i(TAG, "Triggering a job retry."); - jobManager.wakeUp(); - }, delay); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java deleted file mode 100644 index 990207779..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java +++ /dev/null @@ -1,286 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import org.session.libsession.messaging.utilities.Data; -import org.session.libsignal.utilities.Log; - -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * A durable unit of work. - * - * Jobs have {@link Parameters} that describe the conditions upon when you'd like them to run, how - * often they should be retried, and how long they should be retried for. - * - * Never rely on a specific instance of this class being run. It can be created and destroyed as the - * job is retried. State that you want to save is persisted to a {@link Data} object in - * {@link #serialize()}. Your job is then recreated using a {@link Factory} that you register in - * {@link JobManager.Configuration.Builder#setJobFactories(Map)}, which is given the saved - * {@link Data} bundle. - * - * @deprecated - * use WorkManager - * API instead. - */ -public abstract class Job { - - private static final String TAG = Log.tag(Job.class); - - private final Parameters parameters; - - private String id; - private int runAttempt; - private long nextRunAttemptTime; - - protected Context context; - - public Job(@NonNull Parameters parameters) { - this.parameters = parameters; - } - - public final String getId() { - return id; - } - - public final @NonNull Parameters getParameters() { - return parameters; - } - - public final int getRunAttempt() { - return runAttempt; - } - - public final long getNextRunAttemptTime() { - return nextRunAttemptTime; - } - - /** - * This is already called by {@link JobController} during job submission, but if you ever run a - * job without submitting it to the {@link JobManager}, then you'll need to invoke this yourself. - */ - public final void setContext(@NonNull Context context) { - this.context = context; - } - - /** Should only be invoked by {@link JobController} */ - final void setId(@NonNull String id) { - this.id = id; - } - - /** Should only be invoked by {@link JobController} */ - final void setRunAttempt(int runAttempt) { - this.runAttempt = runAttempt; - } - - /** Should only be invoked by {@link JobController} */ - final void setNextRunAttemptTime(long nextRunAttemptTime) { - this.nextRunAttemptTime = nextRunAttemptTime; - } - - @WorkerThread - final void onSubmit() { - Log.i(TAG, JobLogger.format(this, "onSubmit()")); - onAdded(); - } - - /** - * Called when the job is first submitted to the {@link JobManager}. - */ - @WorkerThread - public void onAdded() { - } - - /** - * Called after a job has run and its determined that a retry is required. - */ - @WorkerThread - public void onRetry() { - } - - /** - * Serialize your job state so that it can be recreated in the future. - */ - public abstract @NonNull Data serialize(); - - /** - * Returns the key that can be used to find the relevant factory needed to create your job. - */ - public abstract @NonNull String getFactoryKey(); - - /** - * Called to do your actual work. - */ - @WorkerThread - public abstract @NonNull Result run(); - - /** - * Called when your job has completely failed. - */ - @WorkerThread - public abstract void onCanceled(); - - public interface Factory { - @NonNull T create(@NonNull Parameters parameters, @NonNull Data data); - } - - public enum Result { - SUCCESS, FAILURE, RETRY - } - - public static final class Parameters { - - public static final int IMMORTAL = -1; - public static final int UNLIMITED = -1; - - private final long createTime; - private final long lifespan; - private final int maxAttempts; - private final long maxBackoff; - private final int maxInstances; - private final String queue; - private final List constraintKeys; - - private Parameters(long createTime, - long lifespan, - int maxAttempts, - long maxBackoff, - int maxInstances, - @Nullable String queue, - @NonNull List constraintKeys) - { - this.createTime = createTime; - this.lifespan = lifespan; - this.maxAttempts = maxAttempts; - this.maxBackoff = maxBackoff; - this.maxInstances = maxInstances; - this.queue = queue; - this.constraintKeys = constraintKeys; - } - - public long getCreateTime() { - return createTime; - } - - public long getLifespan() { - return lifespan; - } - - public int getMaxAttempts() { - return maxAttempts; - } - - public long getMaxBackoff() { - return maxBackoff; - } - - public int getMaxInstances() { - return maxInstances; - } - - public @Nullable String getQueue() { - return queue; - } - - public List getConstraintKeys() { - return constraintKeys; - } - - - public static final class Builder { - - private long createTime = System.currentTimeMillis(); - private long maxBackoff = TimeUnit.SECONDS.toMillis(30); - private long lifespan = IMMORTAL; - private int maxAttempts = 1; - private int maxInstances = UNLIMITED; - private String queue = null; - private List constraintKeys = new LinkedList<>(); - - /** Should only be invoked by {@link JobController} */ - Builder setCreateTime(long createTime) { - this.createTime = createTime; - return this; - } - - /** - * Specify the amount of time this job is allowed to be retried. Defaults to {@link #IMMORTAL}. - */ - public @NonNull Builder setLifespan(long lifespan) { - this.lifespan = lifespan; - return this; - } - - /** - * Specify the maximum number of times you want to attempt this job. Defaults to 1. - */ - public @NonNull Builder setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - return this; - } - - /** - * Specify the longest amount of time to wait between retries. No guarantees that this will - * be respected on API >= 26. - */ - public @NonNull Builder setMaxBackoff(long maxBackoff) { - this.maxBackoff = maxBackoff; - return this; - } - - /** - * Specify the maximum number of instances you'd want of this job at any given time. If - * enqueueing this job would put it over that limit, it will be ignored. - * - * Duplicates are determined by two jobs having the same {@link Job#getFactoryKey()}. - * - * This property is ignored if the job is submitted as part of a {@link JobManager.Chain}. - * - * Defaults to {@link #UNLIMITED}. - */ - public @NonNull Builder setMaxInstances(int maxInstances) { - this.maxInstances = maxInstances; - return this; - } - - /** - * Specify a string representing a queue. All jobs within the same queue are run in a - * serialized fashion -- one after the other, in order of insertion. Failure of a job earlier - * in the queue has no impact on the execution of jobs later in the queue. - */ - public @NonNull Builder setQueue(@Nullable String queue) { - this.queue = queue; - return this; - } - - /** - * Add a constraint via the key that was used to register its factory in - * {@link JobManager.Configuration)}; - */ - public @NonNull Builder addConstraint(@NonNull String constraintKey) { - constraintKeys.add(constraintKey); - return this; - } - - /** - * Set constraints via the key that was used to register its factory in - * {@link JobManager.Configuration)}; - */ - public @NonNull Builder setConstraints(@NonNull List constraintKeys) { - this.constraintKeys.clear(); - this.constraintKeys.addAll(constraintKeys); - return this; - } - - public @NonNull Parameters build() { - return new Parameters(createTime, lifespan, maxAttempts, maxBackoff, maxInstances, queue, constraintKeys); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java deleted file mode 100644 index 33345a03e..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobController.java +++ /dev/null @@ -1,354 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.app.Application; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import com.annimon.stream.Stream; - -import org.session.libsession.messaging.utilities.Data; -import org.session.libsession.utilities.Debouncer; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; -import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -/** - * Manages the queue of jobs. This is the only class that should write to {@link JobStorage} to - * ensure consistency. - */ -class JobController { - - private static final String TAG = JobController.class.getSimpleName(); - - private final Application application; - private final JobStorage jobStorage; - private final JobInstantiator jobInstantiator; - private final ConstraintInstantiator constraintInstantiator; - private final Data.Serializer dataSerializer; - private final Scheduler scheduler; - private final Debouncer debouncer; - private final Callback callback; - private final Set runningJobs; - - JobController(@NonNull Application application, - @NonNull JobStorage jobStorage, - @NonNull JobInstantiator jobInstantiator, - @NonNull ConstraintInstantiator constraintInstantiator, - @NonNull Data.Serializer dataSerializer, - @NonNull Scheduler scheduler, - @NonNull Debouncer debouncer, - @NonNull Callback callback) - { - this.application = application; - this.jobStorage = jobStorage; - this.jobInstantiator = jobInstantiator; - this.constraintInstantiator = constraintInstantiator; - this.dataSerializer = dataSerializer; - this.scheduler = scheduler; - this.debouncer = debouncer; - this.callback = callback; - this.runningJobs = new HashSet<>(); - } - - @WorkerThread - synchronized void init() { - jobStorage.init(); - jobStorage.updateAllJobsToBePending(); - notifyAll(); - } - - synchronized void wakeUp() { - notifyAll(); - } - - @WorkerThread - synchronized void submitNewJobChain(@NonNull List> chain) { - chain = Stream.of(chain).filterNot(List::isEmpty).toList(); - - if (chain.isEmpty()) { - Log.w(TAG, "Tried to submit an empty job chain. Skipping."); - return; - } - - if (chainExceedsMaximumInstances(chain)) { - Job solo = chain.get(0).get(0); - Log.w(TAG, JobLogger.format(solo, "Already at the max instance count of " + solo.getParameters().getMaxInstances() + ". Skipping.")); - return; - } - - insertJobChain(chain); - scheduleJobs(chain.get(0)); - triggerOnSubmit(chain); - notifyAll(); - } - - @WorkerThread - synchronized void onRetry(@NonNull Job job) { - int nextRunAttempt = job.getRunAttempt() + 1; - long nextRunAttemptTime = calculateNextRunAttemptTime(System.currentTimeMillis(), nextRunAttempt, job.getParameters().getMaxBackoff()); - - jobStorage.updateJobAfterRetry(job.getId(), false, nextRunAttempt, nextRunAttemptTime); - - List constraints = Stream.of(jobStorage.getConstraintSpecs(job.getId())) - .map(ConstraintSpec::getFactoryKey) - .map(constraintInstantiator::instantiate) - .toList(); - - - long delay = Math.max(0, nextRunAttemptTime - System.currentTimeMillis()); - - Log.i(TAG, JobLogger.format(job, "Scheduling a retry in " + delay + " ms.")); - scheduler.schedule(delay, constraints); - - notifyAll(); - } - - synchronized void onJobFinished(@NonNull Job job) { - runningJobs.remove(job.getId()); - } - - @WorkerThread - synchronized void onSuccess(@NonNull Job job) { - jobStorage.deleteJob(job.getId()); - notifyAll(); - } - - /** - * @return The list of all dependent jobs that should also be failed. - */ - @WorkerThread - synchronized @NonNull List onFailure(@NonNull Job job) { - List dependents = Stream.of(jobStorage.getDependencySpecsThatDependOnJob(job.getId())) - .map(DependencySpec::getJobId) - .map(jobStorage::getJobSpec) - .withoutNulls() - .map(jobSpec -> { - List constraintSpecs = jobStorage.getConstraintSpecs(jobSpec.getId()); - return createJob(jobSpec, constraintSpecs); - }) - .toList(); - - List all = new ArrayList<>(dependents.size() + 1); - all.add(job); - all.addAll(dependents); - - jobStorage.deleteJobs(Stream.of(all).map(Job::getId).toList()); - - return dependents; - } - - /** - * Retrieves the next job that is eligible for execution. To be 'eligible' means that the job: - * - Has no dependencies - * - Has no unmet constraints - * - * This method will block until a job is available. - * When the job returned from this method has been run, you must call {@link #onJobFinished(Job)}. - */ - @WorkerThread - synchronized @NonNull Job pullNextEligibleJobForExecution() { - try { - Job job; - - while ((job = getNextEligibleJobForExecution()) == null) { - if (runningJobs.isEmpty()) { - debouncer.publish(callback::onEmpty); - } - - wait(); - } - - jobStorage.updateJobRunningState(job.getId(), true); - runningJobs.add(job.getId()); - - return job; - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted."); - throw new AssertionError(e); - } - } - - /** - * Retrieves a string representing the state of the job queue. Intended for debugging. - */ - @WorkerThread - synchronized @NonNull String getDebugInfo() { - List jobs = jobStorage.getAllJobSpecs(); - List constraints = jobStorage.getAllConstraintSpecs(); - List dependencies = jobStorage.getAllDependencySpecs(); - - StringBuilder info = new StringBuilder(); - - info.append("-- Jobs\n"); - if (!jobs.isEmpty()) { - Stream.of(jobs).forEach(j -> info.append(j.toString()).append('\n')); - } else { - info.append("None\n"); - } - - info.append("\n-- Constraints\n"); - if (!constraints.isEmpty()) { - Stream.of(constraints).forEach(c -> info.append(c.toString()).append('\n')); - } else { - info.append("None\n"); - } - - info.append("\n-- Dependencies\n"); - if (!dependencies.isEmpty()) { - Stream.of(dependencies).forEach(d -> info.append(d.toString()).append('\n')); - } else { - info.append("None\n"); - } - - return info.toString(); - } - - @WorkerThread - private boolean chainExceedsMaximumInstances(@NonNull List> chain) { - if (chain.size() == 1 && chain.get(0).size() == 1) { - Job solo = chain.get(0).get(0); - - if (solo.getParameters().getMaxInstances() != Job.Parameters.UNLIMITED && - jobStorage.getJobInstanceCount(solo.getFactoryKey()) >= solo.getParameters().getMaxInstances()) - { - return true; - } - } - return false; - } - - @WorkerThread - private void triggerOnSubmit(@NonNull List> chain) { - Stream.of(chain) - .forEach(list -> Stream.of(list).forEach(job -> { - job.setContext(application); - job.onSubmit(); - })); - } - - @WorkerThread - private void insertJobChain(@NonNull List> chain) { - List fullSpecs = new LinkedList<>(); - List dependsOn = Collections.emptyList(); - - for (List jobList : chain) { - for (Job job : jobList) { - fullSpecs.add(buildFullSpec(job, dependsOn)); - } - dependsOn = jobList; - } - - jobStorage.insertJobs(fullSpecs); - } - - @WorkerThread - private @NonNull FullSpec buildFullSpec(@NonNull Job job, @NonNull List dependsOn) { - String id = UUID.randomUUID().toString(); - - job.setId(id); - job.setRunAttempt(0); - - JobSpec jobSpec = new JobSpec(job.getId(), - job.getFactoryKey(), - job.getParameters().getQueue(), - job.getParameters().getCreateTime(), - job.getNextRunAttemptTime(), - job.getRunAttempt(), - job.getParameters().getMaxAttempts(), - job.getParameters().getMaxBackoff(), - job.getParameters().getLifespan(), - job.getParameters().getMaxInstances(), - dataSerializer.serialize(job.serialize()), - false); - - List constraintSpecs = Stream.of(job.getParameters().getConstraintKeys()) - .map(key -> new ConstraintSpec(jobSpec.getId(), key)) - .toList(); - - List dependencySpecs = Stream.of(dependsOn) - .map(depends -> new DependencySpec(job.getId(), depends.getId())) - .toList(); - - return new FullSpec(jobSpec, constraintSpecs, dependencySpecs); - } - - @WorkerThread - private void scheduleJobs(@NonNull List jobs) { - for (Job job : jobs) { - List constraints = Stream.of(job.getParameters().getConstraintKeys()) - .map(key -> new ConstraintSpec(job.getId(), key)) - .map(ConstraintSpec::getFactoryKey) - .map(constraintInstantiator::instantiate) - .toList(); - - scheduler.schedule(0, constraints); - } - } - - @WorkerThread - private @Nullable Job getNextEligibleJobForExecution() { - List jobSpecs = jobStorage.getPendingJobsWithNoDependenciesInCreatedOrder(System.currentTimeMillis()); - - for (JobSpec jobSpec : jobSpecs) { - List constraintSpecs = jobStorage.getConstraintSpecs(jobSpec.getId()); - List constraints = Stream.of(constraintSpecs) - .map(ConstraintSpec::getFactoryKey) - .map(constraintInstantiator::instantiate) - .toList(); - - if (Stream.of(constraints).allMatch(Constraint::isMet)) { - return createJob(jobSpec, constraintSpecs); - } - } - - return null; - } - - private @NonNull Job createJob(@NonNull JobSpec jobSpec, @NonNull List constraintSpecs) { - Job.Parameters parameters = buildJobParameters(jobSpec, constraintSpecs); - Data data = dataSerializer.deserialize(jobSpec.getSerializedData()); - Job job = jobInstantiator.instantiate(jobSpec.getFactoryKey(), parameters, data); - - job.setId(jobSpec.getId()); - job.setRunAttempt(jobSpec.getRunAttempt()); - job.setNextRunAttemptTime(jobSpec.getNextRunAttemptTime()); - job.setContext(application); - - return job; - } - - private @NonNull Job.Parameters buildJobParameters(@NonNull JobSpec jobSpec, @NonNull List constraintSpecs) { - return new Job.Parameters.Builder() - .setCreateTime(jobSpec.getCreateTime()) - .setLifespan(jobSpec.getLifespan()) - .setMaxAttempts(jobSpec.getMaxAttempts()) - .setQueue(jobSpec.getQueueKey()) - .setConstraints(Stream.of(constraintSpecs).map(ConstraintSpec::getFactoryKey).toList()) - .build(); - } - - private long calculateNextRunAttemptTime(long currentTime, int nextAttempt, long maxBackoff) { - int boundedAttempt = Math.min(nextAttempt, 30); - long exponentialBackoff = (long) Math.pow(2, boundedAttempt) * 1000; - long actualBackoff = Math.min(exponentialBackoff, maxBackoff); - - return currentTime + actualBackoff; - } - - interface Callback { - void onEmpty(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java index 6d1527d13..81e378288 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobmanager; import androidx.annotation.NonNull; +import org.session.libsession.messaging.jobs.Job; import org.session.libsession.messaging.utilities.Data; import java.util.HashMap; @@ -15,9 +16,9 @@ class JobInstantiator { this.jobFactories = new HashMap<>(jobFactories); } - public @NonNull Job instantiate(@NonNull String jobFactoryKey, @NonNull Job.Parameters parameters, @NonNull Data data) { + public @NonNull Job instantiate(@NonNull String jobFactoryKey, @NonNull Data data) { if (jobFactories.containsKey(jobFactoryKey)) { - return jobFactories.get(jobFactoryKey).create(parameters, data); + return jobFactories.get(jobFactoryKey).create(data); } else { throw new IllegalStateException("Tried to instantiate a job with key '" + jobFactoryKey + "', but no matching factory was found."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobLogger.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobLogger.java deleted file mode 100644 index c35f6dc1a..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobLogger.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; -import android.text.TextUtils; - -public class JobLogger { - - public static String format(@NonNull Job job, @NonNull String event) { - return format(job, "", event); - } - - public static String format(@NonNull Job job, @NonNull String extraTag, @NonNull String event) { - String id = job.getId(); - String tag = TextUtils.isEmpty(extraTag) ? "" : "[" + extraTag + "]"; - long timeSinceSubmission = System.currentTimeMillis() - job.getParameters().getCreateTime(); - int runAttempt = job.getRunAttempt() + 1; - String maxAttempts = job.getParameters().getMaxAttempts() == Job.Parameters.UNLIMITED ? "Unlimited" - : String.valueOf(job.getParameters().getMaxAttempts()); - String lifespan = job.getParameters().getLifespan() == Job.Parameters.IMMORTAL ? "Immortal" - : String.valueOf(job.getParameters().getLifespan()) + " ms"; - return String.format("[%s][%s]%s %s (Time Since Submission: %d ms, Lifespan: %s, Run Attempt: %d/%s)", - id, job.getClass().getSimpleName(), tag, event, timeSinceSubmission, lifespan, runAttempt, maxAttempts); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java index abb15d9f7..7bbd7679f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java @@ -6,24 +6,14 @@ import android.os.Build; import androidx.annotation.NonNull; -import org.session.libsession.messaging.utilities.Data; -import org.session.libsession.utilities.Debouncer; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.jobmanager.impl.DefaultExecutorFactory; -import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; -import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; /** * Allows the scheduling of durable jobs that will be run as early as possible. @@ -33,32 +23,13 @@ public class JobManager implements ConstraintObserver.Notifier { private static final String TAG = JobManager.class.getSimpleName(); private final ExecutorService executor; - private final JobController jobController; - private final JobRunner[] jobRunners; private final Set emptyQueueListeners = new CopyOnWriteArraySet<>(); public JobManager(@NonNull Application application, @NonNull Configuration configuration) { this.executor = configuration.getExecutorFactory().newSingleThreadExecutor("JobManager"); - this.jobRunners = new JobRunner[configuration.getJobThreadCount()]; - this.jobController = new JobController(application, - configuration.getJobStorage(), - configuration.getJobInstantiator(), - configuration.getConstraintFactories(), - configuration.getDataSerializer(), - Build.VERSION.SDK_INT < 26 ? new AlarmManagerScheduler(application) - : new CompositeScheduler(new InAppScheduler(this), new JobSchedulerScheduler(application)), - new Debouncer(500), - this::onEmptyQueue); executor.execute(() -> { - jobController.init(); - - for (int i = 0; i < jobRunners.length; i++) { - jobRunners[i] = new JobRunner(application, i + 1, jobController); - jobRunners[i].start(); - } - for (ConstraintObserver constraintObserver : configuration.getConstraintObservers()) { constraintObserver.register(this); } @@ -71,13 +42,6 @@ public class JobManager implements ConstraintObserver.Notifier { }); } - /** - * Enqueues a single job to be run. - */ - public void add(@NonNull Job job) { - new Chain(this, Collections.singletonList(job)).enqueue(); - } - /** * Adds a listener to that will be notified when the job queue has been drained. */ @@ -105,166 +69,45 @@ public class JobManager implements ConstraintObserver.Notifier { /** * Pokes the system to take another pass at the job queue. */ - void wakeUp() { - executor.execute(jobController::wakeUp); - } - - private void enqueueChain(@NonNull Chain chain) { - executor.execute(() -> { - jobController.submitNewJobChain(chain.getJobListChain()); - wakeUp(); - }); - } - - private void onEmptyQueue() { - executor.execute(() -> { - for (EmptyQueueListener listener : emptyQueueListeners) { - listener.onQueueEmpty(); - } - }); - } + void wakeUp() {} public interface EmptyQueueListener { void onQueueEmpty(); } - /** - * Allows enqueuing work that depends on each other. Jobs that appear later in the chain will - * only run after all jobs earlier in the chain have been completed. If a job fails, all jobs - * that occur later in the chain will also be failed. - */ - public static class Chain { - - private final JobManager jobManager; - private final List> jobs; - - private Chain(@NonNull JobManager jobManager, @NonNull List jobs) { - this.jobManager = jobManager; - this.jobs = new LinkedList<>(); - - this.jobs.add(new ArrayList<>(jobs)); - } - - public Chain then(@NonNull Job job) { - return then(Collections.singletonList(job)); - } - - public Chain then(@NonNull List jobs) { - if (!jobs.isEmpty()) { - this.jobs.add(new ArrayList<>(jobs)); - } - return this; - } - - public void enqueue() { - jobManager.enqueueChain(this); - } - - private List> getJobListChain() { - return jobs; - } - } - public static class Configuration { private final ExecutorFactory executorFactory; - private final int jobThreadCount; - private final JobInstantiator jobInstantiator; - private final ConstraintInstantiator constraintInstantiator; private final List constraintObservers; - private final Data.Serializer dataSerializer; - private final JobStorage jobStorage; - private Configuration(int jobThreadCount, - @NonNull ExecutorFactory executorFactory, - @NonNull JobInstantiator jobInstantiator, - @NonNull ConstraintInstantiator constraintInstantiator, - @NonNull List constraintObservers, - @NonNull Data.Serializer dataSerializer, - @NonNull JobStorage jobStorage) + private Configuration(@NonNull ExecutorFactory executorFactory, + @NonNull List constraintObservers) { this.executorFactory = executorFactory; - this.jobThreadCount = jobThreadCount; - this.jobInstantiator = jobInstantiator; - this.constraintInstantiator = constraintInstantiator; this.constraintObservers = constraintObservers; - this.dataSerializer = dataSerializer; - this.jobStorage = jobStorage; - } - - int getJobThreadCount() { - return jobThreadCount; } @NonNull ExecutorFactory getExecutorFactory() { return executorFactory; } - @NonNull JobInstantiator getJobInstantiator() { - return jobInstantiator; - } - - @NonNull - ConstraintInstantiator getConstraintFactories() { - return constraintInstantiator; - } - @NonNull List getConstraintObservers() { return constraintObservers; } - @NonNull Data.Serializer getDataSerializer() { - return dataSerializer; - } - - @NonNull JobStorage getJobStorage() { - return jobStorage; - } - - public static class Builder { private ExecutorFactory executorFactory = new DefaultExecutorFactory(); - private int jobThreadCount = 1; - private Map jobFactories = new HashMap<>(); - private Map constraintFactories = new HashMap<>(); private List constraintObservers = new ArrayList<>(); - private Data.Serializer dataSerializer = new JsonDataSerializer(); - private JobStorage jobStorage = null; - - public @NonNull Builder setJobFactories(@NonNull Map jobFactories) { - this.jobFactories = jobFactories; - return this; - } - - public @NonNull Builder setConstraintFactories(@NonNull Map constraintFactories) { - this.constraintFactories = constraintFactories; - return this; - } public @NonNull Builder setConstraintObservers(@NonNull List constraintObservers) { this.constraintObservers = constraintObservers; return this; } - public @NonNull Builder setDataSerializer(@NonNull Data.Serializer dataSerializer) { - this.dataSerializer = dataSerializer; - return this; - } - - public @NonNull Builder setJobStorage(@NonNull JobStorage jobStorage) { - this.jobStorage = jobStorage; - return this; - } - public @NonNull Configuration build() { - return new Configuration(jobThreadCount, - executorFactory, - new JobInstantiator(jobFactories), - new ConstraintInstantiator(constraintFactories), - new ArrayList<>(constraintObservers), - dataSerializer, - jobStorage); + return new Configuration(executorFactory, + new ArrayList<>(constraintObservers)); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobRunner.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobRunner.java deleted file mode 100644 index 6eadf0fd5..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobRunner.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.app.Application; -import android.os.PowerManager; -import androidx.annotation.NonNull; - -import com.annimon.stream.Stream; - -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.util.WakeLockUtil; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -class JobRunner extends Thread { - - private static final String TAG = JobRunner.class.getSimpleName(); - - private static long WAKE_LOCK_TIMEOUT = TimeUnit.MINUTES.toMillis(10); - - private final Application application; - private final int id; - private final JobController jobController; - - JobRunner(@NonNull Application application, int id, @NonNull JobController jobController) { - super("JobRunner-" + id); - - this.application = application; - this.id = id; - this.jobController = jobController; - } - - @Override - public synchronized void run() { - while (true) { - Job job = jobController.pullNextEligibleJobForExecution(); - Job.Result result = run(job); - - jobController.onJobFinished(job); - - switch (result) { - case SUCCESS: - jobController.onSuccess(job); - break; - case RETRY: - jobController.onRetry(job); - job.onRetry(); - break; - case FAILURE: - List dependents = jobController.onFailure(job); - job.onCanceled(); - Stream.of(dependents).forEach(Job::onCanceled); - break; - } - } - } - - private Job.Result run(@NonNull Job job) { - Log.i(TAG, JobLogger.format(job, String.valueOf(id), "Running job.")); - - if (isJobExpired(job)) { - Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Failing after surpassing its lifespan.")); - return Job.Result.FAILURE; - } - - Job.Result result = null; - PowerManager.WakeLock wakeLock = null; - - try { - wakeLock = WakeLockUtil.acquire(application, PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TIMEOUT, job.getId()); - result = job.run(); - } catch (Exception e) { - Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Failing due to an unexpected exception."), e); - return Job.Result.FAILURE; - } finally { - if (wakeLock != null) { - WakeLockUtil.release(wakeLock, job.getId()); - } - } - - printResult(job, result); - - if (result == Job.Result.RETRY && job.getRunAttempt() + 1 >= job.getParameters().getMaxAttempts() && - job.getParameters().getMaxAttempts() != Job.Parameters.UNLIMITED) - { - Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Failing after surpassing its max number of attempts.")); - return Job.Result.FAILURE; - } - - return result; - } - - private boolean isJobExpired(@NonNull Job job) { - long expirationTime = job.getParameters().getCreateTime() + job.getParameters().getLifespan(); - - if (expirationTime < 0) { - expirationTime = Long.MAX_VALUE; - } - - return job.getParameters().getLifespan() != Job.Parameters.IMMORTAL && expirationTime <= System.currentTimeMillis(); - } - - private void printResult(@NonNull Job job, @NonNull Job.Result result) { - if (result == Job.Result.FAILURE) { - Log.w(TAG, JobLogger.format(job, String.valueOf(id), "Job failed.")); - } else { - Log.i(TAG, JobLogger.format(job, String.valueOf(id), "Job finished with result: " + result)); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java deleted file mode 100644 index 40acbf520..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobSchedulerScheduler.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.app.Application; -import android.app.job.JobInfo; -import android.app.job.JobParameters; -import android.app.job.JobScheduler; -import android.app.job.JobService; -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -import org.thoughtcrime.securesms.ApplicationContext; -import org.session.libsignal.utilities.Log; - -import java.util.List; - -@RequiresApi(26) -public class JobSchedulerScheduler implements Scheduler { - - private static final String TAG = JobSchedulerScheduler.class.getSimpleName(); - - private static final String PREF_NAME = "JobSchedulerScheduler_prefs"; - private static final String PREF_NEXT_ID = "pref_next_id"; - - private static final int MAX_ID = 75; - - private final Application application; - - JobSchedulerScheduler(@NonNull Application application) { - this.application = application; - } - - @RequiresApi(26) - @Override - public void schedule(long delay, @NonNull List constraints) { - JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(getNextId(), new ComponentName(application, SystemService.class)) - .setMinimumLatency(delay) - .setPersisted(true); - - for (Constraint constraint : constraints) { - constraint.applyToJobInfo(jobInfoBuilder); - } - - Log.i(TAG, "Scheduling a run in " + delay + " ms."); - JobScheduler jobScheduler = application.getSystemService(JobScheduler.class); - jobScheduler.schedule(jobInfoBuilder.build()); - } - - private int getNextId() { - SharedPreferences prefs = application.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - int returnedId = prefs.getInt(PREF_NEXT_ID, 0); - int nextId = returnedId + 1 > MAX_ID ? 0 : returnedId + 1; - - prefs.edit().putInt(PREF_NEXT_ID, nextId).apply(); - - return returnedId; - } - - @RequiresApi(api = 26) - public static class SystemService extends JobService { - - @Override - public boolean onStartJob(JobParameters params) { - Log.d(TAG, "onStartJob()"); - - JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager(); - - jobManager.addOnEmptyQueueListener(new JobManager.EmptyQueueListener() { - @Override - public void onQueueEmpty() { - jobManager.removeOnEmptyQueueListener(this); - jobFinished(params, false); - Log.d(TAG, "jobFinished()"); - } - }); - - jobManager.wakeUp(); - - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.d(TAG, "onStopJob()"); - return false; - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Scheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Scheduler.java deleted file mode 100644 index 194acd39b..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/Scheduler.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; - -import java.util.List; - -public interface Scheduler { - void schedule(long delay, @NonNull List constraints); -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java deleted file mode 100644 index 0c11cc552..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/BaseJob.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.JobLogger; -import org.session.libsignal.utilities.Log; - -/** - * @deprecated - * use WorkManager - * API instead. - */ -public abstract class BaseJob extends Job { - - private static final String TAG = BaseJob.class.getSimpleName(); - - public BaseJob(@NonNull Parameters parameters) { - super(parameters); - } - - @Override - public @NonNull Result run() { - try { - onRun(); - return Result.SUCCESS; - } catch (Exception e) { - if (onShouldRetry(e)) { - Log.i(TAG, JobLogger.format(this, "Encountered a retryable exception."), e); - return Result.RETRY; - } else { - Log.w(TAG, JobLogger.format(this, "Encountered a failing exception."), e); - return Result.FAILURE; - } - } - } - - protected abstract void onRun() throws Exception; - - protected abstract boolean onShouldRetry(@NonNull Exception e); -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java deleted file mode 100644 index 3b50dc273..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/FastJobStorage.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.database.JobDatabase; -import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; -import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; - -import org.session.libsession.utilities.Util; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; - -public class FastJobStorage implements JobStorage { - - private final JobDatabase jobDatabase; - - private final List jobs; - private final Map> constraintsByJobId; - private final Map> dependenciesByJobId; - - public FastJobStorage(@NonNull JobDatabase jobDatabase) { - this.jobDatabase = jobDatabase; - this.jobs = new ArrayList<>(); - this.constraintsByJobId = new HashMap<>(); - this.dependenciesByJobId = new HashMap<>(); - } - - @Override - public synchronized void init() { - List jobSpecs = jobDatabase.getAllJobSpecs(); - List constraintSpecs = jobDatabase.getAllConstraintSpecs(); - List dependencySpecs = jobDatabase.getAllDependencySpecs(); - - jobs.addAll(jobSpecs); - - for (ConstraintSpec constraintSpec: constraintSpecs) { - List jobConstraints = Util.getOrDefault(constraintsByJobId, constraintSpec.getJobSpecId(), new LinkedList<>()); - jobConstraints.add(constraintSpec); - constraintsByJobId.put(constraintSpec.getJobSpecId(), jobConstraints); - } - - for (DependencySpec dependencySpec : dependencySpecs) { - List jobDependencies = Util.getOrDefault(dependenciesByJobId, dependencySpec.getJobId(), new LinkedList<>()); - jobDependencies.add(dependencySpec); - dependenciesByJobId.put(dependencySpec.getJobId(), jobDependencies); - } - } - - @Override - public synchronized void insertJobs(@NonNull List fullSpecs) { - jobDatabase.insertJobs(fullSpecs); - - for (FullSpec fullSpec : fullSpecs) { - jobs.add(fullSpec.getJobSpec()); - constraintsByJobId.put(fullSpec.getJobSpec().getId(), fullSpec.getConstraintSpecs()); - dependenciesByJobId.put(fullSpec.getJobSpec().getId(), fullSpec.getDependencySpecs()); - } - } - - @Override - public synchronized @Nullable JobSpec getJobSpec(@NonNull String id) { - for (JobSpec jobSpec : jobs) { - if (jobSpec.getId().equals(id)) { - return jobSpec; - } - } - return null; - } - - @Override - public synchronized @NonNull List getAllJobSpecs() { - return new ArrayList<>(jobs); - } - - @Override - public synchronized @NonNull List getPendingJobsWithNoDependenciesInCreatedOrder(long currentTime) { - return Stream.of(jobs) - .filter(j -> JobManagerFactories.hasFactoryForKey(j.getFactoryKey())) - .filterNot(JobSpec::isRunning) - .filter(this::firstInQueue) - .filter(j -> !dependenciesByJobId.containsKey(j.getId()) || dependenciesByJobId.get(j.getId()).isEmpty()) - .filter(j -> j.getNextRunAttemptTime() <= currentTime) - .sorted((j1, j2) -> Long.compare(j1.getCreateTime(), j2.getCreateTime())) - .toList(); - } - - private boolean firstInQueue(@NonNull JobSpec job) { - if (job.getQueueKey() == null) { - return true; - } - - return Stream.of(jobs) - .filter(j -> Util.equals(j.getQueueKey(), job.getQueueKey())) - .sorted((j1, j2) -> Long.compare(j1.getCreateTime(), j2.getCreateTime())) - .toList() - .get(0) - .equals(job); - } - - @Override - public synchronized int getJobInstanceCount(@NonNull String factoryKey) { - return (int) Stream.of(jobs) - .filter(j -> j.getFactoryKey().equals(factoryKey)) - .count(); - } - - @Override - public synchronized void updateJobRunningState(@NonNull String id, boolean isRunning) { - jobDatabase.updateJobRunningState(id, isRunning); - - ListIterator iter = jobs.listIterator(); - - while (iter.hasNext()) { - JobSpec existing = iter.next(); - if (existing.getId().equals(id)) { - JobSpec updated = new JobSpec(existing.getId(), - existing.getFactoryKey(), - existing.getQueueKey(), - existing.getCreateTime(), - existing.getNextRunAttemptTime(), - existing.getRunAttempt(), - existing.getMaxAttempts(), - existing.getMaxBackoff(), - existing.getLifespan(), - existing.getMaxInstances(), - existing.getSerializedData(), - isRunning); - iter.set(updated); - } - } - } - - @Override - public synchronized void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime) { - jobDatabase.updateJobAfterRetry(id, isRunning, runAttempt, nextRunAttemptTime); - - ListIterator iter = jobs.listIterator(); - - while (iter.hasNext()) { - JobSpec existing = iter.next(); - if (existing.getId().equals(id)) { - JobSpec updated = new JobSpec(existing.getId(), - existing.getFactoryKey(), - existing.getQueueKey(), - existing.getCreateTime(), - nextRunAttemptTime, - runAttempt, - existing.getMaxAttempts(), - existing.getMaxBackoff(), - existing.getLifespan(), - existing.getMaxInstances(), - existing.getSerializedData(), - isRunning); - iter.set(updated); - } - } - } - - @Override - public synchronized void updateAllJobsToBePending() { - jobDatabase.updateAllJobsToBePending(); - - ListIterator iter = jobs.listIterator(); - - while (iter.hasNext()) { - JobSpec existing = iter.next(); - JobSpec updated = new JobSpec(existing.getId(), - existing.getFactoryKey(), - existing.getQueueKey(), - existing.getCreateTime(), - existing.getNextRunAttemptTime(), - existing.getRunAttempt(), - existing.getMaxAttempts(), - existing.getMaxBackoff(), - existing.getLifespan(), - existing.getMaxInstances(), - existing.getSerializedData(), - false); - iter.set(updated); - } - } - - @Override - public synchronized void deleteJob(@NonNull String jobId) { - deleteJobs(Collections.singletonList(jobId)); - } - - @Override - public synchronized void deleteJobs(@NonNull List jobIds) { - jobDatabase.deleteJobs(jobIds); - - Set deleteIds = new HashSet<>(jobIds); - - Iterator jobIter = jobs.iterator(); - while (jobIter.hasNext()) { - if (deleteIds.contains(jobIter.next().getId())) { - jobIter.remove(); - } - } - - for (String jobId : jobIds) { - constraintsByJobId.remove(jobId); - dependenciesByJobId.remove(jobId); - - for (Map.Entry> entry : dependenciesByJobId.entrySet()) { - Iterator depedencyIter = entry.getValue().iterator(); - - while (depedencyIter.hasNext()) { - if (depedencyIter.next().getDependsOnJobId().equals(jobId)) { - depedencyIter.remove(); - } - } - } - } - } - - @Override - public synchronized @NonNull List getConstraintSpecs(@NonNull String jobId) { - return Util.getOrDefault(constraintsByJobId, jobId, new LinkedList<>()); - } - - @Override - public synchronized @NonNull List getAllConstraintSpecs() { - return Stream.of(constraintsByJobId) - .map(Map.Entry::getValue) - .flatMap(Stream::of) - .toList(); - } - - @Override - public synchronized @NonNull List getDependencySpecsThatDependOnJob(@NonNull String jobSpecId) { - return Stream.of(dependenciesByJobId.entrySet()) - .map(Map.Entry::getValue) - .flatMap(Stream::of) - .filter(j -> j.getDependsOnJobId().equals(jobSpecId)) - .toList(); - } - - @Override - public @NonNull List getAllDependencySpecs() { - return Stream.of(dependenciesByJobId) - .map(Map.Entry::getValue) - .flatMap(Stream::of) - .toList(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 910a9c09b..d892c0f43 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -26,7 +26,7 @@ public final class JobManagerFactories { private static Collection factoryKeys = new ArrayList<>(); - public static Map getJobFactories(@NonNull Application application) { + public static Map getJobFactories() { HashMap factoryHashMap = new HashMap() {{ put(LocalBackupJob.Companion.getKEY(), new LocalBackupJob.Factory()); put(RetrieveProfileAvatarJob.Companion.getKEY(), new RetrieveProfileAvatarJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java index 0b1b3f842..9cf88f409 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.service; import android.content.Context; import android.content.Intent; +import org.session.libsession.messaging.jobs.JobQueue; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.jobs.LocalBackupJob; import org.session.libsession.utilities.TextSecurePreferences; @@ -21,7 +22,9 @@ public class LocalBackupListener extends PersistentAlarmManagerListener { @Override protected long onAlarm(Context context, long scheduledTime) { if (TextSecurePreferences.isBackupEnabled(context)) { - ApplicationContext.getInstance(context).getJobManager().add(new LocalBackupJob()); + LocalBackupJob job = new LocalBackupJob(); + job.context = context; + JobQueue.getShared().add(job); } long nextTime = System.currentTimeMillis() + INTERVAL; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java index ef27916ac..01ade9421 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java @@ -31,9 +31,9 @@ public class UpdateApkRefreshListener extends PersistentAlarmManagerListener { if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) { Log.i(TAG, "Queueing APK update job..."); - ApplicationContext.getInstance(context) - .getJobManager() - .add(new UpdateApkJob()); + UpdateApkJob job = new UpdateApkJob(); + job.context = context; + JobQueue.getShared().add(job); } long newTime = System.currentTimeMillis() + INTERVAL; diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 2615eff58..3b6db9a76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -41,9 +41,9 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol { } override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { - val job = RetrieveProfileAvatarJob(recipient, profilePictureURL) - val jobManager = ApplicationContext.getInstance(context).jobManager - jobManager.add(job) + val job = RetrieveProfileAvatarJob(profilePictureURL, recipient.address) + job.context = context + JobQueue.shared.add(job) val sessionID = recipient.address.serialize() val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase() var contact = contactDatabase.getContactWithSessionID(sessionID) diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java b/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java deleted file mode 100644 index a6624189d..000000000 --- a/app/src/test/java/org/thoughtcrime/securesms/jobs/FastJobStorageTest.java +++ /dev/null @@ -1,350 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Application; - -import androidx.annotation.NonNull; - -import com.annimon.stream.Stream; - -import org.junit.Test; -import org.session.libsession.messaging.utilities.Data; -import org.thoughtcrime.securesms.database.JobDatabase; -import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; -import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; -import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class FastJobStorageTest { - - private static final JsonDataSerializer serializer = new JsonDataSerializer(); - private static final String EMPTY_DATA = serializer.serialize(Data.EMPTY); - - @Test - public void init_allStoredDataAvailable() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - - DataSet1.assertJobsMatch(subject.getAllJobSpecs()); - DataSet1.assertConstraintsMatch(subject.getAllConstraintSpecs()); - DataSet1.assertDependenciesMatch(subject.getAllDependencySpecs()); - } - - @Test - public void insertJobs_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.insertJobs(DataSet1.FULL_SPECS); - - verify(database).insertJobs(DataSet1.FULL_SPECS); - } - - @Test - public void insertJobs_dataCanBeFound() { - FastJobStorage subject = new FastJobStorage(noopDatabase()); - - subject.insertJobs(DataSet1.FULL_SPECS); - - DataSet1.assertJobsMatch(subject.getAllJobSpecs()); - DataSet1.assertConstraintsMatch(subject.getAllConstraintSpecs()); - DataSet1.assertDependenciesMatch(subject.getAllDependencySpecs()); - } - - @Test - public void insertJobs_individualJobCanBeFound() { - FastJobStorage subject = new FastJobStorage(noopDatabase()); - - subject.insertJobs(DataSet1.FULL_SPECS); - - assertEquals(DataSet1.JOB_1, subject.getJobSpec(DataSet1.JOB_1.getId())); - assertEquals(DataSet1.JOB_2, subject.getJobSpec(DataSet1.JOB_2.getId())); - } - - @Test - public void updateAllJobsToBePending_allArePending() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", AvatarDownloadJob.KEY, null, 1, 1, 1, 1, 1, 1, 1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - - subject.init(); - subject.updateAllJobsToBePending(); - - assertFalse(subject.getJobSpec("1").isRunning()); - assertFalse(subject.getJobSpec("2").isRunning()); - } - - @Test - public void updateJobRunningState_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.updateJobRunningState("1", true); - - verify(database).updateJobRunningState("1", true); - } - - @Test - public void updateJobRunningState_stateUpdated() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - subject.init(); - - subject.updateJobRunningState(DataSet1.JOB_1.getId(), true); - assertTrue(subject.getJobSpec(DataSet1.JOB_1.getId()).isRunning()); - - subject.updateJobRunningState(DataSet1.JOB_1.getId(), false); - assertFalse(subject.getJobSpec(DataSet1.JOB_1.getId()).isRunning()); - } - - @Test - public void updateJobAfterRetry_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - - subject.updateJobAfterRetry("1", true, 1, 10); - - verify(database).updateJobAfterRetry("1", true, 1, 10); - } - - @Test - public void updateJobAfterRetry_stateUpdated() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, null, 0, 0, 0, 3, 30000, -1, -1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - - subject.init(); - subject.updateJobAfterRetry("1", false, 1, 10); - - JobSpec job = subject.getJobSpec("1"); - - assertNotNull(job); - assertFalse(job.isRunning()); - assertEquals(1, job.getRunAttempt()); - assertEquals(10, job.getNextRunAttemptTime()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenEarlierItemInQueueInRunning() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", AvatarDownloadJob.KEY, "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(1).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenAllJobsAreRunning() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenNextRunTimeIsAfterCurrentTime() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, "q", 0, 10, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_noneWhenDependentOnAnotherJob() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", AvatarDownloadJob.KEY, null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.singletonList(new DependencySpec("2", "1"))); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - assertEquals(0, subject.getPendingJobsWithNoDependenciesInCreatedOrder(0).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJob() { - FullSpec fullSpec = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Collections.singletonList(fullSpec))); - subject.init(); - - assertEquals(1, subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_multipleEligibleJobs() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", AvatarDownloadJob.KEY, null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - assertEquals(2, subject.getPendingJobsWithNoDependenciesInCreatedOrder(10).size()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_singleEligibleJobInMixedList() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", AvatarDownloadJob.KEY, null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, true), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", AvatarDownloadJob.KEY, null, 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(1, jobs.size()); - assertEquals("2", jobs.get(0).getId()); - } - - @Test - public void getPendingJobsWithNoDependenciesInCreatedOrder_firstItemInQueue() { - FullSpec fullSpec1 = new FullSpec(new JobSpec("1", RetrieveProfileAvatarJob.KEY, "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - FullSpec fullSpec2 = new FullSpec(new JobSpec("2", RetrieveProfileAvatarJob.KEY, "q", 0, 0, 0, 0, 0, -1, -1, EMPTY_DATA, false), - Collections.emptyList(), - Collections.emptyList()); - - JobManagerFactories.getJobFactories(mock(Application.class)); - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(Arrays.asList(fullSpec1, fullSpec2))); - subject.init(); - - List jobs = subject.getPendingJobsWithNoDependenciesInCreatedOrder(10); - - assertEquals(1, jobs.size()); - assertEquals("1", jobs.get(0).getId()); - } - - @Test - public void deleteJobs_writesToDatabase() { - JobDatabase database = noopDatabase(); - FastJobStorage subject = new FastJobStorage(database); - List ids = Arrays.asList("1", "2"); - - subject.deleteJobs(ids); - - verify(database).deleteJobs(ids); - } - - @Test - public void deleteJobs_deletesAllRelevantPieces() { - FastJobStorage subject = new FastJobStorage(fixedDataDatabase(DataSet1.FULL_SPECS)); - - subject.init(); - subject.deleteJobs(Collections.singletonList("id1")); - - List jobs = subject.getAllJobSpecs(); - List constraints = subject.getAllConstraintSpecs(); - List dependencies = subject.getAllDependencySpecs(); - - assertEquals(1, jobs.size()); - assertEquals(DataSet1.JOB_2, jobs.get(0)); - assertEquals(1, constraints.size()); - assertEquals(DataSet1.CONSTRAINT_2, constraints.get(0)); - assertEquals(0, dependencies.size()); - } - - - private JobDatabase noopDatabase() { - JobDatabase database = mock(JobDatabase.class); - - when(database.getAllJobSpecs()).thenReturn(Collections.emptyList()); - when(database.getAllConstraintSpecs()).thenReturn(Collections.emptyList()); - when(database.getAllDependencySpecs()).thenReturn(Collections.emptyList()); - - return database; - } - - private JobDatabase fixedDataDatabase(List fullSpecs) { - JobDatabase database = mock(JobDatabase.class); - - when(database.getAllJobSpecs()).thenReturn(Stream.of(fullSpecs).map(FullSpec::getJobSpec).toList()); - when(database.getAllConstraintSpecs()).thenReturn(Stream.of(fullSpecs).map(FullSpec::getConstraintSpecs).flatMap(Stream::of).toList()); - when(database.getAllDependencySpecs()).thenReturn(Stream.of(fullSpecs).map(FullSpec::getDependencySpecs).flatMap(Stream::of).toList()); - - return database; - } - - private static final class DataSet1 { - static final JobSpec JOB_1 = new JobSpec("id1", "f1", "q1", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, false); - static final JobSpec JOB_2 = new JobSpec("id2", "f2", "q2", 1, 2, 3, 4, 5, 6, 7, EMPTY_DATA, false); - static final ConstraintSpec CONSTRAINT_1 = new ConstraintSpec("id1", "f1"); - static final ConstraintSpec CONSTRAINT_2 = new ConstraintSpec("id2", "f2"); - static final DependencySpec DEPENDENCY_2 = new DependencySpec("id2", "id1"); - static final FullSpec FULL_SPEC_1 = new FullSpec(JOB_1, Collections.singletonList(CONSTRAINT_1), Collections.emptyList()); - static final FullSpec FULL_SPEC_2 = new FullSpec(JOB_2, Collections.singletonList(CONSTRAINT_2), Collections.singletonList(DEPENDENCY_2)); - static final List FULL_SPECS = Arrays.asList(FULL_SPEC_1, FULL_SPEC_2); - - static void assertJobsMatch(@NonNull List jobs) { - assertEquals(jobs.size(), 2); - assertTrue(jobs.contains(DataSet1.JOB_1)); - assertTrue(jobs.contains(DataSet1.JOB_1)); - } - - static void assertConstraintsMatch(@NonNull List constraints) { - assertEquals(constraints.size(), 2); - assertTrue(constraints.contains(DataSet1.CONSTRAINT_1)); - assertTrue(constraints.contains(DataSet1.CONSTRAINT_2)); - } - - static void assertDependenciesMatch(@NonNull List dependencies) { - assertEquals(dependencies.size(), 1); - assertTrue(dependencies.contains(DataSet1.DEPENDENCY_2)); - } - } -} From f0715f16e0e49df0161302c79a33fc46d5b546ca Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 8 May 2023 14:07:36 +0930 Subject: [PATCH 056/244] Fix scroll to bottom button always visible if last item is taller than RecyclerView --- .../securesms/conversation/v2/ConversationActivityV2.kt | 5 +---- .../java/org/session/libsession/utilities/ViewUtils.kt | 9 ++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) 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 cabb58308..107627b2c 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 @@ -212,10 +212,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe var searchViewItem: MenuItem? = null private val isScrolledToBottom: Boolean - get() { - val position = layoutManager?.findFirstCompletelyVisibleItemPosition() ?: 0 - return position == 0 - } + get() = binding?.conversationRecyclerView?.isScrolledToBottom ?: true private val layoutManager: LinearLayoutManager? get() { return binding?.conversationRecyclerView?.layoutManager as LinearLayoutManager? } diff --git a/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt b/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt index 5781ea3e2..f67abe8ec 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.TypedValue import androidx.annotation.AttrRes import androidx.annotation.ColorInt +import androidx.recyclerview.widget.RecyclerView @ColorInt fun Context.getColorFromAttr( @@ -13,4 +14,10 @@ fun Context.getColorFromAttr( ): Int { theme.resolveAttribute(attrColor, typedValue, resolveRefs) return typedValue.data -} \ No newline at end of file +} + +val RecyclerView.isScrolledToBottom: Boolean + get() { + val contentHeight = height - (paddingTop + paddingBottom) + return computeVerticalScrollRange() == computeVerticalScrollOffset() + contentHeight + } From b6667b83ce116e3f494a34af991e485c4182c126 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 8 May 2023 14:43:08 +0930 Subject: [PATCH 057/244] Fix scroll to bottom button position when input not visible --- app/src/main/res/layout/activity_conversation_v2.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index d2696a45e..099f9b990 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -137,6 +137,7 @@ android:layout_height="50dp" android:layout_alignParentEnd="true" android:layout_above="@+id/messageRequestBar" + android:layout_alignWithParentIfMissing="true" android:layout_marginEnd="12dp" android:layout_marginBottom="32dp"> From b494088c3db0e09a3dc3b8df35e8d969f2b31f83 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Mon, 8 May 2023 17:12:20 +1000 Subject: [PATCH 058/244] WIP: further refactor on old jobs --- .../securesms/ApplicationContext.java | 14 --- .../securesms/database/Storage.kt | 3 +- .../jobmanager/ConstraintObserver.java | 12 -- .../securesms/jobmanager/JobInstantiator.java | 26 ---- .../securesms/jobmanager/JobManager.java | 114 ------------------ .../impl/CellServiceConstraint.java | 48 -------- .../impl/CellServiceConstraintObserver.java | 38 ------ .../impl/NetworkConstraintObserver.java | 36 ------ .../impl/NetworkOrCellServiceConstraint.java | 48 -------- .../impl/SqlCipherMigrationConstraint.java | 48 -------- .../SqlCipherMigrationConstraintObserver.java | 32 ----- .../securesms/jobs/JobManagerFactories.java | 57 --------- .../sskenvironment/ProfileManager.kt | 3 +- .../libsession/database/StorageProtocol.kt | 1 + .../jobs/RetrieveProfileAvatarJob.kt | 14 +-- 15 files changed, 9 insertions(+), 485 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintObserver.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraint.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraintObserver.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkOrCellServiceConstraint.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java rename {app/src/main/java/org/thoughtcrime/securesms => libsession/src/main/java/org/session/libsession/messaging}/jobs/RetrieveProfileAvatarJob.kt (90%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 0d5e57352..df1bed7b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -65,9 +65,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseModule; import org.thoughtcrime.securesms.emoji.EmojiSource; import org.thoughtcrime.securesms.groups.OpenGroupManager; import org.thoughtcrime.securesms.home.HomeActivity; -import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.jobs.JobManagerFactories; import org.thoughtcrime.securesms.logging.AndroidLogger; import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; @@ -130,7 +128,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private ExpiringMessageManager expiringMessageManager; private TypingStatusRepository typingStatusRepository; private TypingStatusSender typingStatusSender; - private JobManager jobManager; private ReadReceiptManager readReceiptManager; private ProfileManager profileManager; public MessageNotifier messageNotifier = null; @@ -227,7 +224,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO initializeProfileManager(); initializePeriodicTasks(); SSKEnvironment.Companion.configure(getTypingStatusRepository(), getReadReceiptManager(), getProfileManager(), messageNotifier, getExpiringMessageManager()); - initializeJobManager(); initializeWebRtc(); initializeBlobProvider(); resubmitProfilePictureIfNeeded(); @@ -284,10 +280,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO LocaleParser.Companion.configure(new LocaleParseHelper()); } - public JobManager getJobManager() { - return jobManager; - } - public ExpiringMessageManager getExpiringMessageManager() { return expiringMessageManager; } @@ -350,12 +342,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler)); } - private void initializeJobManager() { - this.jobManager = new JobManager(this, new JobManager.Configuration.Builder() - .setConstraintObservers(JobManagerFactories.getConstraintObservers(this)) - .build()); - } - private void initializeExpiringMessageManager() { this.expiringMessageManager = new ExpiringMessageManager(this); } 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 891df6be8..3e21f63c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -42,13 +42,12 @@ import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.guava.Optional -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.groups.OpenGroupManager -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob +import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol import java.security.MessageDigest diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintObserver.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintObserver.java deleted file mode 100644 index fd7f4fd43..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ConstraintObserver.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; - -public interface ConstraintObserver { - - void register(@NonNull Notifier notifier); - - interface Notifier { - void onConstraintMet(@NonNull String reason); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java deleted file mode 100644 index 81e378288..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobInstantiator.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; - -import org.session.libsession.messaging.jobs.Job; -import org.session.libsession.messaging.utilities.Data; - -import java.util.HashMap; -import java.util.Map; - -class JobInstantiator { - - private final Map jobFactories; - - JobInstantiator(@NonNull Map jobFactories) { - this.jobFactories = new HashMap<>(jobFactories); - } - - public @NonNull Job instantiate(@NonNull String jobFactoryKey, @NonNull Data data) { - if (jobFactories.containsKey(jobFactoryKey)) { - return jobFactories.get(jobFactoryKey).create(data); - } else { - throw new IllegalStateException("Tried to instantiate a job with key '" + jobFactoryKey + "', but no matching factory was found."); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java deleted file mode 100644 index 7bbd7679f..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/JobManager.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import android.app.Application; -import android.content.Intent; -import android.os.Build; - -import androidx.annotation.NonNull; - -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.jobmanager.impl.DefaultExecutorFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.ExecutorService; - -/** - * Allows the scheduling of durable jobs that will be run as early as possible. - */ -public class JobManager implements ConstraintObserver.Notifier { - - private static final String TAG = JobManager.class.getSimpleName(); - - private final ExecutorService executor; - - private final Set emptyQueueListeners = new CopyOnWriteArraySet<>(); - - public JobManager(@NonNull Application application, @NonNull Configuration configuration) { - this.executor = configuration.getExecutorFactory().newSingleThreadExecutor("JobManager"); - - executor.execute(() -> { - for (ConstraintObserver constraintObserver : configuration.getConstraintObservers()) { - constraintObserver.register(this); - } - - if (Build.VERSION.SDK_INT < 26) { - application.startService(new Intent(application, KeepAliveService.class)); - } - - wakeUp(); - }); - } - - /** - * Adds a listener to that will be notified when the job queue has been drained. - */ - void addOnEmptyQueueListener(@NonNull EmptyQueueListener listener) { - executor.execute(() -> { - emptyQueueListeners.add(listener); - }); - } - - /** - * Removes a listener that was added via {@link #addOnEmptyQueueListener(EmptyQueueListener)}. - */ - void removeOnEmptyQueueListener(@NonNull EmptyQueueListener listener) { - executor.execute(() -> { - emptyQueueListeners.remove(listener); - }); - } - - @Override - public void onConstraintMet(@NonNull String reason) { - Log.i(TAG, "onConstraintMet(" + reason + ")"); - wakeUp(); - } - - /** - * Pokes the system to take another pass at the job queue. - */ - void wakeUp() {} - - public interface EmptyQueueListener { - void onQueueEmpty(); - } - - public static class Configuration { - - private final ExecutorFactory executorFactory; - private final List constraintObservers; - - private Configuration(@NonNull ExecutorFactory executorFactory, - @NonNull List constraintObservers) - { - this.executorFactory = executorFactory; - this.constraintObservers = constraintObservers; - } - - @NonNull ExecutorFactory getExecutorFactory() { - return executorFactory; - } - - @NonNull List getConstraintObservers() { - return constraintObservers; - } - - public static class Builder { - - private ExecutorFactory executorFactory = new DefaultExecutorFactory(); - private List constraintObservers = new ArrayList<>(); - - public @NonNull Builder setConstraintObservers(@NonNull List constraintObservers) { - this.constraintObservers = constraintObservers; - return this; - } - - public @NonNull Configuration build() { - return new Configuration(executorFactory, - new ArrayList<>(constraintObservers)); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraint.java deleted file mode 100644 index 6d6fc0499..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraint.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import android.app.Application; -import android.app.job.JobInfo; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.Constraint; -import org.thoughtcrime.securesms.sms.TelephonyServiceState; - -public class CellServiceConstraint implements Constraint { - - public static final String KEY = "CellServiceConstraint"; - - private final Application application; - - public CellServiceConstraint(@NonNull Application application) { - this.application = application; - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public boolean isMet() { - TelephonyServiceState telephonyServiceState = new TelephonyServiceState(); - return telephonyServiceState.isConnected(application); - } - - @Override - public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) { - } - - public static final class Factory implements Constraint.Factory { - - private final Application application; - - public Factory(@NonNull Application application) { - this.application = application; - } - - @Override - public CellServiceConstraint create() { - return new CellServiceConstraint(application); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraintObserver.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraintObserver.java deleted file mode 100644 index fd0971dc5..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/CellServiceConstraintObserver.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import android.app.Application; -import android.content.Context; -import androidx.annotation.NonNull; -import android.telephony.PhoneStateListener; -import android.telephony.ServiceState; -import android.telephony.TelephonyManager; - -import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; - -public class CellServiceConstraintObserver implements ConstraintObserver { - - private static final String REASON = CellServiceConstraintObserver.class.getSimpleName(); - - private Notifier notifier; - - public CellServiceConstraintObserver(@NonNull Application application) { - TelephonyManager telephonyManager = (TelephonyManager) application.getSystemService(Context.TELEPHONY_SERVICE); - ServiceStateListener serviceStateListener = new ServiceStateListener(); - - telephonyManager.listen(serviceStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); - } - - @Override - public void register(@NonNull Notifier notifier) { - this.notifier = notifier; - } - - private class ServiceStateListener extends PhoneStateListener { - @Override - public void onServiceStateChanged(ServiceState serviceState) { - if (serviceState.getState() == ServiceState.STATE_IN_SERVICE && notifier != null) { - notifier.onConstraintMet(REASON); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java deleted file mode 100644 index ef4a61c7c..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkConstraintObserver.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import android.app.Application; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; - -public class NetworkConstraintObserver implements ConstraintObserver { - - private static final String REASON = NetworkConstraintObserver.class.getSimpleName(); - - private final Application application; - - public NetworkConstraintObserver(Application application) { - this.application = application; - } - - @Override - public void register(@NonNull Notifier notifier) { - application.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - NetworkConstraint constraint = new NetworkConstraint.Factory(application).create(); - - if (constraint.isMet()) { - notifier.onConstraintMet(REASON); - } - } - }, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkOrCellServiceConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkOrCellServiceConstraint.java deleted file mode 100644 index c17931f97..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/NetworkOrCellServiceConstraint.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import android.app.Application; -import android.app.job.JobInfo; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.Constraint; - -public class NetworkOrCellServiceConstraint implements Constraint { - - public static final String KEY = "NetworkOrCellServiceConstraint"; - - private final NetworkConstraint networkConstraint; - private final CellServiceConstraint serviceConstraint; - - public NetworkOrCellServiceConstraint(@NonNull Application application) { - networkConstraint = new NetworkConstraint.Factory(application).create(); - serviceConstraint = new CellServiceConstraint.Factory(application).create(); - } - - @Override - public @NonNull String getFactoryKey() { - return KEY; - } - - @Override - public boolean isMet() { - return networkConstraint.isMet() || serviceConstraint.isMet(); - } - - @Override - public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) { - } - - public static class Factory implements Constraint.Factory { - - private final Application application; - - public Factory(@NonNull Application application) { - this.application = application; - } - - @Override - public NetworkOrCellServiceConstraint create() { - return new NetworkOrCellServiceConstraint(application); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java deleted file mode 100644 index 32fa84b6f..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import android.app.Application; -import android.app.job.JobInfo; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.Constraint; -import org.session.libsession.utilities.TextSecurePreferences; - -public class SqlCipherMigrationConstraint implements Constraint { - - public static final String KEY = "SqlCipherMigrationConstraint"; - - private final Application application; - - private SqlCipherMigrationConstraint(@NonNull Application application) { - this.application = application; - } - - @Override - public boolean isMet() { - return !TextSecurePreferences.getNeedsSqlCipherMigration(application); - } - - @NonNull - @Override - public String getFactoryKey() { - return KEY; - } - - @Override - public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) { - } - - public static final class Factory implements Constraint.Factory { - - private final Application application; - - public Factory(@NonNull Application application) { - this.application = application; - } - - @Override - public SqlCipherMigrationConstraint create() { - return new SqlCipherMigrationConstraint(application); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java deleted file mode 100644 index 0c9225434..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import androidx.annotation.NonNull; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; - -public class SqlCipherMigrationConstraintObserver implements ConstraintObserver { - - private static final String REASON = SqlCipherMigrationConstraintObserver.class.getSimpleName(); - - private Notifier notifier; - - public SqlCipherMigrationConstraintObserver() { - EventBus.getDefault().register(this); - } - - @Override - public void register(@NonNull Notifier notifier) { - this.notifier = notifier; - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(SqlCipherNeedsMigrationEvent event) { - if (notifier != null) notifier.onConstraintMet(REASON); - } - - public static class SqlCipherNeedsMigrationEvent { - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java deleted file mode 100644 index d892c0f43..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.app.Application; - -import androidx.annotation.NonNull; - -import org.session.libsession.messaging.jobs.Job; -import org.thoughtcrime.securesms.jobmanager.Constraint; -import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; -import org.thoughtcrime.securesms.jobmanager.impl.CellServiceConstraint; -import org.thoughtcrime.securesms.jobmanager.impl.CellServiceConstraintObserver; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraintObserver; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; -import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; -import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public final class JobManagerFactories { - - private static Collection factoryKeys = new ArrayList<>(); - - public static Map getJobFactories() { - HashMap factoryHashMap = new HashMap() {{ - put(LocalBackupJob.Companion.getKEY(), new LocalBackupJob.Factory()); - put(RetrieveProfileAvatarJob.Companion.getKEY(), new RetrieveProfileAvatarJob.Factory()); - put(UpdateApkJob.Companion.getKEY(), new UpdateApkJob.Factory()); - }}; - factoryKeys.addAll(factoryHashMap.keySet()); - return factoryHashMap; - } - - public static Map getConstraintFactories(@NonNull Application application) { - return new HashMap() {{ - put(CellServiceConstraint.KEY, new CellServiceConstraint.Factory(application)); - put(NetworkConstraint.KEY, new NetworkConstraint.Factory(application)); - put(NetworkOrCellServiceConstraint.KEY, new NetworkOrCellServiceConstraint.Factory(application)); - put(SqlCipherMigrationConstraint.KEY, new SqlCipherMigrationConstraint.Factory(application)); - }}; - } - - public static List getConstraintObservers(@NonNull Application application) { - return Arrays.asList(new CellServiceConstraintObserver(application), - new NetworkConstraintObserver(application), - new SqlCipherMigrationConstraintObserver()); - } - - public static boolean hasFactoryForKey(String factoryKey) { - return factoryKeys.contains(factoryKey); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 3b6db9a76..08ca6090b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -5,9 +5,8 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob +import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob class ProfileManager : SSKEnvironment.ProfileManagerProtocol { 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 d75e209d1..8ae944274 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -38,6 +38,7 @@ interface StorageProtocol { fun getUserX25519KeyPair(): ECKeyPair fun getUserProfile(): Profile fun setUserProfilePictureURL(newProfilePicture: String) + fun // Signal fun getOrGenerateRegistrationID(): Int diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt similarity index 90% rename from app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt rename to libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt index a5b0fce2d..f00ba49a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt @@ -1,10 +1,9 @@ -package org.thoughtcrime.securesms.jobs +package org.session.libsession.messaging.jobs import android.content.Context import android.text.TextUtils import org.session.libsession.avatars.AvatarHelper -import org.session.libsession.messaging.jobs.Job -import org.session.libsession.messaging.jobs.JobDelegate +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.utilities.Data import org.session.libsession.utilities.DownloadUtilities.downloadFile import org.session.libsession.utilities.TextSecurePreferences.Companion.setProfileAvatarId @@ -27,8 +26,6 @@ class RetrieveProfileAvatarJob(val profileAvatar: String, val recipientAddress: override var failureCount: Int = 0 override val maxFailureCount: Int = 0 - lateinit var context: Context - companion object { val TAG = RetrieveProfileAvatarJob::class.simpleName val KEY: String = "RetrieveProfileAvatarJob" @@ -39,8 +36,9 @@ class RetrieveProfileAvatarJob(val profileAvatar: String, val recipientAddress: } override fun execute(dispatcherName: String) { + val context = MessagingModuleConfiguration.shared.context + val storage = MessagingModuleConfiguration.shared.storage val recipient = Recipient.from(context, recipientAddress, true) - val database = get(context).recipientDatabase() val profileKey = recipient.resolve().profileKey if (profileKey == null || (profileKey.size != 32 && profileKey.size != 16)) { @@ -56,7 +54,7 @@ class RetrieveProfileAvatarJob(val profileAvatar: String, val recipientAddress: if (TextUtils.isEmpty(profileAvatar)) { Log.w(TAG, "Removing profile avatar for: " + recipient.address.serialize()) AvatarHelper.delete(context, recipient.address) - database.setProfileAvatar(recipient, profileAvatar) + storage.setProfileAvatar(recipient, profileAvatar) return } @@ -75,7 +73,7 @@ class RetrieveProfileAvatarJob(val profileAvatar: String, val recipientAddress: if (recipient.isLocalNumber) { setProfileAvatarId(context, SecureRandom().nextInt()) } - database.setProfileAvatar(recipient, profileAvatar) + storage.setProfileAvatar(recipient, profileAvatar) } override fun serialize(): Data { From 8d38d1c0fbbd7f3685ec7d8a53b138a5634ab482 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 9 May 2023 13:25:32 +0930 Subject: [PATCH 059/244] Fix links not working when message is partially offscreen --- .../securesms/conversation/v2/utilities/TextUtilities.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt index 800ace54c..7a47b9275 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/TextUtilities.kt @@ -38,13 +38,12 @@ object TextUtilities { fun TextView.getIntersectedModalSpans(hitRect: Rect): List { val textLayout = layout ?: return emptyList() val lineRect = Rect() - val bodyTextRect = Rect() - getGlobalVisibleRect(bodyTextRect) + val offset = intArrayOf(0, 0).also { getLocationOnScreen(it) } val textSpannable = text.toSpannable() return (0 until textLayout.lineCount).flatMap { line -> textLayout.getLineBounds(line, lineRect) - lineRect.offset(bodyTextRect.left + totalPaddingLeft, bodyTextRect.top + totalPaddingTop) - if ((Rect(lineRect)).contains(hitRect)) { + lineRect.offset(offset[0] + totalPaddingLeft, offset[1] + totalPaddingTop) + if (lineRect.contains(hitRect)) { // calculate the url span intersected with (if any) val off = textLayout.getOffsetForHorizontal(line, hitRect.left.toFloat()) // left and right will be the same textSpannable.getSpans(off, off).toList() From fa71ea1850e0d3444eac4176df2ac82002b53f96 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Tue, 9 May 2023 14:14:49 +1000 Subject: [PATCH 060/244] make RetrieveProfileAvatarJob work --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 5 +++++ .../thoughtcrime/securesms/sskenvironment/ProfileManager.kt | 1 - .../java/org/session/libsession/database/StorageProtocol.kt | 2 +- .../java/org/session/libsession/messaging/jobs/JobQueue.kt | 2 ++ .../libsession/messaging/jobs/RetrieveProfileAvatarJob.kt | 2 -- 5 files changed, 8 insertions(+), 4 deletions(-) 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 3e21f63c0..54d36af89 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -77,6 +77,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, JobQueue.shared.add(RetrieveProfileAvatarJob(newValue, ourRecipient.address)) } + override fun setProfileAvatar(recipient: Recipient, profileAvatar: String) { + val database = DatabaseComponent.get(context).recipientDatabase() + database.setProfileAvatar(recipient, profileAvatar) + } + override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index 08ca6090b..f9f5524ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -41,7 +41,6 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol { override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { val job = RetrieveProfileAvatarJob(profilePictureURL, recipient.address) - job.context = context JobQueue.shared.add(job) val sessionID = recipient.address.serialize() val contactDatabase = DatabaseComponent.get(context).sessionContactDatabase() 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 8ae944274..0727d1ab5 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -38,7 +38,7 @@ interface StorageProtocol { fun getUserX25519KeyPair(): ECKeyPair fun getUserProfile(): Profile fun setUserProfilePictureURL(newProfilePicture: String) - fun + fun setProfileAvatar(recipient: Recipient, profileAvatar: String) // Signal fun getOrGenerateRegistrationID(): Int 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 b78590c72..03b9546c4 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 @@ -125,6 +125,7 @@ class JobQueue : JobDelegate { is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> { txQueue.send(job) } + is RetrieveProfileAvatarJob, is AttachmentDownloadJob -> { mediaQueue.send(job) } @@ -224,6 +225,7 @@ class JobQueue : JobDelegate { GroupAvatarDownloadJob.KEY, BackgroundGroupAddJob.KEY, OpenGroupDeleteJob.KEY, + RetrieveProfileAvatarJob.KEY, ) allJobTypes.forEach { type -> resumePendingJobs(type) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt index f00ba49a5..8ce397a3a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/RetrieveProfileAvatarJob.kt @@ -1,6 +1,5 @@ package org.session.libsession.messaging.jobs -import android.content.Context import android.text.TextUtils import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.MessagingModuleConfiguration @@ -13,7 +12,6 @@ import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.streams.ProfileCipherInputStream import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import java.io.File import java.io.FileInputStream import java.io.FileOutputStream From c8fa2d8d6e2806c5c1de493552281c9887498311 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 9 May 2023 14:51:28 +0930 Subject: [PATCH 061/244] Remove reply from context menu when you can't write --- .../securesms/conversation/v2/ConversationReactionOverlay.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java index 1d81325e0..b311327a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java @@ -660,7 +660,8 @@ public final class ConversationReactionOverlay extends FrameLayout { items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT), getContext().getResources().getString(R.string.AccessibilityId_select))); // Reply - if (!message.isPending() && !message.isFailed()) { + boolean canWrite = openGroup == null || openGroup.getCanWrite(); + if (canWrite && !message.isPending() && !message.isFailed()) { items.add( new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY), getContext().getResources().getString(R.string.AccessibilityId_reply_message)) From 4469d9754a11978e8d5f1b2d8f03f1ef9e9c982a Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 9 May 2023 15:10:03 +0930 Subject: [PATCH 062/244] Fix bubble entrance coordinates --- .../securesms/conversation/v2/ConversationActivityV2.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 cabb58308..97cf699db 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 @@ -1109,12 +1109,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } }) - val contentBounds = Rect() - visibleMessageView.messageContentView.getGlobalVisibleRect(contentBounds) + val topLeft = intArrayOf(0, 0).also { visibleMessageView.messageContentView.getLocationInWindow(it) } val selectedConversationModel = SelectedConversationModel( messageContentBitmap, - contentBounds.left.toFloat(), - contentBounds.top.toFloat(), + topLeft[0].toFloat(), + topLeft[1].toFloat(), visibleMessageView.messageContentView.width, message.isOutgoing, visibleMessageView.messageContentView From d20c27d6d3ef11bfe07511393ee3afe5023217fd Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 9 May 2023 22:26:36 +0930 Subject: [PATCH 063/244] Remove laid out check before drawToBitmap --- .../securesms/conversation/v2/ConversationActivityV2.kt | 1 - .../java/org/thoughtcrime/securesms/util/ViewUtilities.kt | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) 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 cabb58308..b2ec6d123 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 @@ -21,7 +21,6 @@ import android.widget.Toast import androidx.activity.viewModels import androidx.annotation.DimenRes import androidx.appcompat.app.AlertDialog -import androidx.core.view.drawToBitmap import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.Observer diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt index 7b7f3a04f..ffe5e9094 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt @@ -5,6 +5,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.FloatEvaluator import android.animation.ValueAnimator import android.content.Context +import android.graphics.Bitmap import android.graphics.PointF import android.graphics.Rect import android.view.View @@ -13,6 +14,7 @@ import androidx.annotation.DimenRes import network.loki.messenger.R import org.session.libsession.utilities.getColorFromAttr import android.view.inputmethod.InputMethodManager +import androidx.core.graphics.applyCanvas fun View.contains(point: PointF): Boolean { return hitRect.contains(point.x.toInt(), point.y.toInt()) @@ -65,3 +67,9 @@ fun View.hideKeyboard() { val imm = this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(this.windowToken, 0) } + +fun View.drawToBitmap(config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap = + Bitmap.createBitmap(width, height, config).applyCanvas { + translate(-scrollX.toFloat(), -scrollY.toFloat()) + draw(this) + } From 89545f0406176069402f5014e33a4a87de0b76a0 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 10 May 2023 15:36:10 +1000 Subject: [PATCH 064/244] further clean up --- app/src/main/AndroidManifest.xml | 6 - .../securesms/ApplicationContext.java | 7 - .../securesms/backup/BackupEvent.kt | 14 - .../securesms/backup/BackupPassphrase.java | 47 - .../securesms/backup/BackupPreferences.kt | 101 - .../securesms/backup/BackupProtos.java | 6778 ----------------- .../securesms/backup/FullBackupExporter.kt | 447 -- .../securesms/backup/FullBackupImporter.kt | 352 - .../securesms/database/JobDatabase.java | 178 - .../dependencies/DatabaseComponent.kt | 1 - .../securesms/dependencies/DatabaseModule.kt | 4 - .../securesms/jobmanager/ExecutorFactory.java | 9 - .../impl/DefaultExecutorFactory.java | 15 - .../persistence/ConstraintSpec.java | 43 - .../persistence/DependencySpec.java | 43 - .../jobmanager/persistence/FullSpec.java | 50 - .../jobmanager/persistence/JobSpec.java | 129 - .../jobmanager/persistence/JobStorage.java | 55 - .../securesms/jobs/LocalBackupJob.kt | 61 - .../securesms/jobs/UpdateApkJob.kt | 200 - .../service/LocalBackupListener.java | 41 - .../service/UpdateApkRefreshListener.java | 49 - .../thoughtcrime/securesms/util/BackupUtil.kt | 313 - 23 files changed, 8943 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/ExecutorFactory.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DefaultExecutorFactory.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobStorage.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index da3be785c..2d2b2123d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -407,12 +407,6 @@ - - - - - diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index df1bed7b3..5a9999a7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -55,7 +55,6 @@ import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusSender; 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; @@ -78,7 +77,6 @@ import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; import org.thoughtcrime.securesms.sskenvironment.ProfileManager; import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager; import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository; @@ -142,7 +140,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO @Inject LokiAPIDatabase lokiAPIDatabase; @Inject Storage storage; @Inject MessageDataProvider messageDataProvider; - @Inject JobDatabase jobDatabase; @Inject TextSecurePreferences textSecurePreferences; CallMessageProcessor callMessageProcessor; MessagingModuleConfiguration messagingModuleConfiguration; @@ -364,10 +361,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private void initializePeriodicTasks() { BackgroundPollWorker.schedulePeriodic(this); - - if (BuildConfig.PLAY_STORE_DISABLED) { - UpdateApkRefreshListener.schedule(this); - } } private void initializeWebRtc() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt deleted file mode 100644 index 614dc30bb..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupEvent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.thoughtcrime.securesms.backup - -data class BackupEvent constructor(val type: Type, val count: Int, val exception: Exception?) { - - enum class Type { - PROGRESS, FINISHED - } - - companion object { - @JvmStatic fun createProgress(count: Int) = BackupEvent(Type.PROGRESS, count, null) - @JvmStatic fun createFinished() = BackupEvent(Type.FINISHED, 0, null) - @JvmStatic fun createFinished(e: Exception?) = BackupEvent(Type.FINISHED, 0, e) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java deleted file mode 100644 index eec2a2e58..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPassphrase.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.thoughtcrime.securesms.backup; - -import android.content.Context; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.thoughtcrime.securesms.crypto.KeyStoreHelper; -import org.session.libsignal.utilities.Log; -import org.session.libsession.utilities.TextSecurePreferences; - -/** - * Allows the getting and setting of the backup passphrase, which is stored encrypted on API >= 23. - */ -public class BackupPassphrase { - - private static final String TAG = BackupPassphrase.class.getSimpleName(); - - public static @Nullable String get(@NonNull Context context) { - String passphrase = TextSecurePreferences.getBackupPassphrase(context); - String encryptedPassphrase = TextSecurePreferences.getEncryptedBackupPassphrase(context); - - if (Build.VERSION.SDK_INT < 23 || (passphrase == null && encryptedPassphrase == null)) { - return passphrase; - } - - if (encryptedPassphrase == null) { - Log.i(TAG, "Migrating to encrypted passphrase."); - set(context, passphrase); - encryptedPassphrase = TextSecurePreferences.getEncryptedBackupPassphrase(context); - } - - KeyStoreHelper.SealedData data = KeyStoreHelper.SealedData.fromString(encryptedPassphrase); - return new String(KeyStoreHelper.unseal(data)); - } - - public static void set(@NonNull Context context, @Nullable String passphrase) { - if (passphrase == null || Build.VERSION.SDK_INT < 23) { - TextSecurePreferences.setBackupPassphrase(context, passphrase); - TextSecurePreferences.setEncryptedBackupPassphrase(context, null); - } else { - KeyStoreHelper.SealedData encryptedPassphrase = KeyStoreHelper.seal(passphrase.getBytes()); - TextSecurePreferences.setEncryptedBackupPassphrase(context, encryptedPassphrase.serialize()); - TextSecurePreferences.setBackupPassphrase(context, null); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt deleted file mode 100644 index 8ddfc23a8..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupPreferences.kt +++ /dev/null @@ -1,101 +0,0 @@ -package org.thoughtcrime.securesms.backup - -import android.content.Context -import android.content.SharedPreferences -import android.os.Build -import android.preference.PreferenceManager -import android.preference.PreferenceManager.getDefaultSharedPreferencesName -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.backup.FullBackupImporter.PREF_PREFIX_TYPE_BOOLEAN -import org.thoughtcrime.securesms.backup.FullBackupImporter.PREF_PREFIX_TYPE_INT -import java.util.* - -object BackupPreferences { - // region Backup related - fun getBackupRecords(context: Context): List { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - val prefsFileName: String - prefsFileName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - getDefaultSharedPreferencesName(context) - } else { - context.packageName + "_preferences" - } - val prefList: LinkedList = LinkedList() - addBackupEntryInt(prefList, preferences, prefsFileName, TextSecurePreferences.LOCAL_REGISTRATION_ID_PREF) - addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.LOCAL_NUMBER_PREF) - addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_NAME_PREF) - addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_AVATAR_URL_PREF) - addBackupEntryInt(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_AVATAR_ID_PREF) - addBackupEntryString(prefList, preferences, prefsFileName, TextSecurePreferences.PROFILE_KEY_PREF) - addBackupEntryBoolean(prefList, preferences, prefsFileName, TextSecurePreferences.IS_USING_FCM) - return prefList - } - - private fun addBackupEntryString( - outPrefList: MutableList, - prefs: SharedPreferences, - prefFileName: String, - prefKey: String, - ) { - val value = prefs.getString(prefKey, null) - if (value == null) { - logBackupEntry(prefKey, false) - return - } - outPrefList.add(BackupProtos.SharedPreference.newBuilder() - .setFile(prefFileName) - .setKey(prefKey) - .setValue(value) - .build()) - logBackupEntry(prefKey, true) - } - - private fun addBackupEntryInt( - outPrefList: MutableList, - prefs: SharedPreferences, - prefFileName: String, - prefKey: String, - ) { - val value = prefs.getInt(prefKey, -1) - if (value == -1) { - logBackupEntry(prefKey, false) - return - } - outPrefList.add(BackupProtos.SharedPreference.newBuilder() - .setFile(prefFileName) - .setKey(PREF_PREFIX_TYPE_INT + prefKey) // The prefix denotes the type of the preference. - .setValue(value.toString()) - .build()) - logBackupEntry(prefKey, true) - } - - private fun addBackupEntryBoolean( - outPrefList: MutableList, - prefs: SharedPreferences, - prefFileName: String, - prefKey: String, - ) { - if (!prefs.contains(prefKey)) { - logBackupEntry(prefKey, false) - return - } - outPrefList.add(BackupProtos.SharedPreference.newBuilder() - .setFile(prefFileName) - .setKey(PREF_PREFIX_TYPE_BOOLEAN + prefKey) // The prefix denotes the type of the preference. - .setValue(prefs.getBoolean(prefKey, false).toString()) - .build()) - logBackupEntry(prefKey, true) - } - - private fun logBackupEntry(prefName: String, wasIncluded: Boolean) { - val sb = StringBuilder() - sb.append("Backup preference ") - sb.append(if (wasIncluded) "+ " else "- ") - sb.append('\"').append(prefName).append("\" ") - if (!wasIncluded) { - sb.append("(is empty and not included)") - } - Log.d("Loki", sb.toString()) - } // endregion -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java deleted file mode 100644 index f3b78606f..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupProtos.java +++ /dev/null @@ -1,6778 +0,0 @@ -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: Backups.proto - -package org.thoughtcrime.securesms.backup; - -public final class BackupProtos { - private BackupProtos() {} - public static void registerAllExtensions( - com.google.protobuf.ExtensionRegistry registry) { - } - public interface SqlStatementOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional string statement = 1; - /** - * optional string statement = 1; - */ - boolean hasStatement(); - /** - * optional string statement = 1; - */ - java.lang.String getStatement(); - /** - * optional string statement = 1; - */ - com.google.protobuf.ByteString - getStatementBytes(); - - // repeated .signal.SqlStatement.SqlParameter parameters = 2; - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - java.util.List - getParametersList(); - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getParameters(int index); - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - int getParametersCount(); - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - java.util.List - getParametersOrBuilderList(); - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder getParametersOrBuilder( - int index); - } - /** - * Protobuf type {@code signal.SqlStatement} - */ - public static final class SqlStatement extends - com.google.protobuf.GeneratedMessage - implements SqlStatementOrBuilder { - // Use SqlStatement.newBuilder() to construct. - private SqlStatement(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private SqlStatement(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final SqlStatement defaultInstance; - public static SqlStatement getDefaultInstance() { - return defaultInstance; - } - - public SqlStatement getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private SqlStatement( - 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; - statement_ = input.readBytes(); - break; - } - case 18: { - if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - parameters_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000002; - } - parameters_.add(input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.PARSER, extensionRegistry)); - 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_ & 0x00000002) == 0x00000002)) { - parameters_ = java.util.Collections.unmodifiableList(parameters_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public SqlStatement parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new SqlStatement(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public interface SqlParameterOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional string stringParamter = 1; - /** - * optional string stringParamter = 1; - */ - boolean hasStringParamter(); - /** - * optional string stringParamter = 1; - */ - java.lang.String getStringParamter(); - /** - * optional string stringParamter = 1; - */ - com.google.protobuf.ByteString - getStringParamterBytes(); - - // optional uint64 integerParameter = 2; - /** - * optional uint64 integerParameter = 2; - */ - boolean hasIntegerParameter(); - /** - * optional uint64 integerParameter = 2; - */ - long getIntegerParameter(); - - // optional double doubleParameter = 3; - /** - * optional double doubleParameter = 3; - */ - boolean hasDoubleParameter(); - /** - * optional double doubleParameter = 3; - */ - double getDoubleParameter(); - - // optional bytes blobParameter = 4; - /** - * optional bytes blobParameter = 4; - */ - boolean hasBlobParameter(); - /** - * optional bytes blobParameter = 4; - */ - com.google.protobuf.ByteString getBlobParameter(); - - // optional bool nullparameter = 5; - /** - * optional bool nullparameter = 5; - */ - boolean hasNullparameter(); - /** - * optional bool nullparameter = 5; - */ - boolean getNullparameter(); - } - /** - * Protobuf type {@code signal.SqlStatement.SqlParameter} - */ - public static final class SqlParameter extends - com.google.protobuf.GeneratedMessage - implements SqlParameterOrBuilder { - // Use SqlParameter.newBuilder() to construct. - private SqlParameter(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private SqlParameter(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final SqlParameter defaultInstance; - public static SqlParameter getDefaultInstance() { - return defaultInstance; - } - - public SqlParameter getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private SqlParameter( - 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; - stringParamter_ = input.readBytes(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - integerParameter_ = input.readUInt64(); - break; - } - case 25: { - bitField0_ |= 0x00000004; - doubleParameter_ = input.readDouble(); - break; - } - case 34: { - bitField0_ |= 0x00000008; - blobParameter_ = input.readBytes(); - break; - } - case 40: { - bitField0_ |= 0x00000010; - nullparameter_ = input.readBool(); - 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public SqlParameter parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new SqlParameter(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional string stringParamter = 1; - public static final int STRINGPARAMTER_FIELD_NUMBER = 1; - private java.lang.Object stringParamter_; - /** - * optional string stringParamter = 1; - */ - public boolean hasStringParamter() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string stringParamter = 1; - */ - public java.lang.String getStringParamter() { - java.lang.Object ref = stringParamter_; - 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()) { - stringParamter_ = s; - } - return s; - } - } - /** - * optional string stringParamter = 1; - */ - public com.google.protobuf.ByteString - getStringParamterBytes() { - java.lang.Object ref = stringParamter_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - stringParamter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // optional uint64 integerParameter = 2; - public static final int INTEGERPARAMETER_FIELD_NUMBER = 2; - private long integerParameter_; - /** - * optional uint64 integerParameter = 2; - */ - public boolean hasIntegerParameter() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint64 integerParameter = 2; - */ - public long getIntegerParameter() { - return integerParameter_; - } - - // optional double doubleParameter = 3; - public static final int DOUBLEPARAMETER_FIELD_NUMBER = 3; - private double doubleParameter_; - /** - * optional double doubleParameter = 3; - */ - public boolean hasDoubleParameter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional double doubleParameter = 3; - */ - public double getDoubleParameter() { - return doubleParameter_; - } - - // optional bytes blobParameter = 4; - public static final int BLOBPARAMETER_FIELD_NUMBER = 4; - private com.google.protobuf.ByteString blobParameter_; - /** - * optional bytes blobParameter = 4; - */ - public boolean hasBlobParameter() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional bytes blobParameter = 4; - */ - public com.google.protobuf.ByteString getBlobParameter() { - return blobParameter_; - } - - // optional bool nullparameter = 5; - public static final int NULLPARAMETER_FIELD_NUMBER = 5; - private boolean nullparameter_; - /** - * optional bool nullparameter = 5; - */ - public boolean hasNullparameter() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional bool nullparameter = 5; - */ - public boolean getNullparameter() { - return nullparameter_; - } - - private void initFields() { - stringParamter_ = ""; - integerParameter_ = 0L; - doubleParameter_ = 0D; - blobParameter_ = com.google.protobuf.ByteString.EMPTY; - nullparameter_ = false; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getStringParamterBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt64(2, integerParameter_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeDouble(3, doubleParameter_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(4, blobParameter_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBool(5, nullparameter_); - } - 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, getStringParamterBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(2, integerParameter_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeDoubleSize(3, doubleParameter_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(4, blobParameter_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(5, nullparameter_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter 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.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter 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 signal.SqlStatement.SqlParameter} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.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(); - stringParamter_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - integerParameter_ = 0L; - bitField0_ = (bitField0_ & ~0x00000002); - doubleParameter_ = 0D; - bitField0_ = (bitField0_ & ~0x00000004); - blobParameter_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000008); - nullparameter_ = false; - bitField0_ = (bitField0_ & ~0x00000010); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_SqlParameter_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter build() { - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter result = new org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.stringParamter_ = stringParamter_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.integerParameter_ = integerParameter_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.doubleParameter_ = doubleParameter_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.blobParameter_ = blobParameter_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.nullparameter_ = nullparameter_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance()) return this; - if (other.hasStringParamter()) { - bitField0_ |= 0x00000001; - stringParamter_ = other.stringParamter_; - onChanged(); - } - if (other.hasIntegerParameter()) { - setIntegerParameter(other.getIntegerParameter()); - } - if (other.hasDoubleParameter()) { - setDoubleParameter(other.getDoubleParameter()); - } - if (other.hasBlobParameter()) { - setBlobParameter(other.getBlobParameter()); - } - if (other.hasNullparameter()) { - setNullparameter(other.getNullparameter()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional string stringParamter = 1; - private java.lang.Object stringParamter_ = ""; - /** - * optional string stringParamter = 1; - */ - public boolean hasStringParamter() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string stringParamter = 1; - */ - public java.lang.String getStringParamter() { - java.lang.Object ref = stringParamter_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - stringParamter_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string stringParamter = 1; - */ - public com.google.protobuf.ByteString - getStringParamterBytes() { - java.lang.Object ref = stringParamter_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - stringParamter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string stringParamter = 1; - */ - public Builder setStringParamter( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - stringParamter_ = value; - onChanged(); - return this; - } - /** - * optional string stringParamter = 1; - */ - public Builder clearStringParamter() { - bitField0_ = (bitField0_ & ~0x00000001); - stringParamter_ = getDefaultInstance().getStringParamter(); - onChanged(); - return this; - } - /** - * optional string stringParamter = 1; - */ - public Builder setStringParamterBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - stringParamter_ = value; - onChanged(); - return this; - } - - // optional uint64 integerParameter = 2; - private long integerParameter_ ; - /** - * optional uint64 integerParameter = 2; - */ - public boolean hasIntegerParameter() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint64 integerParameter = 2; - */ - public long getIntegerParameter() { - return integerParameter_; - } - /** - * optional uint64 integerParameter = 2; - */ - public Builder setIntegerParameter(long value) { - bitField0_ |= 0x00000002; - integerParameter_ = value; - onChanged(); - return this; - } - /** - * optional uint64 integerParameter = 2; - */ - public Builder clearIntegerParameter() { - bitField0_ = (bitField0_ & ~0x00000002); - integerParameter_ = 0L; - onChanged(); - return this; - } - - // optional double doubleParameter = 3; - private double doubleParameter_ ; - /** - * optional double doubleParameter = 3; - */ - public boolean hasDoubleParameter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional double doubleParameter = 3; - */ - public double getDoubleParameter() { - return doubleParameter_; - } - /** - * optional double doubleParameter = 3; - */ - public Builder setDoubleParameter(double value) { - bitField0_ |= 0x00000004; - doubleParameter_ = value; - onChanged(); - return this; - } - /** - * optional double doubleParameter = 3; - */ - public Builder clearDoubleParameter() { - bitField0_ = (bitField0_ & ~0x00000004); - doubleParameter_ = 0D; - onChanged(); - return this; - } - - // optional bytes blobParameter = 4; - private com.google.protobuf.ByteString blobParameter_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes blobParameter = 4; - */ - public boolean hasBlobParameter() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional bytes blobParameter = 4; - */ - public com.google.protobuf.ByteString getBlobParameter() { - return blobParameter_; - } - /** - * optional bytes blobParameter = 4; - */ - public Builder setBlobParameter(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000008; - blobParameter_ = value; - onChanged(); - return this; - } - /** - * optional bytes blobParameter = 4; - */ - public Builder clearBlobParameter() { - bitField0_ = (bitField0_ & ~0x00000008); - blobParameter_ = getDefaultInstance().getBlobParameter(); - onChanged(); - return this; - } - - // optional bool nullparameter = 5; - private boolean nullparameter_ ; - /** - * optional bool nullparameter = 5; - */ - public boolean hasNullparameter() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional bool nullparameter = 5; - */ - public boolean getNullparameter() { - return nullparameter_; - } - /** - * optional bool nullparameter = 5; - */ - public Builder setNullparameter(boolean value) { - bitField0_ |= 0x00000010; - nullparameter_ = value; - onChanged(); - return this; - } - /** - * optional bool nullparameter = 5; - */ - public Builder clearNullparameter() { - bitField0_ = (bitField0_ & ~0x00000010); - nullparameter_ = false; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.SqlStatement.SqlParameter) - } - - static { - defaultInstance = new SqlParameter(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.SqlStatement.SqlParameter) - } - - private int bitField0_; - // optional string statement = 1; - public static final int STATEMENT_FIELD_NUMBER = 1; - private java.lang.Object statement_; - /** - * optional string statement = 1; - */ - public boolean hasStatement() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string statement = 1; - */ - public java.lang.String getStatement() { - java.lang.Object ref = statement_; - 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()) { - statement_ = s; - } - return s; - } - } - /** - * optional string statement = 1; - */ - public com.google.protobuf.ByteString - getStatementBytes() { - java.lang.Object ref = statement_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - statement_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // repeated .signal.SqlStatement.SqlParameter parameters = 2; - public static final int PARAMETERS_FIELD_NUMBER = 2; - private java.util.List parameters_; - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public java.util.List getParametersList() { - return parameters_; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public java.util.List - getParametersOrBuilderList() { - return parameters_; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public int getParametersCount() { - return parameters_.size(); - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getParameters(int index) { - return parameters_.get(index); - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder getParametersOrBuilder( - int index) { - return parameters_.get(index); - } - - private void initFields() { - statement_ = ""; - parameters_ = java.util.Collections.emptyList(); - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getStatementBytes()); - } - for (int i = 0; i < parameters_.size(); i++) { - output.writeMessage(2, parameters_.get(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, getStatementBytes()); - } - for (int i = 0; i < parameters_.size(); i++) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, parameters_.get(i)); - } - 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.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement 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.thoughtcrime.securesms.backup.BackupProtos.SqlStatement 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 signal.SqlStatement} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.class, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getParametersFieldBuilder(); - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - statement_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - if (parametersBuilder_ == null) { - parameters_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - } else { - parametersBuilder_.clear(); - } - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SqlStatement_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement build() { - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement result = new org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.statement_ = statement_; - if (parametersBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002)) { - parameters_ = java.util.Collections.unmodifiableList(parameters_); - bitField0_ = (bitField0_ & ~0x00000002); - } - result.parameters_ = parameters_; - } else { - result.parameters_ = parametersBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance()) return this; - if (other.hasStatement()) { - bitField0_ |= 0x00000001; - statement_ = other.statement_; - onChanged(); - } - if (parametersBuilder_ == null) { - if (!other.parameters_.isEmpty()) { - if (parameters_.isEmpty()) { - parameters_ = other.parameters_; - bitField0_ = (bitField0_ & ~0x00000002); - } else { - ensureParametersIsMutable(); - parameters_.addAll(other.parameters_); - } - onChanged(); - } - } else { - if (!other.parameters_.isEmpty()) { - if (parametersBuilder_.isEmpty()) { - parametersBuilder_.dispose(); - parametersBuilder_ = null; - parameters_ = other.parameters_; - bitField0_ = (bitField0_ & ~0x00000002); - parametersBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? - getParametersFieldBuilder() : null; - } else { - parametersBuilder_.addAllMessages(other.parameters_); - } - } - } - 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.thoughtcrime.securesms.backup.BackupProtos.SqlStatement parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional string statement = 1; - private java.lang.Object statement_ = ""; - /** - * optional string statement = 1; - */ - public boolean hasStatement() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string statement = 1; - */ - public java.lang.String getStatement() { - java.lang.Object ref = statement_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - statement_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string statement = 1; - */ - public com.google.protobuf.ByteString - getStatementBytes() { - java.lang.Object ref = statement_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - statement_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string statement = 1; - */ - public Builder setStatement( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - statement_ = value; - onChanged(); - return this; - } - /** - * optional string statement = 1; - */ - public Builder clearStatement() { - bitField0_ = (bitField0_ & ~0x00000001); - statement_ = getDefaultInstance().getStatement(); - onChanged(); - return this; - } - /** - * optional string statement = 1; - */ - public Builder setStatementBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - statement_ = value; - onChanged(); - return this; - } - - // repeated .signal.SqlStatement.SqlParameter parameters = 2; - private java.util.List parameters_ = - java.util.Collections.emptyList(); - private void ensureParametersIsMutable() { - if (!((bitField0_ & 0x00000002) == 0x00000002)) { - parameters_ = new java.util.ArrayList(parameters_); - bitField0_ |= 0x00000002; - } - } - - private com.google.protobuf.RepeatedFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder> parametersBuilder_; - - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public java.util.List getParametersList() { - if (parametersBuilder_ == null) { - return java.util.Collections.unmodifiableList(parameters_); - } else { - return parametersBuilder_.getMessageList(); - } - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public int getParametersCount() { - if (parametersBuilder_ == null) { - return parameters_.size(); - } else { - return parametersBuilder_.getCount(); - } - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter getParameters(int index) { - if (parametersBuilder_ == null) { - return parameters_.get(index); - } else { - return parametersBuilder_.getMessage(index); - } - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder setParameters( - int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter value) { - if (parametersBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureParametersIsMutable(); - parameters_.set(index, value); - onChanged(); - } else { - parametersBuilder_.setMessage(index, value); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder setParameters( - int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder builderForValue) { - if (parametersBuilder_ == null) { - ensureParametersIsMutable(); - parameters_.set(index, builderForValue.build()); - onChanged(); - } else { - parametersBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder addParameters(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter value) { - if (parametersBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureParametersIsMutable(); - parameters_.add(value); - onChanged(); - } else { - parametersBuilder_.addMessage(value); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder addParameters( - int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter value) { - if (parametersBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureParametersIsMutable(); - parameters_.add(index, value); - onChanged(); - } else { - parametersBuilder_.addMessage(index, value); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder addParameters( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder builderForValue) { - if (parametersBuilder_ == null) { - ensureParametersIsMutable(); - parameters_.add(builderForValue.build()); - onChanged(); - } else { - parametersBuilder_.addMessage(builderForValue.build()); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder addParameters( - int index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder builderForValue) { - if (parametersBuilder_ == null) { - ensureParametersIsMutable(); - parameters_.add(index, builderForValue.build()); - onChanged(); - } else { - parametersBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder addAllParameters( - java.lang.Iterable values) { - if (parametersBuilder_ == null) { - ensureParametersIsMutable(); - super.addAll(values, parameters_); - onChanged(); - } else { - parametersBuilder_.addAllMessages(values); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder clearParameters() { - if (parametersBuilder_ == null) { - parameters_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - } else { - parametersBuilder_.clear(); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public Builder removeParameters(int index) { - if (parametersBuilder_ == null) { - ensureParametersIsMutable(); - parameters_.remove(index); - onChanged(); - } else { - parametersBuilder_.remove(index); - } - return this; - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder getParametersBuilder( - int index) { - return getParametersFieldBuilder().getBuilder(index); - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder getParametersOrBuilder( - int index) { - if (parametersBuilder_ == null) { - return parameters_.get(index); } else { - return parametersBuilder_.getMessageOrBuilder(index); - } - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public java.util.List - getParametersOrBuilderList() { - if (parametersBuilder_ != null) { - return parametersBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(parameters_); - } - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder addParametersBuilder() { - return getParametersFieldBuilder().addBuilder( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance()); - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder addParametersBuilder( - int index) { - return getParametersFieldBuilder().addBuilder( - index, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.getDefaultInstance()); - } - /** - * repeated .signal.SqlStatement.SqlParameter parameters = 2; - */ - public java.util.List - getParametersBuilderList() { - return getParametersFieldBuilder().getBuilderList(); - } - private com.google.protobuf.RepeatedFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder> - getParametersFieldBuilder() { - if (parametersBuilder_ == null) { - parametersBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameter.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.SqlParameterOrBuilder>( - parameters_, - ((bitField0_ & 0x00000002) == 0x00000002), - getParentForChildren(), - isClean()); - parameters_ = null; - } - return parametersBuilder_; - } - - // @@protoc_insertion_point(builder_scope:signal.SqlStatement) - } - - static { - defaultInstance = new SqlStatement(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.SqlStatement) - } - - public interface SharedPreferenceOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional string file = 1; - /** - * optional string file = 1; - */ - boolean hasFile(); - /** - * optional string file = 1; - */ - java.lang.String getFile(); - /** - * optional string file = 1; - */ - com.google.protobuf.ByteString - getFileBytes(); - - // optional string key = 2; - /** - * optional string key = 2; - */ - boolean hasKey(); - /** - * optional string key = 2; - */ - java.lang.String getKey(); - /** - * optional string key = 2; - */ - com.google.protobuf.ByteString - getKeyBytes(); - - // optional string value = 3; - /** - * optional string value = 3; - */ - boolean hasValue(); - /** - * optional string value = 3; - */ - java.lang.String getValue(); - /** - * optional string value = 3; - */ - com.google.protobuf.ByteString - getValueBytes(); - } - /** - * Protobuf type {@code signal.SharedPreference} - */ - public static final class SharedPreference extends - com.google.protobuf.GeneratedMessage - implements SharedPreferenceOrBuilder { - // Use SharedPreference.newBuilder() to construct. - private SharedPreference(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private SharedPreference(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final SharedPreference defaultInstance; - public static SharedPreference getDefaultInstance() { - return defaultInstance; - } - - public SharedPreference getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private SharedPreference( - 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; - file_ = input.readBytes(); - break; - } - case 18: { - bitField0_ |= 0x00000002; - key_ = input.readBytes(); - break; - } - case 26: { - bitField0_ |= 0x00000004; - value_ = 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.class, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public SharedPreference parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new SharedPreference(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional string file = 1; - public static final int FILE_FIELD_NUMBER = 1; - private java.lang.Object file_; - /** - * optional string file = 1; - */ - public boolean hasFile() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string file = 1; - */ - public java.lang.String getFile() { - java.lang.Object ref = file_; - 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()) { - file_ = s; - } - return s; - } - } - /** - * optional string file = 1; - */ - public com.google.protobuf.ByteString - getFileBytes() { - java.lang.Object ref = file_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - file_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // optional string key = 2; - public static final int KEY_FIELD_NUMBER = 2; - private java.lang.Object key_; - /** - * optional string key = 2; - */ - public boolean hasKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string key = 2; - */ - public java.lang.String getKey() { - java.lang.Object ref = key_; - 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()) { - key_ = s; - } - return s; - } - } - /** - * optional string key = 2; - */ - public com.google.protobuf.ByteString - getKeyBytes() { - java.lang.Object ref = key_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - key_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // optional string value = 3; - public static final int VALUE_FIELD_NUMBER = 3; - private java.lang.Object value_; - /** - * optional string value = 3; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string value = 3; - */ - public java.lang.String getValue() { - java.lang.Object ref = value_; - 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()) { - value_ = s; - } - return s; - } - } - /** - * optional string value = 3; - */ - public com.google.protobuf.ByteString - getValueBytes() { - java.lang.Object ref = value_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - value_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - file_ = ""; - key_ = ""; - value_ = ""; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getFileBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getKeyBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, getValueBytes()); - } - 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, getFileBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getKeyBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, getValueBytes()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference 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.thoughtcrime.securesms.backup.BackupProtos.SharedPreference 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 signal.SharedPreference} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.class, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.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(); - file_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - key_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - value_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_SharedPreference_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference build() { - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference result = new org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.file_ = file_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.key_ = key_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.value_ = value_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance()) return this; - if (other.hasFile()) { - bitField0_ |= 0x00000001; - file_ = other.file_; - onChanged(); - } - if (other.hasKey()) { - bitField0_ |= 0x00000002; - key_ = other.key_; - onChanged(); - } - if (other.hasValue()) { - bitField0_ |= 0x00000004; - value_ = other.value_; - onChanged(); - } - 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.thoughtcrime.securesms.backup.BackupProtos.SharedPreference parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional string file = 1; - private java.lang.Object file_ = ""; - /** - * optional string file = 1; - */ - public boolean hasFile() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string file = 1; - */ - public java.lang.String getFile() { - java.lang.Object ref = file_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - file_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string file = 1; - */ - public com.google.protobuf.ByteString - getFileBytes() { - java.lang.Object ref = file_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - file_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string file = 1; - */ - public Builder setFile( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - file_ = value; - onChanged(); - return this; - } - /** - * optional string file = 1; - */ - public Builder clearFile() { - bitField0_ = (bitField0_ & ~0x00000001); - file_ = getDefaultInstance().getFile(); - onChanged(); - return this; - } - /** - * optional string file = 1; - */ - public Builder setFileBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - file_ = value; - onChanged(); - return this; - } - - // optional string key = 2; - private java.lang.Object key_ = ""; - /** - * optional string key = 2; - */ - public boolean hasKey() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string key = 2; - */ - public java.lang.String getKey() { - java.lang.Object ref = key_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - key_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string key = 2; - */ - public com.google.protobuf.ByteString - getKeyBytes() { - java.lang.Object ref = key_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - key_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string key = 2; - */ - public Builder setKey( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - key_ = value; - onChanged(); - return this; - } - /** - * optional string key = 2; - */ - public Builder clearKey() { - bitField0_ = (bitField0_ & ~0x00000002); - key_ = getDefaultInstance().getKey(); - onChanged(); - return this; - } - /** - * optional string key = 2; - */ - public Builder setKeyBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - key_ = value; - onChanged(); - return this; - } - - // optional string value = 3; - private java.lang.Object value_ = ""; - /** - * optional string value = 3; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string value = 3; - */ - public java.lang.String getValue() { - java.lang.Object ref = value_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - value_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string value = 3; - */ - public com.google.protobuf.ByteString - getValueBytes() { - java.lang.Object ref = value_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - value_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string value = 3; - */ - public Builder setValue( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - value_ = value; - onChanged(); - return this; - } - /** - * optional string value = 3; - */ - public Builder clearValue() { - bitField0_ = (bitField0_ & ~0x00000004); - value_ = getDefaultInstance().getValue(); - onChanged(); - return this; - } - /** - * optional string value = 3; - */ - public Builder setValueBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - value_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.SharedPreference) - } - - static { - defaultInstance = new SharedPreference(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.SharedPreference) - } - - public interface AttachmentOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional uint64 rowId = 1; - /** - * optional uint64 rowId = 1; - */ - boolean hasRowId(); - /** - * optional uint64 rowId = 1; - */ - long getRowId(); - - // optional uint64 attachmentId = 2; - /** - * optional uint64 attachmentId = 2; - */ - boolean hasAttachmentId(); - /** - * optional uint64 attachmentId = 2; - */ - long getAttachmentId(); - - // optional uint32 length = 3; - /** - * optional uint32 length = 3; - */ - boolean hasLength(); - /** - * optional uint32 length = 3; - */ - int getLength(); - } - /** - * Protobuf type {@code signal.Attachment} - */ - public static final class Attachment extends - com.google.protobuf.GeneratedMessage - implements AttachmentOrBuilder { - // Use Attachment.newBuilder() to construct. - private Attachment(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private Attachment(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Attachment defaultInstance; - public static Attachment getDefaultInstance() { - return defaultInstance; - } - - public Attachment getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private Attachment( - 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: { - bitField0_ |= 0x00000001; - rowId_ = input.readUInt64(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - attachmentId_ = input.readUInt64(); - break; - } - case 24: { - bitField0_ |= 0x00000004; - length_ = input.readUInt32(); - 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Attachment.class, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Attachment parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Attachment(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional uint64 rowId = 1; - public static final int ROWID_FIELD_NUMBER = 1; - private long rowId_; - /** - * optional uint64 rowId = 1; - */ - public boolean hasRowId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional uint64 rowId = 1; - */ - public long getRowId() { - return rowId_; - } - - // optional uint64 attachmentId = 2; - public static final int ATTACHMENTID_FIELD_NUMBER = 2; - private long attachmentId_; - /** - * optional uint64 attachmentId = 2; - */ - public boolean hasAttachmentId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint64 attachmentId = 2; - */ - public long getAttachmentId() { - return attachmentId_; - } - - // optional uint32 length = 3; - public static final int LENGTH_FIELD_NUMBER = 3; - private int length_; - /** - * optional uint32 length = 3; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional uint32 length = 3; - */ - public int getLength() { - return length_; - } - - private void initFields() { - rowId_ = 0L; - attachmentId_ = 0L; - length_ = 0; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeUInt64(1, rowId_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt64(2, attachmentId_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeUInt32(3, length_); - } - 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 - .computeUInt64Size(1, rowId_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt64Size(2, attachmentId_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(3, length_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Attachment 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.thoughtcrime.securesms.backup.BackupProtos.Attachment 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 signal.Attachment} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Attachment.class, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Attachment.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(); - rowId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - attachmentId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000002); - length_ = 0; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Attachment_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Attachment getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Attachment build() { - org.thoughtcrime.securesms.backup.BackupProtos.Attachment result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Attachment buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.Attachment result = new org.thoughtcrime.securesms.backup.BackupProtos.Attachment(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.rowId_ = rowId_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.attachmentId_ = attachmentId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.length_ = length_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Attachment) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Attachment)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Attachment other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance()) return this; - if (other.hasRowId()) { - setRowId(other.getRowId()); - } - if (other.hasAttachmentId()) { - setAttachmentId(other.getAttachmentId()); - } - if (other.hasLength()) { - setLength(other.getLength()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Attachment parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Attachment) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional uint64 rowId = 1; - private long rowId_ ; - /** - * optional uint64 rowId = 1; - */ - public boolean hasRowId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional uint64 rowId = 1; - */ - public long getRowId() { - return rowId_; - } - /** - * optional uint64 rowId = 1; - */ - public Builder setRowId(long value) { - bitField0_ |= 0x00000001; - rowId_ = value; - onChanged(); - return this; - } - /** - * optional uint64 rowId = 1; - */ - public Builder clearRowId() { - bitField0_ = (bitField0_ & ~0x00000001); - rowId_ = 0L; - onChanged(); - return this; - } - - // optional uint64 attachmentId = 2; - private long attachmentId_ ; - /** - * optional uint64 attachmentId = 2; - */ - public boolean hasAttachmentId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint64 attachmentId = 2; - */ - public long getAttachmentId() { - return attachmentId_; - } - /** - * optional uint64 attachmentId = 2; - */ - public Builder setAttachmentId(long value) { - bitField0_ |= 0x00000002; - attachmentId_ = value; - onChanged(); - return this; - } - /** - * optional uint64 attachmentId = 2; - */ - public Builder clearAttachmentId() { - bitField0_ = (bitField0_ & ~0x00000002); - attachmentId_ = 0L; - onChanged(); - return this; - } - - // optional uint32 length = 3; - private int length_ ; - /** - * optional uint32 length = 3; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional uint32 length = 3; - */ - public int getLength() { - return length_; - } - /** - * optional uint32 length = 3; - */ - public Builder setLength(int value) { - bitField0_ |= 0x00000004; - length_ = value; - onChanged(); - return this; - } - /** - * optional uint32 length = 3; - */ - public Builder clearLength() { - bitField0_ = (bitField0_ & ~0x00000004); - length_ = 0; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.Attachment) - } - - static { - defaultInstance = new Attachment(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.Attachment) - } - - public interface StickerOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional uint64 rowId = 1; - /** - * optional uint64 rowId = 1; - */ - boolean hasRowId(); - /** - * optional uint64 rowId = 1; - */ - long getRowId(); - - // optional uint32 length = 2; - /** - * optional uint32 length = 2; - */ - boolean hasLength(); - /** - * optional uint32 length = 2; - */ - int getLength(); - } - /** - * Protobuf type {@code signal.Sticker} - */ - public static final class Sticker extends - com.google.protobuf.GeneratedMessage - implements StickerOrBuilder { - // Use Sticker.newBuilder() to construct. - private Sticker(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private Sticker(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Sticker defaultInstance; - public static Sticker getDefaultInstance() { - return defaultInstance; - } - - public Sticker getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private Sticker( - 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: { - bitField0_ |= 0x00000001; - rowId_ = input.readUInt64(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - length_ = input.readUInt32(); - 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Sticker.class, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Sticker parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Sticker(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional uint64 rowId = 1; - public static final int ROWID_FIELD_NUMBER = 1; - private long rowId_; - /** - * optional uint64 rowId = 1; - */ - public boolean hasRowId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional uint64 rowId = 1; - */ - public long getRowId() { - return rowId_; - } - - // optional uint32 length = 2; - public static final int LENGTH_FIELD_NUMBER = 2; - private int length_; - /** - * optional uint32 length = 2; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint32 length = 2; - */ - public int getLength() { - return length_; - } - - private void initFields() { - rowId_ = 0L; - length_ = 0; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeUInt64(1, rowId_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(2, length_); - } - 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 - .computeUInt64Size(1, rowId_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(2, length_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Sticker 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.thoughtcrime.securesms.backup.BackupProtos.Sticker 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 signal.Sticker} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Sticker.class, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Sticker.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(); - rowId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - length_ = 0; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Sticker_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Sticker getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Sticker build() { - org.thoughtcrime.securesms.backup.BackupProtos.Sticker result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Sticker buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.Sticker result = new org.thoughtcrime.securesms.backup.BackupProtos.Sticker(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.rowId_ = rowId_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.length_ = length_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Sticker) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Sticker)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Sticker other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance()) return this; - if (other.hasRowId()) { - setRowId(other.getRowId()); - } - if (other.hasLength()) { - setLength(other.getLength()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Sticker parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Sticker) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional uint64 rowId = 1; - private long rowId_ ; - /** - * optional uint64 rowId = 1; - */ - public boolean hasRowId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional uint64 rowId = 1; - */ - public long getRowId() { - return rowId_; - } - /** - * optional uint64 rowId = 1; - */ - public Builder setRowId(long value) { - bitField0_ |= 0x00000001; - rowId_ = value; - onChanged(); - return this; - } - /** - * optional uint64 rowId = 1; - */ - public Builder clearRowId() { - bitField0_ = (bitField0_ & ~0x00000001); - rowId_ = 0L; - onChanged(); - return this; - } - - // optional uint32 length = 2; - private int length_ ; - /** - * optional uint32 length = 2; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint32 length = 2; - */ - public int getLength() { - return length_; - } - /** - * optional uint32 length = 2; - */ - public Builder setLength(int value) { - bitField0_ |= 0x00000002; - length_ = value; - onChanged(); - return this; - } - /** - * optional uint32 length = 2; - */ - public Builder clearLength() { - bitField0_ = (bitField0_ & ~0x00000002); - length_ = 0; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.Sticker) - } - - static { - defaultInstance = new Sticker(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.Sticker) - } - - public interface AvatarOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional string name = 1; - /** - * optional string name = 1; - */ - boolean hasName(); - /** - * optional string name = 1; - */ - java.lang.String getName(); - /** - * optional string name = 1; - */ - com.google.protobuf.ByteString - getNameBytes(); - - // optional uint32 length = 2; - /** - * optional uint32 length = 2; - */ - boolean hasLength(); - /** - * optional uint32 length = 2; - */ - int getLength(); - } - /** - * Protobuf type {@code signal.Avatar} - */ - public static final class Avatar extends - com.google.protobuf.GeneratedMessage - implements AvatarOrBuilder { - // Use Avatar.newBuilder() to construct. - private Avatar(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private Avatar(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Avatar defaultInstance; - public static Avatar getDefaultInstance() { - return defaultInstance; - } - - public Avatar getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private Avatar( - 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; - name_ = input.readBytes(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - length_ = input.readUInt32(); - 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Avatar.class, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Avatar parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Avatar(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional string name = 1; - public static final int NAME_FIELD_NUMBER = 1; - private java.lang.Object name_; - /** - * optional string name = 1; - */ - public boolean hasName() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string name = 1; - */ - 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 = 1; - */ - 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; - } - } - - // optional uint32 length = 2; - public static final int LENGTH_FIELD_NUMBER = 2; - private int length_; - /** - * optional uint32 length = 2; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint32 length = 2; - */ - public int getLength() { - return length_; - } - - private void initFields() { - name_ = ""; - length_ = 0; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getNameBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(2, length_); - } - 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, getNameBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(2, length_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Avatar 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.thoughtcrime.securesms.backup.BackupProtos.Avatar 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 signal.Avatar} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Avatar.class, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Avatar.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(); - name_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - length_ = 0; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Avatar_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Avatar getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Avatar build() { - org.thoughtcrime.securesms.backup.BackupProtos.Avatar result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Avatar buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.Avatar result = new org.thoughtcrime.securesms.backup.BackupProtos.Avatar(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.name_ = name_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.length_ = length_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Avatar) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Avatar)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Avatar other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance()) return this; - if (other.hasName()) { - bitField0_ |= 0x00000001; - name_ = other.name_; - onChanged(); - } - if (other.hasLength()) { - setLength(other.getLength()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Avatar parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Avatar) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional string name = 1; - private java.lang.Object name_ = ""; - /** - * optional string name = 1; - */ - public boolean hasName() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string name = 1; - */ - 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 = 1; - */ - 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 = 1; - */ - public Builder setName( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - name_ = value; - onChanged(); - return this; - } - /** - * optional string name = 1; - */ - public Builder clearName() { - bitField0_ = (bitField0_ & ~0x00000001); - name_ = getDefaultInstance().getName(); - onChanged(); - return this; - } - /** - * optional string name = 1; - */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - name_ = value; - onChanged(); - return this; - } - - // optional uint32 length = 2; - private int length_ ; - /** - * optional uint32 length = 2; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional uint32 length = 2; - */ - public int getLength() { - return length_; - } - /** - * optional uint32 length = 2; - */ - public Builder setLength(int value) { - bitField0_ |= 0x00000002; - length_ = value; - onChanged(); - return this; - } - /** - * optional uint32 length = 2; - */ - public Builder clearLength() { - bitField0_ = (bitField0_ & ~0x00000002); - length_ = 0; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.Avatar) - } - - static { - defaultInstance = new Avatar(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.Avatar) - } - - public interface DatabaseVersionOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional uint32 version = 1; - /** - * optional uint32 version = 1; - */ - boolean hasVersion(); - /** - * optional uint32 version = 1; - */ - int getVersion(); - } - /** - * Protobuf type {@code signal.DatabaseVersion} - */ - public static final class DatabaseVersion extends - com.google.protobuf.GeneratedMessage - implements DatabaseVersionOrBuilder { - // Use DatabaseVersion.newBuilder() to construct. - private DatabaseVersion(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private DatabaseVersion(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final DatabaseVersion defaultInstance; - public static DatabaseVersion getDefaultInstance() { - return defaultInstance; - } - - public DatabaseVersion getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private DatabaseVersion( - 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: { - bitField0_ |= 0x00000001; - version_ = input.readUInt32(); - 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.class, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public DatabaseVersion parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new DatabaseVersion(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional uint32 version = 1; - public static final int VERSION_FIELD_NUMBER = 1; - private int version_; - /** - * optional uint32 version = 1; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional uint32 version = 1; - */ - public int getVersion() { - return version_; - } - - private void initFields() { - version_ = 0; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeUInt32(1, version_); - } - 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 - .computeUInt32Size(1, version_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion 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.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion 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 signal.DatabaseVersion} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.class, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.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(); - version_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_DatabaseVersion_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion build() { - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion result = new org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.version_ = version_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance()) return this; - if (other.hasVersion()) { - setVersion(other.getVersion()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional uint32 version = 1; - private int version_ ; - /** - * optional uint32 version = 1; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional uint32 version = 1; - */ - public int getVersion() { - return version_; - } - /** - * optional uint32 version = 1; - */ - public Builder setVersion(int value) { - bitField0_ |= 0x00000001; - version_ = value; - onChanged(); - return this; - } - /** - * optional uint32 version = 1; - */ - public Builder clearVersion() { - bitField0_ = (bitField0_ & ~0x00000001); - version_ = 0; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.DatabaseVersion) - } - - static { - defaultInstance = new DatabaseVersion(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.DatabaseVersion) - } - - public interface HeaderOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional bytes iv = 1; - /** - * optional bytes iv = 1; - */ - boolean hasIv(); - /** - * optional bytes iv = 1; - */ - com.google.protobuf.ByteString getIv(); - - // optional bytes salt = 2; - /** - * optional bytes salt = 2; - */ - boolean hasSalt(); - /** - * optional bytes salt = 2; - */ - com.google.protobuf.ByteString getSalt(); - } - /** - * Protobuf type {@code signal.Header} - */ - public static final class Header extends - com.google.protobuf.GeneratedMessage - implements HeaderOrBuilder { - // Use Header.newBuilder() to construct. - private Header(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private Header(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Header defaultInstance; - public static Header getDefaultInstance() { - return defaultInstance; - } - - public Header getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private Header( - 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; - iv_ = input.readBytes(); - break; - } - case 18: { - bitField0_ |= 0x00000002; - salt_ = 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Header.class, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder.class); - } - - public static com.google.protobuf.Parser
PARSER = - new com.google.protobuf.AbstractParser
() { - public Header parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Header(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser
getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional bytes iv = 1; - public static final int IV_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString iv_; - /** - * optional bytes iv = 1; - */ - public boolean hasIv() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional bytes iv = 1; - */ - public com.google.protobuf.ByteString getIv() { - return iv_; - } - - // optional bytes salt = 2; - public static final int SALT_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString salt_; - /** - * optional bytes salt = 2; - */ - public boolean hasSalt() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional bytes salt = 2; - */ - public com.google.protobuf.ByteString getSalt() { - return salt_; - } - - private void initFields() { - iv_ = com.google.protobuf.ByteString.EMPTY; - salt_ = com.google.protobuf.ByteString.EMPTY; - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, iv_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, salt_); - } - 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, iv_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, salt_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.Header 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.thoughtcrime.securesms.backup.BackupProtos.Header 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 signal.Header} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.Header.class, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.Header.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(); - iv_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000001); - salt_ = 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_Header_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Header getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Header build() { - org.thoughtcrime.securesms.backup.BackupProtos.Header result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.Header buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.Header result = new org.thoughtcrime.securesms.backup.BackupProtos.Header(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.iv_ = iv_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.salt_ = salt_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.Header) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.Header)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.Header other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance()) return this; - if (other.hasIv()) { - setIv(other.getIv()); - } - if (other.hasSalt()) { - setSalt(other.getSalt()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.Header parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.Header) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional bytes iv = 1; - private com.google.protobuf.ByteString iv_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes iv = 1; - */ - public boolean hasIv() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional bytes iv = 1; - */ - public com.google.protobuf.ByteString getIv() { - return iv_; - } - /** - * optional bytes iv = 1; - */ - public Builder setIv(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - iv_ = value; - onChanged(); - return this; - } - /** - * optional bytes iv = 1; - */ - public Builder clearIv() { - bitField0_ = (bitField0_ & ~0x00000001); - iv_ = getDefaultInstance().getIv(); - onChanged(); - return this; - } - - // optional bytes salt = 2; - private com.google.protobuf.ByteString salt_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes salt = 2; - */ - public boolean hasSalt() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional bytes salt = 2; - */ - public com.google.protobuf.ByteString getSalt() { - return salt_; - } - /** - * optional bytes salt = 2; - */ - public Builder setSalt(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - salt_ = value; - onChanged(); - return this; - } - /** - * optional bytes salt = 2; - */ - public Builder clearSalt() { - bitField0_ = (bitField0_ & ~0x00000002); - salt_ = getDefaultInstance().getSalt(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signal.Header) - } - - static { - defaultInstance = new Header(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.Header) - } - - public interface BackupFrameOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional .signal.Header header = 1; - /** - * optional .signal.Header header = 1; - */ - boolean hasHeader(); - /** - * optional .signal.Header header = 1; - */ - org.thoughtcrime.securesms.backup.BackupProtos.Header getHeader(); - /** - * optional .signal.Header header = 1; - */ - org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder getHeaderOrBuilder(); - - // optional .signal.SqlStatement statement = 2; - /** - * optional .signal.SqlStatement statement = 2; - */ - boolean hasStatement(); - /** - * optional .signal.SqlStatement statement = 2; - */ - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getStatement(); - /** - * optional .signal.SqlStatement statement = 2; - */ - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder getStatementOrBuilder(); - - // optional .signal.SharedPreference preference = 3; - /** - * optional .signal.SharedPreference preference = 3; - */ - boolean hasPreference(); - /** - * optional .signal.SharedPreference preference = 3; - */ - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getPreference(); - /** - * optional .signal.SharedPreference preference = 3; - */ - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder getPreferenceOrBuilder(); - - // optional .signal.Attachment attachment = 4; - /** - * optional .signal.Attachment attachment = 4; - */ - boolean hasAttachment(); - /** - * optional .signal.Attachment attachment = 4; - */ - org.thoughtcrime.securesms.backup.BackupProtos.Attachment getAttachment(); - /** - * optional .signal.Attachment attachment = 4; - */ - org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder getAttachmentOrBuilder(); - - // optional .signal.DatabaseVersion version = 5; - /** - * optional .signal.DatabaseVersion version = 5; - */ - boolean hasVersion(); - /** - * optional .signal.DatabaseVersion version = 5; - */ - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getVersion(); - /** - * optional .signal.DatabaseVersion version = 5; - */ - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder getVersionOrBuilder(); - - // optional bool end = 6; - /** - * optional bool end = 6; - */ - boolean hasEnd(); - /** - * optional bool end = 6; - */ - boolean getEnd(); - - // optional .signal.Avatar avatar = 7; - /** - * optional .signal.Avatar avatar = 7; - */ - boolean hasAvatar(); - /** - * optional .signal.Avatar avatar = 7; - */ - org.thoughtcrime.securesms.backup.BackupProtos.Avatar getAvatar(); - /** - * optional .signal.Avatar avatar = 7; - */ - org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder getAvatarOrBuilder(); - - // optional .signal.Sticker sticker = 8; - /** - * optional .signal.Sticker sticker = 8; - */ - boolean hasSticker(); - /** - * optional .signal.Sticker sticker = 8; - */ - org.thoughtcrime.securesms.backup.BackupProtos.Sticker getSticker(); - /** - * optional .signal.Sticker sticker = 8; - */ - org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder getStickerOrBuilder(); - } - /** - * Protobuf type {@code signal.BackupFrame} - */ - public static final class BackupFrame extends - com.google.protobuf.GeneratedMessage - implements BackupFrameOrBuilder { - // Use BackupFrame.newBuilder() to construct. - private BackupFrame(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private BackupFrame(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final BackupFrame defaultInstance; - public static BackupFrame getDefaultInstance() { - return defaultInstance; - } - - public BackupFrame getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private BackupFrame( - 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: { - org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder subBuilder = null; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - subBuilder = header_.toBuilder(); - } - header_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Header.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(header_); - header_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000001; - break; - } - case 18: { - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder subBuilder = null; - if (((bitField0_ & 0x00000002) == 0x00000002)) { - subBuilder = statement_.toBuilder(); - } - statement_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(statement_); - statement_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000002; - break; - } - case 26: { - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder subBuilder = null; - if (((bitField0_ & 0x00000004) == 0x00000004)) { - subBuilder = preference_.toBuilder(); - } - preference_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(preference_); - preference_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000004; - break; - } - case 34: { - org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder subBuilder = null; - if (((bitField0_ & 0x00000008) == 0x00000008)) { - subBuilder = attachment_.toBuilder(); - } - attachment_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Attachment.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(attachment_); - attachment_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000008; - break; - } - case 42: { - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder subBuilder = null; - if (((bitField0_ & 0x00000010) == 0x00000010)) { - subBuilder = version_.toBuilder(); - } - version_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(version_); - version_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000010; - break; - } - case 48: { - bitField0_ |= 0x00000020; - end_ = input.readBool(); - break; - } - case 58: { - org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder subBuilder = null; - if (((bitField0_ & 0x00000040) == 0x00000040)) { - subBuilder = avatar_.toBuilder(); - } - avatar_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Avatar.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(avatar_); - avatar_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000040; - break; - } - case 66: { - org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder subBuilder = null; - if (((bitField0_ & 0x00000080) == 0x00000080)) { - subBuilder = sticker_.toBuilder(); - } - sticker_ = input.readMessage(org.thoughtcrime.securesms.backup.BackupProtos.Sticker.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(sticker_); - sticker_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000080; - 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.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.class, org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public BackupFrame parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new BackupFrame(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - // optional .signal.Header header = 1; - public static final int HEADER_FIELD_NUMBER = 1; - private org.thoughtcrime.securesms.backup.BackupProtos.Header header_; - /** - * optional .signal.Header header = 1; - */ - public boolean hasHeader() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional .signal.Header header = 1; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Header getHeader() { - return header_; - } - /** - * optional .signal.Header header = 1; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder getHeaderOrBuilder() { - return header_; - } - - // optional .signal.SqlStatement statement = 2; - public static final int STATEMENT_FIELD_NUMBER = 2; - private org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement statement_; - /** - * optional .signal.SqlStatement statement = 2; - */ - public boolean hasStatement() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getStatement() { - return statement_; - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder getStatementOrBuilder() { - return statement_; - } - - // optional .signal.SharedPreference preference = 3; - public static final int PREFERENCE_FIELD_NUMBER = 3; - private org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference preference_; - /** - * optional .signal.SharedPreference preference = 3; - */ - public boolean hasPreference() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getPreference() { - return preference_; - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder getPreferenceOrBuilder() { - return preference_; - } - - // optional .signal.Attachment attachment = 4; - public static final int ATTACHMENT_FIELD_NUMBER = 4; - private org.thoughtcrime.securesms.backup.BackupProtos.Attachment attachment_; - /** - * optional .signal.Attachment attachment = 4; - */ - public boolean hasAttachment() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional .signal.Attachment attachment = 4; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Attachment getAttachment() { - return attachment_; - } - /** - * optional .signal.Attachment attachment = 4; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder getAttachmentOrBuilder() { - return attachment_; - } - - // optional .signal.DatabaseVersion version = 5; - public static final int VERSION_FIELD_NUMBER = 5; - private org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion version_; - /** - * optional .signal.DatabaseVersion version = 5; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getVersion() { - return version_; - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder getVersionOrBuilder() { - return version_; - } - - // optional bool end = 6; - public static final int END_FIELD_NUMBER = 6; - private boolean end_; - /** - * optional bool end = 6; - */ - public boolean hasEnd() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - * optional bool end = 6; - */ - public boolean getEnd() { - return end_; - } - - // optional .signal.Avatar avatar = 7; - public static final int AVATAR_FIELD_NUMBER = 7; - private org.thoughtcrime.securesms.backup.BackupProtos.Avatar avatar_; - /** - * optional .signal.Avatar avatar = 7; - */ - public boolean hasAvatar() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - * optional .signal.Avatar avatar = 7; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Avatar getAvatar() { - return avatar_; - } - /** - * optional .signal.Avatar avatar = 7; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder getAvatarOrBuilder() { - return avatar_; - } - - // optional .signal.Sticker sticker = 8; - public static final int STICKER_FIELD_NUMBER = 8; - private org.thoughtcrime.securesms.backup.BackupProtos.Sticker sticker_; - /** - * optional .signal.Sticker sticker = 8; - */ - public boolean hasSticker() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - * optional .signal.Sticker sticker = 8; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Sticker getSticker() { - return sticker_; - } - /** - * optional .signal.Sticker sticker = 8; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder getStickerOrBuilder() { - return sticker_; - } - - private void initFields() { - header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance(); - statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance(); - preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance(); - attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance(); - version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance(); - end_ = false; - avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance(); - sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance(); - } - 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(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeMessage(1, header_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeMessage(2, statement_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeMessage(3, preference_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeMessage(4, attachment_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeMessage(5, version_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBool(6, end_); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeMessage(7, avatar_); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - output.writeMessage(8, sticker_); - } - 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(1, header_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(2, statement_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(3, preference_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(4, attachment_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(5, version_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream - .computeBoolSize(6, end_); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(7, avatar_); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(8, sticker_); - } - 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.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame 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.thoughtcrime.securesms.backup.BackupProtos.BackupFrame 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 signal.BackupFrame} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.thoughtcrime.securesms.backup.BackupProtos.BackupFrameOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.class, org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.Builder.class); - } - - // Construct using org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getHeaderFieldBuilder(); - getStatementFieldBuilder(); - getPreferenceFieldBuilder(); - getAttachmentFieldBuilder(); - getVersionFieldBuilder(); - getAvatarFieldBuilder(); - getStickerFieldBuilder(); - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - if (headerBuilder_ == null) { - header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance(); - } else { - headerBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000001); - if (statementBuilder_ == null) { - statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance(); - } else { - statementBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000002); - if (preferenceBuilder_ == null) { - preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance(); - } else { - preferenceBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); - if (attachmentBuilder_ == null) { - attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance(); - } else { - attachmentBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000008); - if (versionBuilder_ == null) { - version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance(); - } else { - versionBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000010); - end_ = false; - bitField0_ = (bitField0_ & ~0x00000020); - if (avatarBuilder_ == null) { - avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance(); - } else { - avatarBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000040); - if (stickerBuilder_ == null) { - sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance(); - } else { - stickerBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000080); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.internal_static_signal_BackupFrame_descriptor; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame getDefaultInstanceForType() { - return org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.getDefaultInstance(); - } - - public org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame build() { - org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame buildPartial() { - org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame result = new org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - if (headerBuilder_ == null) { - result.header_ = header_; - } else { - result.header_ = headerBuilder_.build(); - } - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - if (statementBuilder_ == null) { - result.statement_ = statement_; - } else { - result.statement_ = statementBuilder_.build(); - } - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - if (preferenceBuilder_ == null) { - result.preference_ = preference_; - } else { - result.preference_ = preferenceBuilder_.build(); - } - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - if (attachmentBuilder_ == null) { - result.attachment_ = attachment_; - } else { - result.attachment_ = attachmentBuilder_.build(); - } - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - if (versionBuilder_ == null) { - result.version_ = version_; - } else { - result.version_ = versionBuilder_.build(); - } - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } - result.end_ = end_; - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000040; - } - if (avatarBuilder_ == null) { - result.avatar_ = avatar_; - } else { - result.avatar_ = avatarBuilder_.build(); - } - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000080; - } - if (stickerBuilder_ == null) { - result.sticker_ = sticker_; - } else { - result.sticker_ = stickerBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame) { - return mergeFrom((org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame other) { - if (other == org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame.getDefaultInstance()) return this; - if (other.hasHeader()) { - mergeHeader(other.getHeader()); - } - if (other.hasStatement()) { - mergeStatement(other.getStatement()); - } - if (other.hasPreference()) { - mergePreference(other.getPreference()); - } - if (other.hasAttachment()) { - mergeAttachment(other.getAttachment()); - } - if (other.hasVersion()) { - mergeVersion(other.getVersion()); - } - if (other.hasEnd()) { - setEnd(other.getEnd()); - } - if (other.hasAvatar()) { - mergeAvatar(other.getAvatar()); - } - if (other.hasSticker()) { - mergeSticker(other.getSticker()); - } - 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.thoughtcrime.securesms.backup.BackupProtos.BackupFrame parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional .signal.Header header = 1; - private org.thoughtcrime.securesms.backup.BackupProtos.Header header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Header, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder, org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder> headerBuilder_; - /** - * optional .signal.Header header = 1; - */ - public boolean hasHeader() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional .signal.Header header = 1; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Header getHeader() { - if (headerBuilder_ == null) { - return header_; - } else { - return headerBuilder_.getMessage(); - } - } - /** - * optional .signal.Header header = 1; - */ - public Builder setHeader(org.thoughtcrime.securesms.backup.BackupProtos.Header value) { - if (headerBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - header_ = value; - onChanged(); - } else { - headerBuilder_.setMessage(value); - } - bitField0_ |= 0x00000001; - return this; - } - /** - * optional .signal.Header header = 1; - */ - public Builder setHeader( - org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder builderForValue) { - if (headerBuilder_ == null) { - header_ = builderForValue.build(); - onChanged(); - } else { - headerBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000001; - return this; - } - /** - * optional .signal.Header header = 1; - */ - public Builder mergeHeader(org.thoughtcrime.securesms.backup.BackupProtos.Header value) { - if (headerBuilder_ == null) { - if (((bitField0_ & 0x00000001) == 0x00000001) && - header_ != org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance()) { - header_ = - org.thoughtcrime.securesms.backup.BackupProtos.Header.newBuilder(header_).mergeFrom(value).buildPartial(); - } else { - header_ = value; - } - onChanged(); - } else { - headerBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000001; - return this; - } - /** - * optional .signal.Header header = 1; - */ - public Builder clearHeader() { - if (headerBuilder_ == null) { - header_ = org.thoughtcrime.securesms.backup.BackupProtos.Header.getDefaultInstance(); - onChanged(); - } else { - headerBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000001); - return this; - } - /** - * optional .signal.Header header = 1; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder getHeaderBuilder() { - bitField0_ |= 0x00000001; - onChanged(); - return getHeaderFieldBuilder().getBuilder(); - } - /** - * optional .signal.Header header = 1; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder getHeaderOrBuilder() { - if (headerBuilder_ != null) { - return headerBuilder_.getMessageOrBuilder(); - } else { - return header_; - } - } - /** - * optional .signal.Header header = 1; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Header, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder, org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder> - getHeaderFieldBuilder() { - if (headerBuilder_ == null) { - headerBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Header, org.thoughtcrime.securesms.backup.BackupProtos.Header.Builder, org.thoughtcrime.securesms.backup.BackupProtos.HeaderOrBuilder>( - header_, - getParentForChildren(), - isClean()); - header_ = null; - } - return headerBuilder_; - } - - // optional .signal.SqlStatement statement = 2; - private org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder> statementBuilder_; - /** - * optional .signal.SqlStatement statement = 2; - */ - public boolean hasStatement() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement getStatement() { - if (statementBuilder_ == null) { - return statement_; - } else { - return statementBuilder_.getMessage(); - } - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public Builder setStatement(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement value) { - if (statementBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - statement_ = value; - onChanged(); - } else { - statementBuilder_.setMessage(value); - } - bitField0_ |= 0x00000002; - return this; - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public Builder setStatement( - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder builderForValue) { - if (statementBuilder_ == null) { - statement_ = builderForValue.build(); - onChanged(); - } else { - statementBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000002; - return this; - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public Builder mergeStatement(org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement value) { - if (statementBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002) && - statement_ != org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance()) { - statement_ = - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.newBuilder(statement_).mergeFrom(value).buildPartial(); - } else { - statement_ = value; - } - onChanged(); - } else { - statementBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000002; - return this; - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public Builder clearStatement() { - if (statementBuilder_ == null) { - statement_ = org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.getDefaultInstance(); - onChanged(); - } else { - statementBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder getStatementBuilder() { - bitField0_ |= 0x00000002; - onChanged(); - return getStatementFieldBuilder().getBuilder(); - } - /** - * optional .signal.SqlStatement statement = 2; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder getStatementOrBuilder() { - if (statementBuilder_ != null) { - return statementBuilder_.getMessageOrBuilder(); - } else { - return statement_; - } - } - /** - * optional .signal.SqlStatement statement = 2; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder> - getStatementFieldBuilder() { - if (statementBuilder_ == null) { - statementBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SqlStatementOrBuilder>( - statement_, - getParentForChildren(), - isClean()); - statement_ = null; - } - return statementBuilder_; - } - - // optional .signal.SharedPreference preference = 3; - private org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder> preferenceBuilder_; - /** - * optional .signal.SharedPreference preference = 3; - */ - public boolean hasPreference() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference getPreference() { - if (preferenceBuilder_ == null) { - return preference_; - } else { - return preferenceBuilder_.getMessage(); - } - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public Builder setPreference(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference value) { - if (preferenceBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - preference_ = value; - onChanged(); - } else { - preferenceBuilder_.setMessage(value); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public Builder setPreference( - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder builderForValue) { - if (preferenceBuilder_ == null) { - preference_ = builderForValue.build(); - onChanged(); - } else { - preferenceBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public Builder mergePreference(org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference value) { - if (preferenceBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004) && - preference_ != org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance()) { - preference_ = - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.newBuilder(preference_).mergeFrom(value).buildPartial(); - } else { - preference_ = value; - } - onChanged(); - } else { - preferenceBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public Builder clearPreference() { - if (preferenceBuilder_ == null) { - preference_ = org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.getDefaultInstance(); - onChanged(); - } else { - preferenceBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder getPreferenceBuilder() { - bitField0_ |= 0x00000004; - onChanged(); - return getPreferenceFieldBuilder().getBuilder(); - } - /** - * optional .signal.SharedPreference preference = 3; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder getPreferenceOrBuilder() { - if (preferenceBuilder_ != null) { - return preferenceBuilder_.getMessageOrBuilder(); - } else { - return preference_; - } - } - /** - * optional .signal.SharedPreference preference = 3; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder> - getPreferenceFieldBuilder() { - if (preferenceBuilder_ == null) { - preferenceBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference.Builder, org.thoughtcrime.securesms.backup.BackupProtos.SharedPreferenceOrBuilder>( - preference_, - getParentForChildren(), - isClean()); - preference_ = null; - } - return preferenceBuilder_; - } - - // optional .signal.Attachment attachment = 4; - private org.thoughtcrime.securesms.backup.BackupProtos.Attachment attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Attachment, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder> attachmentBuilder_; - /** - * optional .signal.Attachment attachment = 4; - */ - public boolean hasAttachment() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional .signal.Attachment attachment = 4; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Attachment getAttachment() { - if (attachmentBuilder_ == null) { - return attachment_; - } else { - return attachmentBuilder_.getMessage(); - } - } - /** - * optional .signal.Attachment attachment = 4; - */ - public Builder setAttachment(org.thoughtcrime.securesms.backup.BackupProtos.Attachment value) { - if (attachmentBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - attachment_ = value; - onChanged(); - } else { - attachmentBuilder_.setMessage(value); - } - bitField0_ |= 0x00000008; - return this; - } - /** - * optional .signal.Attachment attachment = 4; - */ - public Builder setAttachment( - org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder builderForValue) { - if (attachmentBuilder_ == null) { - attachment_ = builderForValue.build(); - onChanged(); - } else { - attachmentBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000008; - return this; - } - /** - * optional .signal.Attachment attachment = 4; - */ - public Builder mergeAttachment(org.thoughtcrime.securesms.backup.BackupProtos.Attachment value) { - if (attachmentBuilder_ == null) { - if (((bitField0_ & 0x00000008) == 0x00000008) && - attachment_ != org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance()) { - attachment_ = - org.thoughtcrime.securesms.backup.BackupProtos.Attachment.newBuilder(attachment_).mergeFrom(value).buildPartial(); - } else { - attachment_ = value; - } - onChanged(); - } else { - attachmentBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000008; - return this; - } - /** - * optional .signal.Attachment attachment = 4; - */ - public Builder clearAttachment() { - if (attachmentBuilder_ == null) { - attachment_ = org.thoughtcrime.securesms.backup.BackupProtos.Attachment.getDefaultInstance(); - onChanged(); - } else { - attachmentBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000008); - return this; - } - /** - * optional .signal.Attachment attachment = 4; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder getAttachmentBuilder() { - bitField0_ |= 0x00000008; - onChanged(); - return getAttachmentFieldBuilder().getBuilder(); - } - /** - * optional .signal.Attachment attachment = 4; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder getAttachmentOrBuilder() { - if (attachmentBuilder_ != null) { - return attachmentBuilder_.getMessageOrBuilder(); - } else { - return attachment_; - } - } - /** - * optional .signal.Attachment attachment = 4; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Attachment, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder> - getAttachmentFieldBuilder() { - if (attachmentBuilder_ == null) { - attachmentBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Attachment, org.thoughtcrime.securesms.backup.BackupProtos.Attachment.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AttachmentOrBuilder>( - attachment_, - getParentForChildren(), - isClean()); - attachment_ = null; - } - return attachmentBuilder_; - } - - // optional .signal.DatabaseVersion version = 5; - private org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder> versionBuilder_; - /** - * optional .signal.DatabaseVersion version = 5; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion getVersion() { - if (versionBuilder_ == null) { - return version_; - } else { - return versionBuilder_.getMessage(); - } - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public Builder setVersion(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion value) { - if (versionBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - version_ = value; - onChanged(); - } else { - versionBuilder_.setMessage(value); - } - bitField0_ |= 0x00000010; - return this; - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public Builder setVersion( - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder builderForValue) { - if (versionBuilder_ == null) { - version_ = builderForValue.build(); - onChanged(); - } else { - versionBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000010; - return this; - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public Builder mergeVersion(org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion value) { - if (versionBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010) && - version_ != org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance()) { - version_ = - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.newBuilder(version_).mergeFrom(value).buildPartial(); - } else { - version_ = value; - } - onChanged(); - } else { - versionBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000010; - return this; - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public Builder clearVersion() { - if (versionBuilder_ == null) { - version_ = org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.getDefaultInstance(); - onChanged(); - } else { - versionBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000010); - return this; - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder getVersionBuilder() { - bitField0_ |= 0x00000010; - onChanged(); - return getVersionFieldBuilder().getBuilder(); - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder getVersionOrBuilder() { - if (versionBuilder_ != null) { - return versionBuilder_.getMessageOrBuilder(); - } else { - return version_; - } - } - /** - * optional .signal.DatabaseVersion version = 5; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder> - getVersionFieldBuilder() { - if (versionBuilder_ == null) { - versionBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion.Builder, org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersionOrBuilder>( - version_, - getParentForChildren(), - isClean()); - version_ = null; - } - return versionBuilder_; - } - - // optional bool end = 6; - private boolean end_ ; - /** - * optional bool end = 6; - */ - public boolean hasEnd() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - * optional bool end = 6; - */ - public boolean getEnd() { - return end_; - } - /** - * optional bool end = 6; - */ - public Builder setEnd(boolean value) { - bitField0_ |= 0x00000020; - end_ = value; - onChanged(); - return this; - } - /** - * optional bool end = 6; - */ - public Builder clearEnd() { - bitField0_ = (bitField0_ & ~0x00000020); - end_ = false; - onChanged(); - return this; - } - - // optional .signal.Avatar avatar = 7; - private org.thoughtcrime.securesms.backup.BackupProtos.Avatar avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Avatar, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder> avatarBuilder_; - /** - * optional .signal.Avatar avatar = 7; - */ - public boolean hasAvatar() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - * optional .signal.Avatar avatar = 7; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Avatar getAvatar() { - if (avatarBuilder_ == null) { - return avatar_; - } else { - return avatarBuilder_.getMessage(); - } - } - /** - * optional .signal.Avatar avatar = 7; - */ - public Builder setAvatar(org.thoughtcrime.securesms.backup.BackupProtos.Avatar value) { - if (avatarBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - avatar_ = value; - onChanged(); - } else { - avatarBuilder_.setMessage(value); - } - bitField0_ |= 0x00000040; - return this; - } - /** - * optional .signal.Avatar avatar = 7; - */ - public Builder setAvatar( - org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder builderForValue) { - if (avatarBuilder_ == null) { - avatar_ = builderForValue.build(); - onChanged(); - } else { - avatarBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000040; - return this; - } - /** - * optional .signal.Avatar avatar = 7; - */ - public Builder mergeAvatar(org.thoughtcrime.securesms.backup.BackupProtos.Avatar value) { - if (avatarBuilder_ == null) { - if (((bitField0_ & 0x00000040) == 0x00000040) && - avatar_ != org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance()) { - avatar_ = - org.thoughtcrime.securesms.backup.BackupProtos.Avatar.newBuilder(avatar_).mergeFrom(value).buildPartial(); - } else { - avatar_ = value; - } - onChanged(); - } else { - avatarBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000040; - return this; - } - /** - * optional .signal.Avatar avatar = 7; - */ - public Builder clearAvatar() { - if (avatarBuilder_ == null) { - avatar_ = org.thoughtcrime.securesms.backup.BackupProtos.Avatar.getDefaultInstance(); - onChanged(); - } else { - avatarBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000040); - return this; - } - /** - * optional .signal.Avatar avatar = 7; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder getAvatarBuilder() { - bitField0_ |= 0x00000040; - onChanged(); - return getAvatarFieldBuilder().getBuilder(); - } - /** - * optional .signal.Avatar avatar = 7; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder getAvatarOrBuilder() { - if (avatarBuilder_ != null) { - return avatarBuilder_.getMessageOrBuilder(); - } else { - return avatar_; - } - } - /** - * optional .signal.Avatar avatar = 7; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Avatar, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder> - getAvatarFieldBuilder() { - if (avatarBuilder_ == null) { - avatarBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Avatar, org.thoughtcrime.securesms.backup.BackupProtos.Avatar.Builder, org.thoughtcrime.securesms.backup.BackupProtos.AvatarOrBuilder>( - avatar_, - getParentForChildren(), - isClean()); - avatar_ = null; - } - return avatarBuilder_; - } - - // optional .signal.Sticker sticker = 8; - private org.thoughtcrime.securesms.backup.BackupProtos.Sticker sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Sticker, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder, org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder> stickerBuilder_; - /** - * optional .signal.Sticker sticker = 8; - */ - public boolean hasSticker() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - * optional .signal.Sticker sticker = 8; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Sticker getSticker() { - if (stickerBuilder_ == null) { - return sticker_; - } else { - return stickerBuilder_.getMessage(); - } - } - /** - * optional .signal.Sticker sticker = 8; - */ - public Builder setSticker(org.thoughtcrime.securesms.backup.BackupProtos.Sticker value) { - if (stickerBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - sticker_ = value; - onChanged(); - } else { - stickerBuilder_.setMessage(value); - } - bitField0_ |= 0x00000080; - return this; - } - /** - * optional .signal.Sticker sticker = 8; - */ - public Builder setSticker( - org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder builderForValue) { - if (stickerBuilder_ == null) { - sticker_ = builderForValue.build(); - onChanged(); - } else { - stickerBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000080; - return this; - } - /** - * optional .signal.Sticker sticker = 8; - */ - public Builder mergeSticker(org.thoughtcrime.securesms.backup.BackupProtos.Sticker value) { - if (stickerBuilder_ == null) { - if (((bitField0_ & 0x00000080) == 0x00000080) && - sticker_ != org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance()) { - sticker_ = - org.thoughtcrime.securesms.backup.BackupProtos.Sticker.newBuilder(sticker_).mergeFrom(value).buildPartial(); - } else { - sticker_ = value; - } - onChanged(); - } else { - stickerBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000080; - return this; - } - /** - * optional .signal.Sticker sticker = 8; - */ - public Builder clearSticker() { - if (stickerBuilder_ == null) { - sticker_ = org.thoughtcrime.securesms.backup.BackupProtos.Sticker.getDefaultInstance(); - onChanged(); - } else { - stickerBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000080); - return this; - } - /** - * optional .signal.Sticker sticker = 8; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder getStickerBuilder() { - bitField0_ |= 0x00000080; - onChanged(); - return getStickerFieldBuilder().getBuilder(); - } - /** - * optional .signal.Sticker sticker = 8; - */ - public org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder getStickerOrBuilder() { - if (stickerBuilder_ != null) { - return stickerBuilder_.getMessageOrBuilder(); - } else { - return sticker_; - } - } - /** - * optional .signal.Sticker sticker = 8; - */ - private com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Sticker, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder, org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder> - getStickerFieldBuilder() { - if (stickerBuilder_ == null) { - stickerBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.thoughtcrime.securesms.backup.BackupProtos.Sticker, org.thoughtcrime.securesms.backup.BackupProtos.Sticker.Builder, org.thoughtcrime.securesms.backup.BackupProtos.StickerOrBuilder>( - sticker_, - getParentForChildren(), - isClean()); - sticker_ = null; - } - return stickerBuilder_; - } - - // @@protoc_insertion_point(builder_scope:signal.BackupFrame) - } - - static { - defaultInstance = new BackupFrame(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signal.BackupFrame) - } - - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_SqlStatement_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_SqlStatement_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_SqlStatement_SqlParameter_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_SharedPreference_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_SharedPreference_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_Attachment_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_Attachment_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_Sticker_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_Sticker_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_Avatar_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_Avatar_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_DatabaseVersion_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_DatabaseVersion_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_Header_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_Header_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signal_BackupFrame_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signal_BackupFrame_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor - getDescriptor() { - return descriptor; - } - private static com.google.protobuf.Descriptors.FileDescriptor - descriptor; - static { - java.lang.String[] descriptorData = { - "\n\rBackups.proto\022\006signal\"\342\001\n\014SqlStatement" + - "\022\021\n\tstatement\030\001 \001(\t\0225\n\nparameters\030\002 \003(\0132" + - "!.signal.SqlStatement.SqlParameter\032\207\001\n\014S" + - "qlParameter\022\026\n\016stringParamter\030\001 \001(\t\022\030\n\020i" + - "ntegerParameter\030\002 \001(\004\022\027\n\017doubleParameter" + - "\030\003 \001(\001\022\025\n\rblobParameter\030\004 \001(\014\022\025\n\rnullpar" + - "ameter\030\005 \001(\010\"<\n\020SharedPreference\022\014\n\004file" + - "\030\001 \001(\t\022\013\n\003key\030\002 \001(\t\022\r\n\005value\030\003 \001(\t\"A\n\nAt" + - "tachment\022\r\n\005rowId\030\001 \001(\004\022\024\n\014attachmentId\030" + - "\002 \001(\004\022\016\n\006length\030\003 \001(\r\"(\n\007Sticker\022\r\n\005rowI", - "d\030\001 \001(\004\022\016\n\006length\030\002 \001(\r\"&\n\006Avatar\022\014\n\004nam" + - "e\030\001 \001(\t\022\016\n\006length\030\002 \001(\r\"\"\n\017DatabaseVersi" + - "on\022\017\n\007version\030\001 \001(\r\"\"\n\006Header\022\n\n\002iv\030\001 \001(" + - "\014\022\014\n\004salt\030\002 \001(\014\"\245\002\n\013BackupFrame\022\036\n\006heade" + - "r\030\001 \001(\0132\016.signal.Header\022\'\n\tstatement\030\002 \001" + - "(\0132\024.signal.SqlStatement\022,\n\npreference\030\003" + - " \001(\0132\030.signal.SharedPreference\022&\n\nattach" + - "ment\030\004 \001(\0132\022.signal.Attachment\022(\n\007versio" + - "n\030\005 \001(\0132\027.signal.DatabaseVersion\022\013\n\003end\030" + - "\006 \001(\010\022\036\n\006avatar\030\007 \001(\0132\016.signal.Avatar\022 \n", - "\007sticker\030\010 \001(\0132\017.signal.StickerB1\n!org.t" + - "houghtcrime.securesms.backupB\014BackupProt" + - "os" - }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = - new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { - public com.google.protobuf.ExtensionRegistry assignDescriptors( - com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - internal_static_signal_SqlStatement_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_signal_SqlStatement_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_SqlStatement_descriptor, - new java.lang.String[] { "Statement", "Parameters", }); - internal_static_signal_SqlStatement_SqlParameter_descriptor = - internal_static_signal_SqlStatement_descriptor.getNestedTypes().get(0); - internal_static_signal_SqlStatement_SqlParameter_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_SqlStatement_SqlParameter_descriptor, - new java.lang.String[] { "StringParamter", "IntegerParameter", "DoubleParameter", "BlobParameter", "Nullparameter", }); - internal_static_signal_SharedPreference_descriptor = - getDescriptor().getMessageTypes().get(1); - internal_static_signal_SharedPreference_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_SharedPreference_descriptor, - new java.lang.String[] { "File", "Key", "Value", }); - internal_static_signal_Attachment_descriptor = - getDescriptor().getMessageTypes().get(2); - internal_static_signal_Attachment_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_Attachment_descriptor, - new java.lang.String[] { "RowId", "AttachmentId", "Length", }); - internal_static_signal_Sticker_descriptor = - getDescriptor().getMessageTypes().get(3); - internal_static_signal_Sticker_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_Sticker_descriptor, - new java.lang.String[] { "RowId", "Length", }); - internal_static_signal_Avatar_descriptor = - getDescriptor().getMessageTypes().get(4); - internal_static_signal_Avatar_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_Avatar_descriptor, - new java.lang.String[] { "Name", "Length", }); - internal_static_signal_DatabaseVersion_descriptor = - getDescriptor().getMessageTypes().get(5); - internal_static_signal_DatabaseVersion_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_DatabaseVersion_descriptor, - new java.lang.String[] { "Version", }); - internal_static_signal_Header_descriptor = - getDescriptor().getMessageTypes().get(6); - internal_static_signal_Header_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_Header_descriptor, - new java.lang.String[] { "Iv", "Salt", }); - internal_static_signal_BackupFrame_descriptor = - getDescriptor().getMessageTypes().get(7); - internal_static_signal_BackupFrame_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signal_BackupFrame_descriptor, - new java.lang.String[] { "Header", "Statement", "Preference", "Attachment", "Version", "End", "Avatar", "Sticker", }); - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor - .internalBuildGeneratedFileFrom(descriptorData, - new com.google.protobuf.Descriptors.FileDescriptor[] { - }, assigner); - } - - // @@protoc_insertion_point(outer_class_scope) -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt deleted file mode 100644 index 6b5d47a2e..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt +++ /dev/null @@ -1,447 +0,0 @@ -package org.thoughtcrime.securesms.backup - -import android.content.Context -import android.database.Cursor -import android.net.Uri -import android.text.TextUtils -import androidx.annotation.WorkerThread -import com.annimon.stream.function.Consumer -import com.annimon.stream.function.Predicate -import com.google.protobuf.ByteString -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 -import org.session.libsession.utilities.Conversions -import org.session.libsession.utilities.Util -import org.session.libsignal.crypto.kdf.HKDFv3 -import org.session.libsignal.utilities.ByteUtil -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.backup.BackupProtos.Attachment -import org.thoughtcrime.securesms.backup.BackupProtos.Avatar -import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame -import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion -import org.thoughtcrime.securesms.backup.BackupProtos.Header -import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference -import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement -import org.thoughtcrime.securesms.backup.BackupProtos.Sticker -import org.thoughtcrime.securesms.crypto.AttachmentSecret -import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream -import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream -import org.thoughtcrime.securesms.database.AttachmentDatabase -import org.thoughtcrime.securesms.database.GroupReceiptDatabase -import org.thoughtcrime.securesms.database.JobDatabase -import org.thoughtcrime.securesms.database.LokiAPIDatabase -import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase -import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsColumns -import org.thoughtcrime.securesms.database.PushDatabase -import org.thoughtcrime.securesms.database.SearchDatabase -import org.thoughtcrime.securesms.database.SmsDatabase -import org.thoughtcrime.securesms.util.BackupUtil -import java.io.Closeable -import java.io.File -import java.io.FileInputStream -import java.io.Flushable -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.security.InvalidAlgorithmParameterException -import java.security.InvalidKeyException -import java.security.NoSuchAlgorithmException -import java.util.LinkedList -import javax.crypto.BadPaddingException -import javax.crypto.Cipher -import javax.crypto.IllegalBlockSizeException -import javax.crypto.Mac -import javax.crypto.NoSuchPaddingException -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -object FullBackupExporter { - private val TAG = FullBackupExporter::class.java.simpleName - - @JvmStatic - @WorkerThread - @Throws(IOException::class) - fun export(context: Context, - attachmentSecret: AttachmentSecret, - input: SQLiteDatabase, - fileUri: Uri, - passphrase: String) { - - val baseOutputStream = context.contentResolver.openOutputStream(fileUri) - ?: throw IOException("Cannot open an output stream for the file URI: $fileUri") - - var count = 0 - try { - BackupFrameOutputStream(baseOutputStream, passphrase).use { outputStream -> - outputStream.writeDatabaseVersion(input.version) - val tables = exportSchema(input, outputStream) - for (table in tables) if (shouldExportTable(table)) { - count = when (table) { - SmsDatabase.TABLE_NAME, MmsDatabase.TABLE_NAME -> { - exportTable(table, input, outputStream, - { cursor: Cursor -> - cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.EXPIRES_IN)) <= 0 - }, - null, - count) - } - GroupReceiptDatabase.TABLE_NAME -> { - exportTable(table, input, outputStream, - { cursor: Cursor -> - isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))) - }, - null, - count) - } - AttachmentDatabase.TABLE_NAME -> { - exportTable(table, input, outputStream, - { cursor: Cursor -> - isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))) - }, - { cursor: Cursor -> - exportAttachment(attachmentSecret, cursor, outputStream) - }, - count) - } - else -> { - exportTable(table, input, outputStream, null, null, count) - } - } - } - for (preference in BackupUtil.getBackupRecords(context)) { - EventBus.getDefault().post(BackupEvent.createProgress(++count)) - outputStream.writePreferenceEntry(preference) - } - for (preference in BackupPreferences.getBackupRecords(context)) { - EventBus.getDefault().post(BackupEvent.createProgress(++count)) - outputStream.writePreferenceEntry(preference) - } - for (avatar in AvatarHelper.getAvatarFiles(context)) { - EventBus.getDefault().post(BackupEvent.createProgress(++count)) - outputStream.writeAvatar(avatar.name, FileInputStream(avatar), avatar.length()) - } - outputStream.writeEnd() - } - EventBus.getDefault().post(BackupEvent.createFinished()) - } catch (e: Exception) { - Log.e(TAG, "Failed to make full backup.", e) - EventBus.getDefault().post(BackupEvent.createFinished(e)) - throw e - } - } - - private inline fun shouldExportTable(table: String): Boolean { - return table != PushDatabase.TABLE_NAME && - - table != LokiBackupFilesDatabase.TABLE_NAME && - table != LokiAPIDatabase.openGroupProfilePictureTable && - - table != JobDatabase.Jobs.TABLE_NAME && - table != JobDatabase.Constraints.TABLE_NAME && - table != JobDatabase.Dependencies.TABLE_NAME && - - !table.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME) && - !table.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME) && - !table.startsWith("sqlite_") - } - - @Throws(IOException::class) - private fun exportSchema(input: SQLiteDatabase, outputStream: BackupFrameOutputStream): List { - val tables: MutableList = LinkedList() - input.rawQuery("SELECT sql, name, type FROM sqlite_master", null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - val sql = cursor.getString(0) - val name = cursor.getString(1) - val type = cursor.getString(2) - if (sql != null) { - val isSmsFtsSecretTable = name != null && name != SearchDatabase.SMS_FTS_TABLE_NAME && name.startsWith(SearchDatabase.SMS_FTS_TABLE_NAME) - val isMmsFtsSecretTable = name != null && name != SearchDatabase.MMS_FTS_TABLE_NAME && name.startsWith(SearchDatabase.MMS_FTS_TABLE_NAME) - if (!isSmsFtsSecretTable && !isMmsFtsSecretTable) { - if ("table" == type) { - tables.add(name) - } - outputStream.writeSql(SqlStatement.newBuilder().setStatement(cursor.getString(0)).build()) - } - } - } - } - return tables - } - - @Throws(IOException::class) - private fun exportTable(table: String, - input: SQLiteDatabase, - outputStream: BackupFrameOutputStream, - predicate: Predicate?, - postProcess: Consumer?, - count: Int): Int { - var count = count - val template = "INSERT INTO $table VALUES " - input.rawQuery("SELECT * FROM $table", null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - EventBus.getDefault().post(BackupEvent.createProgress(++count)) - if (predicate != null && !predicate.test(cursor)) continue - - val statement = StringBuilder(template) - val statementBuilder = SqlStatement.newBuilder() - statement.append('(') - for (i in 0 until cursor.columnCount) { - statement.append('?') - when (cursor.getType(i)) { - Cursor.FIELD_TYPE_STRING -> { - statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder() - .setStringParamter(cursor.getString(i))) - } - Cursor.FIELD_TYPE_FLOAT -> { - statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder() - .setDoubleParameter(cursor.getDouble(i))) - } - Cursor.FIELD_TYPE_INTEGER -> { - statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder() - .setIntegerParameter(cursor.getLong(i))) - } - Cursor.FIELD_TYPE_BLOB -> { - statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder() - .setBlobParameter(ByteString.copyFrom(cursor.getBlob(i)))) - } - Cursor.FIELD_TYPE_NULL -> { - statementBuilder.addParameters(SqlStatement.SqlParameter.newBuilder() - .setNullparameter(true)) - } - else -> { - throw AssertionError("unknown type?" + cursor.getType(i)) - } - } - if (i < cursor.columnCount - 1) { - statement.append(',') - } - } - statement.append(')') - outputStream.writeSql(statementBuilder.setStatement(statement.toString()).build()) - postProcess?.accept(cursor) - } - } - return count - } - - private fun exportAttachment(attachmentSecret: AttachmentSecret, cursor: Cursor, outputStream: BackupFrameOutputStream) { - try { - val rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID)) - val uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID)) - var size = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.SIZE)) - val data = cursor.getString(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA)) - val random = cursor.getBlob(cursor.getColumnIndexOrThrow(AttachmentDatabase.DATA_RANDOM)) - if (!TextUtils.isEmpty(data) && size <= 0) { - size = calculateVeryOldStreamLength(attachmentSecret, random, data) - } - if (!TextUtils.isEmpty(data) && size > 0) { - val inputStream: InputStream = if (random != null && random.size == 32) { - ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(data), 0) - } else { - ClassicDecryptingPartInputStream.createFor(attachmentSecret, File(data)) - } - outputStream.writeAttachment(AttachmentId(rowId, uniqueId), inputStream, size) - } - } catch (e: IOException) { - Log.w(TAG, e) - } - } - - @Throws(IOException::class) - private fun calculateVeryOldStreamLength(attachmentSecret: AttachmentSecret, random: ByteArray?, data: String): Long { - var result: Long = 0 - val inputStream: InputStream = if (random != null && random.size == 32) { - ModernDecryptingPartInputStream.createFor(attachmentSecret, random, File(data), 0) - } else { - ClassicDecryptingPartInputStream.createFor(attachmentSecret, File(data)) - } - var read: Int - val buffer = ByteArray(8192) - while (inputStream.read(buffer, 0, buffer.size).also { read = it } != -1) { - result += read.toLong() - } - return result - } - - private fun isForNonExpiringMessage(db: SQLiteDatabase, mmsId: Long): Boolean { - val columns = arrayOf(MmsSmsColumns.EXPIRES_IN) - val where = MmsSmsColumns.ID + " = ?" - val args = arrayOf(mmsId.toString()) - db.query(MmsDatabase.TABLE_NAME, columns, where, args, null, null, null).use { mmsCursor -> - if (mmsCursor != null && mmsCursor.moveToFirst()) { - return mmsCursor.getLong(0) == 0L - } - } - return false - } - - private class BackupFrameOutputStream : Closeable, Flushable { - - private val outputStream: OutputStream - private var cipher: Cipher - private var mac: Mac - private val cipherKey: ByteArray - private val macKey: ByteArray - private val iv: ByteArray - - private var counter: Int = 0 - - constructor(outputStream: OutputStream, passphrase: String) : super() { - try { - val salt = Util.getSecretBytes(32) - val key = BackupUtil.computeBackupKey(passphrase, salt) - val derived = HKDFv3().deriveSecrets(key, "Backup Export".toByteArray(), 64) - val split = ByteUtil.split(derived, 32, 32) - cipherKey = split[0] - macKey = split[1] - cipher = Cipher.getInstance("AES/CTR/NoPadding") - mac = Mac.getInstance("HmacSHA256") - this.outputStream = outputStream - iv = Util.getSecretBytes(16) - counter = Conversions.byteArrayToInt(iv) - mac.init(SecretKeySpec(macKey, "HmacSHA256")) - val header = BackupFrame.newBuilder().setHeader(Header.newBuilder() - .setIv(ByteString.copyFrom(iv)) - .setSalt(ByteString.copyFrom(salt))) - .build().toByteArray() - outputStream.write(Conversions.intToByteArray(header.size)) - outputStream.write(header) - } catch (e: Exception) { - when (e) { - is NoSuchAlgorithmException, - is NoSuchPaddingException, - is InvalidKeyException -> { - throw AssertionError(e) - } - else -> throw e - } - } - } - - @Throws(IOException::class) - fun writeSql(statement: SqlStatement) { - write(outputStream, BackupFrame.newBuilder().setStatement(statement).build()) - } - - @Throws(IOException::class) - fun writePreferenceEntry(preference: SharedPreference?) { - write(outputStream, BackupFrame.newBuilder().setPreference(preference).build()) - } - - @Throws(IOException::class) - fun writeAvatar(avatarName: String, inputStream: InputStream, size: Long) { - write(outputStream, BackupFrame.newBuilder() - .setAvatar(Avatar.newBuilder() - .setName(avatarName) - .setLength(Util.toIntExact(size)) - .build()) - .build()) - writeStream(inputStream) - } - - @Throws(IOException::class) - fun writeAttachment(attachmentId: AttachmentId, inputStream: InputStream, size: Long) { - write(outputStream, BackupFrame.newBuilder() - .setAttachment(Attachment.newBuilder() - .setRowId(attachmentId.rowId) - .setAttachmentId(attachmentId.uniqueId) - .setLength(Util.toIntExact(size)) - .build()) - .build()) - writeStream(inputStream) - } - - @Throws(IOException::class) - fun writeSticker(rowId: Long, inputStream: InputStream, size: Long) { - write(outputStream, BackupFrame.newBuilder() - .setSticker(Sticker.newBuilder() - .setRowId(rowId) - .setLength(Util.toIntExact(size)) - .build()) - .build()) - writeStream(inputStream) - } - - @Throws(IOException::class) - fun writeDatabaseVersion(version: Int) { - write(outputStream, BackupFrame.newBuilder() - .setVersion(DatabaseVersion.newBuilder().setVersion(version)) - .build()) - } - - @Throws(IOException::class) - fun writeEnd() { - write(outputStream, BackupFrame.newBuilder().setEnd(true).build()) - } - - @Throws(IOException::class) - private fun writeStream(inputStream: InputStream) { - try { - Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - mac.update(iv) - val buffer = ByteArray(8192) - var read: Int - while (inputStream.read(buffer).also { read = it } != -1) { - val ciphertext = cipher.update(buffer, 0, read) - if (ciphertext != null) { - outputStream.write(ciphertext) - mac.update(ciphertext) - } - } - val remainder = cipher.doFinal() - outputStream.write(remainder) - mac.update(remainder) - val attachmentDigest = mac.doFinal() - outputStream.write(attachmentDigest, 0, 10) - } catch (e: Exception) { - when (e) { - is InvalidKeyException, - is InvalidAlgorithmParameterException, - is IllegalBlockSizeException, - is BadPaddingException -> { - throw AssertionError(e) - } - else -> throw e - } - } - } - - @Throws(IOException::class) - private fun write(out: OutputStream, frame: BackupFrame) { - try { - Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - val frameCiphertext = cipher.doFinal(frame.toByteArray()) - val frameMac = mac.doFinal(frameCiphertext) - val length = Conversions.intToByteArray(frameCiphertext.size + 10) - out.write(length) - out.write(frameCiphertext) - out.write(frameMac, 0, 10) - } catch (e: Exception) { - when (e) { - is InvalidKeyException, - is InvalidAlgorithmParameterException, - is IllegalBlockSizeException, - is BadPaddingException -> { - throw AssertionError(e) - } - else -> throw e - } - } - } - - @Throws(IOException::class) - override fun flush() { - outputStream.flush() - } - - @Throws(IOException::class) - override fun close() { - outputStream.close() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt deleted file mode 100644 index b40c049bc..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt +++ /dev/null @@ -1,352 +0,0 @@ -package org.thoughtcrime.securesms.backup - -import android.annotation.SuppressLint -import android.content.ContentValues -import android.content.Context -import android.net.Uri -import androidx.annotation.WorkerThread -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 -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.Conversions -import org.session.libsession.utilities.Util -import org.session.libsignal.crypto.kdf.HKDFv3 -import org.session.libsignal.utilities.ByteUtil -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.backup.BackupProtos.Attachment -import org.thoughtcrime.securesms.backup.BackupProtos.Avatar -import org.thoughtcrime.securesms.backup.BackupProtos.BackupFrame -import org.thoughtcrime.securesms.backup.BackupProtos.DatabaseVersion -import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference -import org.thoughtcrime.securesms.backup.BackupProtos.SqlStatement -import org.thoughtcrime.securesms.crypto.AttachmentSecret -import org.thoughtcrime.securesms.crypto.ModernEncryptingPartOutputStream -import org.thoughtcrime.securesms.database.AttachmentDatabase -import org.thoughtcrime.securesms.database.GroupReceiptDatabase -import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsColumns -import org.thoughtcrime.securesms.database.SearchDatabase -import org.thoughtcrime.securesms.database.ThreadDatabase -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.util.BackupUtil -import java.io.Closeable -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.security.InvalidAlgorithmParameterException -import java.security.InvalidKeyException -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.util.LinkedList -import java.util.Locale -import javax.crypto.BadPaddingException -import javax.crypto.Cipher -import javax.crypto.IllegalBlockSizeException -import javax.crypto.Mac -import javax.crypto.NoSuchPaddingException -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec - -object FullBackupImporter { - /** - * Because BackupProtos.SharedPreference was made only to serialize string values, - * we use these 3-char prefixes to explicitly cast the values before inserting to a preference file. - */ - const val PREF_PREFIX_TYPE_INT = "i__" - const val PREF_PREFIX_TYPE_BOOLEAN = "b__" - - private val TAG = FullBackupImporter::class.java.simpleName - - @JvmStatic - @WorkerThread - @Throws(IOException::class) - fun importFromUri(context: Context, - attachmentSecret: AttachmentSecret, - db: SQLiteDatabase, - fileUri: Uri, - passphrase: String) { - - val baseInputStream = context.contentResolver.openInputStream(fileUri) - ?: throw IOException("Cannot open an input stream for the file URI: $fileUri") - - var count = 0 - try { - BackupRecordInputStream(baseInputStream, passphrase).use { inputStream -> - db.beginTransaction() - dropAllTables(db) - var frame: BackupFrame - while (!inputStream.readFrame().also { frame = it }.end) { - if (count++ % 100 == 0) EventBus.getDefault().post(BackupEvent.createProgress(count)) - when { - frame.hasVersion() -> processVersion(db, frame.version) - frame.hasStatement() -> processStatement(db, frame.statement) - frame.hasPreference() -> processPreference(context, frame.preference) - frame.hasAttachment() -> processAttachment(context, attachmentSecret, db, frame.attachment, inputStream) - frame.hasAvatar() -> processAvatar(context, frame.avatar, inputStream) - } - } - trimEntriesForExpiredMessages(context, db) - db.setTransactionSuccessful() - } - } finally { - if (db.inTransaction()) { - db.endTransaction() - } - } - EventBus.getDefault().post(BackupEvent.createFinished()) - } - - @Throws(IOException::class) - private fun processVersion(db: SQLiteDatabase, version: DatabaseVersion) { - if (version.version > db.version) { - throw DatabaseDowngradeException(db.version, version.version) - } - db.version = version.version - } - - private fun processStatement(db: SQLiteDatabase, statement: SqlStatement) { - val isForSmsFtsSecretTable = statement.statement.contains(SearchDatabase.SMS_FTS_TABLE_NAME + "_") - val isForMmsFtsSecretTable = statement.statement.contains(SearchDatabase.MMS_FTS_TABLE_NAME + "_") - val isForSqliteSecretTable = statement.statement.toLowerCase(Locale.ENGLISH).startsWith("create table sqlite_") - if (isForSmsFtsSecretTable || isForMmsFtsSecretTable || isForSqliteSecretTable) { - Log.i(TAG, "Ignoring import for statement: " + statement.statement) - return - } - val parameters: MutableList = LinkedList() - for (parameter in statement.parametersList) { - when { - parameter.hasStringParamter() -> parameters.add(parameter.stringParamter) - parameter.hasDoubleParameter() -> parameters.add(parameter.doubleParameter) - parameter.hasIntegerParameter() -> parameters.add(parameter.integerParameter) - parameter.hasBlobParameter() -> parameters.add(parameter.blobParameter.toByteArray()) - parameter.hasNullparameter() -> parameters.add(null) - } - } - if (parameters.size > 0) { - db.execSQL(statement.statement, parameters.toTypedArray()) - } else { - db.execSQL(statement.statement) - } - } - - @Throws(IOException::class) - private fun processAttachment(context: Context, attachmentSecret: AttachmentSecret, - db: SQLiteDatabase, attachment: Attachment, - inputStream: BackupRecordInputStream) { - val partsDirectory = context.getDir(AttachmentDatabase.DIRECTORY, Context.MODE_PRIVATE) - val dataFile = File.createTempFile("part", ".mms", partsDirectory) - val output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false) - inputStream.readAttachmentTo(output.second, attachment.length) - val contentValues = ContentValues() - contentValues.put(AttachmentDatabase.DATA, dataFile.absolutePath) - contentValues.put(AttachmentDatabase.THUMBNAIL, null as String?) - contentValues.put(AttachmentDatabase.DATA_RANDOM, output.first) - db.update(AttachmentDatabase.TABLE_NAME, contentValues, - "${AttachmentDatabase.ROW_ID} = ? AND ${AttachmentDatabase.UNIQUE_ID} = ?", - arrayOf(attachment.rowId.toString(), attachment.attachmentId.toString())) - } - - @Throws(IOException::class) - private fun processAvatar(context: Context, avatar: Avatar, inputStream: BackupRecordInputStream) { - inputStream.readAttachmentTo(FileOutputStream( - AvatarHelper.getAvatarFile(context, Address.fromExternal(context, avatar.name))), avatar.length) - } - - @SuppressLint("ApplySharedPref") - private fun processPreference(context: Context, preference: SharedPreference) { - val preferences = context.getSharedPreferences(preference.file, 0) - val key = preference.key - val value = preference.value - - // See the comment next to PREF_PREFIX_TYPE_* constants. - when { - key.startsWith(PREF_PREFIX_TYPE_INT) -> - preferences.edit().putInt( - key.substring(PREF_PREFIX_TYPE_INT.length), - value.toInt() - ).commit() - key.startsWith(PREF_PREFIX_TYPE_BOOLEAN) -> - preferences.edit().putBoolean( - key.substring(PREF_PREFIX_TYPE_BOOLEAN.length), - value.toBoolean() - ).commit() - else -> - preferences.edit().putString(key, value).commit() - } - } - - private fun dropAllTables(db: SQLiteDatabase) { - db.rawQuery("SELECT name, type FROM sqlite_master", null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - val name = cursor.getString(0) - val type = cursor.getString(1) - if ("table" == type && !name.startsWith("sqlite_")) { - db.execSQL("DROP TABLE IF EXISTS $name") - } - } - } - } - - private fun trimEntriesForExpiredMessages(context: Context, db: SQLiteDatabase) { - val trimmedCondition = " NOT IN (SELECT ${MmsSmsColumns.ID} FROM ${MmsDatabase.TABLE_NAME})" - db.delete(GroupReceiptDatabase.TABLE_NAME, GroupReceiptDatabase.MMS_ID + trimmedCondition, null) - val columns = arrayOf(AttachmentDatabase.ROW_ID, AttachmentDatabase.UNIQUE_ID) - val where = AttachmentDatabase.MMS_ID + trimmedCondition - db.query(AttachmentDatabase.TABLE_NAME, columns, where, null, null, null, null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - DatabaseComponent.get(context).attachmentDatabase() - .deleteAttachment(AttachmentId(cursor.getLong(0), cursor.getLong(1))) - } - } - db.query(ThreadDatabase.TABLE_NAME, arrayOf(ThreadDatabase.ID), - ThreadDatabase.EXPIRES_IN + " > 0", null, null, null, null).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - DatabaseComponent.get(context).threadDatabase().update(cursor.getLong(0), false) - } - } - } - - private class BackupRecordInputStream : Closeable { - private val inputStream: InputStream - private val cipher: Cipher - private val mac: Mac - private val cipherKey: ByteArray - private val macKey: ByteArray - private val iv: ByteArray - - private var counter = 0 - - @Throws(IOException::class) - constructor(inputStream: InputStream, passphrase: String) : super() { - try { - this.inputStream = inputStream - val headerLengthBytes = ByteArray(4) - Util.readFully(this.inputStream, headerLengthBytes) - val headerLength = Conversions.byteArrayToInt(headerLengthBytes) - val headerFrame = ByteArray(headerLength) - Util.readFully(this.inputStream, headerFrame) - val frame = BackupFrame.parseFrom(headerFrame) - if (!frame.hasHeader()) { - throw IOException("Backup stream does not start with header!") - } - val header = frame.header - iv = header.iv.toByteArray() - if (iv.size != 16) { - throw IOException("Invalid IV length!") - } - val key = BackupUtil.computeBackupKey(passphrase, if (header.hasSalt()) header.salt.toByteArray() else null) - val derived = HKDFv3().deriveSecrets(key, "Backup Export".toByteArray(), 64) - val split = ByteUtil.split(derived, 32, 32) - cipherKey = split[0] - macKey = split[1] - cipher = Cipher.getInstance("AES/CTR/NoPadding") - mac = Mac.getInstance("HmacSHA256") - mac.init(SecretKeySpec(macKey, "HmacSHA256")) - counter = Conversions.byteArrayToInt(iv) - } catch (e: Exception) { - when (e) { - is NoSuchAlgorithmException, - is NoSuchPaddingException, - is InvalidKeyException -> { - throw AssertionError(e) - } - else -> throw e - } - } - } - - @Throws(IOException::class) - fun readFrame(): BackupFrame { - return readFrame(inputStream) - } - - @Throws(IOException::class) - fun readAttachmentTo(out: OutputStream, length: Int) { - var length = length - try { - Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - mac.update(iv) - val buffer = ByteArray(8192) - while (length > 0) { - val read = inputStream.read(buffer, 0, Math.min(buffer.size, length)) - if (read == -1) throw IOException("File ended early!") - mac.update(buffer, 0, read) - val plaintext = cipher.update(buffer, 0, read) - if (plaintext != null) { - out.write(plaintext, 0, plaintext.size) - } - length -= read - } - val plaintext = cipher.doFinal() - if (plaintext != null) { - out.write(plaintext, 0, plaintext.size) - } - out.close() - val ourMac = ByteUtil.trim(mac.doFinal(), 10) - val theirMac = ByteArray(10) - try { - Util.readFully(inputStream, theirMac) - } catch (e: IOException) { - throw IOException(e) - } - if (!MessageDigest.isEqual(ourMac, theirMac)) { - throw IOException("Bad MAC") - } - } catch (e: Exception) { - when (e) { - is InvalidKeyException, - is InvalidAlgorithmParameterException, - is IllegalBlockSizeException, - is BadPaddingException -> { - throw AssertionError(e) - } - else -> throw e - } - } - } - - @Throws(IOException::class) - private fun readFrame(`in`: InputStream?): BackupFrame { - return try { - val length = ByteArray(4) - Util.readFully(`in`, length) - val frame = ByteArray(Conversions.byteArrayToInt(length)) - Util.readFully(`in`, frame) - val theirMac = ByteArray(10) - System.arraycopy(frame, frame.size - 10, theirMac, 0, theirMac.size) - mac.update(frame, 0, frame.size - 10) - val ourMac = ByteUtil.trim(mac.doFinal(), 10) - if (!MessageDigest.isEqual(ourMac, theirMac)) { - throw IOException("Bad MAC") - } - Conversions.intToByteArray(iv, 0, counter++) - cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(cipherKey, "AES"), IvParameterSpec(iv)) - val plaintext = cipher.doFinal(frame, 0, frame.size - 10) - BackupFrame.parseFrom(plaintext) - } catch (e: Exception) { - when (e) { - is InvalidKeyException, - is InvalidAlgorithmParameterException, - is IllegalBlockSizeException, - is BadPaddingException -> { - throw AssertionError(e) - } - else -> throw e - } - } - } - - @Throws(IOException::class) - override fun close() { - inputStream.close() - } - } - - class DatabaseDowngradeException internal constructor(currentVersion: Int, backupVersion: Int) : - IOException("Tried to import a backup with version $backupVersion into a database with version $currentVersion") -} \ No newline at end of file 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 5e84d5e23..ddef0200e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -1,21 +1,7 @@ package org.thoughtcrime.securesms.database; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import androidx.annotation.NonNull; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; - import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; -import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec; -import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec; -import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; public class JobDatabase extends Database { @@ -23,8 +9,6 @@ public class JobDatabase extends Database { Constraints.CREATE_TABLE, Dependencies.CREATE_TABLE }; - private static final ArrayList runningJobs = new ArrayList<>(); - public static final class Jobs { public static final String TABLE_NAME = "job_spec"; private static final String ID = "_id"; @@ -84,166 +68,4 @@ public class JobDatabase extends Database { public JobDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } - - public synchronized void insertJobs(@NonNull List fullSpecs) { - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - - db.beginTransaction(); - - try { - for (FullSpec fullSpec : fullSpecs) { - insertJobSpec(db, fullSpec.getJobSpec()); - insertConstraintSpecs(db, fullSpec.getConstraintSpecs()); - insertDependencySpecs(db, fullSpec.getDependencySpecs()); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public synchronized @NonNull List getAllJobSpecs() { - List jobs = new LinkedList<>(); - - try (Cursor cursor = databaseHelper.getReadableDatabase().query(Jobs.TABLE_NAME, null, null, null, null, null, Jobs.CREATE_TIME + ", " + Jobs.ID + " ASC")) { - while (cursor != null && cursor.moveToNext()) { - jobs.add(jobSpecFromCursor(cursor)); - } - } - - return jobs; - } - - public synchronized void updateJobRunningState(@NonNull String id, boolean isRunning) { - if (!isRunning) { - JobDatabase.runningJobs.remove(id); - } else if (!JobDatabase.runningJobs.contains(id)) { - JobDatabase.runningJobs.add(id); - } - } - - public synchronized void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime) { - updateJobRunningState(id, isRunning); - - ContentValues contentValues = new ContentValues(); - contentValues.put(Jobs.RUN_ATTEMPT, runAttempt); - contentValues.put(Jobs.NEXT_RUN_ATTEMPT_TIME, nextRunAttemptTime); - - String query = Jobs.JOB_SPEC_ID + " = ?"; - String[] args = new String[]{ id }; - - databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args); - } - - public synchronized void updateAllJobsToBePending() { - JobDatabase.runningJobs.clear(); - } - - public synchronized void deleteJobs(@NonNull List jobIds) { - SQLiteDatabase db = databaseHelper.getWritableDatabase(); - - db.beginTransaction(); - - try { - for (String jobId : jobIds) { - String[] arg = new String[]{jobId}; - - db.delete(Jobs.TABLE_NAME, Jobs.JOB_SPEC_ID + " = ?", arg); - db.delete(Constraints.TABLE_NAME, Constraints.JOB_SPEC_ID + " = ?", arg); - db.delete(Dependencies.TABLE_NAME, Dependencies.JOB_SPEC_ID + " = ?", arg); - db.delete(Dependencies.TABLE_NAME, Dependencies.DEPENDS_ON_JOB_SPEC_ID + " = ?", arg); - } - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } - - public synchronized @NonNull List getAllConstraintSpecs() { - List constraints = new LinkedList<>(); - - try (Cursor cursor = databaseHelper.getReadableDatabase().query(Constraints.TABLE_NAME, null, null, null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - constraints.add(constraintSpecFromCursor(cursor)); - } - } - - return constraints; - } - - public synchronized @NonNull List getAllDependencySpecs() { - List dependencies = new LinkedList<>(); - - try (Cursor cursor = databaseHelper.getReadableDatabase().query(Dependencies.TABLE_NAME, null, null, null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - dependencies.add(dependencySpecFromCursor(cursor)); - } - } - - return dependencies; - } - - private void insertJobSpec(@NonNull SQLiteDatabase db, @NonNull JobSpec job) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Jobs.JOB_SPEC_ID, job.getId()); - contentValues.put(Jobs.FACTORY_KEY, job.getFactoryKey()); - contentValues.put(Jobs.QUEUE_KEY, job.getQueueKey()); - contentValues.put(Jobs.CREATE_TIME, job.getCreateTime()); - contentValues.put(Jobs.NEXT_RUN_ATTEMPT_TIME, job.getNextRunAttemptTime()); - contentValues.put(Jobs.RUN_ATTEMPT, job.getRunAttempt()); - contentValues.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts()); - contentValues.put(Jobs.MAX_BACKOFF, job.getMaxBackoff()); - contentValues.put(Jobs.MAX_INSTANCES, job.getMaxInstances()); - contentValues.put(Jobs.LIFESPAN, job.getLifespan()); - contentValues.put(Jobs.SERIALIZED_DATA, job.getSerializedData()); - updateJobRunningState(job.getId(), job.isRunning()); - - db.insertWithOnConflict(Jobs.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); - } - - private void insertConstraintSpecs(@NonNull SQLiteDatabase db, @NonNull List constraints) { - for (ConstraintSpec constraintSpec : constraints) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Constraints.JOB_SPEC_ID, constraintSpec.getJobSpecId()); - contentValues.put(Constraints.FACTORY_KEY, constraintSpec.getFactoryKey()); - db.insertWithOnConflict(Constraints.TABLE_NAME, null ,contentValues, SQLiteDatabase.CONFLICT_IGNORE); - } - } - - private void insertDependencySpecs(@NonNull SQLiteDatabase db, @NonNull List dependencies) { - for (DependencySpec dependencySpec : dependencies) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Dependencies.JOB_SPEC_ID, dependencySpec.getJobId()); - contentValues.put(Dependencies.DEPENDS_ON_JOB_SPEC_ID, dependencySpec.getDependsOnJobId()); - db.insertWithOnConflict(Dependencies.TABLE_NAME, null, contentValues, SQLiteDatabase.CONFLICT_IGNORE); - } - } - - private @NonNull JobSpec jobSpecFromCursor(@NonNull Cursor cursor) { - String jobId = cursor.getString(cursor.getColumnIndexOrThrow(Jobs.JOB_SPEC_ID)); - return new JobSpec(jobId, - cursor.getString(cursor.getColumnIndexOrThrow(Jobs.FACTORY_KEY)), - cursor.getString(cursor.getColumnIndexOrThrow(Jobs.QUEUE_KEY)), - cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.CREATE_TIME)), - cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.NEXT_RUN_ATTEMPT_TIME)), - cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.RUN_ATTEMPT)), - cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_ATTEMPTS)), - cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.MAX_BACKOFF)), - cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.LIFESPAN)), - cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_INSTANCES)), - cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_DATA)), - JobDatabase.runningJobs.contains(jobId)); - } - - private @NonNull ConstraintSpec constraintSpecFromCursor(@NonNull Cursor cursor) { - return new ConstraintSpec(cursor.getString(cursor.getColumnIndexOrThrow(Constraints.JOB_SPEC_ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Constraints.FACTORY_KEY))); - } - - private @NonNull DependencySpec dependencySpecFromCursor(@NonNull Cursor cursor) { - return new DependencySpec(cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.JOB_SPEC_ID)), - cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.DEPENDS_ON_JOB_SPEC_ID))); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt index 648b9c43e..60d31a19d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseComponent.kt @@ -32,7 +32,6 @@ interface DatabaseComponent { fun recipientDatabase(): RecipientDatabase fun groupReceiptDatabase(): GroupReceiptDatabase fun searchDatabase(): SearchDatabase - fun jobDatabase(): JobDatabase fun lokiAPIDatabase(): LokiAPIDatabase fun lokiMessageDatabase(): LokiMessageDatabase fun lokiThreadDatabase(): LokiThreadDatabase 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 fba7a7e50..3372e1033 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -86,10 +86,6 @@ object DatabaseModule { @Singleton fun searchDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = SearchDatabase(context,openHelper) - @Provides - @Singleton - fun provideJobDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = JobDatabase(context, openHelper) - @Provides @Singleton fun provideLokiApiDatabase(@ApplicationContext context: Context, openHelper: SQLCipherOpenHelper) = LokiAPIDatabase(context,openHelper) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ExecutorFactory.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ExecutorFactory.java deleted file mode 100644 index b0c2b974d..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/ExecutorFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager; - -import androidx.annotation.NonNull; - -import java.util.concurrent.ExecutorService; - -public interface ExecutorFactory { - @NonNull ExecutorService newSingleThreadExecutor(@NonNull String name); -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DefaultExecutorFactory.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DefaultExecutorFactory.java deleted file mode 100644 index a9d459100..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/DefaultExecutorFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.ExecutorFactory; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class DefaultExecutorFactory implements ExecutorFactory { - @Override - public @NonNull ExecutorService newSingleThreadExecutor(@NonNull String name) { - return Executors.newSingleThreadExecutor(r -> new Thread(r, name)); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java deleted file mode 100644 index 1dab10ae5..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/ConstraintSpec.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import androidx.annotation.NonNull; - -import java.util.Objects; - -public final class ConstraintSpec { - - private final String jobSpecId; - private final String factoryKey; - - public ConstraintSpec(@NonNull String jobSpecId, @NonNull String factoryKey) { - this.jobSpecId = jobSpecId; - this.factoryKey = factoryKey; - } - - public String getJobSpecId() { - return jobSpecId; - } - - public String getFactoryKey() { - return factoryKey; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ConstraintSpec that = (ConstraintSpec) o; - return Objects.equals(jobSpecId, that.jobSpecId) && - Objects.equals(factoryKey, that.factoryKey); - } - - @Override - public int hashCode() { - return Objects.hash(jobSpecId, factoryKey); - } - - @Override - public @NonNull String toString() { - return String.format("jobSpecId: %s | factoryKey: %s", jobSpecId, factoryKey); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java deleted file mode 100644 index 2faea0485..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/DependencySpec.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import androidx.annotation.NonNull; - -import java.util.Objects; - -public final class DependencySpec { - - private final String jobId; - private final String dependsOnJobId; - - public DependencySpec(@NonNull String jobId, @NonNull String dependsOnJobId) { - this.jobId = jobId; - this.dependsOnJobId = dependsOnJobId; - } - - public @NonNull String getJobId() { - return jobId; - } - - public @NonNull String getDependsOnJobId() { - return dependsOnJobId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DependencySpec that = (DependencySpec) o; - return Objects.equals(jobId, that.jobId) && - Objects.equals(dependsOnJobId, that.dependsOnJobId); - } - - @Override - public int hashCode() { - return Objects.hash(jobId, dependsOnJobId); - } - - @Override - public @NonNull String toString() { - return String.format("jobSpecId: %s | dependsOnJobSpecId: %s", jobId, dependsOnJobId); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java deleted file mode 100644 index f93c0e64b..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/FullSpec.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import androidx.annotation.NonNull; - -import java.util.List; -import java.util.Objects; - -public final class FullSpec { - - private final JobSpec jobSpec; - private final List constraintSpecs; - private final List dependencySpecs; - - public FullSpec(@NonNull JobSpec jobSpec, - @NonNull List constraintSpecs, - @NonNull List dependencySpecs) - { - this.jobSpec = jobSpec; - this.constraintSpecs = constraintSpecs; - this.dependencySpecs = dependencySpecs; - } - - public @NonNull JobSpec getJobSpec() { - return jobSpec; - } - - public @NonNull List getConstraintSpecs() { - return constraintSpecs; - } - - public @NonNull List getDependencySpecs() { - return dependencySpecs; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FullSpec fullSpec = (FullSpec) o; - return Objects.equals(jobSpec, fullSpec.jobSpec) && - Objects.equals(constraintSpecs, fullSpec.constraintSpecs) && - Objects.equals(dependencySpecs, fullSpec.dependencySpecs); - } - - @Override - public int hashCode() { - return Objects.hash(jobSpec, constraintSpecs, dependencySpecs); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java deleted file mode 100644 index d5f5cd5b3..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobSpec.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import android.annotation.SuppressLint; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Objects; - -public final class JobSpec { - - private final String id; - private final String factoryKey; - private final String queueKey; - private final long createTime; - private final long nextRunAttemptTime; - private final int runAttempt; - private final int maxAttempts; - private final long maxBackoff; - private final long lifespan; - private final int maxInstances; - private final String serializedData; - private final boolean isRunning; - - public JobSpec(@NonNull String id, - @NonNull String factoryKey, - @Nullable String queueKey, - long createTime, - long nextRunAttemptTime, - int runAttempt, - int maxAttempts, - long maxBackoff, - long lifespan, - int maxInstances, - @NonNull String serializedData, - boolean isRunning) - { - this.id = id; - this.factoryKey = factoryKey; - this.queueKey = queueKey; - this.createTime = createTime; - this.nextRunAttemptTime = nextRunAttemptTime; - this.maxBackoff = maxBackoff; - this.runAttempt = runAttempt; - this.maxAttempts = maxAttempts; - this.lifespan = lifespan; - this.maxInstances = maxInstances; - this.serializedData = serializedData; - this.isRunning = isRunning; - } - - public @NonNull String getId() { - return id; - } - - public @NonNull String getFactoryKey() { - return factoryKey; - } - - public @Nullable String getQueueKey() { - return queueKey; - } - - public long getCreateTime() { - return createTime; - } - - public long getNextRunAttemptTime() { - return nextRunAttemptTime; - } - - public int getRunAttempt() { - return runAttempt; - } - - public int getMaxAttempts() { - return maxAttempts; - } - - public long getMaxBackoff() { - return maxBackoff; - } - - public int getMaxInstances() { - return maxInstances; - } - - public long getLifespan() { - return lifespan; - } - - public @NonNull String getSerializedData() { - return serializedData; - } - - public boolean isRunning() { - return isRunning; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - JobSpec jobSpec = (JobSpec) o; - return createTime == jobSpec.createTime && - nextRunAttemptTime == jobSpec.nextRunAttemptTime && - runAttempt == jobSpec.runAttempt && - maxAttempts == jobSpec.maxAttempts && - maxBackoff == jobSpec.maxBackoff && - lifespan == jobSpec.lifespan && - maxInstances == jobSpec.maxInstances && - isRunning == jobSpec.isRunning && - Objects.equals(id, jobSpec.id) && - Objects.equals(factoryKey, jobSpec.factoryKey) && - Objects.equals(queueKey, jobSpec.queueKey) && - Objects.equals(serializedData, jobSpec.serializedData); - } - - @Override - public int hashCode() { - return Objects.hash(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, lifespan, maxInstances, serializedData, isRunning); - } - - @SuppressLint("DefaultLocale") - @Override - public @NonNull String toString() { - return String.format("id: %s | factoryKey: %s | queueKey: %s | createTime: %d | nextRunAttemptTime: %d | runAttempt: %d | maxAttempts: %d | maxBackoff: %d | maxInstances: %d | lifespan: %d | isRunning: %b | data: %s", - id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, maxBackoff, maxInstances, lifespan, isRunning, serializedData); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobStorage.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobStorage.java deleted file mode 100644 index b7c035ac6..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/persistence/JobStorage.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.persistence; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import java.util.List; - -public interface JobStorage { - - @WorkerThread - void init(); - - @WorkerThread - void insertJobs(@NonNull List fullSpecs); - - @WorkerThread - @Nullable JobSpec getJobSpec(@NonNull String id); - - @WorkerThread - @NonNull List getAllJobSpecs(); - - @WorkerThread - @NonNull List getPendingJobsWithNoDependenciesInCreatedOrder(long currentTime); - - @WorkerThread - int getJobInstanceCount(@NonNull String factoryKey); - - @WorkerThread - void updateJobRunningState(@NonNull String id, boolean isRunning); - - @WorkerThread - void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime); - - @WorkerThread - void updateAllJobsToBePending(); - - @WorkerThread - void deleteJob(@NonNull String id); - - @WorkerThread - void deleteJobs(@NonNull List ids); - - @WorkerThread - @NonNull List getConstraintSpecs(@NonNull String jobId); - - @WorkerThread - @NonNull List getAllConstraintSpecs(); - - @WorkerThread - @NonNull List getDependencySpecsThatDependOnJob(@NonNull String jobSpecId); - - @WorkerThread - @NonNull List getAllDependencySpecs(); -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt deleted file mode 100644 index 8a71760df..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalBackupJob.kt +++ /dev/null @@ -1,61 +0,0 @@ -package org.thoughtcrime.securesms.jobs - -import android.content.Context -import org.session.libsession.messaging.jobs.Job -import org.session.libsession.messaging.jobs.JobDelegate -import org.session.libsession.messaging.utilities.Data -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.notifications.NotificationChannels -import org.thoughtcrime.securesms.service.GenericForegroundService -import org.thoughtcrime.securesms.util.BackupUtil.createBackupFile -import org.thoughtcrime.securesms.util.BackupUtil.deleteAllBackupFiles - -import network.loki.messenger.R - - -class LocalBackupJob:Job { - override var delegate: JobDelegate? = null - override var id: String? = null - override var failureCount: Int = 0 - override val maxFailureCount: Int = 0 - - lateinit var context: Context - - companion object { - val TAG = LocalBackupJob::class.simpleName - val KEY: String = "LocalBackupJob" - } - - override fun execute(dispatcherName: String) { - Log.i(TAG, "Executing backup job...") - - GenericForegroundService.startForegroundTask( - context, - context.getString(R.string.LocalBackupJob_creating_backup), - NotificationChannels.BACKUPS, - R.drawable.ic_launcher_foreground - ) - - // TODO: Maybe create a new backup icon like ic_signal_backup? - try { - val record = createBackupFile(context) - deleteAllBackupFiles(context, listOf(record)) - } finally { - GenericForegroundService.stopForegroundTask(context) - } - } - - override fun serialize(): Data { - return Data.EMPTY - } - - override fun getFactoryKey(): String { - return KEY - } - - class Factory: Job.Factory { - override fun create(data: Data): LocalBackupJob { - return LocalBackupJob() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt deleted file mode 100644 index 411f01fcd..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/UpdateApkJob.kt +++ /dev/null @@ -1,200 +0,0 @@ -package org.thoughtcrime.securesms.jobs - -import androidx.annotation.Nullable - -import android.app.DownloadManager -import android.content.Context -import android.content.Intent -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.net.Uri -import com.fasterxml.jackson.annotation.JsonProperty -import network.loki.messenger.BuildConfig -import okhttp3.OkHttpClient -import okhttp3.Request -import org.session.libsession.messaging.jobs.Job -import org.session.libsession.messaging.jobs.JobDelegate -import org.session.libsession.messaging.utilities.Data -import org.session.libsession.utilities.FileUtils -import org.session.libsession.utilities.TextSecurePreferences.Companion.getUpdateApkDigest -import org.session.libsession.utilities.TextSecurePreferences.Companion.getUpdateApkDownloadId -import org.session.libsession.utilities.TextSecurePreferences.Companion.setUpdateApkDigest -import org.session.libsession.utilities.TextSecurePreferences.Companion.setUpdateApkDownloadId -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.service.UpdateApkReadyListener -import java.io.FileInputStream -import java.io.IOException -import java.security.MessageDigest - -class UpdateApkJob: Job { - override var delegate: JobDelegate? = null - override var id: String? = null - override var failureCount: Int = 0 - override val maxFailureCount: Int = 0 - - lateinit var context: Context - - companion object { - val TAG = UpdateApkJob::class.simpleName - val KEY: String = "UpdateApkJob" - } - - override fun execute(dispatcherName: String) { - if (!BuildConfig.PLAY_STORE_DISABLED) return - - Log.i(TAG, "Checking for APK update...") - - val client = OkHttpClient() - val request = Request.Builder().url(String.format("%s/latest.json", BuildConfig.NOPLAY_UPDATE_URL)).build() - val response = client.newCall(request).execute() - - if (!response.isSuccessful) { - throw IOException("Bad response: " + response.message()) - } - - val updateDescriptor: UpdateDescriptor = JsonUtil.fromJson( - response.body()!!.string(), - UpdateDescriptor::class.java - ) - val digest = Hex.fromStringCondensed(updateDescriptor.digest) - - Log.i( - TAG, - "Got descriptor: $updateDescriptor" - ) - - if (updateDescriptor.versionCode > getVersionCode()) { - val downloadStatus: DownloadStatus = getDownloadStatus(updateDescriptor.url, digest) - Log.i(TAG, "Download status: " + downloadStatus.status) - if (downloadStatus.status == DownloadStatus.Status.COMPLETE) { - Log.i(TAG, "Download status complete, notifying...") - handleDownloadNotify(downloadStatus.downloadId) - } else if (downloadStatus.status == DownloadStatus.Status.MISSING) { - Log.i(TAG, "Download status missing, starting download...") - handleDownloadStart( - updateDescriptor.url, - updateDescriptor.versionName, - digest - ) - } - } - } - - @Throws(PackageManager.NameNotFoundException::class) - private fun getVersionCode(): Int { - val packageManager: PackageManager = context.getPackageManager() - val packageInfo: PackageInfo = packageManager.getPackageInfo(context.getPackageName(), 0) - return packageInfo.versionCode - } - - private fun getDownloadStatus(uri: String, theirDigest: ByteArray): DownloadStatus { - val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val query = DownloadManager.Query() - query.setFilterByStatus(DownloadManager.STATUS_PAUSED or DownloadManager.STATUS_PENDING or DownloadManager.STATUS_RUNNING or DownloadManager.STATUS_SUCCESSFUL) - val pendingDownloadId = getUpdateApkDownloadId(context) - val pendingDigest = getPendingDigest(context) - val cursor = downloadManager.query(query) - return try { - var status = DownloadStatus(DownloadStatus.Status.MISSING, -1) - while (cursor != null && cursor.moveToNext()) { - val jobStatus = - cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) - val jobRemoteUri = - cursor.getString(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_URI)) - val downloadId = - cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)) - val digest = getDigestForDownloadId(downloadId) - if (jobRemoteUri != null && jobRemoteUri == uri && downloadId == pendingDownloadId) { - if (jobStatus == DownloadManager.STATUS_SUCCESSFUL && digest != null && pendingDigest != null && - MessageDigest.isEqual(pendingDigest, theirDigest) && - MessageDigest.isEqual(digest, theirDigest) - ) { - return DownloadStatus(DownloadStatus.Status.COMPLETE, downloadId) - } else if (jobStatus != DownloadManager.STATUS_SUCCESSFUL) { - status = DownloadStatus(DownloadStatus.Status.PENDING, downloadId) - } - } - } - status - } finally { - cursor?.close() - } - } - - private fun handleDownloadStart(uri: String, versionName: String, digest: ByteArray) { - val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val downloadRequest = DownloadManager.Request(Uri.parse(uri)) - downloadRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) - downloadRequest.setTitle("Downloading Signal update") - downloadRequest.setDescription("Downloading Signal $versionName") - downloadRequest.setVisibleInDownloadsUi(false) - downloadRequest.setDestinationInExternalFilesDir(context, null, "signal-update.apk") - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) - val downloadId = downloadManager.enqueue(downloadRequest) - setUpdateApkDownloadId(context, downloadId) - setUpdateApkDigest(context, Hex.toStringCondensed(digest)) - } - - private fun handleDownloadNotify(downloadId: Long) { - val intent = Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE) - intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId) - UpdateApkReadyListener().onReceive(context, intent) - } - - private fun getDigestForDownloadId(downloadId: Long): ByteArray? { - return try { - val downloadManager = - context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val fin = FileInputStream(downloadManager.openDownloadedFile(downloadId).fileDescriptor) - val digest = FileUtils.getFileDigest(fin) - fin.close() - digest - } catch (e: IOException) { - Log.w(TAG, e) - null - } - } - - private fun getPendingDigest(context: Context): ByteArray? { - return try { - val encodedDigest = getUpdateApkDigest(context) ?: return null - Hex.fromStringCondensed(encodedDigest) - } catch (e: IOException) { - Log.w(TAG, e) - null - } - } - - override fun serialize(): Data { - return Data.EMPTY - } - - override fun getFactoryKey(): String { - return KEY - } - - private class UpdateDescriptor( - @JsonProperty("versionCode") @Nullable val versionCode: Int, - @JsonProperty("versionName") @Nullable val versionName: String, - @JsonProperty("url") @Nullable val url: String, - @JsonProperty("sha256sum") @Nullable val digest: String) - { - override fun toString(): String { - return "[$versionCode, $versionName, $url]" - } - } - - private class DownloadStatus(val status: Status, val downloadId: Long) { - enum class Status { - PENDING, COMPLETE, MISSING - } - } - - class Factory: Job.Factory { - override fun create(data: Data): UpdateApkJob { - return UpdateApkJob() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java deleted file mode 100644 index 9cf88f409..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.thoughtcrime.securesms.service; - -import android.content.Context; -import android.content.Intent; - -import org.session.libsession.messaging.jobs.JobQueue; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.jobs.LocalBackupJob; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.util.concurrent.TimeUnit; - -public class LocalBackupListener extends PersistentAlarmManagerListener { - - private static final long INTERVAL = TimeUnit.DAYS.toMillis(1); - - @Override - protected long getNextScheduledExecutionTime(Context context) { - return TextSecurePreferences.getNextBackupTime(context); - } - - @Override - protected long onAlarm(Context context, long scheduledTime) { - if (TextSecurePreferences.isBackupEnabled(context)) { - LocalBackupJob job = new LocalBackupJob(); - job.context = context; - JobQueue.getShared().add(job); - } - - long nextTime = System.currentTimeMillis() + INTERVAL; - TextSecurePreferences.setNextBackupTime(context, nextTime); - - return nextTime; - } - - public static void schedule(Context context) { - if (TextSecurePreferences.isBackupEnabled(context)) { - new LocalBackupListener().onReceive(context, new Intent()); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java deleted file mode 100644 index 01ade9421..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkRefreshListener.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.thoughtcrime.securesms.service; - - -import android.content.Context; -import android.content.Intent; - -import org.session.libsession.messaging.jobs.JobQueue; -import org.session.libsignal.utilities.Log; - -import org.thoughtcrime.securesms.ApplicationContext; -import network.loki.messenger.BuildConfig; -import org.thoughtcrime.securesms.jobs.UpdateApkJob; -import org.session.libsession.utilities.TextSecurePreferences; - -import java.util.concurrent.TimeUnit; - -public class UpdateApkRefreshListener extends PersistentAlarmManagerListener { - - private static final String TAG = UpdateApkRefreshListener.class.getSimpleName(); - - private static final long INTERVAL = TimeUnit.HOURS.toMillis(6); - - @Override - protected long getNextScheduledExecutionTime(Context context) { - return TextSecurePreferences.getUpdateApkRefreshTime(context); - } - - @Override - protected long onAlarm(Context context, long scheduledTime) { - Log.i(TAG, "onAlarm..."); - - if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) { - Log.i(TAG, "Queueing APK update job..."); - UpdateApkJob job = new UpdateApkJob(); - job.context = context; - JobQueue.getShared().add(job); - } - - long newTime = System.currentTimeMillis() + INTERVAL; - TextSecurePreferences.setUpdateApkRefreshTime(context, newTime); - - return newTime; - } - - public static void schedule(Context context) { - new UpdateApkRefreshListener().onReceive(context, new Intent()); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt deleted file mode 100644 index 074278cb9..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt +++ /dev/null @@ -1,313 +0,0 @@ -package org.thoughtcrime.securesms.util - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.os.Environment -import android.provider.DocumentsContract -import android.widget.Toast -import androidx.annotation.WorkerThread -import androidx.documentfile.provider.DocumentFile -import androidx.fragment.app.Fragment -import network.loki.messenger.R -import org.greenrobot.eventbus.EventBus -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.ByteUtil -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.backup.BackupEvent -import org.thoughtcrime.securesms.backup.BackupPassphrase -import org.thoughtcrime.securesms.backup.BackupProtos.SharedPreference -import org.thoughtcrime.securesms.backup.FullBackupExporter -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 java.io.IOException -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.security.SecureRandom -import java.text.SimpleDateFormat -import java.util.* - -object BackupUtil { - private const val MASTER_SECRET_UTIL_PREFERENCES_NAME = "SecureSMS-Preferences" - private const val TAG = "BackupUtil" - const val BACKUP_FILE_MIME_TYPE = "application/session-backup" - const val BACKUP_PASSPHRASE_LENGTH = 30 - - fun getBackupRecords(context: Context): List { - val prefName = MASTER_SECRET_UTIL_PREFERENCES_NAME - val preferences = context.getSharedPreferences(prefName, 0) - val prefList = LinkedList() - prefList.add(SharedPreference.newBuilder() - .setFile(prefName) - .setKey(IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF) - .setValue(preferences.getString(IdentityKeyUtil.IDENTITY_PUBLIC_KEY_PREF, null)) - .build()) - prefList.add(SharedPreference.newBuilder() - .setFile(prefName) - .setKey(IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF) - .setValue(preferences.getString(IdentityKeyUtil.IDENTITY_PRIVATE_KEY_PREF, null)) - .build()) - if (preferences.contains(IdentityKeyUtil.ED25519_PUBLIC_KEY)) { - prefList.add(SharedPreference.newBuilder() - .setFile(prefName) - .setKey(IdentityKeyUtil.ED25519_PUBLIC_KEY) - .setValue(preferences.getString(IdentityKeyUtil.ED25519_PUBLIC_KEY, null)) - .build()) - } - if (preferences.contains(IdentityKeyUtil.ED25519_SECRET_KEY)) { - prefList.add(SharedPreference.newBuilder() - .setFile(prefName) - .setKey(IdentityKeyUtil.ED25519_SECRET_KEY) - .setValue(preferences.getString(IdentityKeyUtil.ED25519_SECRET_KEY, null)) - .build()) - } - prefList.add(SharedPreference.newBuilder() - .setFile(prefName) - .setKey(IdentityKeyUtil.LOKI_SEED) - .setValue(preferences.getString(IdentityKeyUtil.LOKI_SEED, null)) - .build()) - return prefList - } - - @JvmStatic - fun getLastBackupTimeString(context: Context, locale: Locale): String { - val timestamp = DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFileTime() - if (timestamp == null) { - return context.getString(R.string.BackupUtil_never) - } - return DateUtils.getDisplayFormattedTimeSpanString(context, locale, timestamp.time) - } - - @JvmStatic - fun getLastBackup(context: Context): BackupFileRecord? { - return DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFile() - } - - @JvmStatic - fun generateBackupPassphrase(): Array { - val random = ByteArray(BACKUP_PASSPHRASE_LENGTH).also { SecureRandom().nextBytes(it) } - return Array(6) { i -> - String.format("%05d", ByteUtil.byteArray5ToLong(random, i * 5) % 100000) - } - } - - @JvmStatic - fun validateDirAccess(context: Context, dirUri: Uri): Boolean { - val hasWritePermission = context.contentResolver.persistedUriPermissions.any { - it.isWritePermission && it.uri == dirUri - } - if (!hasWritePermission) return false - - val document = DocumentFile.fromTreeUri(context, dirUri) - if (document == null || !document.exists()) { - return false - } - - return true - } - - @JvmStatic - fun getBackupDirUri(context: Context): Uri? { - val dirUriString = TextSecurePreferences.getBackupSaveDir(context) ?: return null - return Uri.parse(dirUriString) - } - - @JvmStatic - fun setBackupDirUri(context: Context, uriString: String?) { - TextSecurePreferences.setBackupSaveDir(context, uriString) - } - - /** - * @return The selected backup directory if it's valid (exists, is writable). - */ - @JvmStatic - fun getSelectedBackupDirIfValid(context: Context): Uri? { - val dirUri = getBackupDirUri(context) - - if (dirUri == null) { - Log.v(TAG, "The backup dir wasn't selected yet.") - return null - } - if (!validateDirAccess(context, dirUri)) { - Log.v(TAG, "Cannot validate the access to the dir $dirUri.") - return null - } - - return dirUri; - } - - @JvmStatic - @WorkerThread - @Throws(IOException::class) - fun createBackupFile(context: Context): BackupFileRecord { - val backupPassword = BackupPassphrase.get(context) - ?: throw IOException("Backup password is null") - - val dirUri = getSelectedBackupDirIfValid(context) - ?: throw IOException("Backup save directory is not selected or invalid") - - val date = Date() - val timestamp = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(date) - val fileName = String.format("session-%s.backup", timestamp) - - val fileUri = DocumentsContract.createDocument( - context.contentResolver, - DocumentFile.fromTreeUri(context, dirUri)!!.uri, - BACKUP_FILE_MIME_TYPE, - fileName) - - if (fileUri == null) { - Toast.makeText(context, "Cannot create writable file in the dir $dirUri", Toast.LENGTH_LONG).show() - throw IOException("Cannot create writable file in the dir $dirUri") - } - - try { - FullBackupExporter.export(context, - AttachmentSecretProvider.getInstance(context).orCreateAttachmentSecret, - DatabaseComponent.get(context).openHelper().readableDatabase, - fileUri, - backupPassword) - } catch (e: Exception) { - // Delete the backup file on any error. - DocumentsContract.deleteDocument(context.contentResolver, fileUri) - throw e - } - - //TODO Use real file size. - val record = DatabaseComponent.get(context).lokiBackupFilesDatabase() - .insertBackupFile(BackupFileRecord(fileUri, -1, date)) - - Log.v(TAG, "A backup file was created: $fileUri") - - return record - } - - @JvmStatic - @JvmOverloads - fun deleteAllBackupFiles(context: Context, except: Collection? = null) { - val db = DatabaseComponent.get(context).lokiBackupFilesDatabase() - db.getBackupFiles().iterator().forEach { record -> - if (except != null && except.contains(record)) return@forEach - - // Try to delete the related file. The operation may fail in many cases - // (the user moved/deleted the file, revoked the write permission, etc), so that's OK. - try { - val result = DocumentsContract.deleteDocument(context.contentResolver, record.uri) - if (!result) { - Log.w(TAG, "Failed to delete backup file: ${record.uri}") - } - } catch (e: Exception) { - Log.w(TAG, "Failed to delete backup file: ${record.uri}", e) - } - - db.deleteBackupFile(record) - - Log.v(TAG, "Backup file was deleted: ${record.uri}") - } - } - - @JvmStatic - fun computeBackupKey(passphrase: String, salt: ByteArray?): ByteArray { - return try { - EventBus.getDefault().post(BackupEvent.createProgress(0)) - val digest = MessageDigest.getInstance("SHA-512") - val input = passphrase.replace(" ", "").toByteArray() - var hash: ByteArray = input - if (salt != null) digest.update(salt) - for (i in 0..249999) { - if (i % 1000 == 0) EventBus.getDefault().post(BackupEvent.createProgress(0)) - digest.update(hash) - hash = digest.digest(input) - } - ByteUtil.trim(hash, 32) - } catch (e: NoSuchAlgorithmException) { - throw AssertionError(e) - } - } -} - -/** - * An utility class to help perform backup directory selection requests. - * - * An instance of this class should be created per an [Activity] or [Fragment] - * and [onActivityResult] should be called appropriately. - */ -class BackupDirSelector(private val contextProvider: ContextProvider) { - - companion object { - private const val REQUEST_CODE_SAVE_DIR = 7844 - } - - private val context: Context get() = contextProvider.getContext() - - private var listener: Listener? = null - - constructor(activity: Activity) : - this(ActivityContextProvider(activity)) - - constructor(fragment: Fragment) : - this(FragmentContextProvider(fragment)) - - /** - * Performs ACTION_OPEN_DOCUMENT_TREE intent to select backup directory URI. - * If the directory is already selected and valid, the request will be skipped. - * @param force if true, the previous selection is ignored and the user is requested to select another directory. - * @param onSelectedListener an optional action to perform once the directory is selected. - */ - fun selectBackupDir(force: Boolean, onSelectedListener: Listener? = null) { - if (!force) { - val dirUri = BackupUtil.getSelectedBackupDirIfValid(context) - if (dirUri != null && onSelectedListener != null) { - onSelectedListener.onBackupDirSelected(dirUri) - } - return - } - - // Let user pick the dir. - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - - // Request read/write permission grant for the dir. - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - - // Set the default dir. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val dirUri = BackupUtil.getBackupDirUri(context) - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, dirUri - ?: Uri.fromFile(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS))) - } - - if (onSelectedListener != null) { - this.listener = onSelectedListener - } - - contextProvider.startActivityForResult(intent, REQUEST_CODE_SAVE_DIR) - } - - fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != REQUEST_CODE_SAVE_DIR) return - - if (resultCode == Activity.RESULT_OK && data != null && data.data != null) { - // Acquire persistent access permissions for the file selected. - val persistentFlags: Int = data.flags and - (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - context.contentResolver.takePersistableUriPermission(data.data!!, persistentFlags) - - BackupUtil.setBackupDirUri(context, data.dataString) - - listener?.onBackupDirSelected(data.data!!) - } - - listener = null - } - - @FunctionalInterface - interface Listener { - fun onBackupDirSelected(uri: Uri) - } -} \ No newline at end of file From ba3566f7e883d90287749ae88583a483b7eb5f5a Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 11 May 2023 09:30:39 +0930 Subject: [PATCH 065/244] Fix approval text position when input bar is gone (#1181) --- app/src/main/res/layout/activity_conversation_v2.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index d2696a45e..8992a660d 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -128,6 +128,7 @@ android:textSize="12sp" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_alignWithParentIfMissing="true" android:layout_above="@id/messageRequestBar"/> Date: Thu, 11 May 2023 09:31:05 +0930 Subject: [PATCH 066/244] Fix message request button overlap (#1188) --- .../main/res/layout/view_message_request_banner.xml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/view_message_request_banner.xml b/app/src/main/res/layout/view_message_request_banner.xml index d37fde8a3..c03e9c1cd 100644 --- a/app/src/main/res/layout/view_message_request_banner.xml +++ b/app/src/main/res/layout/view_message_request_banner.xml @@ -3,9 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:contentDescription="@string/AccessibilityId_message_request_banner" android:layout_height="wrap_content" android:background="@drawable/conversation_view_background" + android:contentDescription="@string/AccessibilityId_message_request_banner" android:gravity="center_vertical" android:orientation="horizontal" android:paddingStart="@dimen/accent_line_thickness" @@ -72,11 +72,15 @@ android:alpha="0.4" android:ellipsize="end" android:maxLines="1" + android:textAlignment="textEnd" android:textColor="?android:textColorPrimary" android:textSize="@dimen/small_font_size" + app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1" + app:layout_constraintStart_toEndOf="@id/unreadCountIndicator" app:layout_constraintTop_toTopOf="parent" - tools:text="9:41 AM" /> + tools:text="11 Apr, 9:41 AM" /> - \ No newline at end of file + From 8dbabec4e7c90a975df1307bba2c810bbff67b42 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 11 May 2023 09:32:38 +0930 Subject: [PATCH 067/244] Fix send after approval message (#1178) * Fix send after approval message * Fix logic * Utilise isLocalNumber --- .../securesms/conversation/v2/ConversationActivityV2.kt | 2 +- .../securesms/conversation/v2/ConversationViewModel.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) 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 cabb58308..c30a491a8 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 @@ -667,7 +667,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun updateSendAfterApprovalText() { - binding?.textSendAfterApproval?.isGone = viewModel.recipient?.hasApprovedMe() ?: true + binding?.textSendAfterApproval?.isVisible = viewModel.showSendAfterApprovalText } private fun showOrHideInputIfNeeded() { 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 7f6768ee9..fc557f726 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 @@ -31,6 +31,9 @@ class ConversationViewModel( private val storage: Storage ) : ViewModel() { + val showSendAfterApprovalText: Boolean + get() = recipient?.run { isContactRecipient && !isLocalNumber && !hasApprovedMe() } ?: false + private val _uiState = MutableStateFlow(ConversationUiState()) val uiState: StateFlow = _uiState From 88e788a4060ded3fefb052e68889febeac1d12c0 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Thu, 11 May 2023 12:16:37 +1000 Subject: [PATCH 068/244] clean --- .../java/org/thoughtcrime/securesms/ApplicationContext.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 5a9999a7b..53141534a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -106,7 +106,6 @@ import dagger.hilt.EntryPoints; import dagger.hilt.android.HiltAndroidApp; import kotlin.Unit; import kotlinx.coroutines.Job; -import network.loki.messenger.BuildConfig; /** * Will be called once when the TextSecure process is created. @@ -158,10 +157,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO return (ApplicationContext) context.getApplicationContext(); } - public TextSecurePreferences getPrefs() { - return textSecurePreferences; - } - public DatabaseComponent getDatabaseComponent() { return EntryPoints.get(getApplicationContext(), DatabaseComponent.class); } From a334b8912af8dc5aad3705af831711b82981cd64 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 17 May 2023 14:39:47 +0930 Subject: [PATCH 069/244] Fix device notification settings preference color --- app/src/main/res/layout/go_to_device_settings.xml | 14 -------------- app/src/main/res/xml/preferences_notifications.xml | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 app/src/main/res/layout/go_to_device_settings.xml diff --git a/app/src/main/res/layout/go_to_device_settings.xml b/app/src/main/res/layout/go_to_device_settings.xml deleted file mode 100644 index 97d452e53..000000000 --- a/app/src/main/res/layout/go_to_device_settings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_notifications.xml b/app/src/main/res/xml/preferences_notifications.xml index 52a429cc3..03945d695 100644 --- a/app/src/main/res/xml/preferences_notifications.xml +++ b/app/src/main/res/xml/preferences_notifications.xml @@ -10,7 +10,7 @@ android:summary="@string/preferences_notifications_strategy_category_fast_mode_summary" android:defaultValue="false" /> - From 8c5ff1f9449f5f1495367fe90b50450e327900c0 Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 17 May 2023 14:50:20 +0930 Subject: [PATCH 070/244] Fix dialog button color --- app/src/main/res/values/styles.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 1cf88c0c0..9fa605988 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -29,8 +29,6 @@ ?colorPrimary true 0.6 - @style/Widget.Session.AlertDialog.NegativeButtonStyle - @style/Widget.Session.AlertDialog.PositiveButtonStyle @null @null ?android:textColorPrimary @@ -46,14 +44,6 @@ @drawable/default_bottom_sheet_background - - - - From 235b94a905296b9f489744bd6ce99b5ea2309fb7 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 18 May 2023 19:20:44 +0930 Subject: [PATCH 071/244] Fix ripple color --- .../drawable/conversation_pinned_background.xml | 2 +- .../drawable/conversation_unread_background.xml | 2 +- .../drawable/conversation_view_background.xml | 2 +- .../mention_candidate_view_background.xml | 2 +- .../res/drawable/setting_button_background.xml | 2 +- .../main/res/layout/blocked_contact_layout.xml | 1 - app/src/main/res/values/attrs.xml | 2 -- app/src/main/res/values/themes.xml | 16 ++++------------ 8 files changed, 9 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/drawable/conversation_pinned_background.xml b/app/src/main/res/drawable/conversation_pinned_background.xml index 104b9c272..eb64dc7f5 100644 --- a/app/src/main/res/drawable/conversation_pinned_background.xml +++ b/app/src/main/res/drawable/conversation_pinned_background.xml @@ -1,7 +1,7 @@ + android:color="?android:colorControlHighlight"> diff --git a/app/src/main/res/drawable/conversation_unread_background.xml b/app/src/main/res/drawable/conversation_unread_background.xml index de0f5fb68..9e9bb9436 100644 --- a/app/src/main/res/drawable/conversation_unread_background.xml +++ b/app/src/main/res/drawable/conversation_unread_background.xml @@ -1,7 +1,7 @@ + android:color="?android:colorControlHighlight"> diff --git a/app/src/main/res/drawable/conversation_view_background.xml b/app/src/main/res/drawable/conversation_view_background.xml index aaceb7ed5..2f177318e 100644 --- a/app/src/main/res/drawable/conversation_view_background.xml +++ b/app/src/main/res/drawable/conversation_view_background.xml @@ -1,7 +1,7 @@ + android:color="?android:colorControlHighlight"> diff --git a/app/src/main/res/drawable/mention_candidate_view_background.xml b/app/src/main/res/drawable/mention_candidate_view_background.xml index 7b179020a..4e9785a41 100644 --- a/app/src/main/res/drawable/mention_candidate_view_background.xml +++ b/app/src/main/res/drawable/mention_candidate_view_background.xml @@ -1,7 +1,7 @@ + android:color="?android:colorControlHighlight"> diff --git a/app/src/main/res/drawable/setting_button_background.xml b/app/src/main/res/drawable/setting_button_background.xml index aaceb7ed5..2f177318e 100644 --- a/app/src/main/res/drawable/setting_button_background.xml +++ b/app/src/main/res/drawable/setting_button_background.xml @@ -1,7 +1,7 @@ + android:color="?android:colorControlHighlight"> diff --git a/app/src/main/res/layout/blocked_contact_layout.xml b/app/src/main/res/layout/blocked_contact_layout.xml index 40d7f40dd..673779cfd 100644 --- a/app/src/main/res/layout/blocked_contact_layout.xml +++ b/app/src/main/res/layout/blocked_contact_layout.xml @@ -7,7 +7,6 @@ android:paddingHorizontal="@dimen/medium_spacing" android:paddingVertical="@dimen/small_spacing" android:gravity="center_vertical" - android:background="?selectableItemBackground" android:id="@+id/backgroundContainer"> - @@ -176,7 +175,6 @@ - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 43d06ee7a..204089862 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -319,7 +319,6 @@ @color/classic_dark_0 ?android:textColorPrimary ?colorAccent - ?colorAccent @color/classic_dark_6 ?android:textColorPrimary @color/classic_dark_5 @@ -334,7 +333,7 @@ @color/classic_dark_1 @color/classic_dark_1 @color/classic_dark_3 - @color/classic_dark_3 + @color/classic_dark_3 @style/Dark.Popup @null @style/ThemeOverlay.AppCompat.Dark.ActionBar @@ -382,7 +381,6 @@ @color/classic_dark_2 @color/classic_dark_3 @color/classic_dark_2 - @color/classic_dark_3 @color/classic_dark_1 @color/classic_dark_3 @color/classic_dark_4 @@ -397,7 +395,6 @@ @color/classic_light_6 ?android:textColorPrimary ?colorAccent - ?colorAccent @color/classic_light_0 @color/classic_light_1 @color/classic_light_1 @@ -408,7 +405,7 @@ @color/classic_light_6 @color/classic_light_5 @color/classic_light_3 - @color/classic_light_3 + @color/classic_light_3 @style/Classic.Light.BottomSheet ?android:textColorPrimary ?actionBarPopupTheme @@ -467,7 +464,6 @@ @color/classic_light_4 @color/classic_light_2 ?colorCellBackground - ?colorCellRipple @color/classic_light_4 @color/classic_light_2 @color/classic_dark_4 @@ -482,7 +478,6 @@ @color/ocean_dark_2 @color/ocean_dark_7 ?colorAccent - ?colorAccent @color/ocean_dark_7 @color/ocean_dark_5 @color/ocean_dark_5 @@ -496,7 +491,7 @@ @color/ocean_dark_3 @color/ocean_dark_1 @color/ocean_dark_4 - @color/ocean_dark_4 + @color/ocean_dark_4 @style/Ocean.Dark.BottomSheet ?actionBarPopupTheme ?android:textColorPrimary @@ -549,7 +544,6 @@ ?colorPrimary ?colorPrimary @color/ocean_dark_2 - @color/ocean_dark_3 @color/ocean_dark_4 ?colorPrimary @color/ocean_dark_4 @@ -564,7 +558,6 @@ @color/ocean_light_6 @color/ocean_light_1 ?colorAccent - ?colorAccent @color/ocean_light_1 @color/ocean_light_2 @color/ocean_light_2 @@ -578,7 +571,7 @@ @color/ocean_light_5 @color/ocean_light_6 @color/ocean_light_3 - @color/ocean_light_4 + @color/ocean_light_4 @style/Ocean.Light.BottomSheet @style/Light.Popup ?actionBarPopupTheme @@ -633,7 +626,6 @@ @color/ocean_light_5 @color/ocean_light_1 ?colorCellBackground - ?colorCellRipple ?input_bar_button_background_opaque ?input_bar_button_background_opaque_border ?colorAccent From 46acd7878d57f58f1a5c3ba19a5f1902afccbacd Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Thu, 18 May 2023 18:03:00 -0300 Subject: [PATCH 072/244] New SPNS subscription and notifications Finishes the WIP for subscribing to push notifications and handling the new-style pushes we get. --- .../notifications/FirebasePushManager.kt | 46 +++++++++---------- .../notifications/PushNotificationService.kt | 20 ++++---- .../sending_receiving/notifications/Models.kt | 39 +++++++++++++--- .../notifications/PushNotificationAPI.kt | 2 +- .../messaging/utilities/SodiumUtilities.kt | 2 +- 5 files changed, 69 insertions(+), 40 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 39705c75d..12e62cee4 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -8,6 +8,7 @@ import com.goterl.lazysodium.interfaces.Sign import com.goterl.lazysodium.utils.Key import com.goterl.lazysodium.utils.KeyPair import kotlinx.coroutines.Job +import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream @@ -17,6 +18,7 @@ import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse import org.session.libsession.messaging.utilities.SodiumUtilities @@ -26,7 +28,6 @@ import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.bencode.Bencode -import org.session.libsession.utilities.bencode.BencodeDict import org.session.libsession.utilities.bencode.BencodeList import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 @@ -60,35 +61,32 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS ) } - fun decrypt(encPayload: ByteArray) { + fun decrypt(encPayload: ByteArray): ByteArray? { val encKey = getOrCreateNotificationKey() val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray() - val decrypted = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) - ?: return Log.e("Loki", "Failed to decrypt push notification") + val padded = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce) + ?: error("Failed to decrypt push notification") + val decrypted = padded.dropLastWhile { it.toInt() == 0 }.toByteArray() val bencoded = Bencode.Decoder(decrypted) - val expectedList = (bencoded.decode() as? BencodeList) - ?: return Log.e("Loki", "Failed to decode bencoded list from payload") + val expectedList = (bencoded.decode() as? BencodeList)?.values + ?: error("Failed to decode bencoded list from payload") - val (metadata, content) = expectedList.values - val metadataDict = (metadata as? BencodeDict)?.values - ?: return Log.e("Loki", "Failed to decode metadata dict") + val metadataJson = (expectedList[0] as? BencodeString)?.value + ?: error("no metadata") + val metadata:PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) - val push = """ - Push metadata received was: - @: ${metadataDict["@"]} - #: ${metadataDict["#"]} - n: ${metadataDict["n"]} - l: ${metadataDict["l"]} - B: ${metadataDict["B"]} - """.trimIndent() + val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null + // null content is valid only if we got a "data_too_long" flag + if (content == null) + check(metadata.data_too_long) { "missing message data, but no too-long flag" } + else + check(metadata.data_len == content.size) { "wrong message data size" } - Log.d("Loki", "push") + Log.d("Loki", + "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") - val contentBytes = (content as? BencodeString)?.value - ?: return Log.e("Loki", "Failed to decode content string") - - // TODO: something with contentBytes + return content } override fun register(force: Boolean) { @@ -158,10 +156,10 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) } else { val (_, message) = response.errorInfo() - Log.d("Loki", "Couldn't register for FCM due to error: $message.") + Log.e("Loki", "Couldn't register for FCM due to error: $message.") } }.fail { exception -> - Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") + Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.") } } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 4c268ce18..bb625bc54 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -28,15 +28,19 @@ class PushNotificationService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { Log.d("Loki", "Received a push notification.") - if (message.data.containsKey("spns")) { - // assume this is the new push notification content - // deal with the enc payload (probably decrypting through the PushManager? - Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}") - pushManager.decrypt(Base64.decode(message.data["enc_payload"])) - return + val data: ByteArray? = if (message.data.containsKey("spns")) { + // this is a v2 push notification + try { + pushManager.decrypt(Base64.decode(message.data["enc_payload"])) + } catch(e: Exception) { + Log.e("Loki", "Invalid push notification: ${e.message}") + return + } + } else { + // old v1 push notification; we still need this for receiving legacy closed group notifications + val base64EncodedData = message.data?.get("ENCRYPTED_DATA") + base64EncodedData?.let { Base64.decode(it) } } - val base64EncodedData = message.data?.get("ENCRYPTED_DATA") - val data = base64EncodedData?.let { Base64.decode(it) } if (data != null) { try { val envelopeAsData = MessageWrapper.unwrap(data).toByteArray() diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index 78b6cd4a1..e7a0ad79a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -7,7 +7,8 @@ import kotlinx.serialization.Serializable /** * N.B. all of these variable names will be named the same as the actual JSON utf-8 request/responses expected from the server. * Changing the variable names will break how data is serialized/deserialized. - * If it's less than ideally named we can use [SerialName] + * If it's less than ideally named we can use [SerialName], such as for the push metadata which uses + * single-letter keys to be as compact as possible. */ @Serializable @@ -37,11 +38,11 @@ data class SubscriptionRequest( @Serializable data class SubscriptionResponse( - val error: Int?, - val message: String?, - val success: Boolean?, - val added: Boolean?, - val updated: Boolean?, + val error: Int? = null, + val message: String? = null, + val success: Boolean? = null, + val added: Boolean? = null, + val updated: Boolean? = null, ) { companion object { /** invalid values, missing reuqired arguments etc, details in message */ @@ -59,6 +60,32 @@ data class SubscriptionResponse( } else null to null } +@Serializable +data class PushNotificationMetadata( + /** Account ID (such as Session ID or closed group ID) where the message arrived **/ + @SerialName("@") + val account: String, + + /** The hash of the message in the swarm. */ + @SerialName("#") + val msg_hash: String, + + /** The swarm namespace in which this message arrived. */ + @SerialName("n") + val namespace: Int, + + /** The length of the message data. This is always included, even if the message content + * itself was too large to fit into the push notification. */ + @SerialName("l") + val data_len: Int, + + /** This will be true if the data was omitted because it was too long to fit in a push + * notification (around 2.5kB of raw data), in which case the push notification includes + * only this metadata but not the message content itself. */ + @SerialName("B") + val data_too_long : Boolean = false +) + @Serializable data class PushNotificationServerObject( val enc_payload: String, diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index cf037b019..9016f30eb 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -17,7 +17,7 @@ import org.session.libsignal.utilities.retryIfNeeded object PushNotificationAPI { val context = MessagingModuleConfiguration.shared.context val server = "https://push.getsession.org" - val serverPublicKey: String = TODO("get the new server pubkey here") + val serverPublicKey: String = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" private val legacyServer = "https://live.apns.getsession.org" private val legacyServerPublicKey = "642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049" private val maxRetryCount = 4 diff --git a/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt b/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt index 9a1de4f2d..079caee23 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/utilities/SodiumUtilities.kt @@ -205,7 +205,7 @@ object SodiumUtilities { } fun decrypt(ciphertext: ByteArray, decryptionKey: ByteArray, nonce: ByteArray): ByteArray? { - val plaintextSize = ciphertext.size - AEAD.CHACHA20POLY1305_ABYTES + val plaintextSize = ciphertext.size - AEAD.XCHACHA20POLY1305_IETF_ABYTES val plaintext = ByteArray(plaintextSize) return if (sodium.cryptoAeadXChaCha20Poly1305IetfDecrypt( plaintext, From 30d748e1476a7dc9f7cf5f4ee00d65db22544deb Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 19 May 2023 23:44:07 +0930 Subject: [PATCH 073/244] Disable unblock button --- .../securesms/database/RecipientDatabase.java | 2 +- .../securesms/database/Storage.kt | 2 +- .../preferences/BlockedContactsActivity.kt | 78 ++++++------------- .../preferences/BlockedContactsAdapter.kt | 16 ++-- .../preferences/BlockedContactsViewModel.kt | 68 +++++++++++++--- app/src/main/res/color/button_destructive.xml | 5 ++ ...ctive_outline_button_medium_background.xml | 2 +- app/src/main/res/values/styles.xml | 2 +- .../libsession/database/StorageProtocol.kt | 2 +- 9 files changed, 98 insertions(+), 79 deletions(-) create mode 100644 app/src/main/res/color/button_destructive.xml 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 af2faaaca..e3570fd28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -276,7 +276,7 @@ public class RecipientDatabase extends Database { notifyRecipientListeners(); } - public void setBlocked(@NonNull List recipients, boolean blocked) { + public void setBlocked(@NonNull Iterable recipients, boolean blocked) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { 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 33896803b..ed6510f74 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1010,7 +1010,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms)) } - override fun unblock(toUnblock: List) { + override fun unblock(toUnblock: Iterable) { val recipientDb = DatabaseComponent.get(context).recipientDatabase() recipientDb.setBlocked(toUnblock, false) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt index 504194d3a..9b8d800dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsActivity.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.preferences import android.app.AlertDialog import android.os.Bundle -import android.view.View import androidx.activity.viewModels import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint @@ -11,58 +10,31 @@ import network.loki.messenger.databinding.ActivityBlockedContactsBinding import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity @AndroidEntryPoint -class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnClickListener { +class BlockedContactsActivity: PassphraseRequiredActionBarActivity() { lateinit var binding: ActivityBlockedContactsBinding val viewModel: BlockedContactsViewModel by viewModels() - val adapter = BlockedContactsAdapter() + val adapter: BlockedContactsAdapter by lazy { BlockedContactsAdapter(viewModel) } - override fun onClick(v: View?) { - if (v === binding.unblockButton && adapter.getSelectedItems().isNotEmpty()) { - val contactsToUnblock = adapter.getSelectedItems() - // show dialog - val title = if (contactsToUnblock.size == 1) { - getString(R.string.Unblock_dialog__title_single, contactsToUnblock.first().name) - } else { - getString(R.string.Unblock_dialog__title_multiple) + fun unblock() { + // show dialog + val title = viewModel.getTitle(this) + + val message = viewModel.getMessage(this) + + AlertDialog.Builder(this) + .setTitle(title) + .setMessage(message) + .setPositiveButton(R.string.continue_2) { d, _ -> + viewModel.unblock() + d.dismiss() } - - val message = if (contactsToUnblock.size == 1) { - getString(R.string.Unblock_dialog__message, contactsToUnblock.first().name) - } else { - val stringBuilder = StringBuilder() - val iterator = contactsToUnblock.iterator() - var numberAdded = 0 - while (iterator.hasNext() && numberAdded < 3) { - val nextRecipient = iterator.next() - if (numberAdded > 0) stringBuilder.append(", ") - - stringBuilder.append(nextRecipient.name) - numberAdded++ - } - val overflow = contactsToUnblock.size - numberAdded - if (overflow > 0) { - stringBuilder.append(" ") - val string = resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow) - stringBuilder.append(string.format(overflow)) - } - getString(R.string.Unblock_dialog__message, stringBuilder.toString()) + .setNegativeButton(R.string.cancel) { d, _ -> + d.dismiss() } - - AlertDialog.Builder(this) - .setTitle(title) - .setMessage(message) - .setPositiveButton(R.string.continue_2) { d, _ -> - viewModel.unblock(contactsToUnblock) - d.dismiss() - } - .setNegativeButton(R.string.cancel) { d, _ -> - d.dismiss() - } - .show() - } + .show() } override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { @@ -73,15 +45,15 @@ class BlockedContactsActivity: PassphraseRequiredActionBarActivity(), View.OnCli binding.recyclerView.adapter = adapter viewModel.subscribe(this) - .observe(this) { newState -> - adapter.submitList(newState.blockedContacts) - val isEmpty = newState.blockedContacts.isEmpty() - binding.emptyStateMessageTextView.isVisible = isEmpty - binding.nonEmptyStateGroup.isVisible = !isEmpty + .observe(this) { state -> + adapter.submitList(state.blockedContacts) + binding.emptyStateMessageTextView.isVisible = state.emptyStateMessageTextViewVisible + binding.nonEmptyStateGroup.isVisible = state.nonEmptyStateGroupVisible + binding.unblockButton.isEnabled = state.unblockButtonEnabled } - binding.unblockButton.setOnClickListener(this) + binding.unblockButton.setOnClickListener { unblock() } } - -} \ No newline at end of file +} + \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt index 50af49b55..d400ab915 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsAdapter.kt @@ -11,16 +11,14 @@ import network.loki.messenger.databinding.BlockedContactLayoutBinding import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.mms.GlideApp -class BlockedContactsAdapter: ListAdapter(RecipientDiffer()) { +class BlockedContactsAdapter(val viewModel: BlockedContactsViewModel) : ListAdapter(RecipientDiffer()) { class RecipientDiffer: DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem === newItem override fun areContentsTheSame(oldItem: Recipient, newItem: Recipient) = oldItem == newItem } - private val selectedItems = mutableListOf() - - fun getSelectedItems() = selectedItems + fun getSelectedItems() = viewModel.state.selectedItems override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val itemView = LayoutInflater.from(parent.context).inflate(R.layout.blocked_contact_layout, parent, false) @@ -28,19 +26,15 @@ class BlockedContactsAdapter: ListAdapter(capacity = Channel.CONFLATED) - private val _contacts = MutableLiveData(BlockedContactsViewState(emptyList())) + private val _state = MutableLiveData(BlockedContactsViewState(emptyList(), emptySet())) + + val state get() = _state.value!! fun subscribe(context: Context): LiveData { executor.launch(IO) { @@ -45,21 +48,66 @@ class BlockedContactsViewModel @Inject constructor(private val storage: Storage) } executor.launch(IO) { for (update in listUpdateChannel) { - val blockedContactState = BlockedContactsViewState(storage.blockedContacts().sortedBy { it.name }) + val blockedContactState = state.copy( + blockedContacts = storage.blockedContacts().sortedBy { it.name } + ) withContext(Main) { - _contacts.value = blockedContactState + _state.value = blockedContactState } } } - return _contacts + return _state } - fun unblock(toUnblock: List) { - storage.unblock(toUnblock) + fun unblock() { + storage.unblock(state.selectedItems) + _state.value = state.copy(selectedItems = emptySet()) + } + + fun select(selectedItem: Recipient, isSelected: Boolean) { + _state.value = state.run { + if (isSelected) copy(selectedItems = selectedItems + selectedItem) + else copy(selectedItems = selectedItems - selectedItem) + } + } + + fun getTitle(context: Context): String = + if (state.selectedItems.size == 1) { + context.getString(R.string.Unblock_dialog__title_single, state.selectedItems.first().name) + } else { + context.getString(R.string.Unblock_dialog__title_multiple) + } + + fun getMessage(context: Context): String { + if (state.selectedItems.size == 1) { + return context.getString(R.string.Unblock_dialog__message, state.selectedItems.first().name) + } + val stringBuilder = StringBuilder() + val iterator = state.selectedItems.iterator() + var numberAdded = 0 + while (iterator.hasNext() && numberAdded < 3) { + val nextRecipient = iterator.next() + if (numberAdded > 0) stringBuilder.append(", ") + + stringBuilder.append(nextRecipient.name) + numberAdded++ + } + val overflow = state.selectedItems.size - numberAdded + if (overflow > 0) { + stringBuilder.append(" ") + val string = context.resources.getQuantityString(R.plurals.Unblock_dialog__message_multiple_overflow, overflow) + stringBuilder.append(string.format(overflow)) + } + return context.getString(R.string.Unblock_dialog__message, stringBuilder.toString()) } data class BlockedContactsViewState( - val blockedContacts: List - ) - -} \ No newline at end of file + val blockedContacts: List, + val selectedItems: Set + ) { + val isEmpty get() = blockedContacts.isEmpty() + val unblockButtonEnabled get() = selectedItems.isNotEmpty() + val emptyStateMessageTextViewVisible get() = blockedContacts.isEmpty() + val nonEmptyStateGroupVisible get() = blockedContacts.isNotEmpty() + } +} diff --git a/app/src/main/res/color/button_destructive.xml b/app/src/main/res/color/button_destructive.xml new file mode 100644 index 000000000..cefbfed23 --- /dev/null +++ b/app/src/main/res/color/button_destructive.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/destructive_outline_button_medium_background.xml b/app/src/main/res/drawable/destructive_outline_button_medium_background.xml index 7db4da2ec..ac41ee621 100644 --- a/app/src/main/res/drawable/destructive_outline_button_medium_background.xml +++ b/app/src/main/res/drawable/destructive_outline_button_medium_background.xml @@ -7,5 +7,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9fa605988..f4b9d6dba 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -113,7 +113,7 @@ 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 48d15a18a..dc78aec1e 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -202,6 +202,6 @@ interface StorageProtocol { fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean) fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long) fun deleteReactions(messageId: Long, mms: Boolean) - fun unblock(toUnblock: List) + fun unblock(toUnblock: Iterable) fun blockedContacts(): List } From 8f55ac93f8c5bb6d2db208989d13d44f63f22d3c Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 22 May 2023 14:53:05 +0930 Subject: [PATCH 074/244] Fix accept button color --- app/src/main/res/values/styles.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9fa605988..1e97e737b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 204089862..59525f784 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -303,6 +303,8 @@ ?dividerVertical #F2F2F2 @color/classic_accent + ?android:colorControlHighlight + @style/Widget.Session.TabLayout - - - - - + @@ -312,7 +311,6 @@ ?dividerVertical #F2F2F2 @color/ocean_accent - ?android:colorControlHighlight @style/Widget.Session.TabLayout @@ -338,6 +336,7 @@ @color/classic_dark_1 @color/classic_dark_3 @color/classic_dark_3 + @color/classic_dark_3 @style/Dark.Popup @null @style/ThemeOverlay.AppCompat.Dark.ActionBar @@ -410,6 +409,7 @@ @color/classic_light_5 @color/classic_light_3 @color/classic_light_3 + @color/classic_light_3 @style/Classic.Light.BottomSheet ?android:textColorPrimary ?actionBarPopupTheme @@ -496,6 +496,7 @@ @color/ocean_dark_1 @color/ocean_dark_4 @color/ocean_dark_4 + @color/ocean_dark_4 @style/Ocean.Dark.BottomSheet ?actionBarPopupTheme ?android:textColorPrimary @@ -576,6 +577,7 @@ @color/ocean_light_6 @color/ocean_light_3 @color/ocean_light_4 + @color/ocean_light_4 @style/Ocean.Light.BottomSheet @style/Light.Popup ?actionBarPopupTheme From c92ef09d09bcf21930a8fb5f3f834a13e5fb686d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 5 Jun 2023 14:31:41 +1000 Subject: [PATCH 116/244] [SES-570] Fixed an issue where quotes weren't using their quoted message media --- .../thoughtcrime/securesms/database/MmsDatabase.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 9e854698f..e8f65dae0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase.InsertListener import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get @@ -1405,11 +1406,13 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val retrievedQuote = get(context).mmsSmsDatabase().getMessageFor(quoteId, quoteAuthor) val quoteText = retrievedQuote?.body val quoteMissing = retrievedQuote == null - val attachments = get(context).attachmentDatabase().getAttachment(cursor) - val quoteAttachments: List? = - Stream.of(attachments).filter { obj: DatabaseAttachment? -> obj!!.isQuote } + val quoteDeck = ( + (retrievedQuote as? MmsMessageRecord)?.slideDeck ?: + Stream.of(get(context).attachmentDatabase().getAttachment(cursor)) + .filter { obj: DatabaseAttachment? -> obj!!.isQuote } .toList() - val quoteDeck = SlideDeck(context, quoteAttachments!!) + .let { SlideDeck(context, it) } + ) return Quote( quoteId, fromExternal(context, quoteAuthor), From a6cfe5817dbaa5ecabad1e0223165a93388a16d3 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 5 Jun 2023 14:58:56 +0930 Subject: [PATCH 117/244] Change unset profile pic in dialog --- .../securesms/preferences/SettingsActivity.kt | 24 ++++++++++++++---- app/src/main/res/drawable/ic_pictures.xml | 18 +++++++++++++ .../main/res/layout/dialog_change_avatar.xml | 25 +++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/drawable/ic_pictures.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 6b6497982..5a03cebc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -26,8 +26,10 @@ import nl.komponents.kovenant.all import nl.komponents.kovenant.ui.alwaysUi import nl.komponents.kovenant.ui.successUi import org.session.libsession.avatars.AvatarHelper +import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.utilities.* import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.components.ProfilePictureView @@ -100,10 +102,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { private fun setupProfilePictureView(view: ProfilePictureView) { view.glide = glide - view.publicKey = hexEncodedPublicKey - view.displayName = getDisplayName() - view.isLarge = true - view.update() + view.apply { + publicKey = hexEncodedPublicKey + displayName = getDisplayName() + isLarge = true + update() + } } override fun onSaveInstanceState(outState: Bundle) { @@ -273,7 +277,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { } } .show().apply { - findViewById(R.id.profile_picture_view)?.let(::setupProfilePictureView) + val profilePic = findViewById(R.id.profile_picture_view) + ?.also(::setupProfilePictureView) + + val pictureIcon = findViewById(R.id.ic_pictures) + + val recipient = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false) + + val photoSet = (recipient.contactPhoto as ProfileContactPhoto).avatarObject !in setOf("0", "") + + profilePic?.isVisible = photoSet + pictureIcon?.isVisible = !photoSet } } diff --git a/app/src/main/res/drawable/ic_pictures.xml b/app/src/main/res/drawable/ic_pictures.xml new file mode 100644 index 000000000..967d0a65b --- /dev/null +++ b/app/src/main/res/drawable/ic_pictures.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/dialog_change_avatar.xml b/app/src/main/res/layout/dialog_change_avatar.xml index afe3672f1..60705698f 100644 --- a/app/src/main/res/layout/dialog_change_avatar.xml +++ b/app/src/main/res/layout/dialog_change_avatar.xml @@ -5,6 +5,31 @@ android:layout_height="wrap_content" android:layout_width="match_parent"> + + + + + + + + Date: Mon, 5 Jun 2023 17:15:05 +1000 Subject: [PATCH 118/244] Commented out the plus button on the replace profile modal --- .../main/res/layout/dialog_change_avatar.xml | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/app/src/main/res/layout/dialog_change_avatar.xml b/app/src/main/res/layout/dialog_change_avatar.xml index 2eb8adeeb..8cb97bcf6 100644 --- a/app/src/main/res/layout/dialog_change_avatar.xml +++ b/app/src/main/res/layout/dialog_change_avatar.xml @@ -23,23 +23,24 @@ android:background="@color/transparent" android:src="@drawable/ic_pictures"/> - - - + + + + + + + + + + + + + + + + + + From e5db7fc886224caa7462812fba679bb774e3efc9 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 5 Jun 2023 19:15:42 +0930 Subject: [PATCH 119/244] Fix dialog theme so it sets window color and not background of every child view --- .../main/res/drawable/default_dialog_background.xml | 10 ---------- .../res/drawable/default_dialog_background_inset.xml | 6 ------ app/src/main/res/values/styles.xml | 3 +-- app/src/main/res/values/themes.xml | 6 +----- 4 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 app/src/main/res/drawable/default_dialog_background.xml delete mode 100644 app/src/main/res/drawable/default_dialog_background_inset.xml diff --git a/app/src/main/res/drawable/default_dialog_background.xml b/app/src/main/res/drawable/default_dialog_background.xml deleted file mode 100644 index 00953c88a..000000000 --- a/app/src/main/res/drawable/default_dialog_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/default_dialog_background_inset.xml b/app/src/main/res/drawable/default_dialog_background_inset.xml deleted file mode 100644 index 0ff315ebd..000000000 --- a/app/src/main/res/drawable/default_dialog_background_inset.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index ffef59809..f57a0d973 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -23,14 +23,13 @@ diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 59525f784..37c2e6e15 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -246,12 +246,8 @@ true - - From 6edf0d46f8626c27028564527ce21235adb239d2 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Jun 2023 09:30:43 +1000 Subject: [PATCH 120/244] build: increment build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b86122033..357461248 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,8 +160,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 335 -def canonicalVersionName = "1.16.7" +def canonicalVersionCode = 336 +def canonicalVersionName = "1.16.8" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From cea65f3e4573b6e2da6958a79add6b035f687da0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Jun 2023 16:34:23 +1000 Subject: [PATCH 121/244] Fixed an issue where a bunch of dialog backgrounds were missing --- .../securesms/conversation/v2/utilities/BaseDialog.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt index e1456a7f9..406459985 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt @@ -14,7 +14,6 @@ open class BaseDialog : DialogFragment() { val builder = AlertDialog.Builder(requireContext()) setContentView(builder) val result = builder.create() - result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) result.window?.setDimAmount(if (isLightMode) 0.1f else 0.75f) return result From 9ce89087a5dc6823b5849cb8d016550dbe5bd502 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Jun 2023 16:57:27 +1000 Subject: [PATCH 122/244] Updated the dialog dim amount to match the style guide --- .../securesms/conversation/v2/DeleteOptionsBottomSheet.kt | 3 +-- .../securesms/conversation/v2/ModalUrlBottomSheet.kt | 3 +-- .../securesms/conversation/v2/utilities/BaseDialog.kt | 3 +-- .../securesms/home/ConversationOptionsBottomSheet.kt | 3 +-- .../org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt index 66f33cf29..b6212b854 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DeleteOptionsBottomSheet.kt @@ -69,7 +69,6 @@ class DeleteOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListen override fun onStart() { super.onStart() val window = dialog?.window ?: return - val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) - window.setDimAmount(if (isLightMode) 0.1f else 0.75f) + window.setDimAmount(0.6f) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt index 28c86b331..54deea1c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt @@ -60,8 +60,7 @@ class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(), override fun onStart() { super.onStart() val window = dialog?.window ?: return - val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) - window.setDimAmount(if (isLightMode) 0.1f else 0.75f) + window.setDimAmount(0.6f) } override fun onClick(v: View?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt index 406459985..c3a9689a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/BaseDialog.kt @@ -14,8 +14,7 @@ open class BaseDialog : DialogFragment() { val builder = AlertDialog.Builder(requireContext()) setContentView(builder) val result = builder.create() - val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) - result.window?.setDimAmount(if (isLightMode) 0.1f else 0.75f) + result.window?.setDimAmount(0.6f) return result } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index fc85f544f..7e9d2640a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -88,7 +88,6 @@ class ConversationOptionsBottomSheet(private val parentContext: Context) : Botto override fun onStart() { super.onStart() val window = dialog?.window ?: return - val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) - window.setDimAmount(if (isLightMode) 0.1f else 0.75f) + window.setDimAmount(0.6f) } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index f3915abff..bc9a9bece 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -117,8 +117,7 @@ class UserDetailsBottomSheet: BottomSheetDialogFragment() { override fun onStart() { super.onStart() val window = dialog?.window ?: return - val isLightMode = UiModeUtilities.isDayUiMode(requireContext()) - window.setDimAmount(if (isLightMode) 0.1f else 0.75f) + window.setDimAmount(0.6f) } fun saveNickName(recipient: Recipient) = with(binding) { From 082c087105a192a7d10ba6cadcb0fc267cf0bd60 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Jun 2023 17:30:10 +1000 Subject: [PATCH 123/244] Added the custom dialog windowBackground back (fix permission dialogs) --- .../main/res/drawable/default_dialog_background.xml | 10 ++++++++++ app/src/main/res/values/styles.xml | 1 + app/src/main/res/values/themes.xml | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/default_dialog_background.xml diff --git a/app/src/main/res/drawable/default_dialog_background.xml b/app/src/main/res/drawable/default_dialog_background.xml new file mode 100644 index 000000000..00953c88a --- /dev/null +++ b/app/src/main/res/drawable/default_dialog_background.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f57a0d973..a9bf9f4ab 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -23,6 +23,7 @@ From 22a30f19072ea3cd103aa41f7c4722b6435a0ab0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Jun 2023 18:29:02 +1000 Subject: [PATCH 124/244] Fixed the MediaSendFragment progress dialog --- app/src/main/res/layout/progress_dialog.xml | 2 +- app/src/main/res/values/themes.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/progress_dialog.xml b/app/src/main/res/layout/progress_dialog.xml index 29678e121..c8c704ce0 100644 --- a/app/src/main/res/layout/progress_dialog.xml +++ b/app/src/main/res/layout/progress_dialog.xml @@ -11,6 +11,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" - app:SpinKit_Color="?android:textColorPrimary" /> + app:SpinKit_Color="?colorAccent" /> \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 1b56ed788..edf8108d6 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -248,7 +248,7 @@ From fef1fbd57cfd696f27e68588a0b199e2d0f4604a Mon Sep 17 00:00:00 2001 From: Kee Jefferys Date: Wed, 7 Jun 2023 11:18:26 +1000 Subject: [PATCH 125/244] Update Session Android screenshot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c509dd956..723d50c75 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Add the [F-Droid repo](https://fdroid.getsession.org/) Session integrates directly with [Oxen Service Nodes](https://docs.oxen.io/about-the-oxen-blockchain/oxen-service-nodes), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users' IP addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper). - + ## Want to contribute? Found a bug or have a feature request? From 11c1fd382d3ca48564198dfb17388d9d4aee2f00 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 7 Jun 2023 13:31:22 +1000 Subject: [PATCH 126/244] Fixed a few issues with the OpenGroupPoller Fixed an issue where the admin/moderator status wasn't getting stored if set before joining a community Fixed an issue where multiple pollers for the same server could run at the same time when joining multiple rooms within the same app run (very noticeable when restoring/linking) --- .../securesms/groups/OpenGroupManager.kt | 25 ++- .../messaging/jobs/BackgroundGroupAddJob.kt | 8 +- .../messaging/open_groups/OpenGroupApi.kt | 21 +- .../pollers/OpenGroupPoller.kt | 180 ++++++++++-------- 4 files changed, 134 insertions(+), 100 deletions(-) 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 ef4726910..6a342eacd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -41,11 +41,13 @@ object OpenGroupManager { isPolling = true val storage = MessagingModuleConfiguration.shared.storage val servers = storage.getAllOpenGroups().values.map { it.server }.toSet() - servers.forEach { server -> - pollers[server]?.stop() // Shouldn't be necessary - val poller = OpenGroupPoller(server, executorService) - poller.startIfNeeded() - pollers[server] = poller + synchronized(pollUpdaterLock) { + servers.forEach { server -> + pollers[server]?.stop() // Shouldn't be necessary + val poller = OpenGroupPoller(server, executorService) + poller.startIfNeeded() + pollers[server] = poller + } } } @@ -60,7 +62,7 @@ object OpenGroupManager { @WorkerThread fun add(server: String, room: String, publicKey: String, context: Context): OpenGroupApi.RoomInfo? { val openGroupID = "$server.$room" - var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) + val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) val storage = MessagingModuleConfiguration.shared.storage val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() // Check it it's added already @@ -76,13 +78,16 @@ object OpenGroupManager { // Get capabilities & room info val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get() storage.setServerCapabilities(server, capabilities.capabilities) - 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 + GroupManager.createOpenGroup(openGroupID, context, null, info.name) } - 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) + OpenGroupPoller.handleRoomPollInfo( + server = server, + roomToken = room, + pollInfo = info.toPollInfo(), + createGroupIfMissingWithPublicKey = publicKey + ) return info } 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 515410132..c5ec1bc74 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 @@ -39,13 +39,7 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { delegate?.handleJobFailed(this, dispatcherName, DuplicateGroupException()) return } - // get image - storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) - val info = storage.addOpenGroup(openGroup.joinUrl()) - val imageId = info?.imageId - if (imageId != null && storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId) == null) { - JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.server, openGroup.room, imageId)) - } + storage.addOpenGroup(openGroup.joinUrl()) Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") storage.onOpenGroupAdded(openGroup.server) } catch (e: Exception) { 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 ca60fd3cb..a05addcee 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 @@ -109,7 +109,26 @@ object OpenGroupApi { val defaultWrite: Boolean = false, val upload: Boolean = false, val defaultUpload: Boolean = false, - ) + ) { + fun toPollInfo(): RoomPollInfo { + return RoomPollInfo( + token = token, + activeUsers = activeUsers, + admin = admin, + globalAdmin = globalAdmin, + moderator = moderator, + globalModerator = globalModerator, + read = read, + defaultRead = defaultRead, + defaultAccessible = defaultAccessible, + write = write, + defaultWrite = defaultWrite, + upload = upload, + defaultUpload = defaultUpload, + details = this + ) + } + } @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) data class PinnedMessage( 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 562ddda69..fb7b3b6c8 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 @@ -30,6 +30,7 @@ import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.successBackground +import java.util.UUID import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit @@ -39,15 +40,97 @@ class OpenGroupPoller(private val server: String, private val executorService: S var isCaughtUp = false var secondToLastJob: MessageReceiveJob? = null private var future: ScheduledFuture<*>? = null + private var runId: UUID = UUID.randomUUID() companion object { private const val pollInterval: Long = 4000L const val maxInactivityPeriod = 14 * 24 * 60 * 60 * 1000 + + public fun handleRoomPollInfo( + server: String, + roomToken: String, + pollInfo: OpenGroupApi.RoomPollInfo, + createGroupIfMissingWithPublicKey: String? = null + ) { + val storage = MessagingModuleConfiguration.shared.storage + val groupId = "$server.$roomToken" + val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) + val existingOpenGroup = storage.getOpenGroup(roomToken, server) + + // If we don't have an existing group and don't have a 'createGroupIfMissingWithPublicKey' + // value then don't process the poll info + val publicKey = ((existingOpenGroup?.publicKey ?: createGroupIfMissingWithPublicKey) ?: return) + + val openGroup = OpenGroup( + server = server, + room = pollInfo.token, + name = ((pollInfo.details?.name ?: existingOpenGroup?.name) ?: ""), + publicKey = publicKey, + imageId = (pollInfo.details?.imageId ?: existingOpenGroup?.imageId), + canWrite = pollInfo.write, + infoUpdates = ((pollInfo.details?.infoUpdates ?: existingOpenGroup?.infoUpdates) ?: 0) + ) + // - Open Group changes + storage.updateOpenGroup(openGroup) + + // - User Count + storage.setUserCount(roomToken, server, pollInfo.activeUsers) + + // - Moderators + pollInfo.details?.moderators?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.MODERATOR) + }) + } + pollInfo.details?.hiddenModerators?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR) + }) + } + // - Admins + pollInfo.details?.admins?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.ADMIN) + }) + } + pollInfo.details?.hiddenAdmins?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) + }) + } + + // Update the group avatar + if ( + ( + pollInfo.details != null && + pollInfo.details.imageId != null && ( + pollInfo.details.imageId != existingOpenGroup?.imageId || + !storage.hasDownloadedProfilePicture(dbGroupId) + ) && + storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null + ) || ( + pollInfo.details == null && + existingOpenGroup?.imageId != null && + !storage.hasDownloadedProfilePicture(dbGroupId) && + storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null + ) + ) { + JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, openGroup.imageId)) + } + else if ( + pollInfo.details != null && + pollInfo.details.imageId == null && + existingOpenGroup?.imageId != null + ) { + storage.removeProfilePicture(dbGroupId) + } + } } fun startIfNeeded() { if (hasStarted) { return } hasStarted = true + runId = UUID.randomUUID() future = executorService?.schedule(::poll, 0, TimeUnit.MILLISECONDS) } @@ -57,6 +140,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S } fun poll(isPostCapabilitiesRetry: Boolean = false): Promise { + val currentRunId = runId val storage = MessagingModuleConfiguration.shared.storage val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room } @@ -86,22 +170,30 @@ class OpenGroupPoller(private val server: String, private val executorService: S isCaughtUp = true } } - executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) + + // Only poll again if it's the same poller run + if (currentRunId == runId) { + future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) + } }.fail { - updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, it) + updateCapabilitiesIfNeeded(isPostCapabilitiesRetry, currentRunId, it) }.map { } } - private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, exception: Exception) { + private fun updateCapabilitiesIfNeeded(isPostCapabilitiesRetry: Boolean, currentRunId: UUID, exception: Exception) { if (exception is OnionRequestAPI.HTTPRequestFailedBlindingRequiredException) { if (!isPostCapabilitiesRetry) { OpenGroupApi.getCapabilities(server).map { handleCapabilities(server, it) } - executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS) + + // Only poll again if it's the same poller run + if (currentRunId == runId) { + future = executorService?.schedule({ poll(isPostCapabilitiesRetry = true) }, pollInterval, TimeUnit.MILLISECONDS) + } } - } else { - executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) + } else if (currentRunId == runId) { + future = executorService?.schedule(this@OpenGroupPoller::poll, pollInterval, TimeUnit.MILLISECONDS) } } @@ -110,82 +202,6 @@ class OpenGroupPoller(private val server: String, private val executorService: S storage.setServerCapabilities(server, capabilities.capabilities) } - private fun handleRoomPollInfo( - server: String, - roomToken: String, - pollInfo: OpenGroupApi.RoomPollInfo - ) { - 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 = 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) - - // - User Count - storage.setUserCount(roomToken, server, pollInfo.activeUsers) - - // - Moderators - pollInfo.details?.moderators?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.MODERATOR) - }) - } - pollInfo.details?.hiddenModerators?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR) - }) - } - // - Admins - pollInfo.details?.admins?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.ADMIN) - }) - } - pollInfo.details?.hiddenAdmins?.let { moderatorList -> - storage.setGroupMemberRoles(moderatorList.map { - GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) - }) - } - - // Update the group avatar - if ( - ( - pollInfo.details != null && - pollInfo.details.imageId != null && ( - pollInfo.details.imageId != existingOpenGroup.imageId || - !storage.hasDownloadedProfilePicture(dbGroupId) - ) && - storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, pollInfo.details.imageId) == null - ) || ( - pollInfo.details == null && - existingOpenGroup.imageId != null && - !storage.hasDownloadedProfilePicture(dbGroupId) && - storage.getGroupAvatarDownloadJob(openGroup.server, openGroup.room, existingOpenGroup.imageId) == null - ) - ) { - JobQueue.shared.add(GroupAvatarDownloadJob(server, roomToken, existingOpenGroup.imageId)) - } - else if ( - pollInfo.details != null && - pollInfo.details.imageId == null && - existingOpenGroup.imageId != null - ) { - storage.removeProfilePicture(dbGroupId) - } - } - private fun handleMessages( server: String, roomToken: String, From 7699e47f7be4f715a3b0a72cb7dcd257c9864018 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 7 Jun 2023 15:02:32 +1000 Subject: [PATCH 127/244] Responded to PR comments --- .../securesms/groups/OpenGroupManager.kt | 6 ++-- .../messaging/open_groups/OpenGroupApi.kt | 34 +++++++++---------- .../pollers/OpenGroupPoller.kt | 12 ++++--- 3 files changed, 26 insertions(+), 26 deletions(-) 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 6a342eacd..dbdf2615a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -15,7 +15,7 @@ import java.util.concurrent.Executors object OpenGroupManager { private val executorService = Executors.newScheduledThreadPool(4) - private var pollers = mutableMapOf() // One for each server + private val pollers = mutableMapOf() // One for each server private var isPolling = false private val pollUpdaterLock = Any() @@ -44,9 +44,7 @@ object OpenGroupManager { synchronized(pollUpdaterLock) { servers.forEach { server -> pollers[server]?.stop() // Shouldn't be necessary - val poller = OpenGroupPoller(server, executorService) - poller.startIfNeeded() - pollers[server] = poller + pollers[server] = OpenGroupPoller(server, executorService).apply { startIfNeeded() } } } } 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 a05addcee..dc6d1475f 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 @@ -110,24 +110,22 @@ object OpenGroupApi { val upload: Boolean = false, val defaultUpload: Boolean = false, ) { - fun toPollInfo(): RoomPollInfo { - return RoomPollInfo( - token = token, - activeUsers = activeUsers, - admin = admin, - globalAdmin = globalAdmin, - moderator = moderator, - globalModerator = globalModerator, - read = read, - defaultRead = defaultRead, - defaultAccessible = defaultAccessible, - write = write, - defaultWrite = defaultWrite, - upload = upload, - defaultUpload = defaultUpload, - details = this - ) - } + fun toPollInfo() = RoomPollInfo( + token = token, + activeUsers = activeUsers, + admin = admin, + globalAdmin = globalAdmin, + moderator = moderator, + globalModerator = globalModerator, + read = read, + defaultRead = defaultRead, + defaultAccessible = defaultAccessible, + write = write, + defaultWrite = defaultWrite, + upload = upload, + defaultUpload = defaultUpload, + details = this + ) } @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) 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 fb7b3b6c8..387381c9c 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 @@ -40,7 +40,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S var isCaughtUp = false var secondToLastJob: MessageReceiveJob? = null private var future: ScheduledFuture<*>? = null - private var runId: UUID = UUID.randomUUID() + @Volatile private var runId: UUID = UUID.randomUUID() companion object { private const val pollInterval: Long = 4000L @@ -59,16 +59,20 @@ class OpenGroupPoller(private val server: String, private val executorService: S // If we don't have an existing group and don't have a 'createGroupIfMissingWithPublicKey' // value then don't process the poll info - val publicKey = ((existingOpenGroup?.publicKey ?: createGroupIfMissingWithPublicKey) ?: return) + val publicKey = existingOpenGroup?.publicKey ?: createGroupIfMissingWithPublicKey + val name = pollInfo.details?.name ?: existingOpenGroup?.name + val infoUpdates = pollInfo.details?.infoUpdates ?: existingOpenGroup?.infoUpdates + + if (publicKey == null) return val openGroup = OpenGroup( server = server, room = pollInfo.token, - name = ((pollInfo.details?.name ?: existingOpenGroup?.name) ?: ""), + name = name ?: "", publicKey = publicKey, imageId = (pollInfo.details?.imageId ?: existingOpenGroup?.imageId), canWrite = pollInfo.write, - infoUpdates = ((pollInfo.details?.infoUpdates ?: existingOpenGroup?.infoUpdates) ?: 0) + infoUpdates = infoUpdates ?: 0 ) // - Open Group changes storage.updateOpenGroup(openGroup) From 80104f6db82fd925816c1baf070a2491a9c07cc7 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 8 Jun 2023 16:33:34 +1000 Subject: [PATCH 128/244] [SES-627] Fixed an issue where the DocumentView could run off the screen --- .../v2/messages/VisibleMessageView.kt | 2 -- app/src/main/res/layout/view_visible_message.xml | 16 ++++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) 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 ce6019e4c..319140731 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 @@ -338,11 +338,9 @@ class VisibleMessageView : LinearLayout { val container = binding.messageInnerContainer val content = binding.messageContentView.root val expiration = binding.expirationTimerView - val spacing = binding.messageContentSpacing container.removeAllViewsInLayout() container.addView(if (message.isOutgoing) expiration else content) container.addView(if (message.isOutgoing) content else expiration) - container.addView(spacing, if (message.isOutgoing) 0 else 2) val containerParams = container.layoutParams as ConstraintLayout.LayoutParams containerParams.horizontalBias = if (message.isOutgoing) 1f else 0f container.layoutParams = containerParams diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 34b28140a..d328d19ab 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -79,8 +79,9 @@ + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1"/> - - + tools:visibility="visible" + tools:src="@drawable/timer60" + tools:tint="@color/black"/> From f0486061b112537ae2b400c3764cd0d2daed2431 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 8 Jun 2023 17:38:52 +1000 Subject: [PATCH 129/244] build: increment build number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 357461248..8a4241973 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,8 +160,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 336 -def canonicalVersionName = "1.16.8" +def canonicalVersionCode = 338 +def canonicalVersionName = "1.16.9" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From ba6eca2443f06291a3d91e8df90cd65338020f34 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 09:52:11 +0930 Subject: [PATCH 130/244] Add FirebasePushManager#unregister --- .../securesms/notifications/PushManager.kt | 1 - .../notifications/FirebasePushManager.kt | 113 +++++++++++------- .../notifications/NoOpPushManager.kt | 6 +- .../sending_receiving/notifications/Models.kt | 26 ++++ .../utilities/TextSecurePreferences.kt | 2 +- 5 files changed, 100 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt index 36aca1c91..9d4ef23fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt @@ -2,5 +2,4 @@ package org.thoughtcrime.securesms.notifications interface PushManager { fun register(force: Boolean) - fun unregister(token: String) } \ No newline at end of file diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 12e62cee4..4f9a6292f 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -1,6 +1,8 @@ package org.thoughtcrime.securesms.notifications import android.content.Context +import com.google.android.gms.tasks.Task +import com.google.firebase.iid.InstanceIdResult import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.interfaces.AEAD @@ -21,6 +23,8 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse +import org.session.libsession.messaging.sending_receiving.notifications.UnsubscriptionRequest import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI @@ -74,7 +78,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") - val metadata:PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) + val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null // null content is valid only if we got a "data_too_long" flag @@ -90,41 +94,71 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS } override fun register(force: Boolean) { - val currentInstanceIdJob = firebaseInstanceIdJob - if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return - - if (force && currentInstanceIdJob != null) { - currentInstanceIdJob.cancel(null) + firebaseInstanceIdJob?.apply { + if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = getFcmInstanceId { task -> - // context in here is Dispatchers.IO - if (!task.isSuccessful) { - Log.w( - "Loki", - "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception - ) - return@getFcmInstanceId - } - val token: String = task.result?.token ?: return@getFcmInstanceId - val userPublicKey = getLocalNumber(context) ?: return@getFcmInstanceId - val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return@getFcmInstanceId - if (prefs.isUsingFCM()) { - register(token, userPublicKey, userEdKey, force) - } else { - unregister(token) + firebaseInstanceIdJob = getFcmInstanceId { register(it, force) } + } + + private fun register(task: Task, force: Boolean) { + // context in here is Dispatchers.IO + if (!task.isSuccessful) { + Log.w( + "Loki", + "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception + ) + return + } + + val token: String = task.result?.token ?: return + val userPublicKey = getLocalNumber(context) ?: return + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return + + when { + prefs.isUsingFCM() -> register(token, userPublicKey, userEdKey, force) + prefs.getFCMToken() != null -> unregister(token, userPublicKey, userEdKey) + } + } + + private fun unregister(token: String, userPublicKey: String, userEdKey: KeyPair) { + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) + + val requestParameters = UnsubscriptionRequest( + pubkey = userPublicKey, + session_ed25519 = userEdKey.publicKey.asHexString, + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + ) + + val url = "${PushNotificationAPI.server}/unsubscribe" + val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) + val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { + getResponseBody(request.build()).map { response -> + if (response.success == true) { + TextSecurePreferences.setIsUsingFCM(context, false) + TextSecurePreferences.setFCMToken(context, null) + Log.d("Loki", "Unsubscribe FCM success") + } else { + Log.e("Loki", "Couldn't unregister for FCM due to error: ${response.message}") + } + }.fail { exception -> + Log.e("Loki", "Couldn't unregister for FCM due to error: ${exception}.", exception) } } } - override fun unregister(token: String) { - TODO("Not yet implemented") - } - - fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { + private fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } + if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) return val pnKey = getOrCreateNotificationKey() @@ -133,7 +167,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val sigData = "MONITOR${publicKey}${timestamp}1${namespaces.joinToString(separator = ",")}".encodeToByteArray() val signature = ByteArray(Sign.BYTES) sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEd25519Key.secretKey.asBytes) - val requestParameters = SubscriptionRequest ( + val requestParameters = SubscriptionRequest( pubkey = publicKey, session_ed25519 = userEd25519Key.publicKey.asHexString, namespaces = listOf(Namespace.DEFAULT), @@ -149,9 +183,8 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { response -> + getResponseBody(request.build()).map { response -> if (response.isSuccess()) { - TextSecurePreferences.setIsUsingFCM(context, true) TextSecurePreferences.setFCMToken(context, token) TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) } else { @@ -159,18 +192,16 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS Log.e("Loki", "Couldn't register for FCM due to error: $message.") } }.fail { exception -> - Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.") + Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.", exception) } } } - private fun getResponseBody(request: Request): Promise { - return OnionRequestAPI.sendOnionRequest(request, + private inline fun getResponseBody(request: Request): Promise = + OnionRequestAPI.sendOnionRequest( + request, PushNotificationAPI.server, - PushNotificationAPI.serverPublicKey, Version.V4).map { response -> - Json.decodeFromStream(response.body!!.inputStream()) - } - } - - -} \ No newline at end of file + PushNotificationAPI.serverPublicKey, + Version.V4 + ).map { response -> Json.decodeFromStream(response.body!!.inputStream()) } +} diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt index edbf7d710..59f1b9df4 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt @@ -7,8 +7,4 @@ class NoOpPushManager: PushManager { override fun register(force: Boolean) { Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") } - - override fun unregister(token: String) { - Log.d("NoOpPushManager", "Push notifications not supported, not unregistering for push notifications") - } -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index e7a0ad79a..141202ef6 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -36,6 +36,24 @@ data class SubscriptionRequest( val enc_key: String ) +@Serializable +data class UnsubscriptionRequest( + /** the 33-byte account being subscribed to; typically a session ID */ + val pubkey: String, + /** when the pubkey starts with 05 (i.e. a session ID) this is the ed25519 32-byte pubkey associated with the session ID */ + val session_ed25519: String?, + /** 32-byte swarm authentication subkey; omitted (or null) when not using subkey auth (new closed groups) */ + val subkey_tag: String? = null, + /** the signature unix timestamp in seconds, not ms */ + val sig_ts: Long, + /** the 64-byte ed25519 signature */ + val signature: String, + /** the string identifying the notification service, "firebase" for android (currently) */ + val service: String, + /** dict of service-specific data, currently just "token" field with device-specific token but different services might have other requirements */ + val service_info: Map, +) + @Serializable data class SubscriptionResponse( val error: Int? = null, @@ -60,6 +78,14 @@ data class SubscriptionResponse( } else null to null } +@Serializable +data class UnsubscribeResponse( + val error: Int? = null, + val message: String? = null, + val success: Boolean? = null, + val removed: Boolean? = null, +) + @Serializable data class PushNotificationMetadata( /** Account ID (such as Session ID or closed group ID) where the message arrived **/ diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 1f05e4f13..b1ab1090f 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -318,7 +318,7 @@ interface TextSecurePreferences { } @JvmStatic - fun setFCMToken(context: Context, value: String) { + fun setFCMToken(context: Context, value: String?) { setStringPreference(context, FCM_TOKEN, value) } From 3f6229f8412732deec3b531e5beeae12a7533c6d Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 14:50:45 +0930 Subject: [PATCH 131/244] Remove PushNotificationManager --- .../securesms/ApplicationContext.java | 6 +- .../securesms/notifications/PushManager.kt | 2 +- .../notifications/PushNotificationManager.kt | 121 ------------------ .../notifications/FirebasePushManager.kt | 6 +- .../notifications/PushNotificationService.kt | 13 +- .../notifications/NoOpPushManager.kt | 2 +- .../MessageSenderClosedGroupHandler.kt | 10 +- .../ReceivedMessageHandler.kt | 4 +- .../notifications/PushNotificationAPI.kt | 105 +++++++++------ 9 files changed, 87 insertions(+), 182 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 57187235c..885c71663 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -36,6 +36,7 @@ import org.session.libsession.avatars.AvatarHelper; import org.session.libsession.database.MessageDataProvider; import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI; import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2; import org.session.libsession.messaging.sending_receiving.pollers.Poller; import org.session.libsession.snode.SnodeModule; @@ -78,7 +79,6 @@ import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier; import org.thoughtcrime.securesms.notifications.PushManager; -import org.thoughtcrime.securesms.notifications.PushNotificationManager; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.KeyCachingService; @@ -440,7 +440,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO private static class ProviderInitializationException extends RuntimeException { } public void registerForPnIfNeeded(final Boolean force) { - pushManager.register(force); + pushManager.refresh(force); } private void setUpPollingIfNeeded() { @@ -514,7 +514,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO public void clearAllData(boolean isMigratingToV2KeyPair) { String token = TextSecurePreferences.getFCMToken(this); if (token != null && !token.isEmpty()) { - PushNotificationManager.unregister(token, this); + PushNotificationAPI.unregister(token); } if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt index 9d4ef23fe..d094644c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushManager.kt @@ -1,5 +1,5 @@ package org.thoughtcrime.securesms.notifications interface PushManager { - fun register(force: Boolean) + fun refresh(force: Boolean) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt deleted file mode 100644 index 5a53f7af2..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PushNotificationManager.kt +++ /dev/null @@ -1,121 +0,0 @@ -package org.thoughtcrime.securesms.notifications - -import android.content.Context -import nl.komponents.kovenant.Promise -import nl.komponents.kovenant.functional.map -import okhttp3.MediaType -import okhttp3.Request -import okhttp3.RequestBody -import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI -import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.snode.Version -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.utilities.Log -import org.session.libsignal.utilities.retryIfNeeded -import org.thoughtcrime.securesms.dependencies.DatabaseComponent - -object PushNotificationManager { - private val maxRetryCount = 4 - private val tokenExpirationInterval = 12 * 60 * 60 * 1000 - - private val server by lazy { - PushNotificationAPI.server - } - private val pnServerPublicKey by lazy { - PushNotificationAPI.serverPublicKey - } - - enum class ClosedGroupOperation { - Subscribe, Unsubscribe; - - val rawValue: String - get() { - return when (this) { - Subscribe -> "subscribe_closed_group" - Unsubscribe -> "unsubscribe_closed_group" - } - } - } - - @JvmStatic - fun unregister(token: String, context: Context) { - val parameters = mapOf( "token" to token ) - val url = "$server/unregister" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { json -> - val code = json["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, false) - } else { - Log.d("Loki", "Couldn't disable FCM due to error: ${json["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") - } - } - // Unsubscribe from all closed groups - val allClosedGroupPublicKeys = DatabaseComponent.get(context).lokiAPIDatabase().getAllClosedGroupPublicKeys() - val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(context, ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) - } - } - - @JvmStatic - fun register(token: String, publicKey: String, context: Context, force: Boolean) { - val oldToken = TextSecurePreferences.getFCMToken(context) - val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { return } - val parameters = mapOf( "token" to token, "pubKey" to publicKey ) - val url = "$server/register" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { json -> - val code = json["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, true) - TextSecurePreferences.setFCMToken(context, token) - TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) - } else { - Log.d("Loki", "Couldn't register for FCM due to error: ${json["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't register for FCM due to error: ${exception}.") - } - } - // Subscribe to all closed groups - val allClosedGroupPublicKeys = DatabaseComponent.get(context).lokiAPIDatabase().getAllClosedGroupPublicKeys() - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(context, ClosedGroupOperation.Subscribe, closedGroup, publicKey) - } - } - - @JvmStatic - fun performOperation(context: Context, operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { - if (!TextSecurePreferences.isUsingFCM(context)) { return } - val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) - val url = "$server/${operation.rawValue}" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { json -> - val code = json["code"] as? Int - if (code == null || code == 0) { - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${json["message"] as? String ?: "null"}.") - } - }.fail { exception -> - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") - } - } - } - - private fun getResponseBody(request: Request): Promise, Exception> { - return OnionRequestAPI.sendOnionRequest(request, server, pnServerPublicKey, Version.V2).map { response -> - JsonUtil.fromJson(response.body, Map::class.java) - } - } -} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 4f9a6292f..c169f145c 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -93,15 +93,15 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS return content } - override fun register(force: Boolean) { + override fun refresh(force: Boolean) { firebaseInstanceIdJob?.apply { if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = getFcmInstanceId { register(it, force) } + firebaseInstanceIdJob = getFcmInstanceId { refresh(it, force) } } - private fun register(task: Task, force: Boolean) { + private fun refresh(task: Task, force: Boolean) { // context in here is Dispatchers.IO if (!task.isSuccessful) { Log.w( diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index bb625bc54..0d9765d73 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.AndroidEntryPoint import org.session.libsession.messaging.jobs.BatchMessageReceiveJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveParameters +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.Base64 @@ -23,7 +24,7 @@ class PushNotificationService : FirebaseMessagingService() { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") TextSecurePreferences.getLocalNumber(this) ?: return - pushManager.register(true) + pushManager.refresh(true) } override fun onMessageReceived(message: RemoteMessage) { @@ -53,22 +54,18 @@ class PushNotificationService : FirebaseMessagingService() { Log.d("Loki", "Failed to decode data for message.") val builder = NotificationCompat.Builder(this, NotificationChannels.OTHER) .setSmallIcon(network.loki.messenger.R.drawable.ic_notification) - .setColor(this.getResources().getColor(network.loki.messenger.R.color.textsecure_primary)) + .setColor(resources.getColor(network.loki.messenger.R.color.textsecure_primary)) .setContentTitle("Session") .setContentText("You've got a new message.") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) - with(NotificationManagerCompat.from(this)) { - notify(11111, builder.build()) - } + NotificationManagerCompat.from(this).notify(11111, builder.build()) } } override fun onDeletedMessages() { Log.d("Loki", "Called onDeletedMessages.") super.onDeletedMessages() - val token = TextSecurePreferences.getFCMToken(this)!! - val userPublicKey = TextSecurePreferences.getLocalNumber(this) ?: return - PushNotificationManager.register(token, userPublicKey, this, true) + PushNotificationAPI.register() } } \ No newline at end of file diff --git a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt index 59f1b9df4..a817e59d5 100644 --- a/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt +++ b/app/src/website/kotlin/org/thoughtcrime/securesms/notifications/NoOpPushManager.kt @@ -4,7 +4,7 @@ import org.session.libsignal.utilities.Log class NoOpPushManager: PushManager { - override fun register(force: Boolean) { + override fun refresh(force: Boolean) { Log.d("NoOpPushManager", "Push notifications not supported, not registering for push notifications") } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt index b8a903539..39afec418 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSenderClosedGroupHandler.kt @@ -74,7 +74,7 @@ fun MessageSender.create(name: String, members: Collection): Promise, name: St // Update name if needed if (name != group.title) { setName(groupPublicKey, name) } // Add members if needed - val addedMembers = members - group.members.map { it.serialize() } - if (!addedMembers.isEmpty()) { addMembers(groupPublicKey, addedMembers) } + val addedMembers = members - group.members.map { it.serialize() }.toSet() + if (addedMembers.isNotEmpty()) { addMembers(groupPublicKey, addedMembers) } // Remove members if needed - val removedMembers = group.members.map { it.serialize() } - members + val removedMembers = group.members.map { it.serialize() } - members.toSet() if (removedMembers.isEmpty()) { removeMembers(groupPublicKey, removedMembers) } } @@ -195,7 +195,7 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List "subscribe_closed_group" - Unsubscribe -> "unsubscribe_closed_group" - } - } + private enum class ClosedGroupOperation(val rawValue: String) { + Subscribe("subscribe_closed_group"), + Unsubscribe("unsubscribe_closed_group"); } + fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { + token?.let(::unregisterV1) + subscribeGroups() + } + + @JvmStatic fun unregister(token: String) { + unregisterV1(token) + unsubscribeGroups() + } + + private fun unregisterV1(token: String) { val parameters = mapOf( "token" to token ) val url = "$server/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setIsUsingFCM(context, false) - } else { - Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"] as? String ?: "null"}.") + when (response.info["code"]) { + null, 0 -> Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"]}.") } }.fail { exception -> Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") } } - // Unsubscribe from all closed groups - val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() - val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(ClosedGroupOperation.Unsubscribe, closedGroup, userPublicKey) - } } - fun register(token: String, publicKey: String, force: Boolean) { - // Subscribe to all closed groups - val allClosedGroupPublicKeys = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys() - allClosedGroupPublicKeys.iterator().forEach { closedGroup -> - performOperation(ClosedGroupOperation.Subscribe, closedGroup, publicKey) - } + // Legacy Closed Groups + + fun subscribeGroup( + closedGroupPublicKey: String, + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) } - fun performOperation(operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String) { - if (!TextSecurePreferences.isUsingFCM(context)) { return } + private fun subscribeGroups( + closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperations(ClosedGroupOperation.Subscribe, closedGroupPublicKeys, publicKey) + } + + fun unsubscribeGroup( + closedGroupPublicKey: String, + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) + } + + private fun unsubscribeGroups( + closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), + publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + ) { + performGroupOperations(ClosedGroupOperation.Unsubscribe, closedGroupPublicKeys, publicKey) + } + + private fun performGroupOperations( + operation: ClosedGroupOperation, + closedGroupPublicKeys: Collection, + publicKey: String + ) { + closedGroupPublicKeys.forEach { performGroupOperation(operation, it, publicKey) } + } + + private fun performGroupOperation( + operation: ClosedGroupOperation, + closedGroupPublicKey: String, + publicKey: String + ) { + if (!TextSecurePreferences.isUsingFCM(context)) return + val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code == null || code == 0) { - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"] as? String ?: "null"}.") + when (response.info["code"]) { + null, 0 -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"]}.") } }.fail { exception -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") From 01d80ae54bcc095b976182c02c6504164731a5bb Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 15:56:24 +0930 Subject: [PATCH 132/244] cleanup PushNotificationAPI --- .../notifications/FirebasePushManager.kt | 1 - .../ReceivedMessageHandler.kt | 2 +- .../notifications/MessageNotifier.kt | 2 +- .../notifications/PushNotificationAPI.kt | 24 ++++--------------- .../utilities/TextSecurePreferences.kt | 1 - 5 files changed, 6 insertions(+), 24 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index c169f145c..702d68612 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -143,7 +143,6 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS retryIfNeeded(maxRetryCount) { getResponseBody(request.build()).map { response -> if (response.success == true) { - TextSecurePreferences.setIsUsingFCM(context, false) TextSecurePreferences.setFCMToken(context, null) Log.d("Loki", "Unsubscribe FCM success") } else { 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 0d24f630a..7643f67b9 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 @@ -478,7 +478,7 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli // Set expiration timer storage.setExpirationTimer(groupID, expireTimer) // Notify the PN server - PushNotificationAPI.subscribeGroup(groupPublicKey, userPublicKey) + PushNotificationAPI.subscribeGroup(groupPublicKey) // Notify the user if (userPublicKey == sender && !groupExists) { val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt index 37480543b..8de01ca53 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/MessageNotifier.kt @@ -14,4 +14,4 @@ interface MessageNotifier { fun updateNotification(context: Context, threadId: Long, signal: Boolean) fun updateNotification(context: Context, signal: Boolean, reminderCount: Int) fun clearReminder(context: Context) -} \ No newline at end of file +} diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index a7c260ab9..aff4987f8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -59,38 +59,22 @@ object PushNotificationAPI { fun subscribeGroup( closedGroupPublicKey: String, publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) - } + ) = performGroupOperation(ClosedGroupOperation.Subscribe, closedGroupPublicKey, publicKey) private fun subscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperations(ClosedGroupOperation.Subscribe, closedGroupPublicKeys, publicKey) - } + ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) } fun unsubscribeGroup( closedGroupPublicKey: String, publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) - } + ) = performGroupOperation(ClosedGroupOperation.Unsubscribe, closedGroupPublicKey, publicKey) private fun unsubscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) { - performGroupOperations(ClosedGroupOperation.Unsubscribe, closedGroupPublicKeys, publicKey) - } - - private fun performGroupOperations( - operation: ClosedGroupOperation, - closedGroupPublicKeys: Collection, - publicKey: String - ) { - closedGroupPublicKeys.forEach { performGroupOperation(operation, it, publicKey) } - } + ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) } private fun performGroupOperation( operation: ClosedGroupOperation, diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index b1ab1090f..9c13699dd 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -986,7 +986,6 @@ interface TextSecurePreferences { fun clearAll(context: Context) { getDefaultSharedPreferences(context).edit().clear().commit() } - } } From be4d742e8467f01e14833ad4481c5f51642f6a4d Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 9 Jun 2023 18:10:06 +0930 Subject: [PATCH 133/244] Unregister v1 push --- .../notifications/FirebasePushManager.kt | 26 ++++++++++++------- .../notifications/PushNotificationAPI.kt | 26 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 702d68612..801503079 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -41,6 +41,8 @@ import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities +private const val TAG = "FirebasePushManager" + class FirebasePushManager(private val context: Context, private val prefs: TextSecurePreferences): PushManager { companion object { @@ -87,8 +89,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS else check(metadata.data_len == content.size) { "wrong message data size" } - Log.d("Loki", - "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") + Log.d(TAG, "Received push for ${metadata.account}/${metadata.namespace}, msg ${metadata.msg_hash}, ${metadata.data_len}B") return content } @@ -102,11 +103,11 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS } private fun refresh(task: Task, force: Boolean) { + Log.d(TAG, "refresh") + // context in here is Dispatchers.IO if (!task.isSuccessful) { - Log.w( - "Loki", - "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception + Log.w(TAG, "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception ) return } @@ -143,18 +144,21 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS retryIfNeeded(maxRetryCount) { getResponseBody(request.build()).map { response -> if (response.success == true) { + Log.d(TAG, "Unsubscribe FCM success") TextSecurePreferences.setFCMToken(context, null) - Log.d("Loki", "Unsubscribe FCM success") + PushNotificationAPI.unregister(token) } else { - Log.e("Loki", "Couldn't unregister for FCM due to error: ${response.message}") + Log.e(TAG, "Couldn't unregister for FCM due to error: ${response.message}") } }.fail { exception -> - Log.e("Loki", "Couldn't unregister for FCM due to error: ${exception}.", exception) + Log.e(TAG, "Couldn't unregister for FCM due to error: ${exception}.", exception) } } } private fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { + Log.d(TAG, "register token: $token") + val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) return @@ -184,14 +188,16 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS retryIfNeeded(maxRetryCount) { getResponseBody(request.build()).map { response -> if (response.isSuccess()) { + Log.d(TAG, "Success $token") TextSecurePreferences.setFCMToken(context, token) TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) + PushNotificationAPI.register(token) } else { val (_, message) = response.errorInfo() - Log.e("Loki", "Couldn't register for FCM due to error: $message.") + Log.e(TAG, "Couldn't register for FCM due to error: $message.") } }.fail { exception -> - Log.e("Loki", "Couldn't register for FCM due to error: ${exception}.", exception) + Log.e(TAG, "Couldn't register for FCM due to error: ${exception}.", exception) } } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index aff4987f8..d9e4293cf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -15,6 +15,8 @@ import org.session.libsignal.utilities.retryIfNeeded @SuppressLint("StaticFieldLeak") object PushNotificationAPI { + private const val TAG = "PushNotificationAPI" + val context = MessagingModuleConfiguration.shared.context const val server = "https://push.getsession.org" const val serverPublicKey: String = "d7557fe563e2610de876c0ac7341b62f3c82d5eea4b62c702392ea4368f51b3b" @@ -28,28 +30,33 @@ object PushNotificationAPI { } fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { + Log.d(TAG, "register: $token") + token?.let(::unregisterV1) subscribeGroups() } @JvmStatic fun unregister(token: String) { + Log.d(TAG, "unregister: $token") + unregisterV1(token) unsubscribeGroups() } private fun unregisterV1(token: String) { val parameters = mapOf( "token" to token ) - val url = "$server/unregister" + val url = "$legacyServer/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + val request = Request.Builder().url(url).post(body).build() retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), server, serverPublicKey, Version.V2).map { response -> + OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { - null, 0 -> Log.d("Loki", "Couldn't disable FCM due to error: ${response.info["message"]}.") + null, 0 -> Log.d(TAG, "Couldn't disable FCM due to error: ${response.info["message"]}.") + else -> Log.d(TAG, "unregisterV1 success token: $token") } }.fail { exception -> - Log.d("Loki", "Couldn't disable FCM due to error: ${exception}.") + Log.d(TAG, "Couldn't disable FCM due to error: ${exception}.") } } } @@ -86,15 +93,16 @@ object PushNotificationAPI { val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + val request = Request.Builder().url(url).post(body).build() retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2).map { response -> + OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { - null, 0 -> Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${response.info["message"]}.") + null, 0 -> Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${response.info["message"]}.") + else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") } }.fail { exception -> - Log.d("Loki", "Couldn't subscribe/unsubscribe closed group: $closedGroupPublicKey due to error: ${exception}.") + Log.d(TAG, "Couldn't ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") } } } From b2a1b5fe4694e5a1e7d1e9c5962fa3fe30c3f96b Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 10 Jun 2023 11:44:43 +0930 Subject: [PATCH 134/244] Add token to FCM logs --- .../sending_receiving/notifications/PushNotificationAPI.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index d9e4293cf..32bd17c8c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -52,11 +52,11 @@ object PushNotificationAPI { retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { - null, 0 -> Log.d(TAG, "Couldn't disable FCM due to error: ${response.info["message"]}.") + null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") else -> Log.d(TAG, "unregisterV1 success token: $token") } }.fail { exception -> - Log.d(TAG, "Couldn't disable FCM due to error: ${exception}.") + Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") } } } From 288b70bb1467bab0d957c3cd0970b56eeefdca27 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 13 Jun 2023 10:21:52 +0930 Subject: [PATCH 135/244] Store legacy fcm token to reduce unregister api calls --- .../notifications/PushNotificationAPI.kt | 16 ++++++++++++---- .../utilities/TextSecurePreferences.kt | 13 ++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index 32bd17c8c..9d36ff001 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -32,7 +32,7 @@ object PushNotificationAPI { fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { Log.d(TAG, "register: $token") - token?.let(::unregisterV1) + unregisterV1IfRequired() subscribeGroups() } @@ -40,11 +40,16 @@ object PushNotificationAPI { fun unregister(token: String) { Log.d(TAG, "unregister: $token") - unregisterV1(token) + unregisterV1IfRequired() unsubscribeGroups() } - private fun unregisterV1(token: String) { + /** + * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. + */ + private fun unregisterV1IfRequired() { + val token = TextSecurePreferences.getLegacyFCMToken(context) ?: return + val parameters = mapOf( "token" to token ) val url = "$legacyServer/unregister" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) @@ -53,7 +58,10 @@ object PushNotificationAPI { OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> when (response.info["code"]) { null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") - else -> Log.d(TAG, "unregisterV1 success token: $token") + else -> { + TextSecurePreferences.clearLegacyFCMToken(context) + Log.d(TAG, "unregisterV1 success token: $token") + } } }.fail { exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 9c13699dd..a1482d96b 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -250,7 +250,8 @@ interface TextSecurePreferences { const val GIF_METADATA_WARNING = "has_seen_gif_metadata_warning" const val GIF_GRID_LAYOUT = "pref_gif_grid_layout" const val IS_USING_FCM = "pref_is_using_fcm" - const val FCM_TOKEN = "pref_fcm_token" + const val FCM_TOKEN_LEGACY = "pref_fcm_token" + const val FCM_TOKEN = "pref_fcm_token_2" const val LAST_FCM_TOKEN_UPLOAD_TIME = "pref_last_fcm_token_upload_time_2" const val LAST_CONFIGURATION_SYNC_TIME = "pref_last_configuration_sync_time" const val CONFIGURATION_SYNCED = "pref_configuration_synced" @@ -312,6 +313,16 @@ interface TextSecurePreferences { setBooleanPreference(context, IS_USING_FCM, value) } + @JvmStatic + fun getLegacyFCMToken(context: Context): String? { + return getStringPreference(context, FCM_TOKEN_LEGACY, "") + } + + @JvmStatic + fun clearLegacyFCMToken(context: Context) { + removePreference(context, FCM_TOKEN_LEGACY) + } + @JvmStatic fun getFCMToken(context: Context): String? { return getStringPreference(context, FCM_TOKEN, "") From cc015c45bd0442718fbcd18e1a7c6a45de2aec99 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 14 Jun 2023 10:34:49 +0930 Subject: [PATCH 136/244] Add 50 dp buffer to isScrolledToBottom (#1228) --- .../org/thoughtcrime/securesms/util/GeneralUtilities.kt | 7 +++++++ .../java/org/session/libsession/utilities/ViewUtils.kt | 5 ----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt index 00e3e4441..a38c93831 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GeneralUtilities.kt @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.util import android.content.res.Resources import android.os.Build import androidx.annotation.ColorRes +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.max import kotlin.math.roundToInt fun Resources.getColorWithID(@ColorRes id: Int, theme: Resources.Theme?): Int { @@ -30,3 +32,8 @@ fun toDp(px: Float, resources: Resources): Float { val scale = resources.displayMetrics.density return (px / scale) } + +val RecyclerView.isScrolledToBottom: Boolean + get() = computeVerticalScrollOffset().coerceAtLeast(0) + + computeVerticalScrollExtent() + + toPx(50, resources) >= computeVerticalScrollRange() diff --git a/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt b/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt index dfe0c3b45..80c029323 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/ViewUtils.kt @@ -4,8 +4,6 @@ import android.content.Context import android.util.TypedValue import androidx.annotation.AttrRes import androidx.annotation.ColorInt -import androidx.recyclerview.widget.RecyclerView -import kotlin.math.max @ColorInt fun Context.getColorFromAttr( @@ -16,6 +14,3 @@ fun Context.getColorFromAttr( theme.resolveAttribute(attrColor, typedValue, resolveRefs) return typedValue.data } - -val RecyclerView.isScrolledToBottom: Boolean - get() = max(0, computeVerticalScrollOffset()) + computeVerticalScrollExtent() >= computeVerticalScrollRange() From 153aa4ceaa0d305e1c24bc10269c8083421ca8ba Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 14 Jun 2023 18:46:28 +0930 Subject: [PATCH 137/244] Reinstate push v1 --- .../securesms/ApplicationContext.java | 9 +--- .../notifications/FirebasePushManager.kt | 7 ++- .../notifications/PushNotificationService.kt | 4 +- .../notifications/PushNotificationAPI.kt | 44 +++++++++++++------ .../utilities/TextSecurePreferences.kt | 5 +++ 5 files changed, 45 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 885c71663..688e30d87 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -218,10 +218,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO broadcaster = new Broadcaster(this); LokiAPIDatabase apiDB = getDatabaseComponent().lokiAPIDatabase(); SnodeModule.Companion.configure(apiDB, broadcaster); - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (userPublicKey != null) { - registerForPnIfNeeded(false); - } initializeExpiringMessageManager(); initializeTypingStatusRepository(); initializeTypingStatusSender(); @@ -512,10 +508,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO } public void clearAllData(boolean isMigratingToV2KeyPair) { - String token = TextSecurePreferences.getFCMToken(this); - if (token != null && !token.isEmpty()) { - PushNotificationAPI.unregister(token); - } + PushNotificationAPI.unregister(); if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 801503079..108b10f14 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -146,7 +146,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS if (response.success == true) { Log.d(TAG, "Unsubscribe FCM success") TextSecurePreferences.setFCMToken(context, null) - PushNotificationAPI.unregister(token) + PushNotificationAPI.unregister() } else { Log.e(TAG, "Couldn't unregister for FCM due to error: ${response.message}") } @@ -161,7 +161,10 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val oldToken = TextSecurePreferences.getFCMToken(context) val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) - if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) return +// if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { +// Log.d(TAG, "not registering now... not forced or expired") +// return +// } val pnKey = getOrCreateNotificationKey() diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt index 0d9765d73..3e669dbf8 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/PushNotificationService.kt @@ -24,7 +24,9 @@ class PushNotificationService : FirebaseMessagingService() { super.onNewToken(token) Log.d("Loki", "New FCM token: $token.") TextSecurePreferences.getLocalNumber(this) ?: return - pushManager.refresh(true) + if (TextSecurePreferences.getLocalNumber(this) != token) { + pushManager.refresh(true) + } } override fun onMessageReceived(message: RemoteMessage) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index 9d36ff001..a2eaf353f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -29,25 +29,43 @@ object PushNotificationAPI { Unsubscribe("unsubscribe_closed_group"); } - fun register(token: String? = TextSecurePreferences.getFCMToken(context)) { + fun register(token: String? = TextSecurePreferences.getLegacyFCMToken(context)) { Log.d(TAG, "register: $token") - unregisterV1IfRequired() + register(token, TextSecurePreferences.getLocalNumber(context)) subscribeGroups() } - @JvmStatic - fun unregister(token: String) { - Log.d(TAG, "unregister: $token") + fun register(token: String?, publicKey: String?) { + Log.d(TAG, "register($token)") - unregisterV1IfRequired() - unsubscribeGroups() + token ?: return + publicKey ?: return + val parameters = mapOf("token" to token, "pubKey" to publicKey) + val url = "$legacyServer/register" + val body = + RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body) + retryIfNeeded(maxRetryCount) { + OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2) + .map { response -> + val code = response.info["code"] as? Int + if (code != null && code != 0) { + TextSecurePreferences.setLegacyFCMToken(context, token) + } else { + Log.d(TAG, "Couldn't register for FCM due to error: ${response.info["message"] as? String ?: "null"}.") + } + }.fail { exception -> + Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") + } + } } /** * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ - private fun unregisterV1IfRequired() { + @JvmStatic + fun unregister() { val token = TextSecurePreferences.getLegacyFCMToken(context) ?: return val parameters = mapOf( "token" to token ) @@ -56,17 +74,17 @@ object PushNotificationAPI { val request = Request.Builder().url(url).post(body).build() retryIfNeeded(maxRetryCount) { OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> + TextSecurePreferences.clearLegacyFCMToken(context) when (response.info["code"]) { null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") - else -> { - TextSecurePreferences.clearLegacyFCMToken(context) - Log.d(TAG, "unregisterV1 success token: $token") - } + else -> Log.d(TAG, "unregisterV1 success token: $token") } }.fail { exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") } } + + unsubscribeGroups() } // Legacy Closed Groups @@ -110,7 +128,7 @@ object PushNotificationAPI { else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") } }.fail { exception -> - Log.d(TAG, "Couldn't ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") + Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") } } } diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index a1482d96b..97fe9d3b4 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -318,6 +318,11 @@ interface TextSecurePreferences { return getStringPreference(context, FCM_TOKEN_LEGACY, "") } + @JvmStatic + fun setLegacyFCMToken(context: Context, token: String?) { + setStringPreference(context, FCM_TOKEN_LEGACY, token) + } + @JvmStatic fun clearLegacyFCMToken(context: Context) { removePreference(context, FCM_TOKEN_LEGACY) From 667af27bfb49164cdc5f7d7307b16d8fd7eaf7b3 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 16 Jun 2023 10:38:33 +0930 Subject: [PATCH 138/244] Utilise TokenManager and ExpiryManager --- .../securesms/ApplicationContext.java | 1 - .../notifications/FcmTokenManager.kt | 26 +++ .../securesms/notifications/ExpiryManager.kt | 25 +++ .../securesms/notifications/FcmUtils.kt | 7 +- .../notifications/FirebasePushManager.kt | 207 ++++++++++-------- .../notifications/FirebasePushModule.kt | 3 +- .../messaging/jobs/NotifyPNServerJob.kt | 18 +- .../sending_receiving/notifications/Models.kt | 48 ++-- .../notifications/PushNotificationAPI.kt | 104 ++++----- .../libsignal/utilities/PromiseUtilities.kt | 4 + .../session/libsignal/utilities/Retrying.kt | 1 + 11 files changed, 260 insertions(+), 184 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt create mode 100644 app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 688e30d87..e10a7a7dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -508,7 +508,6 @@ public class ApplicationContext extends Application implements DefaultLifecycleO } public void clearAllData(boolean isMigratingToV2KeyPair) { - PushNotificationAPI.unregister(); if (firebaseInstanceIdJob != null && firebaseInstanceIdJob.isActive()) { firebaseInstanceIdJob.cancel(null); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt new file mode 100644 index 000000000..e1182a480 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FcmTokenManager.kt @@ -0,0 +1,26 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import org.session.libsession.utilities.TextSecurePreferences + +class FcmTokenManager( + private val context: Context, + private val expiryManager: ExpiryManager +) { + val isUsingFCM get() = TextSecurePreferences.isUsingFCM(context) + + var fcmToken + get() = TextSecurePreferences.getFCMToken(context) + set(value) { + TextSecurePreferences.setFCMToken(context, value) + if (value != null) markTime() else clearTime() + } + + val requiresUnregister get() = fcmToken != null + + private fun clearTime() = expiryManager.clearTime() + private fun markTime() = expiryManager.markTime() + private fun isExpired() = expiryManager.isExpired() + + fun isInvalid(): Boolean = fcmToken == null || isExpired() +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt new file mode 100644 index 000000000..36c986f0e --- /dev/null +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/ExpiryManager.kt @@ -0,0 +1,25 @@ +package org.thoughtcrime.securesms.notifications + +import android.content.Context +import org.session.libsession.utilities.TextSecurePreferences + +class ExpiryManager( + private val context: Context, + private val interval: Int = 12 * 60 * 60 * 1000 +) { + fun isExpired() = currentTime() > time + interval + + fun markTime() { + time = currentTime() + } + + fun clearTime() { + time = 0 + } + + private var time + get() = TextSecurePreferences.getLastFCMUploadTime(context) + set(value) = TextSecurePreferences.setLastFCMUploadTime(context, value) + + private fun currentTime() = System.currentTimeMillis() +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt index de3d14d43..af3f49a26 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FcmUtils.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.notifications import com.google.android.gms.tasks.Task +import com.google.android.gms.tasks.Tasks import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.InstanceIdResult import kotlinx.coroutines.* @@ -9,9 +10,7 @@ import kotlinx.coroutines.* fun getFcmInstanceId(body: (Task)->Unit): Job = MainScope().launch(Dispatchers.IO) { val task = FirebaseInstanceId.getInstance().instanceId - while (!task.isComplete && isActive) { - // wait for task to complete while we are active - } + Tasks.await(task) if (!isActive) return@launch // don't 'complete' task if we were canceled body(task) -} \ No newline at end of file +} diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt index 108b10f14..4a0d8307e 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushManager.kt @@ -1,8 +1,6 @@ package org.thoughtcrime.securesms.notifications import android.content.Context -import com.google.android.gms.tasks.Task -import com.google.firebase.iid.InstanceIdResult import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid import com.goterl.lazysodium.interfaces.AEAD @@ -15,12 +13,14 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.combine.and import nl.komponents.kovenant.functional.map import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationMetadata +import org.session.libsession.messaging.sending_receiving.notifications.Response import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse import org.session.libsession.messaging.sending_receiving.notifications.UnsubscribeResponse @@ -29,7 +29,6 @@ import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.Version -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber import org.session.libsession.utilities.bencode.Bencode import org.session.libsession.utilities.bencode.BencodeList @@ -37,19 +36,20 @@ import org.session.libsession.utilities.bencode.BencodeString import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace +import org.session.libsignal.utilities.emptyPromise import org.session.libsignal.utilities.retryIfNeeded import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.KeyPairUtilities private const val TAG = "FirebasePushManager" -class FirebasePushManager(private val context: Context, private val prefs: TextSecurePreferences): PushManager { +class FirebasePushManager (private val context: Context): PushManager { companion object { private const val maxRetryCount = 4 - private const val tokenExpirationInterval = 12 * 60 * 60 * 1000 } + private val tokenManager = FcmTokenManager(context, ExpiryManager(context)) private var firebaseInstanceIdJob: Job? = null private val sodium = LazySodiumAndroid(SodiumAndroid()) @@ -59,12 +59,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val key = sodium.keygen(AEAD.Method.XCHACHA20_POLY1305_IETF) IdentityKeyUtil.save(context, IdentityKeyUtil.NOTIFICATION_KEY, key.asHexString) } - return Key.fromHexString( - IdentityKeyUtil.retrieve( - context, - IdentityKeyUtil.NOTIFICATION_KEY - ) - ) + return Key.fromHexString(IdentityKeyUtil.retrieve(context, IdentityKeyUtil.NOTIFICATION_KEY)) } fun decrypt(encPayload: ByteArray): ByteArray? { @@ -78,8 +73,7 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS val expectedList = (bencoded.decode() as? BencodeList)?.values ?: error("Failed to decode bencoded list from payload") - val metadataJson = (expectedList[0] as? BencodeString)?.value - ?: error("no metadata") + val metadataJson = (expectedList[0] as? BencodeString)?.value ?: error("no metadata") val metadata: PushNotificationMetadata = Json.decodeFromString(String(metadataJson)) val content: ByteArray? = if (expectedList.size >= 2) (expectedList[1] as? BencodeString)?.value else null @@ -94,78 +88,81 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS return content } + @Synchronized override fun refresh(force: Boolean) { firebaseInstanceIdJob?.apply { if (force) cancel() else if (isActive) return } - firebaseInstanceIdJob = getFcmInstanceId { refresh(it, force) } - } - - private fun refresh(task: Task, force: Boolean) { - Log.d(TAG, "refresh") - - // context in here is Dispatchers.IO - if (!task.isSuccessful) { - Log.w(TAG, "FirebaseInstanceId.getInstance().getInstanceId() failed." + task.exception - ) - return - } - - val token: String = task.result?.token ?: return - val userPublicKey = getLocalNumber(context) ?: return - val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return - - when { - prefs.isUsingFCM() -> register(token, userPublicKey, userEdKey, force) - prefs.getFCMToken() != null -> unregister(token, userPublicKey, userEdKey) - } - } - - private fun unregister(token: String, userPublicKey: String, userEdKey: KeyPair) { - val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s - // if we want to support passing namespace list, here is the place to do it - val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() - val signature = ByteArray(Sign.BYTES) - sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) - - val requestParameters = UnsubscriptionRequest( - pubkey = userPublicKey, - session_ed25519 = userEdKey.publicKey.asHexString, - service = "firebase", - sig_ts = timestamp, - signature = Base64.encodeBytes(signature), - service_info = mapOf("token" to token), - ) - - val url = "${PushNotificationAPI.server}/unsubscribe" - val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { response -> - if (response.success == true) { - Log.d(TAG, "Unsubscribe FCM success") - TextSecurePreferences.setFCMToken(context, null) - PushNotificationAPI.unregister() - } else { - Log.e(TAG, "Couldn't unregister for FCM due to error: ${response.message}") - } - }.fail { exception -> - Log.e(TAG, "Couldn't unregister for FCM due to error: ${exception}.", exception) + firebaseInstanceIdJob = getFcmInstanceId { task -> + when { + task.isSuccessful -> task.result?.token?.let { refresh(it, force).get() } + else -> Log.w(TAG, "getFcmInstanceId failed." + task.exception) } } } - private fun register(token: String, publicKey: String, userEd25519Key: KeyPair, force: Boolean, namespaces: List = listOf(Namespace.DEFAULT)) { - Log.d(TAG, "register token: $token") + private fun refresh(token: String, force: Boolean): Promise<*, Exception> { + val userPublicKey = getLocalNumber(context) ?: return emptyPromise() + val userEdKey = KeyPairUtilities.getUserED25519KeyPair(context) ?: return emptyPromise() - val oldToken = TextSecurePreferences.getFCMToken(context) - val lastUploadDate = TextSecurePreferences.getLastFCMUploadTime(context) -// if (!force && token == oldToken && System.currentTimeMillis() - lastUploadDate < tokenExpirationInterval) { -// Log.d(TAG, "not registering now... not forced or expired") -// return -// } + return when { + tokenManager.isUsingFCM -> register(force, token, userPublicKey, userEdKey) + tokenManager.requiresUnregister -> unregister(token, userPublicKey, userEdKey) + else -> emptyPromise() + } + } + /** + * Register for push notifications if: + * force is true + * there is no FCM Token + * FCM Token has expired + */ + private fun register( + force: Boolean, + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List = listOf(Namespace.DEFAULT) + ): Promise<*, Exception> = if (force || tokenManager.isInvalid()) { + register(token, publicKey, userEd25519Key, namespaces) + } else emptyPromise() + + /** + * Register for push notifications. + */ + private fun register( + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List = listOf(Namespace.DEFAULT) + ): Promise<*, Exception> = PushNotificationAPI.register(token) and getSubscription( + token, publicKey, userEd25519Key, namespaces + ) fail { + Log.e(TAG, "Couldn't register for FCM due to error: $it.", it) + } success { + tokenManager.fcmToken = token + } + + private fun unregister( + token: String, + userPublicKey: String, + userEdKey: KeyPair + ): Promise<*, Exception> = PushNotificationAPI.unregister() and getUnsubscription( + token, userPublicKey, userEdKey + ) fail { + Log.e(TAG, "Couldn't unregister for FCM due to error: ${it}.", it) + } success { + tokenManager.fcmToken = null + } + + private fun getSubscription( + token: String, + publicKey: String, + userEd25519Key: KeyPair, + namespaces: List + ): Promise { val pnKey = getOrCreateNotificationKey() val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s @@ -183,33 +180,51 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS signature = Base64.encodeBytes(signature), service_info = mapOf("token" to token), enc_key = pnKey.asHexString, - ) + ).let(Json::encodeToString) - val url = "${PushNotificationAPI.server}/subscribe" - val body = RequestBody.create(MediaType.get("application/json"), Json.encodeToString(requestParameters)) - val request = Request.Builder().url(url).post(body) - retryIfNeeded(maxRetryCount) { - getResponseBody(request.build()).map { response -> - if (response.isSuccess()) { - Log.d(TAG, "Success $token") - TextSecurePreferences.setFCMToken(context, token) - TextSecurePreferences.setLastFCMUploadTime(context, System.currentTimeMillis()) - PushNotificationAPI.register(token) - } else { - val (_, message) = response.errorInfo() - Log.e(TAG, "Couldn't register for FCM due to error: $message.") - } - }.fail { exception -> - Log.e(TAG, "Couldn't register for FCM due to error: ${exception}.", exception) - } - } + return retryResponseBody("subscribe", requestParameters) } - private inline fun getResponseBody(request: Request): Promise = - OnionRequestAPI.sendOnionRequest( + private fun getUnsubscription( + token: String, + userPublicKey: String, + userEdKey: KeyPair + ): Promise { + val timestamp = SnodeAPI.nowWithOffset / 1000 // get timestamp in ms -> s + // if we want to support passing namespace list, here is the place to do it + val sigData = "UNSUBSCRIBE${userPublicKey}${timestamp}".encodeToByteArray() + val signature = ByteArray(Sign.BYTES) + sodium.cryptoSignDetached(signature, sigData, sigData.size.toLong(), userEdKey.secretKey.asBytes) + + val requestParameters = UnsubscriptionRequest( + pubkey = userPublicKey, + session_ed25519 = userEdKey.publicKey.asHexString, + service = "firebase", + sig_ts = timestamp, + signature = Base64.encodeBytes(signature), + service_info = mapOf("token" to token), + ).let(Json::encodeToString) + + return retryResponseBody("unsubscribe", requestParameters) + } + + private inline fun retryResponseBody(path: String, requestParameters: String): Promise = + retryIfNeeded(maxRetryCount) { getResponseBody(path, requestParameters) } + + private inline fun getResponseBody(path: String, requestParameters: String): Promise { + val url = "${PushNotificationAPI.server}/$path" + val body = RequestBody.create(MediaType.get("application/json"), requestParameters) + val request = Request.Builder().url(url).post(body).build() + + return OnionRequestAPI.sendOnionRequest( request, PushNotificationAPI.server, PushNotificationAPI.serverPublicKey, Version.V4 - ).map { response -> Json.decodeFromStream(response.body!!.inputStream()) } + ).map { response -> + response.body!!.inputStream() + .let { Json.decodeFromStream(it) } + .also { if (it.isFailure()) throw Exception("error: ${it.message}.") } + } + } } diff --git a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt index 30045d6c2..3f33016a0 100644 --- a/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt +++ b/app/src/play/kotlin/org/thoughtcrime/securesms/notifications/FirebasePushModule.kt @@ -17,8 +17,7 @@ object FirebasePushModule { @Singleton fun provideFirebasePushManager( @ApplicationContext context: Context, - prefs: TextSecurePreferences, - ) = FirebasePushManager(context, prefs) + ) = FirebasePushManager(context) } @Module diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt index 25fb2194c..7bb2707a8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/NotifyPNServerJob.kt @@ -10,6 +10,7 @@ import okhttp3.RequestBody import org.session.libsession.messaging.jobs.Job.Companion.MAX_BUFFER_SIZE import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI.server import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.OnionRequestAPI @@ -33,18 +34,21 @@ class NotifyPNServerJob(val message: SnodeMessage) : Job { } override fun execute(dispatcherName: String) { - val server = PushNotificationAPI.server val parameters = mapOf( "data" to message.data, "send_to" to message.recipient ) val url = "${server}/notify" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + val request = Request.Builder().url(url).post(body).build() retryIfNeeded(4) { - OnionRequestAPI.sendOnionRequest(request.build(), server, PushNotificationAPI.serverPublicKey, Version.V2).map { response -> - val code = response.info["code"] as? Int - if (code == null || code == 0) { - Log.d("Loki", "Couldn't notify PN server due to error: ${response.info["message"] as? String ?: "null"}.") + OnionRequestAPI.sendOnionRequest( + request, + server, + PushNotificationAPI.serverPublicKey, + Version.V2 + ) success { response -> + when (response.info["code"]) { + null, 0 -> Log.d("Loki", "Couldn't notify PN server due to error: ${response.info["message"]}.") } - }.fail { exception -> + } fail { exception -> Log.d("Loki", "Couldn't notify PN server due to error: $exception.") } }.success { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt index 141202ef6..ea2492d5a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/Models.kt @@ -54,37 +54,39 @@ data class UnsubscriptionRequest( val service_info: Map, ) +/** invalid values, missing reuqired arguments etc, details in message */ +private const val UNPARSEABLE_ERROR = 1 +/** the "service" value is not active / valid */ +private const val SERVICE_NOT_AVAILABLE = 2 +/** something getting wrong internally talking to the backend */ +private const val SERVICE_TIMEOUT = 3 +/** other error processing the subscription (details in the message) */ +private const val GENERIC_ERROR = 4 + @Serializable data class SubscriptionResponse( - val error: Int? = null, - val message: String? = null, - val success: Boolean? = null, + override val error: Int? = null, + override val message: String? = null, + override val success: Boolean? = null, val added: Boolean? = null, val updated: Boolean? = null, -) { - companion object { - /** invalid values, missing reuqired arguments etc, details in message */ - const val UNPARSEABLE_ERROR = 1 - /** the "service" value is not active / valid */ - const val SERVICE_NOT_AVAILABLE = 2 - /** something getting wrong internally talking to the backend */ - const val SERVICE_TIMEOUT = 3 - /** other error processing the subscription (details in the message) */ - const val GENERIC_ERROR = 4 - } - fun isSuccess() = success == true && error == null - fun errorInfo() = if (success != true && error != null) { - error to message - } else null to null -} +): Response @Serializable data class UnsubscribeResponse( - val error: Int? = null, - val message: String? = null, - val success: Boolean? = null, + override val error: Int? = null, + override val message: String? = null, + override val success: Boolean? = null, val removed: Boolean? = null, -) +): Response + +interface Response { + val error: Int? + val message: String? + val success: Boolean? + fun isSuccess() = success == true && error == null + fun isFailure() = !isSuccess() +} @Serializable data class PushNotificationMetadata( diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt index a2eaf353f..b39b30f09 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/notifications/PushNotificationAPI.kt @@ -1,7 +1,10 @@ package org.session.libsession.messaging.sending_receiving.notifications import android.annotation.SuppressLint +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.all import nl.komponents.kovenant.functional.map +import nl.komponents.kovenant.task import okhttp3.MediaType import okhttp3.Request import okhttp3.RequestBody @@ -11,6 +14,7 @@ import org.session.libsession.snode.Version import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.emptyPromise import org.session.libsignal.utilities.retryIfNeeded @SuppressLint("StaticFieldLeak") @@ -29,62 +33,57 @@ object PushNotificationAPI { Unsubscribe("unsubscribe_closed_group"); } - fun register(token: String? = TextSecurePreferences.getLegacyFCMToken(context)) { - Log.d(TAG, "register: $token") - - register(token, TextSecurePreferences.getLocalNumber(context)) + fun register( + token: String? = TextSecurePreferences.getLegacyFCMToken(context) + ): Promise<*, Exception> = all( + register(token, TextSecurePreferences.getLocalNumber(context)), subscribeGroups() - } + ) - fun register(token: String?, publicKey: String?) { - Log.d(TAG, "register($token)") - - token ?: return - publicKey ?: return - val parameters = mapOf("token" to token, "pubKey" to publicKey) - val url = "$legacyServer/register" - val body = - RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body) + fun register(token: String?, publicKey: String?): Promise = retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request.build(), legacyServer, legacyServerPublicKey, Version.V2) - .map { response -> - val code = response.info["code"] as? Int - if (code != null && code != 0) { - TextSecurePreferences.setLegacyFCMToken(context, token) - } else { - Log.d(TAG, "Couldn't register for FCM due to error: ${response.info["message"] as? String ?: "null"}.") - } - }.fail { exception -> + val parameters = mapOf("token" to token!!, "pubKey" to publicKey!!) + val url = "$legacyServer/register" + val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body).build() + + OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> + when (response.info["code"]) { + null, 0 -> throw Exception("error: ${response.info["message"]}.") + else -> TextSecurePreferences.setLegacyFCMToken(context, token) + } + } fail { exception -> Log.d(TAG, "Couldn't register for FCM due to error: ${exception}.") } } - } /** * Unregister push notifications for 1-1 conversations as this is now done in FirebasePushManager. */ - @JvmStatic - fun unregister() { - val token = TextSecurePreferences.getLegacyFCMToken(context) ?: return + fun unregister(): Promise<*, Exception> { + val token = TextSecurePreferences.getLegacyFCMToken(context) ?: emptyPromise() - val parameters = mapOf( "token" to token ) - val url = "$legacyServer/unregister" - val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) - val request = Request.Builder().url(url).post(body).build() - retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> - TextSecurePreferences.clearLegacyFCMToken(context) - when (response.info["code"]) { - null, 0 -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${response.info["message"]}.") + return retryIfNeeded(maxRetryCount) { + val parameters = mapOf( "token" to token ) + val url = "$legacyServer/unregister" + val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) + val request = Request.Builder().url(url).post(body).build() + + OnionRequestAPI.sendOnionRequest( + request, + legacyServer, + legacyServerPublicKey, + Version.V2 + ) success { + when (it.info["code"]) { + null, 0 -> throw Exception("error: ${it.info["message"]}.") else -> Log.d(TAG, "unregisterV1 success token: $token") } - }.fail { exception -> - Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") + TextSecurePreferences.clearLegacyFCMToken(context) + } fail { + exception -> Log.d(TAG, "Couldn't disable FCM with token: $token due to error: ${exception}.") } } - - unsubscribeGroups() } // Legacy Closed Groups @@ -97,7 +96,7 @@ object PushNotificationAPI { private fun subscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) } + ) = closedGroupPublicKeys.map { performGroupOperation(ClosedGroupOperation.Subscribe, it, publicKey) }.let(::all) fun unsubscribeGroup( closedGroupPublicKey: String, @@ -107,27 +106,30 @@ object PushNotificationAPI { private fun unsubscribeGroups( closedGroupPublicKeys: Collection = MessagingModuleConfiguration.shared.storage.getAllClosedGroupPublicKeys(), publicKey: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! - ) = closedGroupPublicKeys.forEach { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) } + ) = closedGroupPublicKeys.map { performGroupOperation(ClosedGroupOperation.Unsubscribe, it, publicKey) }.let(::all) private fun performGroupOperation( operation: ClosedGroupOperation, closedGroupPublicKey: String, publicKey: String - ) { - if (!TextSecurePreferences.isUsingFCM(context)) return - + ): Promise<*, Exception> { val parameters = mapOf( "closedGroupPublicKey" to closedGroupPublicKey, "pubKey" to publicKey ) val url = "$legacyServer/${operation.rawValue}" val body = RequestBody.create(MediaType.get("application/json"), JsonUtil.toJson(parameters)) val request = Request.Builder().url(url).post(body).build() - retryIfNeeded(maxRetryCount) { - OnionRequestAPI.sendOnionRequest(request, legacyServer, legacyServerPublicKey, Version.V2).map { response -> - when (response.info["code"]) { - null, 0 -> Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${response.info["message"]}.") + return retryIfNeeded(maxRetryCount) { + OnionRequestAPI.sendOnionRequest( + request, + legacyServer, + legacyServerPublicKey, + Version.V2 + ) success { + when (it.info["code"]) { + null, 0 -> throw Exception("${it.info["message"]}") else -> Log.d(TAG, "performGroupOperation success: ${operation.rawValue}") } - }.fail { exception -> + } fail { exception -> Log.d(TAG, "performGroupOperation fail: ${operation.rawValue}: $closedGroupPublicKey due to error: ${exception}.") } } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt b/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt index d6361289c..8095b4459 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/PromiseUtilities.kt @@ -3,8 +3,12 @@ package org.session.libsignal.utilities import nl.komponents.kovenant.Promise import nl.komponents.kovenant.deferred +import nl.komponents.kovenant.task import java.util.concurrent.TimeoutException +fun emptyPromise() = EMPTY_PROMISE +private val EMPTY_PROMISE: Promise<*, java.lang.Exception> = task {} + fun Promise.get(defaultValue: V): V { return try { get() diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt index 9f1b1a3e8..28ed93b5a 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Retrying.kt @@ -28,3 +28,4 @@ fun > retryIfNeeded(maxRetryCount: Int, retryInterv retryIfNeeded() return deferred.promise } + From 92a6447b8a46814cfdd4b0505e3a7cf2cf05e55b Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 19 Jun 2023 11:51:14 +0930 Subject: [PATCH 139/244] Add button downstates throughout (#1220) --- .../preferences/BlockedContactsPreference.kt | 19 ++++++-------- .../main/res/color/prominent_button_color.xml | 5 ++++ .../borderless_button_medium_background.xml | 11 ++++++++ ...tructive_dialog_text_button_background.xml | 19 +++++++------- .../filled_button_medium_background.xml | 10 ++++++++ ...minent_filled_button_medium_background.xml | 19 +++++++------- ...mportant_dialog_text_button_background.xml | 19 +++++++------- ...rtant_outline_button_medium_background.xml | 22 ++++++++-------- .../res/layout-sw400dp/activity_landing.xml | 2 +- .../main/res/layout-sw400dp/activity_seed.xml | 9 +++---- app/src/main/res/layout/activity_home.xml | 1 + app/src/main/res/layout/activity_landing.xml | 2 +- app/src/main/res/layout/activity_pn_mode.xml | 2 +- app/src/main/res/layout/activity_seed.xml | 10 ++++---- app/src/main/res/layout/activity_settings.xml | 10 ++++++++ .../layout/blocked_contacts_preference.xml | 9 ++++--- .../main/res/layout/export_logs_widget.xml | 4 +-- .../main/res/layout/fragment_create_group.xml | 21 +++++----------- .../res/layout/fragment_join_community.xml | 5 +++- .../layout/fragment_new_conversation_home.xml | 5 ++++ .../main/res/layout/fragment_new_message.xml | 4 +++ .../res/layout/view_global_search_input.xml | 4 ++- app/src/main/res/values/styles.xml | 25 +++++++------------ 23 files changed, 132 insertions(+), 105 deletions(-) create mode 100644 app/src/main/res/color/prominent_button_color.xml create mode 100644 app/src/main/res/drawable/borderless_button_medium_background.xml create mode 100644 app/src/main/res/drawable/filled_button_medium_background.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt index 254d34978..e985ba6d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/BlockedContactsPreference.kt @@ -9,20 +9,15 @@ import androidx.preference.PreferenceViewHolder class BlockedContactsPreference @JvmOverloads constructor( context: Context, - attributeSet: AttributeSet? = null) : PreferenceCategory(context, attributeSet), View.OnClickListener { - - override fun onClick(v: View?) { - if (v is BlockedContactsLayout) { - val intent = Intent(context, BlockedContactsActivity::class.java) - context.startActivity(intent) - } - } + attributeSet: AttributeSet? = null +) : PreferenceCategory(context, attributeSet) { override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) - val itemView = holder.itemView - itemView.setOnClickListener(this) + holder.itemView.setOnClickListener { + val intent = Intent(context, BlockedContactsActivity::class.java) + context.startActivity(intent) + } } - -} \ No newline at end of file +} diff --git a/app/src/main/res/color/prominent_button_color.xml b/app/src/main/res/color/prominent_button_color.xml new file mode 100644 index 000000000..8f2e692fd --- /dev/null +++ b/app/src/main/res/color/prominent_button_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/borderless_button_medium_background.xml b/app/src/main/res/drawable/borderless_button_medium_background.xml new file mode 100644 index 000000000..6c72f9e72 --- /dev/null +++ b/app/src/main/res/drawable/borderless_button_medium_background.xml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/destructive_dialog_text_button_background.xml b/app/src/main/res/drawable/destructive_dialog_text_button_background.xml index 1eff84a69..f3e13c800 100644 --- a/app/src/main/res/drawable/destructive_dialog_text_button_background.xml +++ b/app/src/main/res/drawable/destructive_dialog_text_button_background.xml @@ -1,11 +1,10 @@ - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/app/src/main/res/drawable/filled_button_medium_background.xml b/app/src/main/res/drawable/filled_button_medium_background.xml new file mode 100644 index 000000000..10eb6de67 --- /dev/null +++ b/app/src/main/res/drawable/filled_button_medium_background.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/prominent_filled_button_medium_background.xml b/app/src/main/res/drawable/prominent_filled_button_medium_background.xml index a06a0d11e..698a67c0a 100644 --- a/app/src/main/res/drawable/prominent_filled_button_medium_background.xml +++ b/app/src/main/res/drawable/prominent_filled_button_medium_background.xml @@ -1,11 +1,10 @@ - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/app/src/main/res/drawable/unimportant_dialog_text_button_background.xml b/app/src/main/res/drawable/unimportant_dialog_text_button_background.xml index 1eff84a69..f3e13c800 100644 --- a/app/src/main/res/drawable/unimportant_dialog_text_button_background.xml +++ b/app/src/main/res/drawable/unimportant_dialog_text_button_background.xml @@ -1,11 +1,10 @@ - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/app/src/main/res/drawable/unimportant_outline_button_medium_background.xml b/app/src/main/res/drawable/unimportant_outline_button_medium_background.xml index 6e0de35a5..d12f7408d 100644 --- a/app/src/main/res/drawable/unimportant_outline_button_medium_background.xml +++ b/app/src/main/res/drawable/unimportant_outline_button_medium_background.xml @@ -1,11 +1,13 @@ - - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/app/src/main/res/layout-sw400dp/activity_landing.xml b/app/src/main/res/layout-sw400dp/activity_landing.xml index 1e89cd967..5e5a36704 100644 --- a/app/src/main/res/layout-sw400dp/activity_landing.xml +++ b/app/src/main/res/layout-sw400dp/activity_landing.xml @@ -52,6 +52,7 @@ android:text="@string/activity_landing_restore_button_title" />