From a8d1db47f07f04faa6c365546a5f7453000d061a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 16 Dec 2024 20:32:25 +0100 Subject: [PATCH] homogenize ChooserTargetService and Shortcuts --- build.gradle | 6 +- src/main/AndroidManifest.xml | 14 +- .../persistance/DatabaseBackend.java | 1877 ++++++++++++----- .../services/ContactChooserTargetService.java | 117 - .../services/NotificationService.java | 41 +- .../services/ShortcutService.java | 154 +- .../services/XmppConnectionService.java | 179 +- .../ui/ConversationsActivity.java | 174 +- .../conversations/ui/ShareWithActivity.java | 54 +- src/main/res/xml/shortcuts.xml | 7 + 10 files changed, 1746 insertions(+), 877 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java create mode 100644 src/main/res/xml/shortcuts.xml diff --git a/build.gradle b/build.gradle index 1f95a139f8661c06a627c15f890fb0638ea45445..1839ee0bae642d83e5c6b25859706e5427c4c989 100644 --- a/build.gradle +++ b/build.gradle @@ -42,13 +42,13 @@ spotless { dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3' implementation project(':libs:annotation') annotationProcessor project(':libs:annotation-processor') - implementation 'androidx.viewpager:viewpager:1.0.0' + implementation 'androidx.viewpager:viewpager:1.1.0' playstoreImplementation('com.google.firebase:firebase-messaging:24.1.0') { exclude group: 'com.google.firebase', module: 'firebase-core' @@ -59,6 +59,8 @@ dependencies { quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0' implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1' implementation("com.github.CanHub:Android-Image-Cropper:2.0.0") + implementation "androidx.sharetarget:sharetarget:1.2.0" + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.cardview:cardview:1.0.0' diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index b621147724ca0088d4bf5aad295d37e79b867d75..ddc93f285f4393df4950420a44525b281cf870cd 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -124,14 +124,6 @@ android:name=".services.ImportBackupService" android:exported="false" android:foregroundServiceType="dataSync" /> - - - - - + - + android:value="androidx.sharetarget.ChooserTargetServiceCompat" /> = 2) { - db.execSQL("update " + Account.TABLENAME + " set " - + Account.OPTIONS + " = " + Account.OPTIONS + " | 8"); + db.execSQL( + "update " + + Account.TABLENAME + + " set " + + Account.OPTIONS + + " = " + + Account.OPTIONS + + " | 8"); } if (oldVersion < 3 && newVersion >= 3) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.TYPE + " NUMBER"); + db.execSQL( + "ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.TYPE + " NUMBER"); } if (oldVersion < 5 && newVersion >= 5) { db.execSQL("DROP TABLE " + Contact.TABLENAME); db.execSQL(CREATE_CONTATCS_STATEMENT); - db.execSQL("UPDATE " + Account.TABLENAME + " SET " - + Account.ROSTERVERSION + " = NULL"); + db.execSQL("UPDATE " + Account.TABLENAME + " SET " + Account.ROSTERVERSION + " = NULL"); } if (oldVersion < 6 && newVersion >= 6) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.TRUE_COUNTERPART + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.TRUE_COUNTERPART + + " TEXT"); } if (oldVersion < 7 && newVersion >= 7) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.REMOTE_MSG_ID + " TEXT"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.AVATAR + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " - + Account.AVATAR + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.REMOTE_MSG_ID + + " TEXT"); + db.execSQL( + "ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.AVATAR + " TEXT"); + db.execSQL( + "ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.AVATAR + " TEXT"); } if (oldVersion < 8 && newVersion >= 8) { - db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN " - + Conversation.ATTRIBUTES + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Conversation.TABLENAME + + " ADD COLUMN " + + Conversation.ATTRIBUTES + + " TEXT"); } if (oldVersion < 9 && newVersion >= 9) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_TIME + " NUMBER"); - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.LAST_PRESENCE + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Contact.TABLENAME + + " ADD COLUMN " + + Contact.LAST_TIME + + " NUMBER"); + db.execSQL( + "ALTER TABLE " + + Contact.TABLENAME + + " ADD COLUMN " + + Contact.LAST_PRESENCE + + " TEXT"); } if (oldVersion < 10 && newVersion >= 10) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.RELATIVE_FILE_PATH + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.RELATIVE_FILE_PATH + + " TEXT"); } if (oldVersion < 11 && newVersion >= 11) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " - + Contact.GROUPS + " TEXT"); + db.execSQL( + "ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.GROUPS + " TEXT"); db.execSQL("delete from " + Contact.TABLENAME); db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL"); } if (oldVersion < 12 && newVersion >= 12) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.SERVER_MSG_ID + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.SERVER_MSG_ID + + " TEXT"); } if (oldVersion < 13 && newVersion >= 13) { db.execSQL("delete from " + Contact.TABLENAME); @@ -348,26 +602,60 @@ public class DatabaseBackend extends SQLiteOpenHelper { } if (oldVersion < 15 && newVersion >= 15) { recreateAxolotlDb(db); - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.FINGERPRINT + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.FINGERPRINT + + " TEXT"); } if (oldVersion < 16 && newVersion >= 16) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " - + Message.CARBON + " INTEGER"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.CARBON + + " INTEGER"); } if (oldVersion < 19 && newVersion >= 19) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.DISPLAY_NAME + + " TEXT"); } if (oldVersion < 20 && newVersion >= 20) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.HOSTNAME + + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.PORT + + " NUMBER DEFAULT 5222"); } if (oldVersion < 26 && newVersion >= 26) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT"); + db.execSQL( + "ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.STATUS_MESSAGE + + " TEXT"); } if (oldVersion < 40 && newVersion >= 40) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.RESOURCE + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.RESOURCE + + " TEXT"); } /* Any migrations that alter the Account table need to happen BEFORE this migration, as it * depends on account de-serialization. @@ -375,45 +663,67 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 17 && newVersion >= 17 && newVersion < 31) { List accounts = getAccounts(db); for (Account account : accounts) { - String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID); + String ownDeviceIdString = + account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID); if (ownDeviceIdString == null) { continue; } int ownDeviceId = Integer.valueOf(ownDeviceIdString); - SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownDeviceId); + SignalProtocolAddress ownAddress = + new SignalProtocolAddress( + account.getJid().asBareJid().toString(), ownDeviceId); deleteSession(db, account, ownAddress); IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account); if (identityKeyPair != null) { String[] selectionArgs = { - account.getUuid(), - CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize()) + account.getUuid(), + CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize()) }; ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.TRUSTED, 2); - db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.FINGERPRINT + " = ? ", + db.update( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + values, + SQLiteAxolotlStore.ACCOUNT + + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + + " = ? ", selectionArgs); } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not load own identity key pair"); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + + ": could not load own identity key pair"); } } } if (oldVersion < 18 && newVersion >= 18) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.READ + + " NUMBER DEFAULT 1"); } if (oldVersion < 21 && newVersion >= 21) { List accounts = getAccounts(db); for (Account account : accounts) { account.unsetPgpSignature(); - db.update(Account.TABLENAME, account.getContentValues(), Account.UUID - + "=?", new String[]{account.getUuid()}); + db.update( + Account.TABLENAME, + account.getContentValues(), + Account.UUID + "=?", + new String[] {account.getUuid()}); } } if (oldVersion >= 15 && oldVersion < 22 && newVersion >= 22) { - db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE); + db.execSQL( + "ALTER TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + + " ADD COLUMN " + + SQLiteAxolotlStore.CERTIFICATE); } if (oldVersion < 23 && newVersion >= 23) { @@ -421,11 +731,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { } if (oldVersion < 24 && newVersion >= 24) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT"); + db.execSQL( + "ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT"); } if (oldVersion < 25 && newVersion >= 25) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER"); + db.execSQL( + "ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER"); } if (oldVersion < 26 && newVersion >= 26) { @@ -441,51 +753,116 @@ public class DatabaseBackend extends SQLiteOpenHelper { } if (oldVersion < 29 && newVersion >= 29) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.ERROR_MESSAGE + + " TEXT"); } if (oldVersion >= 15 && oldVersion < 31 && newVersion >= 31) { - db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.TRUST + " TEXT"); - db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.ACTIVE + " NUMBER"); + db.execSQL( + "ALTER TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + + " ADD COLUMN " + + SQLiteAxolotlStore.TRUST + + " TEXT"); + db.execSQL( + "ALTER TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + + " ADD COLUMN " + + SQLiteAxolotlStore.ACTIVE + + " NUMBER"); HashMap migration = new HashMap<>(); - migration.put(0, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); - migration.put(1, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); - migration.put(2, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true)); - migration.put(3, createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false)); - migration.put(4, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); - migration.put(5, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); - migration.put(6, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false)); - migration.put(7, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true)); - migration.put(8, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false)); + migration.put( + 0, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); + migration.put( + 1, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true)); + migration.put( + 2, + createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true)); + migration.put( + 3, + createFingerprintStatusContentValues( + FingerprintStatus.Trust.COMPROMISED, false)); + migration.put( + 4, + createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); + migration.put( + 5, + createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false)); + migration.put( + 6, + createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false)); + migration.put( + 7, + createFingerprintStatusContentValues( + FingerprintStatus.Trust.VERIFIED_X509, true)); + migration.put( + 8, + createFingerprintStatusContentValues( + FingerprintStatus.Trust.VERIFIED_X509, false)); for (Map.Entry entry : migration.entrySet()) { String whereClause = SQLiteAxolotlStore.TRUSTED + "=?"; String[] where = {String.valueOf(entry.getKey())}; - db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, entry.getValue(), whereClause, where); + db.update( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + entry.getValue(), + whereClause, + where); } - } if (oldVersion >= 15 && oldVersion < 32 && newVersion >= 32) { - db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER"); + db.execSQL( + "ALTER TABLE " + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + + " ADD COLUMN " + + SQLiteAxolotlStore.LAST_ACTIVATION + + " NUMBER"); ContentValues defaults = new ContentValues(); defaults.put(SQLiteAxolotlStore.LAST_ACTIVATION, System.currentTimeMillis()); db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, defaults, null, null); } if (oldVersion >= 15 && oldVersion < 33 && newVersion >= 33) { String whereClause = SQLiteAxolotlStore.OWN + "=1"; - db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED, true), whereClause, null); + db.update( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED, true), + whereClause, + null); } if (oldVersion < 34 && newVersion >= 34) { db.execSQL(CREATE_MESSAGE_TIME_INDEX); - final File oldPicturesDirectory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/Conversations/"); - final File oldFilesDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/"); - final File newFilesDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations Files/"); - final File newVideosDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations Videos/"); + final File oldPicturesDirectory = + new File( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES) + + "/Conversations/"); + final File oldFilesDirectory = + new File(Environment.getExternalStorageDirectory() + "/Conversations/"); + final File newFilesDirectory = + new File( + Environment.getExternalStorageDirectory() + + "/Conversations/Media/Conversations Files/"); + final File newVideosDirectory = + new File( + Environment.getExternalStorageDirectory() + + "/Conversations/Media/Conversations Videos/"); if (oldPicturesDirectory.exists() && oldPicturesDirectory.isDirectory()) { - final File newPicturesDirectory = new File(Environment.getExternalStorageDirectory() + "/Conversations/Media/Conversations Images/"); + final File newPicturesDirectory = + new File( + Environment.getExternalStorageDirectory() + + "/Conversations/Media/Conversations Images/"); newPicturesDirectory.getParentFile().mkdirs(); if (oldPicturesDirectory.renameTo(newPicturesDirectory)) { - Log.d(Config.LOGTAG, "moved " + oldPicturesDirectory.getAbsolutePath() + " to " + newPicturesDirectory.getAbsolutePath()); + Log.d( + Config.LOGTAG, + "moved " + + oldPicturesDirectory.getAbsolutePath() + + " to " + + newPicturesDirectory.getAbsolutePath()); } } if (oldFilesDirectory.exists() && oldFilesDirectory.isDirectory()) { @@ -498,17 +875,26 @@ public class DatabaseBackend extends SQLiteOpenHelper { for (File file : files) { if (file.getName().equals(".nomedia")) { if (file.delete()) { - Log.d(Config.LOGTAG, "deleted nomedia file in " + oldFilesDirectory.getAbsolutePath()); + Log.d( + Config.LOGTAG, + "deleted nomedia file in " + + oldFilesDirectory.getAbsolutePath()); } } else if (file.isFile()) { final String name = file.getName(); boolean isVideo = false; int start = name.lastIndexOf('.') + 1; if (start < name.length()) { - String mime = MimeUtils.guessMimeTypeFromExtension(name.substring(start)); + String mime = + MimeUtils.guessMimeTypeFromExtension(name.substring(start)); isVideo = mime != null && mime.startsWith("video/"); } - File dst = new File((isVideo ? newVideosDirectory : newFilesDirectory).getAbsolutePath() + "/" + file.getName()); + File dst = + new File( + (isVideo ? newVideosDirectory : newFilesDirectory) + .getAbsolutePath() + + "/" + + file.getName()); if (file.renameTo(dst)) { Log.d(Config.LOGTAG, "moved " + file + " to " + dst); } @@ -524,17 +910,30 @@ public class DatabaseBackend extends SQLiteOpenHelper { for (Account account : accounts) { account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, true); account.setOption(Account.OPTION_LOGGED_IN_SUCCESSFULLY, false); - db.update(Account.TABLENAME, account.getContentValues(), Account.UUID - + "=?", new String[]{account.getUuid()}); + db.update( + Account.TABLENAME, + account.getContentValues(), + Account.UUID + "=?", + new String[] {account.getUuid()}); } } if (oldVersion < 37 && newVersion >= 37) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ_BY_MARKERS + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.READ_BY_MARKERS + + " TEXT"); } if (oldVersion < 38 && newVersion >= 38) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.MARKABLE + " NUMBER DEFAULT 0"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.MARKABLE + + " NUMBER DEFAULT 0"); } if (oldVersion < 39 && newVersion >= 39) { @@ -545,13 +944,21 @@ public class DatabaseBackend extends SQLiteOpenHelper { List accounts = getAccounts(db); for (Account account : accounts) { account.setOption(Account.OPTION_MAGIC_CREATE, true); - db.update(Account.TABLENAME, account.getContentValues(), Account.UUID - + "=?", new String[]{account.getUuid()}); + db.update( + Account.TABLENAME, + account.getContentValues(), + Account.UUID + "=?", + new String[] {account.getUuid()}); } } if (oldVersion < 44 && newVersion >= 44) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.DELETED + " NUMBER DEFAULT 0"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.DELETED + + " NUMBER DEFAULT 0"); db.execSQL(CREATE_MESSAGE_DELETED_INDEX); db.execSQL(CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX); db.execSQL(CREATE_MESSAGE_TYPE_INDEX); @@ -570,10 +977,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { Log.d(Config.LOGTAG, "deleted old edit information in " + diff + "ms"); } if (oldVersion < 47 && newVersion >= 47) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.PRESENCE_NAME + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Contact.TABLENAME + + " ADD COLUMN " + + Contact.PRESENCE_NAME + + " TEXT"); } if (oldVersion < 48 && newVersion >= 48) { - db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.RTP_CAPABILITY + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Contact.TABLENAME + + " ADD COLUMN " + + Contact.RTP_CAPABILITY + + " TEXT"); } if (oldVersion < 49 && newVersion >= 49) { db.beginTransaction(); @@ -596,16 +1013,46 @@ public class DatabaseBackend extends SQLiteOpenHelper { requiresMessageIndexRebuild = true; } if (oldVersion < 50 && newVersion >= 50) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PINNED_MECHANISM + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PINNED_CHANNEL_BINDING + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.PINNED_MECHANISM + + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.PINNED_CHANNEL_BINDING + + " TEXT"); } if (oldVersion < 51 && newVersion >= 51) { - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.FAST_MECHANISM + " TEXT"); - db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.FAST_TOKEN + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.FAST_MECHANISM + + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Account.TABLENAME + + " ADD COLUMN " + + Account.FAST_TOKEN + + " TEXT"); } if (oldVersion < 52 && newVersion >= 52) { - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OCCUPANT_ID + " TEXT"); - db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.REACTIONS + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.OCCUPANT_ID + + " TEXT"); + db.execSQL( + "ALTER TABLE " + + Message.TABLENAME + + " ADD COLUMN " + + Message.REACTIONS + + " TEXT"); } } @@ -617,21 +1064,33 @@ public class DatabaseBackend extends SQLiteOpenHelper { while (cursor.moveToNext()) { String newJid; try { - newJid = Jid.of(cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))).toString(); + newJid = + Jid.of(cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))) + .toString(); } catch (IllegalArgumentException ignored) { - Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID " - + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) - + ": " + ignored + ". Skipping..."); + Log.e( + Config.LOGTAG, + "Failed to migrate Conversation CONTACTJID " + + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID)) + + ": " + + ignored + + ". Skipping..."); continue; } final String[] updateArgs = { - newJid, - cursor.getString(cursor.getColumnIndex(Conversation.UUID)), + newJid, cursor.getString(cursor.getColumnIndex(Conversation.UUID)), }; - db.execSQL("update " + Conversation.TABLENAME - + " set " + Conversation.CONTACTJID + " = ? " - + " where " + Conversation.UUID + " = ?", updateArgs); + db.execSQL( + "update " + + Conversation.TABLENAME + + " set " + + Conversation.CONTACTJID + + " = ? " + + " where " + + Conversation.UUID + + " = ?", + updateArgs); } cursor.close(); @@ -642,21 +1101,33 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { newJid = Jid.of(cursor.getString(cursor.getColumnIndex(Contact.JID))).toString(); } catch (final IllegalArgumentException e) { - Log.e(Config.LOGTAG, "Failed to migrate Contact JID " - + cursor.getString(cursor.getColumnIndex(Contact.JID)) - + ": Skipping...", e); + Log.e( + Config.LOGTAG, + "Failed to migrate Contact JID " + + cursor.getString(cursor.getColumnIndex(Contact.JID)) + + ": Skipping...", + e); continue; } final String[] updateArgs = { - newJid, - cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)), - cursor.getString(cursor.getColumnIndex(Contact.JID)), + newJid, + cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)), + cursor.getString(cursor.getColumnIndex(Contact.JID)), }; - db.execSQL("update " + Contact.TABLENAME - + " set " + Contact.JID + " = ? " - + " where " + Contact.ACCOUNT + " = ? " - + " AND " + Contact.JID + " = ?", updateArgs); + db.execSQL( + "update " + + Contact.TABLENAME + + " set " + + Contact.JID + + " = ? " + + " where " + + Contact.ACCOUNT + + " = ? " + + " AND " + + Contact.JID + + " = ?", + updateArgs); } cursor.close(); @@ -665,25 +1136,37 @@ public class DatabaseBackend extends SQLiteOpenHelper { while (cursor.moveToNext()) { String newServer; try { - newServer = Jid.of( - cursor.getString(cursor.getColumnIndex(Account.USERNAME)), - cursor.getString(cursor.getColumnIndex(Account.SERVER)), - null - ).getDomain().toEscapedString(); + newServer = + Jid.of( + cursor.getString(cursor.getColumnIndex(Account.USERNAME)), + cursor.getString(cursor.getColumnIndex(Account.SERVER)), + null) + .getDomain() + .toEscapedString(); } catch (IllegalArgumentException ignored) { - Log.e(Config.LOGTAG, "Failed to migrate Account SERVER " - + cursor.getString(cursor.getColumnIndex(Account.SERVER)) - + ": " + ignored + ". Skipping..."); + Log.e( + Config.LOGTAG, + "Failed to migrate Account SERVER " + + cursor.getString(cursor.getColumnIndex(Account.SERVER)) + + ": " + + ignored + + ". Skipping..."); continue; } String[] updateArgs = { - newServer, - cursor.getString(cursor.getColumnIndex(Account.UUID)), + newServer, cursor.getString(cursor.getColumnIndex(Account.UUID)), }; - db.execSQL("update " + Account.TABLENAME - + " set " + Account.SERVER + " = ? " - + " where " + Account.UUID + " = ?", updateArgs); + db.execSQL( + "update " + + Account.TABLENAME + + " set " + + Account.SERVER + + " = ? " + + " where " + + Account.UUID + + " = ?", + updateArgs); } cursor.close(); } @@ -711,9 +1194,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = {hash, ver}; - Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null, - ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?", - selectionArgs, null, null, null); + Cursor cursor = + db.query( + ServiceDiscoveryResult.TABLENAME, + null, + ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?", + selectionArgs, + null, + null, + null); if (cursor.getCount() == 0) { cursor.close(); return null; @@ -723,7 +1212,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { ServiceDiscoveryResult result = null; try { result = new ServiceDiscoveryResult(cursor); - } catch (JSONException e) { /* result is still null */ } + } catch (JSONException e) { + /* result is still null */ + } cursor.close(); return result; @@ -740,7 +1231,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); String where = Resolver.Result.DOMAIN + "=?"; String[] whereArgs = {domain}; - final Cursor cursor = db.query(RESOLVER_RESULTS_TABLENAME, null, where, whereArgs, null, null, null); + final Cursor cursor = + db.query(RESOLVER_RESULTS_TABLENAME, null, where, whereArgs, null, null, null); Resolver.Result result = null; if (cursor != null) { try { @@ -748,7 +1240,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { result = Resolver.Result.fromCursor(cursor); } } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to find cached resolver result in database " + e.getMessage()); + Log.d( + Config.LOGTAG, + "unable to find cached resolver result in database " + e.getMessage()); return null; } finally { cursor.close(); @@ -762,14 +1256,32 @@ public class DatabaseBackend extends SQLiteOpenHelper { String whereToDelete = PresenceTemplate.MESSAGE + "=?"; String[] whereToDeleteArgs = {template.getStatusMessage()}; db.delete(PresenceTemplate.TABELNAME, whereToDelete, whereToDeleteArgs); - db.delete(PresenceTemplate.TABELNAME, PresenceTemplate.UUID + " not in (select " + PresenceTemplate.UUID + " from " + PresenceTemplate.TABELNAME + " order by " + PresenceTemplate.LAST_USED + " desc limit 9)", null); + db.delete( + PresenceTemplate.TABELNAME, + PresenceTemplate.UUID + + " not in (select " + + PresenceTemplate.UUID + + " from " + + PresenceTemplate.TABELNAME + + " order by " + + PresenceTemplate.LAST_USED + + " desc limit 9)", + null); db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues()); } public List getPresenceTemplates() { ArrayList templates = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.query(PresenceTemplate.TABELNAME, null, null, null, null, null, PresenceTemplate.LAST_USED + " desc"); + Cursor cursor = + db.query( + PresenceTemplate.TABELNAME, + null, + null, + null, + null, + null, + PresenceTemplate.LAST_USED + " desc"); while (cursor.moveToNext()) { templates.add(PresenceTemplate.fromCursor(cursor)); } @@ -781,9 +1293,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = {Integer.toString(status)}; - Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME - + " where " + Conversation.STATUS + " = ? and " + Conversation.CONTACTJID + " is not null order by " - + Conversation.CREATED + " desc", selectionArgs); + Cursor cursor = + db.rawQuery( + "select * from " + + Conversation.TABLENAME + + " where " + + Conversation.STATUS + + " = ? and " + + Conversation.CONTACTJID + + " is not null order by " + + Conversation.CREATED + + " desc", + selectionArgs); while (cursor.moveToNext()) { final Conversation conversation = Conversation.fromCursor(cursor); if (conversation.getJid() instanceof InvalidJid) { @@ -805,16 +1326,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { Cursor cursor; if (timestamp == -1) { String[] selectionArgs = {conversation.getUuid()}; - cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=?", selectionArgs, null, null, Message.TIME_SENT - + " DESC", String.valueOf(limit)); + cursor = + db.query( + Message.TABLENAME, + null, + Message.CONVERSATION + "=?", + selectionArgs, + null, + null, + Message.TIME_SENT + " DESC", + String.valueOf(limit)); } else { - String[] selectionArgs = {conversation.getUuid(), - Long.toString(timestamp)}; - cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION - + "=? and " + Message.TIME_SENT + " uuids = new ArrayList<>(); - Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID}, selection, selectionArgs, null, null, null); + Cursor cursor = + db.query( + Message.TABLENAME, + new String[] {Message.UUID}, + selection, + selectionArgs, + null, + null, + null); while (cursor != null && cursor.moveToNext()) { uuids.add(cursor.getString(0)); } @@ -880,7 +1472,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { contentValues.put(Message.DELETED, 1); db.beginTransaction(); for (String uuid : uuids) { - db.update(Message.TABLENAME, contentValues, where, new String[]{uuid}); + db.update(Message.TABLENAME, contentValues, where, new String[] {uuid}); } db.setTransactionSuccessful(); db.endTransaction(); @@ -893,7 +1485,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { for (FilePathInfo info : files) { final ContentValues contentValues = new ContentValues(); contentValues.put(Message.DELETED, info.deleted ? 1 : 0); - db.update(Message.TABLENAME, contentValues, where, new String[]{info.uuid.toString()}); + db.update(Message.TABLENAME, contentValues, where, new String[] {info.uuid.toString()}); } db.setTransactionSuccessful(); db.endTransaction(); @@ -901,10 +1493,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getFilePathInfo() { final SQLiteDatabase db = this.getReadableDatabase(); - final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH, Message.DELETED}, "type in (1,2,5) and " + Message.RELATIVE_FILE_PATH + " is not null", null, null, null, null); + final Cursor cursor = + db.query( + Message.TABLENAME, + new String[] {Message.UUID, Message.RELATIVE_FILE_PATH, Message.DELETED}, + "type in (1,2,5) and " + Message.RELATIVE_FILE_PATH + " is not null", + null, + null, + null, + null); final List list = new ArrayList<>(); while (cursor != null && cursor.moveToNext()) { - list.add(new FilePathInfo(cursor.getString(0), cursor.getString(1), cursor.getInt(2) > 0)); + list.add( + new FilePathInfo( + cursor.getString(0), cursor.getString(1), cursor.getInt(2) > 0)); } if (cursor != null) { cursor.close(); @@ -914,7 +1516,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getRelativeFilePaths(String account, Jid jid, int limit) { SQLiteDatabase db = this.getReadableDatabase(); - final String SQL = "select uuid,relativeFilePath from messages where type in (1,2,5) and deleted=0 and " + Message.RELATIVE_FILE_PATH + " is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; + final String SQL = + "select uuid,relativeFilePath from messages where type in (1,2,5) and deleted=0 and" + + " " + + Message.RELATIVE_FILE_PATH + + " is not null and conversationUuid=(select uuid from conversations where" + + " accountUuid=? and (contactJid=? or contactJid like ?)) order by" + + " timeSent desc"; final String[] args = {account, jid.toString(), jid.toString() + "/%"}; Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + limit : ""), args); List filesPaths = new ArrayList<>(); @@ -949,7 +1557,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { final Conversation conversation, final String messageId) { final var db = this.getReadableDatabase(); final String sql = - "select * from messages where conversationUuid=? and (uuid=? OR remoteMsgId=?) LIMIT 1"; + "select * from messages where conversationUuid=? and (uuid=? OR remoteMsgId=?)" + + " LIMIT 1"; final String[] args = {conversation.getUuid(), messageId, messageId}; final Cursor cursor = db.rawQuery(sql, args); if (cursor == null) { @@ -990,15 +1599,51 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } + public Conversation findConversation(final String uuid) { + final var db = this.getReadableDatabase(); + final String[] selectionArgs = {uuid}; + try (final Cursor cursor = + db.query( + Conversation.TABLENAME, + null, + Conversation.UUID + "=?", + selectionArgs, + null, + null, + null)) { + if (cursor.getCount() == 0) { + return null; + } + cursor.moveToFirst(); + final Conversation conversation = Conversation.fromCursor(cursor); + if (conversation.getJid() instanceof InvalidJid) { + return null; + } + return conversation; + } + } + public Conversation findConversation(final Account account, final Jid contactJid) { - SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = {account.getUuid(), - contactJid.asBareJid().toString() + "/%", - contactJid.asBareJid().toString() + final SQLiteDatabase db = this.getReadableDatabase(); + final String[] selectionArgs = { + account.getUuid(), + contactJid.asBareJid().toString() + "/%", + contactJid.asBareJid().toString() }; - try(final Cursor cursor = db.query(Conversation.TABLENAME, null, - Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID - + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null)) { + try (final Cursor cursor = + db.query( + Conversation.TABLENAME, + null, + Conversation.ACCOUNT + + "=? AND (" + + Conversation.CONTACTJID + + " like ? OR " + + Conversation.CONTACTJID + + "=?)", + selectionArgs, + null, + null, + null)) { if (cursor.getCount() == 0) { return null; } @@ -1007,6 +1652,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (conversation.getJid() instanceof InvalidJid) { return null; } + conversation.setAccount(account); return conversation; } } @@ -1014,8 +1660,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void updateConversation(final Conversation conversation) { final SQLiteDatabase db = this.getWritableDatabase(); final String[] args = {conversation.getUuid()}; - db.update(Conversation.TABLENAME, conversation.getContentValues(), - Conversation.UUID + "=?", args); + db.update( + Conversation.TABLENAME, + conversation.getContentValues(), + Conversation.UUID + "=?", + args); } public List getAccounts() { @@ -1026,9 +1675,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getAccountJids(final boolean enabledOnly) { final SQLiteDatabase db = this.getReadableDatabase(); final List jids = new ArrayList<>(); - final String[] columns = new String[]{Account.USERNAME, Account.SERVER}; + final String[] columns = new String[] {Account.USERNAME, Account.SERVER}; final String where = enabledOnly ? "not options & (1 <<1)" : null; - try (final Cursor cursor = db.query(Account.TABLENAME, columns, where, null, null, null, null)) { + try (final Cursor cursor = + db.query(Account.TABLENAME, columns, where, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { jids.add(Jid.of(cursor.getString(0), cursor.getString(1), null)); } @@ -1101,7 +1751,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); for (Contact contact : roster.getContacts()) { - if (contact.getOption(Contact.Options.IN_ROSTER) || contact.hasAvatarOrPresenceName() || contact.getOption(Contact.Options.SYNCED_VIA_OTHER)) { + if (contact.getOption(Contact.Options.IN_ROSTER) + || contact.hasAvatarOrPresenceName() + || contact.getOption(Contact.Options.SYNCED_VIA_OTHER)) { db.insert(Contact.TABLENAME, null, contact.getContentValues()); } else { String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?"; @@ -1114,7 +1766,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { account.setRosterVersion(roster.getVersion()); updateAccount(account); long duration = SystemClock.elapsedRealtime() - start; - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": persisted roster in " + duration + "ms"); + Log.d( + Config.LOGTAG, + account.getJid().asBareJid() + ": persisted roster in " + duration + "ms"); } public void deleteMessagesInConversation(Conversation conversation) { @@ -1125,7 +1779,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); db.setTransactionSuccessful(); db.endTransaction(); - Log.d(Config.LOGTAG, "deleted " + num + " messages for " + conversation.getJid().asBareJid() + " in " + (SystemClock.elapsedRealtime() - start) + "ms"); + Log.d( + Config.LOGTAG, + "deleted " + + num + + " messages for " + + conversation.getJid().asBareJid() + + " in " + + (SystemClock.elapsedRealtime() - start) + + "ms"); } public void expireOldMessages(long timestamp) { @@ -1141,7 +1803,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { Cursor cursor = null; try { SQLiteDatabase db = this.getReadableDatabase(); - String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) and (conversations.mode=0 or (messages.serverMsgId not null and messages.type=4)) order by messages.timesent desc limit 1"; + String sql = + "select messages.timeSent,messages.serverMsgId from accounts join conversations" + + " on accounts.uuid=conversations.accountUuid join messages on" + + " conversations.uuid=messages.conversationUuid where accounts.uuid=? and" + + " (messages.status=0 or messages.carbon=1 or messages.serverMsgId not" + + " null) and (conversations.mode=0 or (messages.serverMsgId not null and" + + " messages.type=4)) order by messages.timesent desc limit 1"; String[] args = {account.getUuid()}; cursor = db.rawQuery(sql, args); if (cursor.getCount() == 0) { @@ -1160,7 +1828,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public long getLastTimeFingerprintUsed(Account account, String fingerprint) { - String SQL = "select messages.timeSent from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and messages.axolotl_fingerprint=? order by messages.timesent desc limit 1"; + String SQL = + "select messages.timeSent from accounts join conversations on" + + " accounts.uuid=conversations.accountUuid join messages on" + + " conversations.uuid=messages.conversationUuid where accounts.uuid=? and" + + " messages.axolotl_fingerprint=? order by messages.timesent desc limit 1"; String[] args = {account.getUuid(), fingerprint}; Cursor cursor = getReadableDatabase().rawQuery(SQL, args); long time; @@ -1178,14 +1850,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] columns = {Conversation.ATTRIBUTES}; String selection = Conversation.ACCOUNT + "=?"; String[] args = {account.getUuid()}; - Cursor cursor = db.query(Conversation.TABLENAME, columns, selection, args, null, null, null); + Cursor cursor = + db.query(Conversation.TABLENAME, columns, selection, args, null, null, null); MamReference maxClearDate = new MamReference(0); while (cursor.moveToNext()) { try { final JSONObject o = new JSONObject(cursor.getString(0)); - maxClearDate = MamReference.max(maxClearDate, MamReference.fromAttribute(o.getString(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY))); + maxClearDate = + MamReference.max( + maxClearDate, + MamReference.fromAttribute( + o.getString(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY))); } catch (Exception e) { - //ignored + // ignored } } cursor.close(); @@ -1194,16 +1871,22 @@ public class DatabaseBackend extends SQLiteOpenHelper { private Cursor getCursorForSession(Account account, SignalProtocolAddress contact) { final SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = {account.getUuid(), - contact.getName(), - Integer.toString(contact.getDeviceId())}; - return db.query(SQLiteAxolotlStore.SESSION_TABLENAME, + String[] selectionArgs = { + account.getUuid(), contact.getName(), Integer.toString(contact.getDeviceId()) + }; + return db.query( + SQLiteAxolotlStore.SESSION_TABLENAME, null, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.NAME + " = ? AND " - + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + SQLiteAxolotlStore.ACCOUNT + + " = ? AND " + + SQLiteAxolotlStore.NAME + + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + + " = ? ", selectionArgs, - null, null, null); + null, + null, + null); } public SessionRecord loadSession(Account account, SignalProtocolAddress contact) { @@ -1212,7 +1895,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() != 0) { cursor.moveToFirst(); try { - session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + session = + new SessionRecord( + Base64.decode( + cursor.getString( + cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), + Base64.DEFAULT)); } catch (IOException e) { cursor.close(); throw new AssertionError(e); @@ -1227,21 +1915,23 @@ public class DatabaseBackend extends SQLiteOpenHelper { return getSubDeviceSessions(db, account, contact); } - private List getSubDeviceSessions(SQLiteDatabase db, Account account, SignalProtocolAddress contact) { + private List getSubDeviceSessions( + SQLiteDatabase db, Account account, SignalProtocolAddress contact) { List devices = new ArrayList<>(); String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; - String[] selectionArgs = {account.getUuid(), - contact.getName()}; - Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.NAME + " = ?", - selectionArgs, - null, null, null); + String[] selectionArgs = {account.getUuid(), contact.getName()}; + Cursor cursor = + db.query( + SQLiteAxolotlStore.SESSION_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.NAME + " = ?", + selectionArgs, + null, + null, + null); while (cursor.moveToNext()) { - devices.add(cursor.getInt( - cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); + devices.add(cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); } cursor.close(); @@ -1252,12 +1942,16 @@ public class DatabaseBackend extends SQLiteOpenHelper { List addresses = new ArrayList<>(); String[] colums = {"DISTINCT " + SQLiteAxolotlStore.NAME}; String[] selectionArgs = {account.getUuid()}; - Cursor cursor = getReadableDatabase().query(SQLiteAxolotlStore.SESSION_TABLENAME, - colums, - SQLiteAxolotlStore.ACCOUNT + " = ?", - selectionArgs, - null, null, null - ); + Cursor cursor = + getReadableDatabase() + .query( + SQLiteAxolotlStore.SESSION_TABLENAME, + colums, + SQLiteAxolotlStore.ACCOUNT + " = ?", + selectionArgs, + null, + null, + null); while (cursor.moveToNext()) { addresses.add(cursor.getString(0)); } @@ -1272,12 +1966,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { return count != 0; } - public void storeSession(Account account, SignalProtocolAddress contact, SessionRecord session) { + public void storeSession( + Account account, SignalProtocolAddress contact, SessionRecord session) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.NAME, contact.getName()); values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); - values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT)); + values.put( + SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT)); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); } @@ -1288,22 +1984,26 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private void deleteSession(SQLiteDatabase db, Account account, SignalProtocolAddress contact) { - String[] args = {account.getUuid(), - contact.getName(), - Integer.toString(contact.getDeviceId())}; - db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.NAME + " = ? AND " - + SQLiteAxolotlStore.DEVICE_ID + " = ? ", + String[] args = { + account.getUuid(), contact.getName(), Integer.toString(contact.getDeviceId()) + }; + db.delete( + SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + + " = ? AND " + + SQLiteAxolotlStore.NAME + + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + + " = ? ", args); } public void deleteAllSessions(Account account, SignalProtocolAddress contact) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {account.getUuid(), contact.getName()}; - db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.NAME + " = ?", + db.delete( + SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.NAME + " = ?", args); } @@ -1311,12 +2011,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; - Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.ID + "=?", - selectionArgs, - null, null, null); + Cursor cursor = + db.query( + SQLiteAxolotlStore.PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, + null, + null); return cursor; } @@ -1327,7 +2030,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + record = + new PreKeyRecord( + Base64.decode( + cursor.getString( + cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), + Base64.DEFAULT)); } catch (IOException e) { throw new AssertionError(e); } @@ -1347,7 +2055,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ID, record.getId()); - values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put( + SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); } @@ -1355,9 +2064,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { public int deletePreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {account.getUuid(), Integer.toString(preKeyId)}; - return db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.ID + "=?", + return db.delete( + SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", args); } @@ -1365,11 +2074,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; - Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", - selectionArgs, - null, null, null); + Cursor cursor = + db.query( + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", + selectionArgs, + null, + null, + null); return cursor; } @@ -1380,7 +2093,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + record = + new SignedPreKeyRecord( + Base64.decode( + cursor.getString( + cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), + Base64.DEFAULT)); } catch (IOException e) { throw new AssertionError(e); } @@ -1394,15 +2112,24 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid()}; - Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=?", - selectionArgs, - null, null, null); + Cursor cursor = + db.query( + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=?", + selectionArgs, + null, + null, + null); while (cursor.moveToNext()) { try { - prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); + prekeys.add( + new SignedPreKeyRecord( + Base64.decode( + cursor.getString( + cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), + Base64.DEFAULT))); } catch (IOException ignored) { } } @@ -1414,11 +2141,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] columns = {"count(" + SQLiteAxolotlStore.KEY + ")"}; String[] selectionArgs = {account.getUuid()}; SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - columns, - SQLiteAxolotlStore.ACCOUNT + "=?", - selectionArgs, - null, null, null); + Cursor cursor = + db.query( + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + columns, + SQLiteAxolotlStore.ACCOUNT + "=?", + selectionArgs, + null, + null, + null); final int count; if (cursor.moveToFirst()) { count = cursor.getInt(0); @@ -1440,7 +2171,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ID, record.getId()); - values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); + values.put( + SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT)); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); } @@ -1448,9 +2180,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void deleteSignedPreKey(Account account, int signedPreKeyId) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; - db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + "=? AND " - + SQLiteAxolotlStore.ID + "=?", + db.delete( + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", args); } @@ -1459,7 +2191,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { return getIdentityKeyCursor(db, account, name, own); } - private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) { + private Cursor getIdentityKeyCursor( + SQLiteDatabase db, Account account, String name, boolean own) { return getIdentityKeyCursor(db, account, name, own, null); } @@ -1472,11 +2205,14 @@ public class DatabaseBackend extends SQLiteOpenHelper { return getIdentityKeyCursor(db, account, null, null, fingerprint); } - private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { - String[] columns = {SQLiteAxolotlStore.TRUST, - SQLiteAxolotlStore.ACTIVE, - SQLiteAxolotlStore.LAST_ACTIVATION, - SQLiteAxolotlStore.KEY}; + private Cursor getIdentityKeyCursor( + SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) { + String[] columns = { + SQLiteAxolotlStore.TRUST, + SQLiteAxolotlStore.ACTIVE, + SQLiteAxolotlStore.LAST_ACTIVATION, + SQLiteAxolotlStore.KEY + }; ArrayList selectionArgs = new ArrayList<>(4); selectionArgs.add(account.getUuid()); String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; @@ -1492,11 +2228,15 @@ public class DatabaseBackend extends SQLiteOpenHelper { selectionArgs.add(own ? "1" : "0"); selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; } - Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, - columns, - selectionString, - selectionArgs.toArray(new String[selectionArgs.size()]), - null, null, null); + Cursor cursor = + db.query( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + columns, + selectionString, + selectionArgs.toArray(new String[selectionArgs.size()]), + null, + null, + null); return cursor; } @@ -1513,9 +2253,20 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (cursor.getCount() != 0) { cursor.moveToFirst(); try { - identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)); + identityKeyPair = + new IdentityKeyPair( + Base64.decode( + cursor.getString( + cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), + Base64.DEFAULT)); } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().asBareJid() + ", address: " + name); + Log.d( + Config.LOGTAG, + AxolotlService.getLogprefix(account) + + "Encountered invalid IdentityKey in database for account" + + account.getJid().asBareJid() + + ", address: " + + name); } } cursor.close(); @@ -1527,7 +2278,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadIdentityKeys(account, name, null); } - public Set loadIdentityKeys(Account account, String name, FingerprintStatus status) { + public Set loadIdentityKeys( + Account account, String name, FingerprintStatus status) { Set identityKeys = new HashSet<>(); Cursor cursor = getIdentityKeyCursor(account, name, false); @@ -1540,10 +2292,22 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (key != null) { identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0)); } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().asBareJid() + ", address: " + name); + Log.d( + Config.LOGTAG, + AxolotlService.getLogprefix(account) + + "Missing key (possibly preverified) in database for account" + + account.getJid().asBareJid() + + ", address: " + + name); } } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().asBareJid() + ", address: " + name); + Log.d( + Config.LOGTAG, + AxolotlService.getLogprefix(account) + + "Encountered invalid IdentityKey in database for account" + + account.getJid().asBareJid() + + ", address: " + + name); } } cursor.close(); @@ -1554,22 +2318,40 @@ public class DatabaseBackend extends SQLiteOpenHelper { public long numTrustedKeys(Account account, String name) { SQLiteDatabase db = getReadableDatabase(); String[] args = { - account.getUuid(), - name, - FingerprintStatus.Trust.TRUSTED.toString(), - FingerprintStatus.Trust.VERIFIED.toString(), - FingerprintStatus.Trust.VERIFIED_X509.toString() + account.getUuid(), + name, + FingerprintStatus.Trust.TRUSTED.toString(), + FingerprintStatus.Trust.VERIFIED.toString(), + FingerprintStatus.Trust.VERIFIED_X509.toString() }; - return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, - SQLiteAxolotlStore.ACCOUNT + " = ?" - + " AND " + SQLiteAxolotlStore.NAME + " = ?" - + " AND (" + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ?)" - + " AND " + SQLiteAxolotlStore.ACTIVE + " > 0", - args - ); + return DatabaseUtils.queryNumEntries( + db, + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + + " = ?" + + " AND " + + SQLiteAxolotlStore.NAME + + " = ?" + + " AND (" + + SQLiteAxolotlStore.TRUST + + " = ? OR " + + SQLiteAxolotlStore.TRUST + + " = ? OR " + + SQLiteAxolotlStore.TRUST + + " = ?)" + + " AND " + + SQLiteAxolotlStore.ACTIVE + + " > 0", + args); } - private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) { + private void storeIdentityKey( + Account account, + String name, + boolean own, + String fingerprint, + String base64Serialized, + FingerprintStatus status) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); @@ -1578,7 +2360,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); values.put(SQLiteAxolotlStore.KEY, base64Serialized); values.putAll(status.toContentValues()); - String where = SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.NAME + "=? AND " + SQLiteAxolotlStore.FINGERPRINT + " =?"; + String where = + SQLiteAxolotlStore.ACCOUNT + + "=? AND " + + SQLiteAxolotlStore.NAME + + "=? AND " + + SQLiteAxolotlStore.FINGERPRINT + + " =?"; String[] whereArgs = {account.getUuid(), name, fingerprint}; int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, where, whereArgs); if (rows == 0) { @@ -1586,7 +2374,8 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } - public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) { + public void storePreVerification( + Account account, String name, String fingerprint, FingerprintStatus status) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); @@ -1610,36 +2399,43 @@ public class DatabaseBackend extends SQLiteOpenHelper { return status; } - public boolean setIdentityKeyTrust(Account account, String fingerprint, FingerprintStatus fingerprintStatus) { + public boolean setIdentityKeyTrust( + Account account, String fingerprint, FingerprintStatus fingerprintStatus) { SQLiteDatabase db = this.getWritableDatabase(); return setIdentityKeyTrust(db, account, fingerprint, fingerprintStatus); } - private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) { - String[] selectionArgs = { - account.getUuid(), - fingerprint - }; - int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, status.toContentValues(), - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.FINGERPRINT + " = ? ", - selectionArgs); + private boolean setIdentityKeyTrust( + SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) { + String[] selectionArgs = {account.getUuid(), fingerprint}; + int rows = + db.update( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + status.toContentValues(), + SQLiteAxolotlStore.ACCOUNT + + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + + " = ? ", + selectionArgs); return rows == 1; } - public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) { + public boolean setIdentityKeyCertificate( + Account account, String fingerprint, X509Certificate x509Certificate) { SQLiteDatabase db = this.getWritableDatabase(); - String[] selectionArgs = { - account.getUuid(), - fingerprint - }; + String[] selectionArgs = {account.getUuid(), fingerprint}; try { ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded()); - return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + SQLiteAxolotlStore.FINGERPRINT + " = ? ", - selectionArgs) == 1; + return db.update( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + values, + SQLiteAxolotlStore.ACCOUNT + + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + + " = ? ", + selectionArgs) + == 1; } catch (CertificateEncodingException e) { Log.d(Config.LOGTAG, "could not encode certificate"); return false; @@ -1648,25 +2444,34 @@ public class DatabaseBackend extends SQLiteOpenHelper { public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) { SQLiteDatabase db = this.getReadableDatabase(); - String[] selectionArgs = { - account.getUuid(), - fingerprint - }; + String[] selectionArgs = {account.getUuid(), fingerprint}; String[] colums = {SQLiteAxolotlStore.CERTIFICATE}; - String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? "; - Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null); + String selection = + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? "; + Cursor cursor = + db.query( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, + colums, + selection, + selectionArgs, + null, + null, + null); if (cursor.getCount() < 1) { return null; } else { cursor.moveToFirst(); - byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); + byte[] certificate = + cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE)); cursor.close(); if (certificate == null || certificate.length == 0) { return null; } try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate)); + return (X509Certificate) + certificateFactory.generateCertificate( + new ByteArrayInputStream(certificate)); } catch (CertificateException e) { Log.d(Config.LOGTAG, "certificate exception " + e.getMessage()); return null; @@ -1674,17 +2479,31 @@ public class DatabaseBackend extends SQLiteOpenHelper { } } - public void storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) { - storeIdentityKey(account, name, false, CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status); + public void storeIdentityKey( + Account account, String name, IdentityKey identityKey, FingerprintStatus status) { + storeIdentityKey( + account, + name, + false, + CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()), + Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), + status); } public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, account.getJid().asBareJid().toString(), true, CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize()), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false)); + storeIdentityKey( + account, + account.getJid().asBareJid().toString(), + true, + CryptoHelper.bytesToHex(identityKeyPair.getPublicKey().serialize()), + Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), + FingerprintStatus.createActiveVerified(false)); } - private void recreateAxolotlDb(SQLiteDatabase db) { - Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); + Log.d( + Config.LOGTAG, + AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<"); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL(CREATE_SESSIONS_STATEMENT); db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); @@ -1697,36 +2516,70 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void wipeAxolotlDb(Account account) { String accountName = account.getUuid(); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<"); + Log.d( + Config.LOGTAG, + AxolotlService.getLogprefix(account) + + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + + accountName + + " <<<"); SQLiteDatabase db = this.getWritableDatabase(); - String[] deleteArgs = { - accountName - }; - db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + String[] deleteArgs = {accountName}; + db.delete( + SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + db.delete( + SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + db.delete( + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + db.delete( + SQLiteAxolotlStore.IDENTITIES_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); } - public List getFrequentContacts(int days) { - SQLiteDatabase db = this.getReadableDatabase(); - final String SQL = "select " + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + " from " + Conversation.TABLENAME + " join " + Message.TABLENAME + " on conversations.uuid=messages.conversationUuid where messages.status!=0 and carbon==0 and conversations.mode=0 and messages.timeSent>=? group by conversations.uuid order by count(body) desc limit 4;"; - String[] whereArgs = new String[]{String.valueOf(System.currentTimeMillis() - (Config.MILLISECONDS_IN_DAY * days))}; + public List getFrequentContacts(final int days) { + final var db = this.getReadableDatabase(); + final String SQL = + "select " + + Conversation.TABLENAME + + "." + + Conversation.UUID + + "," + + Conversation.TABLENAME + + "." + + Conversation.ACCOUNT + + "," + + Conversation.TABLENAME + + "." + + Conversation.CONTACTJID + + " from " + + Conversation.TABLENAME + + " join " + + Message.TABLENAME + + " on conversations.uuid=messages.conversationUuid where" + + " messages.status!=0 and carbon==0 and conversations.mode=0 and" + + " messages.timeSent>=? group by conversations.uuid order by count(body)" + + " desc limit 4;"; + String[] whereArgs = + new String[] { + String.valueOf(System.currentTimeMillis() - (Config.MILLISECONDS_IN_DAY * days)) + }; Cursor cursor = db.rawQuery(SQL, whereArgs); ArrayList contacts = new ArrayList<>(); while (cursor.moveToNext()) { try { - contacts.add(new ShortcutService.FrequentContact(cursor.getString(0), Jid.of(cursor.getString(1)))); - } catch (Exception e) { - Log.d(Config.LOGTAG, e.getMessage()); + contacts.add( + new ShortcutService.FrequentContact( + cursor.getString(0), + cursor.getString(1), + Jid.of(cursor.getString(2)))); + } catch (final Exception e) { + Log.e(Config.LOGTAG, "could not create frequent contact", e); } } cursor.close(); diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java deleted file mode 100644 index 63a9afc3fa8558f1096d47f9a4f1689e4b1cf11e..0000000000000000000000000000000000000000 --- a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java +++ /dev/null @@ -1,117 +0,0 @@ -package eu.siacs.conversations.services; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.graphics.drawable.Icon; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.service.chooser.ChooserTarget; -import android.service.chooser.ChooserTargetService; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import eu.siacs.conversations.Config; -import eu.siacs.conversations.entities.Conversation; -import eu.siacs.conversations.receiver.SystemEventReceiver; -import eu.siacs.conversations.ui.ConversationsActivity; -import eu.siacs.conversations.utils.Compatibility; - -@SuppressLint("Deprecated") -@TargetApi(Build.VERSION_CODES.M) -public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection { - - private final Object lock = new Object(); - private static final int MAX_TARGETS = 5; - private XmppConnectionService mXmppConnectionService; - - private static boolean textOnly(IntentFilter filter) { - for (int i = 0; i < filter.countDataTypes(); ++i) { - if (!"text/plain".equals(filter.getDataType(i))) { - return false; - } - } - return true; - } - - @Override - public List onGetChooserTargets( - final ComponentName targetActivityName, final IntentFilter matchedFilter) { - if (!SystemEventReceiver.hasEnabledAccounts(this)) { - return Collections.emptyList(); - } - final Intent intent = new Intent(this, XmppConnectionService.class); - intent.setAction("contact_chooser"); - Compatibility.startService(this, intent); - bindService(intent, this, Context.BIND_AUTO_CREATE); - try { - waitForService(); - if (!mXmppConnectionService.areMessagesInitialized()) { - return Collections.emptyList(); - } - final ArrayList conversations = new ArrayList<>(); - mXmppConnectionService.populateWithOrderedConversations( - conversations, textOnly(matchedFilter)); - final ComponentName componentName = - new ComponentName(this, ConversationsActivity.class); - final int pixel = AvatarService.getSystemUiAvatarSize(this); - final ArrayList chooserTargets = new ArrayList<>(); - for (final Conversation conversation : conversations) { - if (conversation.sentMessagesCount() == 0) { - continue; - } - final String name = conversation.getName().toString(); - final Icon icon = - Icon.createWithBitmap( - mXmppConnectionService.getAvatarService().get(conversation, pixel)); - final float score = 1 - (1.0f / MAX_TARGETS) * chooserTargets.size(); - final Bundle extras = new Bundle(); - extras.putString(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); - chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras)); - if (chooserTargets.size() >= MAX_TARGETS) { - return chooserTargets; - } - } - return chooserTargets; - } catch (final InterruptedException e) { - Log.d( - Config.LOGTAG, - "Thread got interrupted before binding to XmppConnectionService", - e); - } finally { - unbindService(this); - } - return Collections.emptyList(); - } - - @Override - public void onServiceConnected(final ComponentName name, final IBinder service) { - XmppConnectionService.XmppConnectionBinder binder = - (XmppConnectionService.XmppConnectionBinder) service; - mXmppConnectionService = binder.getService(); - synchronized (this.lock) { - lock.notifyAll(); - } - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mXmppConnectionService = null; - } - - private void waitForService() throws InterruptedException { - if (mXmppConnectionService == null) { - synchronized (this.lock) { - lock.wait(); - } - } - } -} diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 409a05d573282b2b7a677fabb6f3c8b523101090..4fd89f8a776cf6345fb96e652612ae25e9214f17 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -28,7 +28,6 @@ import android.text.SpannableString; import android.text.style.StyleSpan; import android.util.DisplayMetrics; import android.util.Log; - import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; @@ -41,7 +40,6 @@ import androidx.core.app.RemoteInput; import androidx.core.content.ContextCompat; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.graphics.drawable.IconCompat; - import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Splitter; @@ -49,7 +47,6 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.primitives.Ints; - import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -70,7 +67,6 @@ import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.Media; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -505,7 +501,8 @@ public class NotificationService { Log.d( Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() - + ": suppressing failed delivery notification because conversation is open"); + + ": suppressing failed delivery notification because conversation is" + + " open"); return; } final PendingIntent pendingIntent = createContentIntent(conversation); @@ -631,10 +628,11 @@ public class NotificationService { .build()); modifyIncomingCall(builder); final Notification notification = builder.build(); - notification.audioAttributes = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) - .build(); + notification.audioAttributes = + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); notification.flags = notification.flags | Notification.FLAG_INSISTENT; notify(INCOMING_CALL_NOTIFICATION_ID, notification); } @@ -708,7 +706,8 @@ public class NotificationService { if (jingleRtpConnection == null) { return false; } - final var notificationManager = mXmppConnectionService.getSystemService(NotificationManager.class); + final var notificationManager = + mXmppConnectionService.getSystemService(NotificationManager.class); if (Iterables.any( Arrays.asList(notificationManager.getActiveNotifications()), n -> n.getId() == INCOMING_CALL_NOTIFICATION_ID)) { @@ -820,7 +819,8 @@ public class NotificationService { Log.d( Config.LOGTAG, conversational.getAccount().getJid().asBareJid() - + ": dismissed missed call because call was picked up on other device"); + + ": dismissed missed call because call was picked up on" + + " other device"); iterator.remove(); } } @@ -1345,12 +1345,15 @@ public class NotificationService { if (systemAccount != null) { notificationBuilder.addPerson(systemAccount.toString()); } - info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact); + info = + mXmppConnectionService + .getShortcutService() + .getShortcutInfo(contact, conversation.getUuid()); } else { info = mXmppConnectionService .getShortcutService() - .getShortcutInfoCompat(conversation.getMucOptions()); + .getShortcutInfo(conversation.getMucOptions()); } notificationBuilder.setWhen(conversation.getLatestMessage().getTimeSent()); notificationBuilder.setSmallIcon(R.drawable.ic_app_icon_notification); @@ -1384,16 +1387,16 @@ public class NotificationService { } final BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); bigPictureStyle.bigPicture(bitmap); - if (tmp.size() > 0) { - CharSequence text = getMergedBodies(tmp); - bigPictureStyle.setSummaryText(text); - builder.setContentText(text); - builder.setTicker(text); - } else { + if (tmp.isEmpty()) { final String description = UIHelper.getFileDescriptionString(mXmppConnectionService, message); builder.setContentText(description); builder.setTicker(description); + } else { + final CharSequence text = getMergedBodies(tmp); + bigPictureStyle.setSummaryText(text); + builder.setContentText(text); + builder.setTicker(text); } builder.setStyle(bigPictureStyle); } catch (final IOException e) { diff --git a/src/main/java/eu/siacs/conversations/services/ShortcutService.java b/src/main/java/eu/siacs/conversations/services/ShortcutService.java index c6ae77b63e1d13fabf1f8cdc751fe35f0d6e6ded..ef208d690fb174aee109ff1072f2b0c16f4cdd77 100644 --- a/src/main/java/eu/siacs/conversations/services/ShortcutService.java +++ b/src/main/java/eu/siacs/conversations/services/ShortcutService.java @@ -2,37 +2,38 @@ package eu.siacs.conversations.services; import android.annotation.TargetApi; import android.content.Intent; -import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.Bitmap; -import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Build; +import android.os.PersistableBundle; import android.util.Log; - import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.MucOptions; +import eu.siacs.conversations.ui.ConversationsActivity; import eu.siacs.conversations.ui.StartConversationActivity; import eu.siacs.conversations.utils.ReplacingSerialSingleThreadExecutor; import eu.siacs.conversations.xmpp.Jid; +import java.util.Collection; +import java.util.List; public class ShortcutService { private final XmppConnectionService xmppConnectionService; - private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName()); + private final ReplacingSerialSingleThreadExecutor replacingSerialSingleThreadExecutor = + new ReplacingSerialSingleThreadExecutor(ShortcutService.class.getSimpleName()); - public ShortcutService(XmppConnectionService xmppConnectionService) { + public ShortcutService(final XmppConnectionService xmppConnectionService) { this.xmppConnectionService = xmppConnectionService; } @@ -42,12 +43,7 @@ public class ShortcutService { public void refresh(final boolean forceUpdate) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - final Runnable r = new Runnable() { - @Override - public void run() { - refreshImpl(forceUpdate); - } - }; + final Runnable r = () -> refreshImpl(forceUpdate); replacingSerialSingleThreadExecutor.execute(r); } } @@ -55,87 +51,88 @@ public class ShortcutService { @TargetApi(25) public void report(Contact contact) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class); + ShortcutManager shortcutManager = + xmppConnectionService.getSystemService(ShortcutManager.class); shortcutManager.reportShortcutUsed(getShortcutId(contact)); } } @TargetApi(25) - private void refreshImpl(boolean forceUpdate) { - List frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30); - HashMap accounts = new HashMap<>(); - for(Account account : xmppConnectionService.getAccounts()) { - accounts.put(account.getUuid(),account); - } - List contacts = new ArrayList<>(); - for(FrequentContact frequentContact : frequentContacts) { - Account account = accounts.get(frequentContact.account); + private void refreshImpl(final boolean forceUpdate) { + final var frequentContacts = xmppConnectionService.databaseBackend.getFrequentContacts(30); + final var accounts = + ImmutableMap.copyOf( + Maps.uniqueIndex(xmppConnectionService.getAccounts(), Account::getUuid)); + final var contactBuilder = new ImmutableMap.Builder(); + for (final var frequentContact : frequentContacts) { + final Account account = accounts.get(frequentContact.account); if (account != null) { - contacts.add(account.getRoster().getContact(frequentContact.contact)); + final var contact = account.getRoster().getContact(frequentContact.contact); + contactBuilder.put(frequentContact, contact); } } - ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class); - boolean needsUpdate = forceUpdate || contactsChanged(contacts,shortcutManager.getDynamicShortcuts()); + final var contacts = contactBuilder.build(); + final var current = ShortcutManagerCompat.getDynamicShortcuts(xmppConnectionService); + boolean needsUpdate = forceUpdate || contactsChanged(contacts.values(), current); if (!needsUpdate) { - Log.d(Config.LOGTAG,"skipping shortcut update"); + Log.d(Config.LOGTAG, "skipping shortcut update"); return; } - List newDynamicShortCuts = new ArrayList<>(); - for (Contact contact : contacts) { - ShortcutInfo shortcut = getShortcutInfo(contact); - newDynamicShortCuts.add(shortcut); + final var newDynamicShortcuts = new ImmutableList.Builder(); + for (final var entry : contacts.entrySet()) { + final var contact = entry.getValue(); + final var conversation = entry.getKey().conversation; + final var shortcut = getShortcutInfo(contact, conversation); + newDynamicShortcuts.add(shortcut); } - if (shortcutManager.setDynamicShortcuts(newDynamicShortCuts)) { - Log.d(Config.LOGTAG,"updated dynamic shortcuts"); + if (ShortcutManagerCompat.setDynamicShortcuts( + xmppConnectionService, newDynamicShortcuts.build())) { + Log.d(Config.LOGTAG, "updated dynamic shortcuts"); } else { Log.d(Config.LOGTAG, "unable to update dynamic shortcuts"); } } - public ShortcutInfoCompat getShortcutInfoCompat(final Contact contact) { + public ShortcutInfoCompat getShortcutInfo(final Contact contact, final String conversation) { final ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(contact)) .setShortLabel(contact.getDisplayName()) .setIntent(getShortcutIntent(contact)) .setIsConversation(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - builder.setIcon( - IconCompat.createFromIcon( - xmppConnectionService, - Icon.createWithBitmap( - xmppConnectionService - .getAvatarService() - .getRoundedShortcut(contact)))); + builder.setIcon( + IconCompat.createWithBitmap( + xmppConnectionService.getAvatarService().getRoundedShortcut(contact))); + if (conversation != null) { + setConversation(builder, conversation); } return builder.build(); } - public ShortcutInfoCompat getShortcutInfoCompat(final MucOptions mucOptions) { + public ShortcutInfoCompat getShortcutInfo(final MucOptions mucOptions) { final ShortcutInfoCompat.Builder builder = new ShortcutInfoCompat.Builder(xmppConnectionService, getShortcutId(mucOptions)) .setShortLabel(mucOptions.getConversation().getName()) .setIntent(getShortcutIntent(mucOptions)) .setIsConversation(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - builder.setIcon( - IconCompat.createFromIcon( - xmppConnectionService, - Icon.createWithBitmap( - xmppConnectionService - .getAvatarService() - .getRoundedShortcut(mucOptions)))); - } + builder.setIcon( + IconCompat.createWithBitmap( + xmppConnectionService.getAvatarService().getRoundedShortcut(mucOptions))); + setConversation(builder, mucOptions.getConversation().getUuid()); return builder.build(); } - @TargetApi(Build.VERSION_CODES.N_MR1) - private ShortcutInfo getShortcutInfo(final Contact contact) { - return getShortcutInfoCompat(contact).toShortcutInfo(); + private static void setConversation( + final ShortcutInfoCompat.Builder builder, @NonNull final String conversation) { + builder.setCategories(ImmutableSet.of("eu.siacs.conversations.category.SHARE_TARGET")); + final var extras = new PersistableBundle(); + extras.putString(ConversationsActivity.EXTRA_CONVERSATION, conversation); + builder.setExtras(extras); } - private static boolean contactsChanged(List needles, List haystack) { - for(Contact needle : needles) { - if(!contactExists(needle,haystack)) { + private static boolean contactsChanged( + final Collection needles, final List haystack) { + for (final Contact needle : needles) { + if (!contactExists(needle, haystack)) { return true; } } @@ -143,17 +140,22 @@ public class ShortcutService { } @TargetApi(25) - private static boolean contactExists(Contact needle, List haystack) { - for(ShortcutInfo shortcutInfo : haystack) { - if (getShortcutId(needle).equals(shortcutInfo.getId()) && needle.getDisplayName().equals(shortcutInfo.getShortLabel())) { + private static boolean contactExists( + final Contact needle, final List haystack) { + for (final ShortcutInfoCompat shortcutInfo : haystack) { + final var label = shortcutInfo.getShortLabel(); + if (getShortcutId(needle).equals(shortcutInfo.getId()) + && needle.getDisplayName().equals(label.toString())) { return true; } } return false; } - private static String getShortcutId(Contact contact) { - return contact.getAccount().getJid().asBareJid().toEscapedString()+"#"+contact.getJid().asBareJid().toEscapedString(); + private static String getShortcutId(final Contact contact) { + return contact.getAccount().getJid().asBareJid().toEscapedString() + + "#" + + contact.getJid().asBareJid().toEscapedString(); } private static String getShortcutId(final MucOptions mucOptions) { @@ -194,12 +196,15 @@ public class ShortcutService { } @NonNull - public Intent createShortcut(Contact contact, boolean legacy) { + public Intent createShortcut(final Contact contact, final boolean legacy) { Intent intent; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !legacy) { - ShortcutInfo shortcut = getShortcutInfo(contact); - ShortcutManager shortcutManager = xmppConnectionService.getSystemService(ShortcutManager.class); - intent = shortcutManager.createShortcutResultIntent(shortcut); + final var conversation = xmppConnectionService.find(contact); + final var uuid = conversation == null ? null : conversation.getUuid(); + final var shortcut = getShortcutInfo(contact, uuid); + intent = + ShortcutManagerCompat.createShortcutResultIntent( + xmppConnectionService, shortcut); } else { intent = createShortcutResultIntent(contact); } @@ -207,7 +212,7 @@ public class ShortcutService { } @NonNull - private Intent createShortcutResultIntent(Contact contact) { + private Intent createShortcutResultIntent(final Contact contact) { AvatarService avatarService = xmppConnectionService.getAvatarService(); Bitmap icon = avatarService.getRoundedShortcutWithIcon(contact); Intent intent = new Intent(); @@ -218,13 +223,14 @@ public class ShortcutService { } public static class FrequentContact { + private final String conversation; private final String account; private final Jid contact; - public FrequentContact(String account, Jid contact) { + public FrequentContact(final String conversation, final String account, final Jid contact) { + this.conversation = conversation; this.account = account; this.contact = contact; } } - } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 17a9a3ce9da5059469df6ed01747c2e9edbe037c..a5511bd23c0c9189fb44dd9f3883fa9a49366aff 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -55,8 +55,10 @@ import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -147,7 +149,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.ListIterator; @@ -253,12 +254,13 @@ public class XmppConnectionService extends Service { private final MessageGenerator mMessageGenerator = new MessageGenerator(this); public OnContactStatusChanged onContactStatusChanged = (contact, online) -> { - Conversation conversation = find(getConversations(), contact); - if (conversation != null) { - if (online) { - if (contact.getPresences().size() == 1) { - sendUnsentMessages(conversation); - } + final var conversation = find(contact); + if (conversation == null) { + return; + } + if (online) { + if (contact.getPresences().size() == 1) { + sendUnsentMessages(conversation); } } }; @@ -998,16 +1000,17 @@ public class XmppConnectionService extends Service { } if (pingNow) { for (final Account account : pingCandidates) { + final var connection = account.getXmppConnection(); final boolean lowTimeout = isInLowPingTimeoutMode(account); - account.getXmppConnection().sendPing(); + final var delta = + (SystemClock.elapsedRealtime() - connection.getLastPacketReceived()) + / 1000L; + connection.sendPing(); Log.d( Config.LOGTAG, - account.getJid().asBareJid() - + " send ping (action=" - + action - + ",lowTimeout=" - + lowTimeout - + ")"); + String.format( + "%s: send ping (action=%s,lowTimeout=%s,interval=%s)", + account.getJid().asBareJid(), action, lowTimeout, delta)); scheduleWakeUpCall( lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode()); @@ -1485,7 +1488,7 @@ public class XmppConnectionService extends Service { ContextCompat.RECEIVER_EXPORTED); mForceDuringOnCreate.set(false); toggleForegroundService(); - internalPingExecutor.scheduleAtFixedRate( + internalPingExecutor.scheduleWithFixedDelay( this::manageAccountConnectionStatesInternal, 10, 10, TimeUnit.SECONDS); final SharedPreferences sharedPreferences = androidx.preference.PreferenceManager.getDefaultSharedPreferences(this); @@ -2428,10 +2431,8 @@ public class XmppConnectionService extends Service { private void restoreFromDatabase() { synchronized (this.conversations) { - final Map accountLookupTable = new Hashtable<>(); - for (Account account : this.accounts) { - accountLookupTable.put(account.getUuid(), account); - } + final Map accountLookupTable = + ImmutableMap.copyOf(Maps.uniqueIndex(this.accounts, Account::getUuid)); Log.d(Config.LOGTAG, "restoring conversations..."); final long startTimeConversationsRestore = SystemClock.elapsedRealtime(); this.conversations.addAll( @@ -2735,8 +2736,8 @@ public class XmppConnectionService extends Service { return results; } - public Conversation find(final Iterable haystack, final Contact contact) { - for (final Conversation conversation : haystack) { + public Conversation find(final Contact contact) { + for (final Conversation conversation : this.conversations) { if (conversation.getContact() == contact) { return conversation; } @@ -2798,27 +2799,19 @@ public class XmppConnectionService extends Service { final MessageArchiveService.Query query, final boolean async) { synchronized (this.conversations) { - Conversation conversation = find(account, jid); - if (conversation != null) { - return conversation; + final var cached = find(account, jid); + if (cached != null) { + return cached; } - conversation = databaseBackend.findConversation(account, jid); + final var existing = databaseBackend.findConversation(account, jid); + final Conversation conversation; final boolean loadMessagesFromDb; - if (conversation != null) { - conversation.setStatus(Conversation.STATUS_AVAILABLE); - conversation.setAccount(account); - if (muc) { - conversation.setMode(Conversation.MODE_MULTI); - conversation.setContactJid(jid); - } else { - conversation.setMode(Conversation.MODE_SINGLE); - conversation.setContactJid(jid.asBareJid()); - } - databaseBackend.updateConversation(conversation); - loadMessagesFromDb = conversation.messagesLoaded.compareAndSet(true, false); + if (existing != null) { + conversation = existing; + loadMessagesFromDb = restoreFromArchive(conversation, jid, muc); } else { String conversationName; - Contact contact = account.getRoster().getContact(jid); + final Contact contact = account.getRoster().getContact(jid); if (contact != null) { conversationName = contact.getDisplayName(); } else { @@ -2839,35 +2832,13 @@ public class XmppConnectionService extends Service { this.databaseBackend.createConversation(conversation); loadMessagesFromDb = false; } - final Conversation c = conversation; - final Runnable runnable = - () -> { - if (loadMessagesFromDb) { - c.addAll(0, databaseBackend.getMessages(c, Config.PAGE_SIZE)); - updateConversationUi(); - c.messagesLoaded.set(true); - } - if (account.getXmppConnection() != null - && !c.getContact().isBlocked() - && account.getXmppConnection().getFeatures().mam() - && !muc) { - if (query == null) { - mMessageArchiveService.query(c); - } else { - if (query.getConversation() == null) { - mMessageArchiveService.query( - c, query.getStart(), query.isCatchup()); - } - } - } - if (joinAfterCreate) { - joinMuc(c); - } - }; if (async) { - mDatabaseReaderExecutor.execute(runnable); + mDatabaseReaderExecutor.execute( + () -> + postProcessConversation( + conversation, loadMessagesFromDb, joinAfterCreate, query)); } else { - runnable.run(); + postProcessConversation(conversation, loadMessagesFromDb, joinAfterCreate, query); } this.conversations.add(conversation); updateConversationUi(); @@ -2875,6 +2846,84 @@ public class XmppConnectionService extends Service { } } + public Conversation findConversationByUuidReliable(final String uuid) { + final var cached = findConversationByUuid(uuid); + if (cached != null) { + return cached; + } + final var existing = databaseBackend.findConversation(uuid); + if (existing == null) { + return null; + } + Log.d( + Config.LOGTAG, + existing.getJid().asBareJid() + + ": restoring conversation with " + + existing.getJid() + + " from DB"); + final Map accounts = + ImmutableMap.copyOf(Maps.uniqueIndex(this.accounts, Account::getUuid)); + existing.setAccount(accounts.get(existing.getAccountUuid())); + final var loadMessagesFromDb = restoreFromArchive(existing); + mDatabaseReaderExecutor.execute( + () -> + postProcessConversation( + existing, + loadMessagesFromDb, + existing.getMode() == Conversational.MODE_MULTI, + null)); + this.conversations.add(existing); + updateConversationUi(); + return existing; + } + + private boolean restoreFromArchive( + final Conversation conversation, final Jid jid, final boolean muc) { + if (muc) { + conversation.setMode(Conversation.MODE_MULTI); + conversation.setContactJid(jid); + } else { + conversation.setMode(Conversation.MODE_SINGLE); + conversation.setContactJid(jid.asBareJid()); + } + return restoreFromArchive(conversation); + } + + private boolean restoreFromArchive(final Conversation conversation) { + conversation.setStatus(Conversation.STATUS_AVAILABLE); + databaseBackend.updateConversation(conversation); + return conversation.messagesLoaded.compareAndSet(true, false); + } + + private void postProcessConversation( + final Conversation c, + final boolean loadMessagesFromDb, + final boolean joinAfterCreate, + final MessageArchiveService.Query query) { + final var singleMode = c.getMode() == Conversational.MODE_SINGLE; + final var account = c.getAccount(); + if (loadMessagesFromDb) { + c.addAll(0, databaseBackend.getMessages(c, Config.PAGE_SIZE)); + updateConversationUi(); + c.messagesLoaded.set(true); + } + if (account.getXmppConnection() != null + && !c.getContact().isBlocked() + && account.getXmppConnection().getFeatures().mam() + && singleMode) { + if (query == null) { + mMessageArchiveService.query(c); + } else { + if (query.getConversation() == null) { + mMessageArchiveService.query(c, query.getStart(), query.isCatchup()); + } + } + } + if (joinAfterCreate) { + joinMuc(c); + } + } + public void archiveConversation(Conversation conversation) { archiveConversation(conversation, true); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 2c7d71d79c91cbe0dd7dc700977fc8abfcf4b0a0..f90f99b8b317c22f023f42d1223585787fb0c7d2 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -29,7 +29,6 @@ package eu.siacs.conversations.ui; - import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP; import android.Manifest; @@ -51,22 +50,13 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.databinding.DataBindingUtil; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.openintents.openpgp.util.OpenPgpApi; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; @@ -90,8 +80,22 @@ import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import org.openintents.openpgp.util.OpenPgpApi; -public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged { +public class ConversationsActivity extends XmppActivity + implements OnConversationSelected, + OnConversationArchived, + OnConversationsListItemUpdated, + OnConversationRead, + XmppConnectionService.OnAccountUpdate, + XmppConnectionService.OnConversationUpdate, + XmppConnectionService.OnRosterUpdate, + OnUpdateBlocklist, + XmppConnectionService.OnShowErrorToast, + XmppConnectionService.OnAffiliationChanged { public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW"; public static final String EXTRA_CONVERSATION = "conversationUuid"; @@ -104,19 +108,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio public static final String POST_ACTION_RECORD_VOICE = "record_voice"; public static final String EXTRA_TYPE = "type"; - private static final List VIEW_AND_SHARE_ACTIONS = Arrays.asList( - ACTION_VIEW_CONVERSATION, - Intent.ACTION_SEND, - Intent.ACTION_SEND_MULTIPLE - ); + private static final List VIEW_AND_SHARE_ACTIONS = + Arrays.asList( + ACTION_VIEW_CONVERSATION, Intent.ACTION_SEND, Intent.ACTION_SEND_MULTIPLE); public static final int REQUEST_OPEN_MESSAGE = 0x9876; public static final int REQUEST_PLAY_PAUSE = 0x5432; - - //secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment - private static final @IdRes - int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment}; + // secondary fragment (when holding the conversation, must be initialized before refreshing the + // overview fragment + private static final @IdRes int[] FRAGMENT_ID_NOTIFICATION_ORDER = { + R.id.secondary_fragment, R.id.main_fragment + }; private final PendingItem pendingViewIntent = new PendingItem<>(); private final PendingItem postponedActivityResult = new PendingItem<>(); private ActivityConversationsBinding binding; @@ -125,7 +128,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private static boolean isViewOrShareIntent(Intent i) { Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction())); - return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION); + return i != null + && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) + && i.hasExtra(EXTRA_CONVERSATION); } private static Intent createLauncherIntent(Context context) { @@ -169,7 +174,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } invalidateActionBarTitle(); - if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) { + if (binding.secondaryFragment != null + && ConversationFragment.getConversation(this) == null) { Conversation conversation = ConversationsOverviewFragment.getSuggestion(this); if (conversation != null) { openConversation(conversation, null); @@ -182,7 +188,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio return performRedirectIfNecessary(null, noAnimation); } - private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) { + private boolean performRedirectIfNecessary( + final Conversation ignore, final boolean noAnimation) { if (xmppConnectionService == null) { return false; } @@ -192,12 +199,13 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (noAnimation) { intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); } - runOnUiThread(() -> { - startActivity(intent); - if (noAnimation) { - overridePendingTransition(0, 0); - } - }); + runOnUiThread( + () -> { + startActivity(intent); + if (noAnimation) { + overridePendingTransition(0, 0); + } + }); } return mRedirectInProcess.get(); } @@ -219,7 +227,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private String getBatteryOptimizationPreferenceKey() { - @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); + @SuppressLint("HardwareIds") + String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); return "show_battery_optimization" + (device == null ? "" : device); } @@ -228,20 +237,31 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private boolean openBatteryOptimizationDialogIfNeeded() { - if (isOptimizingBattery() && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) { + if (isOptimizingBattery() + && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) { final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); builder.setTitle(R.string.battery_optimizations_enabled); - builder.setMessage(getString(R.string.battery_optimizations_enabled_dialog, getString(R.string.app_name))); - builder.setPositiveButton(R.string.next, (dialog, which) -> { - final Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - final Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (final ActivityNotFoundException e) { - Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } - }); + builder.setMessage( + getString( + R.string.battery_optimizations_enabled_dialog, + getString(R.string.app_name))); + builder.setPositiveButton( + R.string.next, + (dialog, which) -> { + final Intent intent = + new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + final Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (final ActivityNotFoundException e) { + Toast.makeText( + this, + R.string.device_does_not_support_battery_op, + Toast.LENGTH_SHORT) + .show(); + } + }); builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain()); final AlertDialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); @@ -252,8 +272,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void requestNotificationPermissionIfNeeded() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_POST_NOTIFICATION); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + requestPermissions( + new String[] {Manifest.permission.POST_NOTIFICATIONS}, + REQUEST_POST_NOTIFICATION); } } @@ -271,9 +295,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } } - private boolean processViewIntent(Intent intent) { + private boolean processViewIntent(final Intent intent) { final String uuid = intent.getStringExtra(EXTRA_CONVERSATION); - final Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null; + final Conversation conversation = + uuid != null ? xmppConnectionService.findConversationByUuidReliable(uuid) : null; if (conversation == null) { Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid); return false; @@ -283,7 +308,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + public void onRequestPermissionsResult( + int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults); if (grantResults.length > 0) { @@ -397,8 +423,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (qrCodeScanMenuItem != null) { if (isCameraFeatureAvailable()) { Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment); - boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan) - && fragment instanceof ConversationsOverviewFragment; + boolean visible = + getResources().getBoolean(R.bool.show_qr_code_scan) + && fragment instanceof ConversationsOverviewFragment; qrCodeScanMenuItem.setVisible(visible); } else { qrCodeScanMenuItem.setVisible(false); @@ -411,7 +438,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio public void onConversationSelected(Conversation conversation) { clearPendingViewIntent(); if (ConversationFragment.getConversation(this) == conversation) { - Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open"); + Log.d( + Config.LOGTAG, + "ignore onConversationSelected() because conversation is already open"); return; } openConversation(conversation, null); @@ -424,13 +453,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void displayToast(final String msg) { - runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show()); + runOnUiThread( + () -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show()); } @Override - public void onAffiliationChangedSuccessful(Jid jid) { - - } + public void onAffiliationChangedSuccessful(Jid jid) {} @Override public void onAffiliationChangeFailed(Jid jid, int resId) { @@ -440,7 +468,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private void openConversation(Conversation conversation, Bundle extras) { final FragmentManager fragmentManager = getFragmentManager(); executePendingTransactions(fragmentManager); - ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); + ConversationFragment conversationFragment = + (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); final boolean mainNeedsRefresh; if (conversationFragment == null) { mainNeedsRefresh = false; @@ -456,7 +485,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio fragmentTransaction.commit(); } catch (IllegalStateException e) { Log.w(Config.LOGTAG, "sate loss while opening conversation", e); - //allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored' + // allowing state loss is probably fine since view intents et all are already + // stored and a click can probably be 'ignored' return; } } @@ -474,14 +504,15 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio try { fragmentManager.executePendingTransactions(); } catch (final Exception e) { - Log.e(Config.LOGTAG,"unable to execute pending fragment transactions"); + Log.e(Config.LOGTAG, "unable to execute pending fragment transactions"); } } public boolean onXmppUriClicked(Uri uri) { XmppUri xmppUri = new XmppUri(uri); if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) { - final Conversation conversation = xmppConnectionService.findUniqueConversationByJid(xmppUri); + final Conversation conversation = + xmppConnectionService.findUniqueConversationByJid(xmppUri); if (conversation != null) { openConversation(conversation, null); return true; @@ -540,7 +571,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onSaveInstanceState(final Bundle savedInstanceState) { final Intent pendingIntent = pendingViewIntent.peek(); - savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent()); + savedInstanceState.putParcelable( + "intent", pendingIntent != null ? pendingIntent : getIntent()); super.onSaveInstanceState(savedInstanceState); } @@ -580,7 +612,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio final FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); - final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); + final Fragment secondaryFragment = + fragmentManager.findFragmentById(R.id.secondary_fragment); if (mainFragment != null) { if (binding.secondaryFragment != null) { if (mainFragment instanceof ConversationFragment) { @@ -628,13 +661,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio actionBar.setTitle(conversation.getName()); actionBar.setDisplayHomeAsUpEnabled(true); ToolbarUtils.setActionBarOnClickListener( - binding.toolbar, - (v) -> openConversationDetails(conversation) - ); + binding.toolbar, (v) -> openConversationDetails(conversation)); return; } } - final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); + final Fragment secondaryFragment = + fragmentManager.findFragmentById(R.id.secondary_fragment); if (secondaryFragment instanceof ConversationFragment conversationFragment) { final Conversation conversation = conversationFragment.getConversation(); if (conversation != null) { @@ -673,15 +705,21 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio try { fragmentManager.popBackStack(); } catch (final IllegalStateException e) { - Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e); - //this usually means activity is no longer active; meaning on the next open we will run through this again + Log.w( + Config.LOGTAG, + "state loss while popping back state after archiving conversation", + e); + // this usually means activity is no longer active; meaning on the next open we will + // run through this again } return; } - final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); + final Fragment secondaryFragment = + fragmentManager.findFragmentById(R.id.secondary_fragment); if (secondaryFragment instanceof ConversationFragment) { if (((ConversationFragment) secondaryFragment).getConversation() == conversation) { - Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation); + Conversation suggestion = + ConversationsOverviewFragment.getSuggestion(this, conversation); if (suggestion != null) { openConversation(suggestion, null); } diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index c33c3e6bd7ba2c7c23ba57b6dd4609fcbb2afb58..1f83d1f4546f8a027b4eec96fcc2335710aa4cc4 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -8,11 +8,11 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import androidx.annotation.NonNull; +import androidx.core.content.pm.ShortcutManagerCompat; import androidx.databinding.DataBindingUtil; import androidx.recyclerview.widget.LinearLayoutManager; - +import com.google.common.collect.Iterables; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityShareWithBinding; @@ -21,7 +21,6 @@ import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.adapter.ConversationAdapter; import eu.siacs.conversations.xmpp.Jid; - import java.util.ArrayList; import java.util.List; @@ -112,7 +111,34 @@ public class ShareWithActivity extends XmppActivity new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); binding.chooseConversationList.setAdapter(mAdapter); mAdapter.setConversationClickListener((view, conversation) -> share(conversation)); + final var intent = getIntent(); + final var shortcutId = intent.getStringExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID); this.share = new Share(); + if (shortcutId != null) { + final var conversation = shortcutIdToConversation(shortcutId); + if (conversation != null) { + // we have everything we need. Jump into chat + populateShare(intent); + share(conversation); + } + } + } + + private String shortcutIdToConversation(final String shortcutId) { + final var shortcut = + Iterables.tryFind( + ShortcutManagerCompat.getDynamicShortcuts(this), + si -> si.getId().equals(shortcutId)); + if (shortcut.isPresent()) { + final var extras = shortcut.get().getExtras(); + if (extras == null) { + return null; + } else { + return extras.getString(ConversationsActivity.EXTRA_CONVERSATION); + } + } else { + return null; + } } @Override @@ -137,10 +163,18 @@ public class ShareWithActivity extends XmppActivity @Override public void onStart() { super.onStart(); - Intent intent = getIntent(); + final Intent intent = getIntent(); if (intent == null) { return; } + populateShare(intent); + if (xmppConnectionServiceBound) { + xmppConnectionService.populateWithOrderedConversations( + mConversations, this.share.uris.isEmpty(), false); + } + } + + private void populateShare(final Intent intent) { final String type = intent.getType(); final String action = intent.getAction(); final Uri data = intent.getData(); @@ -165,10 +199,6 @@ public class ShareWithActivity extends XmppActivity final ArrayList uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); this.share.uris = uris == null ? new ArrayList<>() : uris; } - if (xmppConnectionServiceBound) { - xmppConnectionService.populateWithOrderedConversations( - mConversations, this.share.uris.isEmpty(), false); - } } @Override @@ -209,8 +239,12 @@ public class ShareWithActivity extends XmppActivity mPendingConversation = conversation; return; } + share(conversation.getUuid()); + } + + private void share(final String conversation) { final Intent intent = new Intent(this, ConversationsActivity.class); - intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); + intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation); if (!share.uris.isEmpty()) { intent.setAction(Intent.ACTION_SEND_MULTIPLE); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris); @@ -225,7 +259,7 @@ public class ShareWithActivity extends XmppActivity } try { startActivity(intent); - } catch (SecurityException e) { + } catch (final SecurityException e) { Toast.makeText( this, R.string.sharing_application_not_grant_permission, diff --git a/src/main/res/xml/shortcuts.xml b/src/main/res/xml/shortcuts.xml new file mode 100644 index 0000000000000000000000000000000000000000..8bb176555063882535621d602d02275994e7acea --- /dev/null +++ b/src/main/res/xml/shortcuts.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file