diff --git a/.gitignore b/.gitignore index 44662de2..c3f9a005 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ keystore.properties ks.passwd venv/* /release.properties +*.sw* diff --git a/Makefile b/Makefile index 3b4edec4..b9b75ee5 100644 --- a/Makefile +++ b/Makefile @@ -71,10 +71,6 @@ check: echo "+ [NOT FOUND] ${RELEASE_VERSION_PYTHON_FILENAME}"; \ echo ">> This file releases the build on the various distribution outlets"; \ fi - @if [ ! -f ${KEYSTORE_PASSWD} ]; then \ - echo "+ [NOT FOUND] ${KEYSTORE_PASSWD}"; \ - echo ">> This file contains the password for the keystore"; \ - fi info: check @echo "- Branch name: ${branch}" diff --git a/app/build.gradle b/app/build.gradle index f5d2fe7d..6d89e2c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,25 +49,27 @@ android { // generateLocaleConfig true // } sourceSets { + androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) + main { jniLibs.srcDirs = ['libs'] java { exclude 'com/afkanerd/deku/Images/Images/' /* The holder name I want to excludes its all classes */ } + resources { + srcDirs 'src/main/resources' + } } } buildTypes { release { minifyEnabled false +// minifyEnabled true +// shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } - - nightly { - versionNameSuffix "-nightly" - debuggable true - } } compileOptions { @@ -124,6 +126,7 @@ android { dependencies { implementation project(':smswithoutborders_libsignal-doubleratchet') + implementation 'androidx.room:room-testing:2.6.1' def paging_version = "3.2.1" testImplementation 'junit:junit:4.13.2' diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json index e401280d..26448a1a 100644 --- a/app/release/output-metadata.json +++ b/app/release/output-metadata.json @@ -11,8 +11,8 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 37, - "versionName": "0.25.0", + "versionCode": 49, + "versionName": "0.37.0", "outputFile": "app-release.apk" } ], diff --git a/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/11.json b/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/11.json new file mode 100644 index 00000000..146eff19 --- /dev/null +++ b/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/11.json @@ -0,0 +1,570 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "0f71195933c162abd930f61eb8c0cced", + "entities": [ + { + "tableName": "ThreadedConversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` TEXT NOT NULL, `address` TEXT, `msg_count` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `is_archived` INTEGER NOT NULL, `is_blocked` INTEGER NOT NULL, `is_shortcode` INTEGER NOT NULL, `is_read` INTEGER NOT NULL, `snippet` TEXT, `contact_name` TEXT, `formatted_datetime` TEXT, PRIMARY KEY(`thread_id`))", + "fields": [ + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "msg_count", + "columnName": "msg_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_archived", + "columnName": "is_archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_blocked", + "columnName": "is_blocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_shortcode", + "columnName": "is_shortcode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_read", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snippet", + "columnName": "snippet", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contact_name", + "columnName": "contact_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "formatted_datetime", + "columnName": "formatted_datetime", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "thread_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CustomKeyStore", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `keystoreAlias` TEXT, `publicKey` TEXT, `privateKey` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keystoreAlias", + "columnName": "keystoreAlias", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "privateKey", + "columnName": "privateKey", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_CustomKeyStore_keystoreAlias", + "unique": true, + "columnNames": [ + "keystoreAlias" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_CustomKeyStore_keystoreAlias` ON `${TABLE_NAME}` (`keystoreAlias`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Archive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` TEXT NOT NULL, `is_archived` INTEGER NOT NULL, PRIMARY KEY(`thread_id`))", + "fields": [ + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "is_archived", + "columnName": "is_archived", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "thread_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GatewayServer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`URL` TEXT, `protocol` TEXT, `tag` TEXT, `format` TEXT, `date` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "URL", + "columnName": "URL", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GatewayClientProjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gatewayClientId` INTEGER NOT NULL, `name` TEXT, `binding1Name` TEXT, `binding2Name` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "gatewayClientId", + "columnName": "gatewayClientId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "binding1Name", + "columnName": "binding1Name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "binding2Name", + "columnName": "binding2Name", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationsThreadsEncryption", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `keystoreAlias` TEXT, `publicKey` TEXT, `states` TEXT, `exchangeDate` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keystoreAlias", + "columnName": "keystoreAlias", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "states", + "columnName": "states", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "exchangeDate", + "columnName": "exchangeDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_ConversationsThreadsEncryption_keystoreAlias", + "unique": true, + "columnNames": [ + "keystoreAlias" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ConversationsThreadsEncryption_keystoreAlias` ON `${TABLE_NAME}` (`keystoreAlias`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Conversation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `message_id` TEXT, `thread_id` TEXT, `date` TEXT, `date_sent` TEXT, `type` INTEGER NOT NULL, `num_segments` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, `status` INTEGER NOT NULL, `error_code` INTEGER NOT NULL, `read` INTEGER NOT NULL, `is_encrypted` INTEGER NOT NULL, `is_key` INTEGER NOT NULL, `is_image` INTEGER NOT NULL, `formatted_date` TEXT, `address` TEXT, `text` TEXT, `data` TEXT, `_mk` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message_id", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date_sent", + "columnName": "date_sent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "num_segments", + "columnName": "num_segments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription_id", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error_code", + "columnName": "error_code", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_encrypted", + "columnName": "is_encrypted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_key", + "columnName": "is_key", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_image", + "columnName": "is_image", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "formatted_date", + "columnName": "formatted_date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "_mk", + "columnName": "_mk", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Conversation_message_id", + "unique": true, + "columnNames": [ + "message_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Conversation_message_id` ON `${TABLE_NAME}` (`message_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "GatewayClient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `hostUrl` TEXT, `username` TEXT, `password` TEXT, `port` INTEGER NOT NULL, `friendlyConnectionName` TEXT, `virtualHost` TEXT, `connectionTimeout` INTEGER NOT NULL, `prefetch_count` INTEGER NOT NULL, `heartbeat` INTEGER NOT NULL, `protocol` TEXT, `projectName` TEXT, `projectBinding` TEXT, `projectBinding2` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hostUrl", + "columnName": "hostUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "friendlyConnectionName", + "columnName": "friendlyConnectionName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "virtualHost", + "columnName": "virtualHost", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connectionTimeout", + "columnName": "connectionTimeout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "prefetch_count", + "columnName": "prefetch_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "heartbeat", + "columnName": "heartbeat", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectName", + "columnName": "projectName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectBinding", + "columnName": "projectBinding", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectBinding2", + "columnName": "projectBinding2", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0f71195933c162abd930f61eb8c0cced')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/12.json b/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/12.json new file mode 100644 index 00000000..2c70255e --- /dev/null +++ b/app/schemas/com.afkanerd.deku.DefaultSMS.Models.Database.Datastore/12.json @@ -0,0 +1,584 @@ +{ + "formatVersion": 1, + "database": { + "version": 12, + "identityHash": "5446df8aef65bd90a18b55a234c3e980", + "entities": [ + { + "tableName": "ThreadedConversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`is_secured` INTEGER NOT NULL DEFAULT 0, `thread_id` TEXT NOT NULL, `address` TEXT, `msg_count` INTEGER NOT NULL, `type` INTEGER NOT NULL, `date` TEXT, `is_archived` INTEGER NOT NULL, `is_blocked` INTEGER NOT NULL, `is_shortcode` INTEGER NOT NULL, `is_read` INTEGER NOT NULL, `snippet` TEXT, `contact_name` TEXT, `formatted_datetime` TEXT, `is_mute` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`thread_id`))", + "fields": [ + { + "fieldPath": "is_secured", + "columnName": "is_secured", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "msg_count", + "columnName": "msg_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_archived", + "columnName": "is_archived", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_blocked", + "columnName": "is_blocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_shortcode", + "columnName": "is_shortcode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_read", + "columnName": "is_read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "snippet", + "columnName": "snippet", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contact_name", + "columnName": "contact_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "formatted_datetime", + "columnName": "formatted_datetime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "is_mute", + "columnName": "is_mute", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "thread_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CustomKeyStore", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `keystoreAlias` TEXT, `publicKey` TEXT, `privateKey` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keystoreAlias", + "columnName": "keystoreAlias", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "privateKey", + "columnName": "privateKey", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_CustomKeyStore_keystoreAlias", + "unique": true, + "columnNames": [ + "keystoreAlias" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_CustomKeyStore_keystoreAlias` ON `${TABLE_NAME}` (`keystoreAlias`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Archive", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`thread_id` TEXT NOT NULL, `is_archived` INTEGER NOT NULL, PRIMARY KEY(`thread_id`))", + "fields": [ + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "is_archived", + "columnName": "is_archived", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "thread_id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GatewayServer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`URL` TEXT, `protocol` TEXT, `tag` TEXT, `format` TEXT, `date` INTEGER, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "URL", + "columnName": "URL", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "format", + "columnName": "format", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "GatewayClientProjects", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `gatewayClientId` INTEGER NOT NULL, `name` TEXT, `binding1Name` TEXT, `binding2Name` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "gatewayClientId", + "columnName": "gatewayClientId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "binding1Name", + "columnName": "binding1Name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "binding2Name", + "columnName": "binding2Name", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ConversationsThreadsEncryption", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `keystoreAlias` TEXT, `publicKey` TEXT, `states` TEXT, `exchangeDate` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "keystoreAlias", + "columnName": "keystoreAlias", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "publicKey", + "columnName": "publicKey", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "states", + "columnName": "states", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "exchangeDate", + "columnName": "exchangeDate", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_ConversationsThreadsEncryption_keystoreAlias", + "unique": true, + "columnNames": [ + "keystoreAlias" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ConversationsThreadsEncryption_keystoreAlias` ON `${TABLE_NAME}` (`keystoreAlias`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Conversation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `message_id` TEXT, `thread_id` TEXT, `date` TEXT, `date_sent` TEXT, `type` INTEGER NOT NULL, `num_segments` INTEGER NOT NULL, `subscription_id` INTEGER NOT NULL, `status` INTEGER NOT NULL, `error_code` INTEGER NOT NULL, `read` INTEGER NOT NULL, `is_encrypted` INTEGER NOT NULL, `is_key` INTEGER NOT NULL, `is_image` INTEGER NOT NULL, `formatted_date` TEXT, `address` TEXT, `text` TEXT, `data` TEXT, `_mk` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message_id", + "columnName": "message_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread_id", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "date_sent", + "columnName": "date_sent", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "num_segments", + "columnName": "num_segments", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription_id", + "columnName": "subscription_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error_code", + "columnName": "error_code", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_encrypted", + "columnName": "is_encrypted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_key", + "columnName": "is_key", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "is_image", + "columnName": "is_image", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "formatted_date", + "columnName": "formatted_date", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "_mk", + "columnName": "_mk", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Conversation_message_id", + "unique": true, + "columnNames": [ + "message_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Conversation_message_id` ON `${TABLE_NAME}` (`message_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "GatewayClient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` INTEGER NOT NULL, `hostUrl` TEXT, `username` TEXT, `password` TEXT, `port` INTEGER NOT NULL, `friendlyConnectionName` TEXT, `virtualHost` TEXT, `connectionTimeout` INTEGER NOT NULL, `prefetch_count` INTEGER NOT NULL, `heartbeat` INTEGER NOT NULL, `protocol` TEXT, `projectName` TEXT, `projectBinding` TEXT, `projectBinding2` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hostUrl", + "columnName": "hostUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "friendlyConnectionName", + "columnName": "friendlyConnectionName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "virtualHost", + "columnName": "virtualHost", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "connectionTimeout", + "columnName": "connectionTimeout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "prefetch_count", + "columnName": "prefetch_count", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "heartbeat", + "columnName": "heartbeat", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectName", + "columnName": "projectName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectBinding", + "columnName": "projectBinding", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "projectBinding2", + "columnName": "projectBinding2", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5446df8aef65bd90a18b55a234c3e980')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsTest.java index 37286230..0326ed0b 100644 --- a/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsTest.java +++ b/app/src/androidTest/java/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsTest.java @@ -25,18 +25,4 @@ public class ThreadedConversationsTest { public ThreadedConversationsTest() { context = InstrumentationRegistry.getInstrumentation().getTargetContext(); } - @Test - public void testThreadedConversationsBuildMethods() { - ThreadedConversations threadedConversation = new ThreadedConversations(); - ThreadedConversationsDao threadedConversationsDao = threadedConversation.getDaoInstance(context); - List threadedConversations = threadedConversationsDao.getAll(); - threadedConversation.close(); - - Conversation conversation = new Conversation(); - ConversationDao conversationDao = conversation.getDaoInstance(context); - List conversations = conversationDao.getComplete(); - conversation.close(); - - assertEquals(conversations.get(0).getText(), threadedConversations.get(0).getSnippet()); - } } diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java new file mode 100644 index 00000000..e870bec2 --- /dev/null +++ b/app/src/androidTest/java/java/com/afkanerd/deku/QueueListener/RMQConnectionTest.java @@ -0,0 +1,252 @@ +package java.com.afkanerd.deku.QueueListener; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.telephony.SubscriptionInfo; +import android.util.Log; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.afkanerd.deku.DefaultSMS.BuildConfig; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ConversationHandler; +import com.afkanerd.deku.DefaultSMS.Models.Database.SemaphoreManager; +import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; +import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; +import com.afkanerd.deku.DefaultSMS.R; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient; +import com.afkanerd.deku.QueueListener.RMQ.RMQConnection; +import com.afkanerd.deku.QueueListener.RMQ.RMQMonitor; +import com.rabbitmq.client.CancelCallback; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.ConsumerShutdownSignalCallback; +import com.rabbitmq.client.DeliverCallback; +import com.rabbitmq.client.Delivery; +import com.rabbitmq.client.ShutdownListener; +import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.impl.DefaultExceptionHandler; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidJUnit4.class) +public class RMQConnectionTest { + + Context context; + + Properties properties = new Properties(); + ExecutorService consumerExecutorService = Executors.newFixedThreadPool(1); // Create a pool of 5 worker threads + + public RMQConnectionTest() throws IOException { + this.context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + InputStream inputStream = this.context.getResources() + .openRawResource(R.raw.app); + properties.load(inputStream); + } + + @Test + public void connectionTest() throws IOException, TimeoutException { + ConnectionFactory factory = new ConnectionFactory(); + factory.setUsername(properties.getProperty("username")); + factory.setPassword(properties.getProperty("password")); + factory.setVirtualHost(properties.getProperty("virtualhost")); + factory.setHost(properties.getProperty("host")); + factory.setPort(Integer.parseInt(properties.getProperty("port"))); + factory.setAutomaticRecoveryEnabled(true); + factory.setNetworkRecoveryInterval(10000); + factory.setExceptionHandler(new DefaultExceptionHandler()); + + Connection connection = factory.newConnection(consumerExecutorService, + "android-studio-test-case"); + + RMQConnection rmqConnection = new RMQConnection(connection); + final Channel channel = rmqConnection.createChannel(); + channel.basicRecover(true); + + String defaultExchange = properties.getProperty("exchange"); + String defaultQueueName = "android_studio_testing_queue"; + String defaultQueueName1 = "android_studio_testing_queue1"; + String defaultBindingKey = "#.62401"; + String defaultBindingKey1 = "*.routing.62401"; + String defaultRoutingKey = "testing.routing.62401"; + + rmqConnection.createQueue(defaultExchange, defaultBindingKey, channel, defaultQueueName); + rmqConnection.createQueue(defaultExchange, defaultBindingKey1, channel, defaultQueueName1); + channel.queuePurge(defaultQueueName); + channel.queuePurge(defaultQueueName1); + + long messageCount = channel.messageCount(defaultQueueName); + assertEquals(0, messageCount); + + messageCount = channel.messageCount(defaultQueueName1); + assertEquals(0, messageCount); + + String basicMessage = "hello world 0"; + channel.basicPublish(defaultExchange, defaultRoutingKey, null, + basicMessage.getBytes(StandardCharsets.UTF_8)); + + Set consumerTags = new HashSet<>(); + final boolean[] shutdownDown = {false}; + ConsumerShutdownSignalCallback consumerShutdownSignalCallback = new ConsumerShutdownSignalCallback() { + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + shutdownDown[0] = true; + consumerTags.remove(consumerTag); + rmqConnection.removeChannel(channel); + } + }; + + final boolean[] delivered = {false}; + DeliverCallback deliverCallback = new DeliverCallback() { + @Override + public void handle(String consumerTag, Delivery message) throws IOException { + delivered[0] = true; + channel.basicAck(message.getEnvelope().getDeliveryTag(), false); + } + }; + + DeliverCallback deliverCallback1 = new DeliverCallback() { + @Override + public void handle(String consumerTag, Delivery message) throws IOException { + + } + }; + + String consumerTag = channel.basicConsume(defaultQueueName, false, deliverCallback, + consumerShutdownSignalCallback); + consumerTags.add(consumerTag); + + /** + * This causes an error which forces the channel to close. + * This behaviour can then be observed. + */ + try { + String nonExistentExchangeName = "nonExistentExchangeName"; + String nonExistentBindingName = "nonExistentBindingName"; + rmqConnection.createQueue(nonExistentExchangeName, + nonExistentBindingName, channel, null); + } catch (Exception e) { + e.printStackTrace(); + } finally { + assertTrue(connection.isOpen()); + assertFalse(channel.isOpen()); + } + + assertTrue(delivered[0]); + assertTrue(shutdownDown[0]); + assertFalse(consumerTags.contains(consumerTag)); + + assertEquals(0, rmqConnection.channelList.size()); + + Channel channel1 = rmqConnection.createChannel(); + messageCount = channel1.messageCount(defaultQueueName); + assertEquals(0, messageCount); + + messageCount = channel1.messageCount(defaultQueueName1); + assertEquals(1, messageCount); + + channel1.basicConsume(defaultQueueName1, false, deliverCallback1, + consumerShutdownSignalCallback); + + messageCount = channel1.messageCount(defaultQueueName1); + assertEquals(0, messageCount); + + try { + String nonExistentExchangeName = "nonExistentExchangeName"; + String nonExistentBindingName = "nonExistentBindingName"; + rmqConnection.createQueue(nonExistentExchangeName, + nonExistentBindingName, channel1, null); + } catch (Exception e) { + e.printStackTrace(); + } finally { + assertTrue(connection.isOpen()); + assertFalse(channel1.isOpen()); + } + + connection.abort(); + assertFalse(connection.isOpen()); + assertFalse(channel1.isOpen()); + } + + @Test + public void semaphoreTest() throws InterruptedException { + final long[] startTime = {0}; + final long[] endTime = {0}; + + final long[] startTime1 = {0}; + final long[] endTime1 = {0}; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + SemaphoreManager.acquireSemaphore(); + Log.d(getClass().getName(), "Thread 1 acquired!"); + startTime[0] = System.currentTimeMillis(); + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + try { + SemaphoreManager.releaseSemaphore(); + Log.d(getClass().getName(), "Thread 1 released!: " + + System.currentTimeMillis()); + endTime[0] = System.currentTimeMillis(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }); + + Thread thread1 = new Thread(new Runnable() { + @Override + public void run() { + try { + Log.d(getClass().getName(), "Thread 2 requested!: " + + System.currentTimeMillis()); + SemaphoreManager.acquireSemaphore(); + Log.d(getClass().getName(), "Thread 2 acquired!: " + + System.currentTimeMillis()); + startTime1[0] = System.currentTimeMillis(); + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + try { + SemaphoreManager.releaseSemaphore(); + Log.d(getClass().getName(), "Thread 2 released!: " + + System.currentTimeMillis()); + endTime1[0] = System.currentTimeMillis(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }); + + thread.start(); + thread1.start(); + thread1.join(); + thread.join(); + + assertTrue(endTime[0] <= startTime1[0]); + } +} diff --git a/app/src/androidTest/java/java/com/afkanerd/deku/RoomMigrationTest.java b/app/src/androidTest/java/java/com/afkanerd/deku/RoomMigrationTest.java new file mode 100644 index 00000000..8b9d059c --- /dev/null +++ b/app/src/androidTest/java/java/com/afkanerd/deku/RoomMigrationTest.java @@ -0,0 +1,90 @@ +package java.com.afkanerd.deku; + +import android.content.Context; +import android.database.sqlite.SQLiteStatement; + +import androidx.room.testing.MigrationTestHelper; +import androidx.sqlite.db.SupportSQLiteDatabase; +import androidx.sqlite.db.SupportSQLiteStatement; +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) + +public class RoomMigrationTest { + private static final String TEST_DB = Datastore.databaseName; + + @Rule + public MigrationTestHelper helper; + + Context context; + public RoomMigrationTest() { + this.context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), + Datastore.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory()); + } + + @Test + public void migrate11To12Test() throws IOException { + SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 11); + String tableName = "ThreadedConversations"; + + String sql = "INSERT INTO " + tableName + " (" + + "thread_id, " + + "address, " + + "msg_count, " + + "type, " + + "date, " + + "is_archived, " + + "is_blocked, " + + "is_shortcode, " + + "is_read, " + + "snippet, " + + "contact_name, " + + "formatted_datetime, " + + "is_read" + + ") VALUES "; + + // Add each row as a separate VALUES clause + sql += "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?)"; + + // Prepare the SQL statement with placeholders + SupportSQLiteStatement statement = db.compileStatement(sql); + + // Bind values for each row + statement.bindString(1, "test_thread_id_1"); + statement.bindString(2, "test_address_1"); + statement.bindLong(3, 5); + statement.bindLong(13, 5); + statement.bindString(4, "test_address_1"); + statement.bindLong(5, 5); + statement.bindLong(6, 5); + statement.bindLong(7, 5); + statement.bindLong(8, 5); + statement.bindString(9, "test_address_1"); + statement.bindString(10, "test_address_1"); + statement.bindString(11, "test_address_1"); + statement.bindLong(12, 5); + + // Execute the statement + statement.execute(); + + // Close the statement + statement.close(); + + db = helper.runMigrationsAndValidate(TEST_DB, 12, true, + new Migrations.MIGRATION_11_12()); + } + +} diff --git a/app/src/debug/res/values/ic_launcher_background.xml b/app/src/debug/res/values/ic_launcher_background.xml index c5d5899f..a6b3daec 100644 --- a/app/src/debug/res/values/ic_launcher_background.xml +++ b/app/src/debug/res/values/ic_launcher_background.xml @@ -1,4 +1,2 @@ - - #FFFFFF - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d49bc48b..9188234c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ + + + + android:parentActivityName=".ThreadedConversationsActivity" /> + android:parentActivityName=".SettingsActivity" /> + android:parentActivityName=".ThreadedConversationsActivity" /> @@ -86,15 +92,15 @@ + android:parentActivityName=".ThreadedConversationsActivity" /> + android:parentActivityName=".ConversationActivity" /> + android:parentActivityName="com.afkanerd.deku.Router.Router.RouterActivity" /> + android:parentActivityName=".ThreadedConversationsActivity" /> + android:parentActivityName=".ThreadedConversationsActivity"> @@ -126,26 +132,23 @@ - - - @@ -156,7 +159,7 @@ @@ -166,7 +169,7 @@ @@ -178,7 +181,7 @@ @@ -191,7 +194,7 @@ @@ -201,7 +204,7 @@ @@ -210,7 +213,7 @@ @@ -218,7 +221,7 @@ @@ -228,7 +231,7 @@ @@ -245,7 +248,8 @@ + android:exported="false" + android:foregroundServiceType="dataSync" /> load( @Override public void run() { list[0] = conversationDao.getDefault(threadId); - /** - * Decrypt encrypted messages using their key - */ - String address = ""; - if(list[0].size() > 0) - address = list[0].get(0).getAddress(); - for(int i=0;i 0) +// address = list[0].get(0).getAddress(); +// for(int i=0;i(); this.threadedConversations = threadedConversations; @@ -98,7 +94,7 @@ public ConversationsRecyclerAdapter(Context context, @Override public ConversationTemplateViewHandler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // https://developer.android.com/reference/android/provider/Telephony.TextBasedSmsColumns#MESSAGE_TYPE_OUTBOX - LayoutInflater inflater = LayoutInflater.from(this.context); + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); ConversationTemplateViewHandler returnView; switch(viewType) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ConversationsViewModel.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ConversationsViewModel.java index 6d878a48..90d0f2c5 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ConversationsViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ConversationsViewModel.java @@ -4,13 +4,8 @@ import android.content.Context; import android.database.Cursor; import android.provider.Telephony; -import android.util.Log; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModel; import androidx.paging.Pager; import androidx.paging.PagingConfig; @@ -19,21 +14,18 @@ import androidx.paging.PagingSource; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; -import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; -import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; -import java.sql.Ref; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class ConversationsViewModel extends ViewModel { + public Datastore datastore; public String threadId; public String address; - public ConversationDao conversationDao; public int pageSize = 10; int prefetchDistance = 3 * pageSize; boolean enablePlaceholder = false; @@ -45,13 +37,12 @@ public class ConversationsViewModel extends ViewModel { int pointer = 0; Pager pager; - public LiveData> getSearch(Context context, ConversationDao conversationDao, - String threadId, List positions) { + public LiveData> getSearch(Context context, String threadId, + List positions) { int pageSize = 5; int prefetchDistance = 3 * pageSize; boolean enablePlaceholder = false; int initialLoadSize = 10; - this.conversationDao = conversationDao; this.threadId = threadId; this.positions = positions; @@ -64,72 +55,50 @@ public LiveData> getSearch(Context context, Conversatio return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } - PagingSource searchPagingSource; + PagingSource customPagingSource; public PagingSource getNewConversationPagingSource(Context context) { - searchPagingSource = new ConversationPagingSource(context, this.conversationDao, threadId, + customPagingSource = new ConversationPagingSource(context, datastore.conversationDao(), + threadId, pointer >= this.positions.size()-1 ? null : this.positions.get(++pointer)); - return searchPagingSource; + return customPagingSource; } - public LiveData> get(Context context, ConversationDao conversationDao, - String threadId) + public LiveData> get(String threadId) throws InterruptedException { - this.conversationDao = conversationDao; this.threadId = threadId; -// Pager pager = new Pager<>(new PagingConfig( -// pageSize, -// prefetchDistance, -// enablePlaceholder, -// initialLoadSize -// ), ()-> this.conversationDao.get(threadId)); -// return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); - pager = new Pager<>(new PagingConfig( pageSize, prefetchDistance, enablePlaceholder, initialLoadSize - ), null, ()->getNewConversationPagingSource(context)); + ), null, ()->datastore.conversationDao().get(threadId)); return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } public Conversation fetch(String messageId) throws InterruptedException { - return conversationDao.getMessage(messageId); + return datastore.conversationDao().getMessage(messageId); } - public long insert(Conversation conversation) throws InterruptedException { - long id = conversationDao.insert(conversation); - searchPagingSource.invalidate(); + public long insert(Context context, Conversation conversation) throws InterruptedException { + long id = datastore.conversationDao().insert(conversation); + ThreadedConversations threadedConversations = + ThreadedConversations.build(context, conversation); + threadedConversations.setIs_read(true); + datastore.threadedConversationsDao().update(threadedConversations); + if(customPagingSource != null) + customPagingSource.invalidate(); return id; } public void update(Conversation conversation) { - conversationDao.update(conversation); - searchPagingSource.invalidate(); - } - - public void insertFromNative(Context context, String messageId) throws InterruptedException { - Cursor cursor = NativeSMSDB.fetchByMessageId(context, messageId); - if(cursor.moveToFirst()) { - Conversation conversation = Conversation.build(cursor); - insert(conversation); - } - cursor.close(); - } - - public void updateFromNative(Context context, String messageId ) { - Cursor cursor = NativeSMSDB.fetchByMessageId(context, messageId); - if(cursor.moveToFirst()) { - Conversation conversation1 = Conversation.build(cursor); - cursor.close(); - update(conversation1); - } + datastore.conversationDao().update(conversation); + customPagingSource.invalidate(); } public List search(String input) throws InterruptedException { List positions = new ArrayList<>(); - List list = conversationDao.getAll(threadId); + List list = datastore.conversationDao().getAll(threadId); for(int i=0;i search(String input) throws InterruptedException { public void updateToRead(Context context) { if(threadId != null && !threadId.isEmpty()) { - Conversation conversation1 = new Conversation(); - ConversationDao conversationDao = conversation1.getDaoInstance(context); - - List conversations = conversationDao.getAll(threadId); + List conversations = datastore.conversationDao().getAll(threadId); List updateList = new ArrayList<>(); for(Conversation conversation : conversations) { if(!conversation.isRead()) { @@ -153,38 +119,35 @@ public void updateToRead(Context context) { updateList.add(conversation); } } - conversationDao.update(updateList); - conversation1.close(); + datastore.conversationDao().update(updateList); } } public void deleteItems(Context context, List conversations) { - Conversation conversation1 = new Conversation(); - ConversationDao conversationDao = conversation1.getDaoInstance(context); - - conversationDao.delete(conversations); + datastore.conversationDao().delete(conversations); String[] ids = new String[conversations.size()]; for(int i=0;i mDiffer = new AsyncListDiffer(this, ThreadedConversations.DIFF_CALLBACK); - public SearchConversationRecyclerAdapter(Context context) { - super(context); + public SearchConversationRecyclerAdapter() { + super(); } @Override @@ -43,12 +44,12 @@ public void onBindViewHolder(@NonNull ThreadedConversationsTemplateViewHolder ho View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View view) { - Intent singleMessageThreadIntent = new Intent(context, ConversationActivity.class); + Intent singleMessageThreadIntent = new Intent(holder.itemView.getContext(), ConversationActivity.class); singleMessageThreadIntent.putExtra(Conversation.THREAD_ID, threadId); singleMessageThreadIntent.putExtra(ConversationActivity.SEARCH_STRING, searchString); singleMessageThreadIntent.putExtra(ConversationActivity.SEARCH_INDEX, searchIndex); singleMessageThreadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(singleMessageThreadIntent); + holder.itemView.getContext().startActivity(singleMessageThreadIntent); } }; View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() { @@ -58,6 +59,7 @@ public boolean onLongClick(View view) { } }; - holder.bind(threadedConversations, onClickListener, onLongClickListener); + String defaultRegion = Helpers.getUserCountry(holder.itemView.getContext()); + holder.bind(threadedConversations, onClickListener, onLongClickListener, defaultRegion); } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/SearchViewModel.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/SearchViewModel.java index b37657cd..a911b9e3 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/SearchViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/SearchViewModel.java @@ -11,6 +11,8 @@ import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import java.util.ArrayList; import java.util.List; @@ -19,21 +21,18 @@ public class SearchViewModel extends ViewModel { MutableLiveData, Integer>> liveData; - ThreadedConversationsDao threadedConversationsDao; - String threadId; - public LiveData,Integer>> get(ThreadedConversationsDao threadedConversationsDao){ - this.threadedConversationsDao = threadedConversationsDao; + public Datastore databaseConnector; + + public LiveData,Integer>> get(){ if(this.liveData == null) { liveData = new MutableLiveData<>(); } return liveData; } - public LiveData,Integer>> getByThreadId( - ThreadedConversationsDao threadedConversationsDao, String threadId){ - this.threadedConversationsDao = threadedConversationsDao; + public LiveData,Integer>> getByThreadId(String threadId){ if(this.liveData == null) { liveData = new MutableLiveData<>(); this.threadId = threadId; @@ -42,17 +41,18 @@ public LiveData,Integer>> getByThreadId( } public void search(Context context, String input) throws InterruptedException { - Thread thread = new Thread(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { List conversations = new ArrayList<>(); Integer index = null; if(threadId == null || threadId.isEmpty()) - conversations = threadedConversationsDao.findAddresses(input); + conversations = databaseConnector.threadedConversationsDao().findAddresses(input); else { - conversations = threadedConversationsDao.findByThread(input, threadId); - ConversationDao conversationDao = new Conversation().getDaoInstance(context); - List conversationList = conversationDao.getAll(threadId); + conversations = databaseConnector.threadedConversationsDao() + .findByThread(input, threadId); + List conversationList = databaseConnector.conversationDao() + .getAll(threadId); if(!conversationList.isEmpty()) { index = conversationList.indexOf(conversationList.get(0)); } @@ -63,7 +63,6 @@ public void run() { liveData.postValue(new Pair<>(threadedConversations, index)); } }); - thread.start(); } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationRecyclerAdapter.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationRecyclerAdapter.java index 976a6cd4..b31d6058 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationRecyclerAdapter.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadedConversationRecyclerAdapter.java @@ -11,6 +11,7 @@ import androidx.lifecycle.MutableLiveData; import androidx.paging.PagingDataAdapter; +import com.afkanerd.deku.DefaultSMS.Commons.Helpers; import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; @@ -24,23 +25,12 @@ import java.util.HashMap; public class ThreadedConversationRecyclerAdapter extends PagingDataAdapter { - - Context context; - Boolean isSearch = false; public String searchString = ""; public MutableLiveData> selectedItems = new MutableLiveData<>(); - final int MESSAGE_TYPE_SENT = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_SENT; - final int MESSAGE_TYPE_INBOX = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX; - final int MESSAGE_TYPE_DRAFT = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT; - final int MESSAGE_TYPE_OUTBOX = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_OUTBOX; - final int MESSAGE_TYPE_FAILED = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED; - final int MESSAGE_TYPE_QUEUED = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_QUEUED; - public final static int RECEIVED_VIEW_TYPE = 1; public final static int RECEIVED_UNREAD_VIEW_TYPE = 2; public final static int RECEIVED_ENCRYPTED_UNREAD_VIEW_TYPE = 3; - public final static int RECEIVED_ENCRYPTED_VIEW_TYPE = 4; public final static int SENT_VIEW_TYPE = 5; public final static int SENT_UNREAD_VIEW_TYPE = 6; @@ -49,21 +39,19 @@ public class ThreadedConversationRecyclerAdapter extends PagingDataAdapter> getArchived(){ Pager pager = new Pager<>(new PagingConfig( pageSize, @@ -47,7 +46,7 @@ public LiveData> getArchived(){ enablePlaceholder, initialLoadSize, maxSize - ), ()-> this.threadedConversationsDao.getArchived()); + ), ()-> databaseConnector.threadedConversationsDao().getArchived()); return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } public LiveData> getDrafts(){ @@ -57,44 +56,78 @@ public LiveData> getDrafts(){ enablePlaceholder, initialLoadSize, maxSize - ), ()-> this.threadedConversationsDao.getThreadedDrafts( + ), ()-> databaseConnector.threadedConversationsDao().getThreadedDrafts( Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT)); return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } - public LiveData> getUnread(){ + public LiveData> getBlocked(){ Pager pager = new Pager<>(new PagingConfig( pageSize, prefetchDistance, enablePlaceholder, initialLoadSize, maxSize - ), ()-> this.threadedConversationsDao.getAllUnreadWithoutArchived()); + ), ()-> databaseConnector.threadedConversationsDao().getBlocked()); return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } - public LiveData> get(){ + public LiveData> getMuted(){ + Pager pager = new Pager<>(new PagingConfig( + pageSize, + prefetchDistance, + enablePlaceholder, + initialLoadSize, + maxSize + ), ()-> databaseConnector.threadedConversationsDao().getMuted()); + return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); + } + + PagingSource mutedPagingSource; + public LiveData> getUnread(){ Pager pager = new Pager<>(new PagingConfig( pageSize, prefetchDistance, enablePlaceholder, initialLoadSize, maxSize - ), ()-> this.threadedConversationsDao.getAllWithoutArchived()); + ), ()-> databaseConnector.threadedConversationsDao().getAllUnreadWithoutArchived()); return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } - public LiveData> getEncrypted(Context context) throws InterruptedException { + public LiveData> get(){ + try { + Pager pager = new Pager<>(new PagingConfig( + pageSize, + prefetchDistance, + enablePlaceholder, + initialLoadSize, + maxSize + ), ()-> databaseConnector.threadedConversationsDao().getAllWithoutArchived()); + return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); + } catch(Exception e) { + e.printStackTrace(); + } + return null; + } + + public String getAllExport() { + List conversations = databaseConnector.conversationDao().getComplete(); + + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setPrettyPrinting().serializeNulls(); + + Gson gson = gsonBuilder.create(); + return gson.toJson(conversations); + } + + public LiveData> getEncrypted() throws InterruptedException { List address = new ArrayList<>(); - ConversationsThreadsEncryption conversationsThreadsEncryption1 = - new ConversationsThreadsEncryption(); - ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption1.getDaoInstance(context); Thread thread = new Thread(new Runnable() { @Override public void run() { List conversationsThreadsEncryptionList = - conversationsThreadsEncryptionDao.getAll(); + Datastore.datastore.conversationsThreadsEncryptionDao().getAll(); for(ConversationsThreadsEncryption conversationsThreadsEncryption : conversationsThreadsEncryptionList) { String derivedAddress = @@ -113,42 +146,15 @@ public void run() { prefetchDistance, enablePlaceholder, initialLoadSize - ), ()-> this.threadedConversationsDao.getByAddress(address)); - return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); - } - - public LiveData> getNotEncrypted(Context context) throws InterruptedException { - List address = new ArrayList<>(); - ConversationsThreadsEncryption conversationsThreadsEncryption1 = - new ConversationsThreadsEncryption(); - ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption1.getDaoInstance(context); - List conversationsThreadsEncryptionList = - conversationsThreadsEncryptionDao.getAll(); - - for(ConversationsThreadsEncryption conversationsThreadsEncryption : - conversationsThreadsEncryptionList) { - String derivedAddress = - E2EEHandler.getAddressFromKeystore( - conversationsThreadsEncryption.getKeystoreAlias()); - address.add(derivedAddress); - } - Pager pager = new Pager<>(new PagingConfig( - pageSize, - prefetchDistance, - enablePlaceholder, - initialLoadSize - ), ()-> this.threadedConversationsDao.getNotInAddress(address)); + ), ()-> databaseConnector.threadedConversationsDao().getByAddress(address)); return PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), this); } - public void insert(ThreadedConversations threadedConversations) { - threadedConversationsDao.insert(threadedConversations); + databaseConnector.threadedConversationsDao().insert(threadedConversations); } public void reset(Context context) { - Conversation conversation = new Conversation(); Cursor cursor = NativeSMSDB.fetchAll(context); List conversationList = new ArrayList<>(); @@ -159,32 +165,23 @@ public void reset(Context context) { cursor.close(); } - ConversationDao conversationDao = conversation.getDaoInstance(context); - conversationDao.insertAll(conversationList); - - ThreadedConversations threadedConversations = new ThreadedConversations(); - ThreadedConversationsDao threadedConversationsDao1 = - threadedConversations.getDaoInstance(context); - threadedConversationsDao1.deleteAll(); - threadedConversations.close(); + databaseConnector.conversationDao().insertAll(conversationList); + databaseConnector.threadedConversationsDao().deleteAll(); refresh(context); } public void archive(List archiveList) { - threadedConversationsDao.archive(archiveList); + databaseConnector.threadedConversationsDao().archive(archiveList); } public void delete(Context context, List ids) { - Conversation conversation = new Conversation(); - ConversationDao conversationDao = conversation.getDaoInstance(context); - conversationDao.deleteAll(ids); - threadedConversationsDao.delete(ids); + databaseConnector.conversationDao().deleteAll(ids); + databaseConnector.threadedConversationsDao().delete(ids); NativeSMSDB.deleteThreads(context, ids.toArray(new String[0])); - conversation.close(); } - public void refresh(Context context) { + private void refresh(Context context) { List newThreadedConversationsList = new ArrayList<>(); Cursor cursor = context.getContentResolver().query( Telephony.Threads.CONTENT_URI, @@ -195,11 +192,10 @@ public void refresh(Context context) { ); List threadedDraftsList = - threadedConversationsDao.getThreadedDraftsList( + databaseConnector.threadedConversationsDao().getThreadedDraftsList( Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); - List archivedThreads = threadedConversationsDao.getArchivedList(); - + List archivedThreads = databaseConnector.threadedConversationsDao().getArchivedList(); List threadsIdsInDrafts = new ArrayList<>(); for(ThreadedConversations threadedConversations : threadedDraftsList) threadsIdsInDrafts.add(threadedConversations.getThread_id()); @@ -211,7 +207,7 @@ public void refresh(Context context) { m_cls, d_rpt, v, person, service_center, error_code, _id, m_type, status] */ List threadedConversationsList = - threadedConversationsDao.getAll(); + databaseConnector.threadedConversationsDao().getAll(); if(cursor != null && cursor.moveToFirst()) { do { ThreadedConversations threadedConversations = new ThreadedConversations(); @@ -243,6 +239,8 @@ public void refresh(Context context) { threadedConversations.setType(cursor.getInt(typeIndex)); threadedConversations.setDate(cursor.getString(dateIndex)); } + if(BlockedNumberContract.isBlocked(context, threadedConversations.getAddress())) + threadedConversations.setIs_blocked(true); threadedConversations.setIs_archived( archivedThreads.contains(threadedConversations.getThread_id())); @@ -262,58 +260,78 @@ public void refresh(Context context) { } while(cursor.moveToNext()); cursor.close(); } - threadedConversationsDao.insertAll(newThreadedConversationsList); - getCount(); + databaseConnector.threadedConversationsDao().insertAll(newThreadedConversationsList); + getCount(context); } public void unarchive(List archiveList) { - threadedConversationsDao.unarchive(archiveList); + databaseConnector.threadedConversationsDao().unarchive(archiveList); + } + + public void unblock(Context context, List threadIds) { + List threadedConversationsList = + databaseConnector.threadedConversationsDao().getList(threadIds); + for(ThreadedConversations threadedConversations : threadedConversationsList) { + BlockedNumberContract.unblock(context, threadedConversations.getAddress()); + } + refresh(context); } public void clearDrafts(Context context) { SMSDatabaseWrapper.deleteAllDraft(context); - threadedConversationsDao.clearDrafts(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); + databaseConnector.threadedConversationsDao() + .clearDrafts(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); refresh(context); } public boolean hasUnread(List ids) { - return threadedConversationsDao.getAllUnreadWithoutArchivedCount(ids) > 0; + return databaseConnector.threadedConversationsDao().getCountUnread(ids) > 0; } public void markUnRead(Context context, List threadIds) { NativeSMSDB.Incoming.update_all_read(context, 0, threadIds.toArray(new String[0])); - threadedConversationsDao.updateRead(0, threadIds); + databaseConnector.threadedConversationsDao().updateRead(0, threadIds); refresh(context); } public void markRead(Context context, List threadIds) { NativeSMSDB.Incoming.update_all_read(context, 1, threadIds.toArray(new String[0])); - threadedConversationsDao.updateRead(1, threadIds); - refresh(context); - } - - public void markAllUnRead(Context context) { - NativeSMSDB.Incoming.update_all_read(context, 0); - threadedConversationsDao.updateRead(0); + databaseConnector.threadedConversationsDao().updateRead(1, threadIds); refresh(context); } public void markAllRead(Context context) { NativeSMSDB.Incoming.update_all_read(context, 1); - threadedConversationsDao.updateRead(1); + databaseConnector.threadedConversationsDao().updateRead(1); refresh(context); } public MutableLiveData> folderMetrics = new MutableLiveData<>(); - private void getCount() { - int draftsListCount = threadedConversationsDao + public void getCount(Context context) { + int draftsListCount = databaseConnector.threadedConversationsDao() .getThreadedDraftsListCount( Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); - int encryptedCount = threadedConversationsDao.getAllEncryptedCount(); - int unreadCount = threadedConversationsDao.getAllUnreadWithoutArchivedCount(); + int encryptedCount = databaseConnector.threadedConversationsDao().getCountEncrypted(); + int unreadCount = databaseConnector.threadedConversationsDao().getCountUnread(); + int blockedCount = databaseConnector.threadedConversationsDao().getCountBlocked(); + int mutedCount = databaseConnector.threadedConversationsDao().getCountMuted(); List list = new ArrayList<>(); list.add(draftsListCount); list.add(encryptedCount); list.add(unreadCount); + list.add(blockedCount); + list.add(mutedCount); folderMetrics.postValue(list); } + + public void unMute(List threadIds) { + databaseConnector.threadedConversationsDao().updateMuted(0, threadIds); + } + + public void mute(List threadIds) { + databaseConnector.threadedConversationsDao().updateMuted(1, threadIds); + } + + public void unMuteAll() { + databaseConnector.threadedConversationsDao().updateUnMuteAll(); + } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadsPagingSource.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadsPagingSource.java deleted file mode 100644 index 5dc9bd3f..00000000 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/AdaptersViewModels/ThreadsPagingSource.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.afkanerd.deku.DefaultSMS.AdaptersViewModels; - -import android.content.Context; -import android.database.Cursor; -import android.provider.Telephony; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.paging.PagingSource; -import androidx.paging.PagingState; -import androidx.room.Room; -import androidx.room.RoomDatabase; - -import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; -import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; -import com.afkanerd.deku.DefaultSMS.Models.Contacts; -import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; -import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; -import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; -import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; - -import java.util.ArrayList; -import java.util.List; - -import kotlin.coroutines.Continuation; - -public class ThreadsPagingSource extends PagingSource { - - Context context; - public ThreadsPagingSource(Context context) { - this.context = context; - } - - @Nullable - @Override - public Integer getRefreshKey(@NonNull PagingState state) { - // Try to find the page key of the closest page to anchorPosition from - // either the prevKey or the nextKey; you need to handle nullability - // here. - // * prevKey == null -> anchorPage is the first page. - // * nextKey == null -> anchorPage is the last page. - // * both prevKey and nextKey are null -> anchorPage is the - // initial page, so return null. - Integer anchorPosition = state.getAnchorPosition(); - if (anchorPosition == null) { - return null; - } - - LoadResult.Page anchorPage = state.closestPageToPosition(anchorPosition); - if (anchorPage == null) { - return null; - } - - Integer prevKey = anchorPage.getPrevKey(); - if (prevKey != null) { - return prevKey + 1; - } - - Integer nextKey = anchorPage.getNextKey(); - if (nextKey != null) { - return nextKey - 1; - } - - return null; - } - - @Nullable - @Override - public Object load(@NonNull LoadParams loadParams, @NonNull Continuation> continuation) { - Cursor cursor = context.getContentResolver().query( - Telephony.Threads.CONTENT_URI, - null, - null, - null, - "date DESC" - ); - - - List threadedConversationsList = new ArrayList<>(); - - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - ThreadedConversations tc = new ThreadedConversations(); - ThreadedConversationsDao threadedConversationsDao = tc.getDaoInstance(context); - List threadedDraftsList = - threadedConversationsDao.getThreadedDraftsList(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT); - tc.close(); - List threadIds = new ArrayList<>(); - for(ThreadedConversations threadedConversations : threadedDraftsList) - threadIds.add(threadedConversations.getThread_id()); - Log.d(getClass().getName(), "# drafts: " + threadedDraftsList.size()); - - if(cursor != null && cursor.moveToFirst()) { - do { - int recipientIdIndex = cursor.getColumnIndex("address"); - int snippetIndex = cursor.getColumnIndex("body"); - int dateIndex = cursor.getColumnIndex("date"); - int threadIdIndex = cursor.getColumnIndex("thread_id"); - int typeIndex = cursor.getColumnIndex("type"); - int readIndex = cursor.getColumnIndex("read"); - - ThreadedConversations threadedConversations = new ThreadedConversations(); - threadedConversations.setAddress(cursor.getString(recipientIdIndex)); - if(threadedConversations.getAddress() == null || threadedConversations.getAddress().isEmpty()) - continue; - threadedConversations.setThread_id(cursor.getString(threadIdIndex)); - if(threadIds.contains(threadedConversations.getThread_id())) { - threadedConversations.setSnippet(threadedDraftsList.get(threadIds - .indexOf(threadedConversations.getThread_id())) - .getSnippet()); - threadedConversations.setType(threadedDraftsList.get(threadIds - .indexOf(threadedConversations.getThread_id())) - .getType()); - } - else { - threadedConversations.setSnippet(cursor.getString(snippetIndex)); - threadedConversations.setType(cursor.getInt(typeIndex)); - } - String contactName = Contacts.retrieveContactName(context, - threadedConversations.getAddress()); - threadedConversations.setContact_name(contactName); - threadedConversations.setDate(cursor.getString(dateIndex)); - threadedConversations.setType(cursor.getInt(typeIndex)); - threadedConversations.setIs_read(cursor.getInt(readIndex) == 1); - threadedConversationsList.add(threadedConversations); - } while(cursor.moveToNext()); - } - } - }); - thread.start(); - try { - thread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - if(cursor != null) - cursor.close(); - - return new LoadResult.Page<>(threadedConversationsList, - null, - null, -// loadParams.getKey() != null ? loadParams.getKey() + 1 : null, - LoadResult.Page.COUNT_UNDEFINED, - LoadResult.Page.COUNT_UNDEFINED); - } - -} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java index 21802459..32030bcf 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingDataSMSBroadcastReceiver.java @@ -8,11 +8,17 @@ import android.util.Base64; import android.util.Log; +import androidx.room.Room; + import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; +import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.BuildConfig; import com.afkanerd.deku.DefaultSMS.Models.NotificationsHandler; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.E2EE.E2EEHandler; import com.google.i18n.phonenumbers.NumberParseException; @@ -25,6 +31,8 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class IncomingDataSMSBroadcastReceiver extends BroadcastReceiver { @@ -39,12 +47,23 @@ public class IncomingDataSMSBroadcastReceiver extends BroadcastReceiver { public static String DATA_UPDATED_BROADCAST_INTENT = BuildConfig.APPLICATION_ID + ".DATA_UPDATED_BROADCAST_INTENT"; + ExecutorService executorService = Executors.newFixedThreadPool(4); + + Datastore databaseConnector; @Override public void onReceive(Context context, Intent intent) { /** * Important note: either image or dump it */ + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; + if (intent.getAction().equals(Telephony.Sms.Intents.DATA_SMS_RECEIVED_ACTION)) { if (getResultCode() == Activity.RESULT_OK) { @@ -73,10 +92,11 @@ public void onReceive(Context context, Intent intent) { conversation.setDate(dateSent); conversation.setDate(date); - ConversationDao conversationDao = conversation.getDaoInstance(context); - Thread thread = new Thread(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { + databaseConnector.conversationDao().insert(conversation); + if(isValidKey) { try { processForEncryptionKey(context, conversation); @@ -86,43 +106,31 @@ public void run() { } } - conversationDao.insert(conversation); - - NotificationsHandler.sendIncomingTextMessageNotification(context, - conversation); - Intent broadcastIntent = new Intent(DATA_DELIVER_ACTION); broadcastIntent.putExtra(Conversation.ID, messageId); broadcastIntent.putExtra(Conversation.THREAD_ID, threadId); context.sendBroadcast(broadcastIntent); + + ThreadedConversations threadedConversations = + databaseConnector.threadedConversationsDao().get(threadId); + if(!threadedConversations.isIs_mute()) + NotificationsHandler.sendIncomingTextMessageNotification(context, + conversation); } }); - thread.start(); - thread.join(); - conversation.close(); - } catch (IOException | InterruptedException e) { + } catch (IOException e) { e.printStackTrace(); } } } } - boolean processForEncryptionKey(Context context, Conversation conversation) throws NumberParseException, GeneralSecurityException, IOException, InterruptedException, JSONException { + void processForEncryptionKey(Context context, Conversation conversation) throws NumberParseException, GeneralSecurityException, IOException, InterruptedException, JSONException { byte[] data = Base64.decode(conversation.getData(), Base64.DEFAULT); - boolean isValidKey = E2EEHandler.isValidDefaultPublicKey(data); - - if(isValidKey) { - String keystoreAlias = E2EEHandler.deriveKeystoreAlias(conversation.getAddress(), 0); - byte[] extractedTransmissionKey = E2EEHandler.extractTransmissionKey(data); + String keystoreAlias = E2EEHandler.deriveKeystoreAlias(conversation.getAddress(), 0); + byte[] extractedTransmissionKey = E2EEHandler.extractTransmissionKey(data); - E2EEHandler.insertNewAgreementKeyDefault(context, extractedTransmissionKey, keystoreAlias); - -// if(E2EEHandler.getKeyType(context, keystoreAlias, extractedTransmissionKey) == -// E2EEHandler.AGREEMENT_KEY) { -// E2EEHandler.insertNewPeerPublicKey(context, extractedTransmissionKey, keystoreAlias); -// } - } - return isValidKey; + E2EEHandler.insertNewAgreementKeyDefault(context, extractedTransmissionKey, keystoreAlias); } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java index bb309c9f..e88b2c82 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSBroadcastReceiver.java @@ -12,22 +12,32 @@ import android.provider.Telephony; import android.util.Log; +import androidx.room.Room; + import com.afkanerd.deku.DefaultSMS.BuildConfig; +import com.afkanerd.deku.DefaultSMS.Commons.Helpers; import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; +import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ConversationHandler; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.Database.SemaphoreManager; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.Models.NotificationsHandler; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.E2EE.E2EEHandler; +import com.afkanerd.deku.Router.GatewayServers.GatewayServerHandler; import com.afkanerd.deku.Router.Router.RouterItem; import com.afkanerd.deku.Router.Router.RouterHandler; +import org.checkerframework.checker.units.qual.C; + import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class IncomingTextSMSBroadcastReceiver extends BroadcastReceiver { - Context context; - public static final String TAG_NAME = "RECEIVED_SMS_ROUTING"; public static final String TAG_ROUTING_URL = "swob.work.route.url,"; @@ -54,9 +64,17 @@ public class IncomingTextSMSBroadcastReceiver extends BroadcastReceiver { ExecutorService executorService = Executors.newFixedThreadPool(4); + Datastore databaseConnector; + @Override public void onReceive(Context context, Intent intent) { - this.context = context; + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; if (intent.getAction().equals(Telephony.Sms.Intents.SMS_DELIVER_ACTION)) { if (getResultCode() == Activity.RESULT_OK) { @@ -64,54 +82,18 @@ public void onReceive(Context context, Intent intent) { final String[] regIncomingOutput = NativeSMSDB.Incoming.register_incoming_text(context, intent); if(regIncomingOutput != null) { final String messageId = regIncomingOutput[NativeSMSDB.MESSAGE_ID]; - final String incomingText = regIncomingOutput[NativeSMSDB.BODY]; + final String body = regIncomingOutput[NativeSMSDB.BODY]; final String threadId = regIncomingOutput[NativeSMSDB.THREAD_ID]; final String address = regIncomingOutput[NativeSMSDB.ADDRESS]; final String date = regIncomingOutput[NativeSMSDB.DATE]; final String dateSent = regIncomingOutput[NativeSMSDB.DATE_SENT]; - final int subscriptionId = Integer.parseInt(regIncomingOutput[NativeSMSDB.SUBSCRIPTION_ID]); - - executorService.execute(new Runnable() { - @Override - public void run() { - String text = incomingText; - try { - text = processEncryptedIncoming(context, address, incomingText); - } catch (Throwable e) { - e.printStackTrace(); - } - Conversation conversation = new Conversation(); - conversation.setMessage_id(messageId); - conversation.setText(text); - conversation.setThread_id(threadId); - conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX); - conversation.setAddress(address); - conversation.setSubscription_id(subscriptionId); - conversation.setDate(date); - conversation.setDate_sent(dateSent); - - try { - conversation.getDaoInstance(context).insert(conversation); - }catch (Exception e) { - e.printStackTrace(); - } - - Intent broadcastIntent = new Intent(SMS_DELIVER_ACTION); - broadcastIntent.putExtra(Conversation.ID, messageId); - context.sendBroadcast(broadcastIntent); - - NotificationsHandler.sendIncomingTextMessageNotification(context, - conversation); - } - }); - - executorService.execute(new Runnable() { - @Override - public void run() { -// handleEncryption(text); - router_activities(messageId); - } - }); + final int subscriptionId = + Integer.parseInt(regIncomingOutput[NativeSMSDB.SUBSCRIPTION_ID]); + + insertConversation(context, address, messageId, threadId, body, + subscriptionId, date, dateSent); + + router_activities(context, messageId); } } catch (IOException e) { e.printStackTrace(); @@ -124,10 +106,8 @@ else if(intent.getAction().equals(SMS_SENT_BROADCAST_INTENT)) { @Override public void run() { String id = intent.getStringExtra(NativeSMSDB.ID); - Conversation conversation1 = new Conversation(); - ConversationDao conversationDao = conversation1.getDaoInstance(context); - Conversation conversation = conversationDao.getMessage(id); + Conversation conversation = databaseConnector.conversationDao().getMessage(id); if(conversation == null) return; @@ -145,12 +125,12 @@ public void run() { e.printStackTrace(); } } - conversationDao.update(conversation); - conversation1.close(); + databaseConnector.conversationDao().update(conversation); Intent broadcastIntent = new Intent(SMS_UPDATED_BROADCAST_INTENT); broadcastIntent.putExtra(Conversation.ID, conversation.getMessage_id()); broadcastIntent.putExtra(Conversation.THREAD_ID, conversation.getThread_id()); + if(intent.getExtras() != null) broadcastIntent.putExtras(intent.getExtras()); @@ -158,14 +138,16 @@ public void run() { } }); } + else if(intent.getAction().equals(SMS_DELIVERED_BROADCAST_INTENT)) { executorService.execute(new Runnable() { @Override public void run() { String id = intent.getStringExtra(NativeSMSDB.ID); - Conversation conversation1 = new Conversation(); - ConversationDao conversationDao = conversation1.getDaoInstance(context); - Conversation conversation = conversationDao.getMessage(id); + + Conversation conversation = databaseConnector.conversationDao().getMessage(id); + if(conversation == null) + return; if (getResultCode() == Activity.RESULT_OK) { NativeSMSDB.Outgoing.register_delivered(context, id); @@ -176,8 +158,8 @@ public void run() { conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED); conversation.setError_code(getResultCode()); } - conversationDao.update(conversation); - conversation1.close(); + + databaseConnector.conversationDao().update(conversation); Intent broadcastIntent = new Intent(SMS_UPDATED_BROADCAST_INTENT); broadcastIntent.putExtra(Conversation.ID, conversation.getMessage_id()); @@ -190,15 +172,12 @@ public void run() { }); } - else if(intent.getAction().equals(DATA_SENT_BROADCAST_INTENT)) { executorService.execute(new Runnable() { @Override public void run() { String id = intent.getStringExtra(NativeSMSDB.ID); - Conversation conversation1 = new Conversation(); - ConversationDao conversationDao = conversation1.getDaoInstance(context); - Conversation conversation = conversationDao.getMessage(id); + Conversation conversation = databaseConnector.conversationDao().getMessage(id); if (getResultCode() == Activity.RESULT_OK) { conversation.setStatus(Telephony.TextBasedSmsColumns.STATUS_NONE); @@ -208,8 +187,7 @@ public void run() { conversation.setError_code(getResultCode()); conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED); } - conversationDao.update(conversation); - conversation1.close(); + databaseConnector.conversationDao().update(conversation); Intent broadcastIntent = new Intent(DATA_UPDATED_BROADCAST_INTENT); broadcastIntent.putExtra(Conversation.ID, conversation.getMessage_id()); @@ -219,14 +197,13 @@ public void run() { } }); } + else if(intent.getAction().equals(DATA_DELIVERED_BROADCAST_INTENT)) { executorService.execute(new Runnable() { @Override public void run() { String id = intent.getStringExtra(NativeSMSDB.ID); - Conversation conversation1 = new Conversation(); - ConversationDao conversationDao = conversation1.getDaoInstance(context); - Conversation conversation = conversationDao.getMessage(id); + Conversation conversation = databaseConnector.conversationDao().getMessage(id); if (getResultCode() == Activity.RESULT_OK) { conversation.setStatus(Telephony.TextBasedSmsColumns.STATUS_COMPLETE); @@ -237,8 +214,7 @@ public void run() { conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED); } - conversationDao.update(conversation); - conversation1.close(); + databaseConnector.conversationDao().update(conversation); Intent broadcastIntent = new Intent(DATA_UPDATED_BROADCAST_INTENT); broadcastIntent.putExtra(Conversation.ID, conversation.getMessage_id()); @@ -250,6 +226,61 @@ public void run() { } } + public void insertThreads(Context context, Conversation conversation) { + ThreadedConversations threadedConversations = + ThreadedConversations.build(context, conversation); + String contactName = Contacts.retrieveContactName(context, conversation.getAddress()); + threadedConversations.setContact_name(contactName); + databaseConnector.threadedConversationsDao().insert(threadedConversations); + } + + public void insertConversation(Context context, String address, String messageId, + String threadId, String body, int subscriptionId, String date, + String dateSent) { + + Conversation conversation = new Conversation(); + conversation.setMessage_id(messageId); + conversation.setThread_id(threadId); + conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX); + conversation.setAddress(address); + conversation.setSubscription_id(subscriptionId); + conversation.setDate(date); + conversation.setDate_sent(dateSent); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + String text = body; + try { + text = processEncryptedIncoming(context, address, body); + } catch (Throwable e) { + e.printStackTrace(); + } + conversation.setText(text); + + try { + databaseConnector.conversationDao().insert(conversation); + insertThreads(context, conversation); + } catch (Exception e) { + e.printStackTrace(); + } + + Intent broadcastIntent = new Intent(SMS_DELIVER_ACTION); + broadcastIntent.putExtra(Conversation.ID, messageId); + context.sendBroadcast(broadcastIntent); + +// String defaultRegion = Helpers.getUserCountry(context); +// String e16Address = Helpers.getFormatCompleteNumber(address, defaultRegion); + ThreadedConversations threadedConversations = + databaseConnector.threadedConversationsDao().get(threadId); + if(!threadedConversations.isIs_mute()) + NotificationsHandler.sendIncomingTextMessageNotification(context, + conversation); + + } + }); + } + + public String processEncryptedIncoming(Context context, String address, String text) throws Throwable { if(E2EEHandler.isValidDefaultText(text)) { String keystoreAlias = E2EEHandler.deriveKeystoreAlias(address, 0); @@ -259,13 +290,14 @@ public String processEncryptedIncoming(Context context, String address, String t return text; } - public void router_activities(String messageId) { + public void router_activities(Context context, String messageId) { try { Cursor cursor = NativeSMSDB.fetchByMessageId(context, messageId); if(cursor.moveToFirst()) { + GatewayServerHandler gatewayServerHandler = new GatewayServerHandler(context); RouterItem routerItem = new RouterItem(cursor); + RouterHandler.route(context, routerItem, gatewayServerHandler); cursor.close(); - RouterHandler.route(context, routerItem); } } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyActionBroadcastReceiver.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyActionBroadcastReceiver.java index 78422416..d2eaf42e 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyActionBroadcastReceiver.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/BroadcastReceivers/IncomingTextSMSReplyActionBroadcastReceiver.java @@ -19,19 +19,25 @@ import androidx.core.app.NotificationManagerCompat; import androidx.core.app.Person; import androidx.core.app.RemoteInput; +import androidx.room.Room; import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; +import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.BuildConfig; import com.afkanerd.deku.DefaultSMS.Models.NotificationsHandler; import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.DefaultSMS.R; public class IncomingTextSMSReplyActionBroadcastReceiver extends BroadcastReceiver { public static String REPLY_BROADCAST_INTENT = BuildConfig.APPLICATION_ID + ".REPLY_BROADCAST_ACTION"; public static String MARK_AS_READ_BROADCAST_INTENT = BuildConfig.APPLICATION_ID + ".MARK_AS_READ_BROADCAST_ACTION"; + public static String MUTE_BROADCAST_INTENT = BuildConfig.APPLICATION_ID + ".MUTE_BROADCAST_ACTION"; public static String REPLY_ADDRESS = "REPLY_ADDRESS"; public static String REPLY_THREAD_ID = "REPLY_THREAD_ID"; @@ -40,8 +46,19 @@ public class IncomingTextSMSReplyActionBroadcastReceiver extends BroadcastReceiv // Key for the string that's delivered in the action's intent. public static final String KEY_TEXT_REPLY = "KEY_TEXT_REPLY"; + Datastore databaseConnector; + @Override public void onReceive(Context context, Intent intent) { + + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; + if (intent.getAction() != null && intent.getAction().equals(REPLY_BROADCAST_INTENT)) { Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); if (remoteInput != null) { @@ -65,65 +82,73 @@ public void onReceive(Context context, Intent intent) { conversation.setDate(String.valueOf(System.currentTimeMillis())); conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_OUTBOX); conversation.setStatus(Telephony.TextBasedSmsColumns.STATUS_PENDING); - ConversationDao conversationDao = conversation.getDaoInstance(context); - Thread thread = new Thread(new Runnable() { + + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { - conversationDao.insert(conversation); - } - }); - thread.start(); + try { + databaseConnector.conversationDao().insert(conversation); + + SMSDatabaseWrapper.send_text(context, conversation, null); + Intent broadcastIntent = new Intent(SMS_UPDATED_BROADCAST_INTENT); + broadcastIntent.putExtra(Conversation.ID, conversation.getMessage_id()); + broadcastIntent.putExtra(Conversation.THREAD_ID, conversation.getThread_id()); + if(intent.getExtras() != null) + broadcastIntent.putExtras(intent.getExtras()); + + context.sendBroadcast(broadcastIntent); - try { - thread.join(); + NotificationCompat.MessagingStyle messagingStyle = + NotificationsHandler.getMessagingStyle(context, conversation, reply.toString()); - SMSDatabaseWrapper.send_text(context, conversation, null); - Intent broadcastIntent = new Intent(SMS_UPDATED_BROADCAST_INTENT); - broadcastIntent.putExtra(Conversation.ID, conversation.getMessage_id()); - broadcastIntent.putExtra(Conversation.THREAD_ID, conversation.getThread_id()); - if(intent.getExtras() != null) - broadcastIntent.putExtras(intent.getExtras()); + Intent replyIntent = NotificationsHandler.getReplyIntent(context, conversation); + PendingIntent pendingIntent = NotificationsHandler.getPendingIntent(context, conversation); - context.sendBroadcast(broadcastIntent); - } catch (Exception e) { - e.printStackTrace(); - } + NotificationCompat.Builder builder = + NotificationsHandler.getNotificationBuilder(context, replyIntent, + conversation, pendingIntent); - NotificationCompat.MessagingStyle messagingStyle = - NotificationsHandler.getMessagingStyle(context, conversation, reply.toString()); + builder.setStyle(messagingStyle); + NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context); + notificationManagerCompat.notify(Integer.parseInt(threadId), builder.build()); - Intent replyIntent = NotificationsHandler.getReplyIntent(context, conversation); - PendingIntent pendingIntent = NotificationsHandler.getPendingIntent(context, conversation); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); - NotificationCompat.Builder builder = - NotificationsHandler.getNotificationBuilder(context, replyIntent, - conversation, pendingIntent); - builder.setStyle(messagingStyle); - NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context); - notificationManagerCompat.notify(Integer.parseInt(threadId), builder.build()); - conversation.close(); } } else if(intent.getAction() != null && intent.getAction().equals(MARK_AS_READ_BROADCAST_INTENT)) { - String threadId = intent.getStringExtra(Conversation.THREAD_ID); - String messageId = intent.getStringExtra(Conversation.ID); + final String threadId = intent.getStringExtra(Conversation.THREAD_ID); + final String messageId = intent.getStringExtra(Conversation.ID); try { - NativeSMSDB.Incoming.update_read(context, 1, threadId, null); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + NativeSMSDB.Incoming.update_read(context, 1, threadId, null); + databaseConnector.threadedConversationsDao().updateRead(1, + Long.parseLong(threadId)); + } + }); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); notificationManager.cancel(Integer.parseInt(threadId)); - - Intent broadcastIntent = new Intent(SMS_UPDATED_BROADCAST_INTENT); - broadcastIntent.putExtra(Conversation.ID, messageId); - broadcastIntent.putExtra(Conversation.THREAD_ID, threadId); - if(intent.getExtras() != null) - broadcastIntent.putExtras(intent.getExtras()); - - context.sendBroadcast(broadcastIntent); } catch(Exception e) { e.printStackTrace(); } } + + else if(intent.getAction() != null && intent.getAction().equals(MUTE_BROADCAST_INTENT)) { + String threadId = intent.getStringExtra(Conversation.THREAD_ID); + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + notificationManager.cancel(Integer.parseInt(threadId)); + + databaseConnector.threadedConversationsDao().updateMuted(1, threadId); + } } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java index 923e2fd4..04dd7553 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ConversationActivity.java @@ -1,13 +1,19 @@ package com.afkanerd.deku.DefaultSMS; +import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; import android.os.Bundle; +import android.provider.BlockedNumberContract; import android.provider.Telephony; +import android.telecom.TelecomManager; import android.telephony.SmsManager; import android.text.Editable; import android.text.TextWatcher; @@ -21,7 +27,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.Toolbar; @@ -34,18 +39,21 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingDataSMSBroadcastReceiver; +import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver; import com.afkanerd.deku.DefaultSMS.Commons.Helpers; import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; -import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsRecyclerAdapter; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversationsHandler; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ViewHolders.ConversationTemplateViewHandler; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; +import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.E2EE.E2EECompactActivity; -import com.afkanerd.deku.E2EE.E2EEHandler; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputLayout; @@ -53,7 +61,6 @@ import java.io.IOException; import java.security.GeneralSecurityException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -78,7 +85,6 @@ public class ConversationActivity extends E2EECompactActivity { LinearLayoutManager linearLayoutManager; RecyclerView singleMessagesThreadRecyclerView; - MutableLiveData> searchPositions = new MutableLiveData<>(); ImageButton backSearchBtn; @@ -86,6 +92,8 @@ public class ConversationActivity extends E2EECompactActivity { Toolbar toolbar; + BroadcastReceiver broadcastReceiver; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -104,7 +112,6 @@ protected void onCreate(Bundle savedInstanceState) { configureMessagesTextBox(); configureLayoutForMessageType(); - configureBroadcastListeners(); } catch (Exception e) { e.printStackTrace(); } @@ -132,16 +139,18 @@ protected void onResume() { TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); layout.requestFocus(); - if(threadedConversations.secured) + if(threadedConversations.is_secured) layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { NativeSMSDB.Incoming.update_read(getApplicationContext(), 1, threadedConversations.getThread_id(), null); conversationsViewModel.updateToRead(getApplicationContext()); + threadedConversations.setIs_read(true); + databaseConnector.threadedConversationsDao().update(threadedConversations); }catch (Exception e) { e.printStackTrace(); } @@ -160,6 +169,10 @@ public boolean onCreateOptionsMenu(Menu menu) { } catch (Exception e) { e.printStackTrace(); } + if(threadedConversations.isIs_mute()) { + menu.findItem(R.id.conversations_menu_unmute).setVisible(true); + menu.findItem(R.id.conversations_menu_mute).setVisible(false); + } return super.onCreateOptionsMenu(menu); } @@ -181,10 +194,54 @@ else if(R.id.conversation_main_menu_search == item.getItemId()) { intent.putExtra(Conversation.THREAD_ID, threadedConversations.getThread_id()); startActivity(intent); } -// if(isSearchActive()) { -// resetSearch(); -// return true; -// } + else if (R.id.conversations_menu_block == item.getItemId()) { + blockContact(); + if(actionMode != null) + actionMode.finish(); + return true; + } + else if (R.id.conversations_menu_mute == item.getItemId()) { + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + conversationsViewModel.mute(); + threadedConversations.setIs_mute(true); + invalidateMenu(); + runOnUiThread(new Runnable() { + @Override + public void run() { + configureToolbars(); + Toast.makeText(getApplicationContext(), getString(R.string.conversation_menu_muted), + Toast.LENGTH_SHORT).show(); + if(actionMode != null) + actionMode.finish(); + } + }); + } + }); + return true; + } + else if (R.id.conversations_menu_unmute == item.getItemId()) { + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + conversationsViewModel.unMute(); + threadedConversations.setIs_mute(false); + invalidateMenu(); + runOnUiThread(new Runnable() { + @Override + public void run() { + configureToolbars(); + Toast.makeText(getApplicationContext(), getString(R.string.conversation_menu_unmuted), + Toast.LENGTH_SHORT).show(); + if(actionMode != null) + actionMode.finish(); + } + }); + } + }); + return true; + } return super.onOptionsItemSelected(item); } @@ -214,8 +271,8 @@ private void configureActivityDependencies() throws Exception { if(getIntent().hasExtra(Conversation.THREAD_ID)) { ThreadedConversations threadedConversations = new ThreadedConversations(); threadedConversations.setThread_id(getIntent().getStringExtra(Conversation.THREAD_ID)); - this.threadedConversations = ThreadedConversationsHandler.get(getApplicationContext(), - threadedConversations); + this.threadedConversations = ThreadedConversationsHandler.get( + databaseConnector.threadedConversationsDao(), threadedConversations); } else if(getIntent().hasExtra(Conversation.ADDRESS)) { ThreadedConversations threadedConversations = new ThreadedConversations(); @@ -273,11 +330,11 @@ private void instantiateGlobals() throws GeneralSecurityException, IOException { linearLayoutManager.setReverseLayout(true); singleMessagesThreadRecyclerView.setLayoutManager(linearLayoutManager); - conversationsRecyclerAdapter = new ConversationsRecyclerAdapter(getApplicationContext(), - threadedConversations); + conversationsRecyclerAdapter = new ConversationsRecyclerAdapter(threadedConversations); conversationsViewModel = new ViewModelProvider(this) .get(ConversationsViewModel.class); + conversationsViewModel.datastore = Datastore.datastore; backSearchBtn.setOnClickListener(new View.OnClickListener() { @Override @@ -314,7 +371,6 @@ public void onChanged(List integers) { } - ConversationDao conversationDao; boolean firstScrollInitiated = false; LifecycleOwner lifecycleOwner; @@ -323,7 +379,6 @@ public void onChanged(List integers) { private void configureRecyclerView() throws InterruptedException { singleMessagesThreadRecyclerView.setAdapter(conversationsRecyclerAdapter); singleMessagesThreadRecyclerView.setItemViewCacheSize(500); - conversationDao = conversation.getDaoInstance(getApplicationContext()); lifecycleOwner = this; @@ -351,11 +406,10 @@ else if(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) { if(this.threadedConversations != null) { if(getIntent().hasExtra(SEARCH_STRING)) { - conversationsViewModel.conversationDao = conversationDao; conversationsViewModel.threadId = threadedConversations.getThread_id(); findViewById(R.id.conversations_search_results_found).setVisibility(View.VISIBLE); String searching = getIntent().getStringExtra(SEARCH_STRING); - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { searchForInput(searching); @@ -365,7 +419,7 @@ public void run() { searchPositions.setValue(new ArrayList<>( Collections.singletonList( getIntent().getIntExtra(SEARCH_INDEX, 0)))); - conversationsViewModel.getSearch(getApplicationContext(), conversationDao, + conversationsViewModel.getSearch(getApplicationContext(), threadedConversations.getThread_id(), searchPositions.getValue()) .observe(this, new Observer>() { @Override @@ -374,11 +428,36 @@ public void onChanged(PagingData conversationPagingData) { conversationPagingData); } }); + broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String messageId = intent.getStringExtra(Conversation.ID); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + Conversation conversation = databaseConnector.conversationDao() + .getMessage(messageId); + conversation.setRead(true); + conversationsViewModel.update(conversation); + } + }); + } + }; + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_DELIVER_ACTION); + intentFilter.addAction(IncomingDataSMSBroadcastReceiver.DATA_DELIVER_ACTION); + + intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_UPDATED_BROADCAST_INTENT); + intentFilter.addAction(IncomingDataSMSBroadcastReceiver.DATA_UPDATED_BROADCAST_INTENT); + + if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); + else + registerReceiver(broadcastReceiver, intentFilter); } else if(this.threadedConversations.getThread_id()!= null && !this.threadedConversations.getThread_id().isEmpty()) { - conversationsViewModel.get(getApplicationContext(), conversationDao, - this.threadedConversations.getThread_id()) + conversationsViewModel.get(this.threadedConversations.getThread_id()) .observe(this, new Observer>() { @Override public void onChanged(PagingData smsList) { @@ -393,7 +472,7 @@ public void onChanged(PagingData smsList) { public void onChanged(Conversation conversation) { List list = new ArrayList<>(); list.add(conversation); - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { conversationsViewModel.deleteItems(getApplicationContext(), list); @@ -412,12 +491,17 @@ public void run() { public void onChanged(Conversation conversation) { List list = new ArrayList<>(); list.add(conversation); - conversationsViewModel.deleteItems(getApplicationContext(), list); - try { - sendDataMessage(threadedConversations); - } catch (Exception e) { - e.printStackTrace(); - } + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + conversationsViewModel.deleteItems(getApplicationContext(), list); + try { + sendDataMessage(threadedConversations); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); } }); @@ -472,9 +556,12 @@ private String getAbTitle() { this.threadedConversations.getContact_name(): this.threadedConversations.getAddress(); } private String getAbSubTitle() { - return this.threadedConversations != null && - this.threadedConversations.getAddress() != null ? - this.threadedConversations.getAddress(): ""; +// return this.threadedConversations != null && +// this.threadedConversations.getAddress() != null ? +// this.threadedConversations.getAddress(): ""; + if(threadedConversations.isIs_mute()) + return getString(R.string.conversation_menu_mute); + return ""; } boolean isShortCode = false; @@ -495,7 +582,8 @@ protected void onPause() { @Override public void onDestroy() { super.onDestroy(); - conversation.close(); + if(broadcastReceiver != null) + unregisterReceiver(broadcastReceiver); } static final String DRAFT_TEXT = "DRAFT_TEXT"; @@ -537,9 +625,10 @@ public void onChanged(String s) { counterView.setText(getSMSCount(s)); visibility = View.VISIBLE; } - if(simCount > 1) { - findViewById(R.id.conversation_compose_dual_sim_send_sim_name) - .setVisibility(visibility); + TextView dualSimCardName = + (TextView) findViewById(R.id.conversation_compose_dual_sim_send_sim_name); + if(SIMHandler.isDualSim(getApplicationContext())) { + dualSimCardName.setVisibility(View.VISIBLE); } sendBtn.setVisibility(visibility); counterView.setVisibility(visibility); @@ -607,12 +696,12 @@ public void afterTextChanged(Editable s) { private void checkDrafts() throws InterruptedException { if(smsTextView.getText() == null || smsTextView.getText().toString().isEmpty()) - new Thread(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { Conversation conversation = - conversationsViewModel.fetchDraft(getApplicationContext()); + conversationsViewModel.fetchDraft(); if (conversation != null) { runOnUiThread(new Runnable() { @Override @@ -627,7 +716,7 @@ public void run() { emptyDraft(); } } - }).start(); + }); } private void configureLayoutForMessageType() { @@ -662,6 +751,28 @@ public void onClick(View v) { } } + private void blockContact() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversations.setIs_blocked(true); + databaseConnector.threadedConversationsDao().update(threadedConversations); + } + }); + + ContentValues contentValues = new ContentValues(); + contentValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER, + threadedConversations.getAddress()); + Uri uri = getContentResolver().insert(BlockedNumberContract.BlockedNumbers.CONTENT_URI, + contentValues); + + Toast.makeText(getApplicationContext(), getString(R.string.conversations_menu_block_toast), + Toast.LENGTH_SHORT).show(); + TelecomManager telecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE); + startActivity(telecomManager.createManageBlockedNumbersIntent(), null); + } + + private void shareItem() { Set> entry = conversationsRecyclerAdapter.mutableSelectedItems.getValue().entrySet(); @@ -717,41 +828,49 @@ private void viewDetailsPopUp() throws InterruptedException { Set> entry = conversationsRecyclerAdapter.mutableSelectedItems.getValue().entrySet(); String messageId = entry.iterator().next().getValue().getMessage_id(); - Conversation conversation = conversationsViewModel.fetch(messageId); - StringBuilder detailsBuilder = new StringBuilder(); - detailsBuilder.append(getString(R.string.conversation_menu_view_details_type)) - .append(!conversation.getText().isEmpty() ? - getString(R.string.conversation_menu_view_details_type_text): - getString(R.string.conversation_menu_view_details_type_data)) - .append("\n") - .append(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ? - getString(R.string.conversation_menu_view_details_from) : - getString(R.string.conversation_menu_view_details_to)) - .append(conversation.getAddress()) - .append("\n") - .append(getString(R.string.conversation_menu_view_details_sent)) - .append(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ? - Helpers.formatLongDate(Long.parseLong(conversation.getDate_sent())) : - Helpers.formatLongDate(Long.parseLong(conversation.getDate()))); - if(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ) { - detailsBuilder.append("\n") - .append(getString(R.string.conversation_menu_view_details_received)) - .append(Helpers.formatLongDate(Long.parseLong(conversation.getDate()))); - } + StringBuilder detailsBuilder = new StringBuilder(); AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle(getString(R.string.conversation_menu_view_details_title)) .setMessage(detailsBuilder); -// View conversationSecurePopView = View.inflate(getApplicationContext(), -// R.layout.conversation_secure_popup_menu, null); -// builder.setView(conversationSecurePopView); - -// Button yesButton = conversationSecurePopView.findViewById(R.id.conversation_secure_popup_menu_send); -// Button cancelButton = conversationSecurePopView.findViewById(R.id.conversation_secure_popup_menu_cancel); + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + try { + Conversation conversation = conversationsViewModel.fetch(messageId); + runOnUiThread(new Runnable() { + @Override + public void run() { + detailsBuilder.append(getString(R.string.conversation_menu_view_details_type)) + .append(!conversation.getText().isEmpty() ? + getString(R.string.conversation_menu_view_details_type_text): + getString(R.string.conversation_menu_view_details_type_data)) + .append("\n") + .append(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ? + getString(R.string.conversation_menu_view_details_from) : + getString(R.string.conversation_menu_view_details_to)) + .append(conversation.getAddress()) + .append("\n") + .append(getString(R.string.conversation_menu_view_details_sent)) + .append(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ? + Helpers.formatLongDate(Long.parseLong(conversation.getDate_sent())) : + Helpers.formatLongDate(Long.parseLong(conversation.getDate()))); + if(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ) { + detailsBuilder.append("\n") + .append(getString(R.string.conversation_menu_view_details_received)) + .append(Helpers.formatLongDate(Long.parseLong(conversation.getDate()))); + } - AlertDialog dialog = builder.create(); - dialog.show(); + AlertDialog dialog = builder.create(); + dialog.show(); + } + }); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); } @Override @@ -803,7 +922,7 @@ public void afterTextChanged(Editable editable) { if(editable != null && editable.length() > 1) { conversationsRecyclerAdapter.searchString = editable.toString(); conversationsRecyclerAdapter.resetSearchItems(searchPositions.getValue()); - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { searchForInput(editable.toString()); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java index d25a8071..b7f1ade8 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/CustomAppCompactActivity.java @@ -11,6 +11,7 @@ import androidx.annotation.Nullable; import androidx.core.app.NotificationManagerCompat; +import androidx.room.Room; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ConversationsViewModel; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ThreadedConversationsViewModel; @@ -20,8 +21,11 @@ import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.E2EE.E2EEHandler; import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientHandler; import com.google.i18n.phonenumbers.NumberParseException; @@ -34,23 +38,14 @@ import java.util.concurrent.Executors; public class CustomAppCompactActivity extends DualSIMConversationActivity { - BroadcastReceiver generateUpdateEventsBroadcastReceiver; - BroadcastReceiver smsDeliverActionBroadcastReceiver; - BroadcastReceiver smsSentBroadcastIntent; - BroadcastReceiver smsDeliveredBroadcastIntent; - BroadcastReceiver dataSentBroadcastIntent; - BroadcastReceiver dataDeliveredBroadcastIntent; - - protected static final String TAG_NAME = "NATIVE_CONVERSATION_TAG"; - protected static final String UNIQUE_WORK_NAME = "NATIVE_CONVERSATION_TAG_UNIQUE_WORK_NAME"; - protected final static String DRAFT_PRESENT_BROADCAST = "DRAFT_PRESENT_BROADCAST"; protected ConversationsViewModel conversationsViewModel; protected ThreadedConversationsViewModel threadedConversationsViewModel; - protected ExecutorService executorService = Executors.newFixedThreadPool(4); + + public Datastore databaseConnector; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -60,7 +55,17 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { startActivity(new Intent(this, DefaultCheckActivity.class)); finish(); } + + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Log.d(getClass().getName(), "Yes I am closed"); + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; } + private boolean _checkIsDefaultApp() { final String myPackageName = getPackageName(); final String defaultPackage = Telephony.Sms.getDefaultSmsPackage(this); @@ -68,86 +73,6 @@ private boolean _checkIsDefaultApp() { return myPackageName.equals(defaultPackage); } - protected void configureBroadcastListeners() { - - generateUpdateEventsBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if(intent.getAction() != null && ( - intent.getAction().equals(IncomingTextSMSBroadcastReceiver.SMS_DELIVER_ACTION) || - intent.getAction().equals(IncomingDataSMSBroadcastReceiver.DATA_DELIVER_ACTION))) { - String messageId = intent.getStringExtra(Conversation.ID); - if(conversationsViewModel != null) { - executorService.execute(new Runnable() { - @Override - public void run() { - Conversation conversation = conversationsViewModel - .conversationDao.getMessage(messageId); - conversation.setRead(true); - conversationsViewModel.update(conversation); - try { - if(E2EEHandler.canCommunicateSecurely(getApplicationContext(), - E2EEHandler.deriveKeystoreAlias( - conversation.getAddress(), 0))) { - informSecured(true); - } - } catch (CertificateException | KeyStoreException | IOException | - NoSuchAlgorithmException | NumberParseException e) { - e.printStackTrace(); - } - } - }); - } - } else { - String messageId = intent.getStringExtra(Conversation.ID); - if(conversationsViewModel != null && messageId != null) { - executorService.execute(new Runnable() { - @Override - public void run() { - Conversation conversation = conversationsViewModel - .conversationDao.getMessage(messageId); - conversation.setRead(true); - conversationsViewModel.update(conversation); - try { - if(E2EEHandler.canCommunicateSecurely(getApplicationContext(), - E2EEHandler.deriveKeystoreAlias( conversation.getAddress(), - 0))) { - informSecured(true); - } - } catch (CertificateException | KeyStoreException | IOException | - NoSuchAlgorithmException | NumberParseException e) { - e.printStackTrace(); - } - } - }); - } - } - if(threadedConversationsViewModel != null) { - executorService.execute(new Runnable() { - @Override - public void run() { - threadedConversationsViewModel.refresh(context); - } - }); - } - } - }; - - IntentFilter intentFilter = new IntentFilter(); - - intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_DELIVER_ACTION); - intentFilter.addAction(IncomingDataSMSBroadcastReceiver.DATA_DELIVER_ACTION); - - intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_UPDATED_BROADCAST_INTENT); - intentFilter.addAction(DRAFT_PRESENT_BROADCAST); - intentFilter.addAction(IncomingDataSMSBroadcastReceiver.DATA_UPDATED_BROADCAST_INTENT); - - if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) - registerReceiver(generateUpdateEventsBroadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED); - else - registerReceiver(generateUpdateEventsBroadcastReceiver, intentFilter); - } - protected void informSecured(boolean secured) { } private void cancelAllNotifications(int id) { @@ -169,6 +94,7 @@ protected void sendTextMessage(final String text, int subscriptionId, if(text != null) { if(messageId == null) messageId = String.valueOf(System.currentTimeMillis()); + final String messageIdFinal = messageId; Conversation conversation = new Conversation(); conversation.setMessage_id(messageId); conversation.setText(text); @@ -183,16 +109,21 @@ protected void sendTextMessage(final String text, int subscriptionId, conversation.set_mk(Base64.encodeToString(_mk, Base64.NO_WRAP)); if(conversationsViewModel != null) { - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { - conversationsViewModel.insert(conversation); + conversationsViewModel.insert(getApplicationContext(), conversation); SMSDatabaseWrapper.send_text(getApplicationContext(), conversation, null); // conversationsViewModel.updateThreadId(conversation.getThread_id(), // _messageId, id); } catch (Exception e) { e.printStackTrace(); + NativeSMSDB.Outgoing.register_failed(getApplicationContext(), messageIdFinal, 1); + conversation.setStatus(Telephony.TextBasedSmsColumns.STATUS_FAILED); + conversation.setType(Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED); + conversation.setError_code(1); + conversationsViewModel.update(conversation); } } }); @@ -203,7 +134,7 @@ public void run() { protected void saveDraft(final String messageId, final String text, ThreadedConversations threadedConversations) throws InterruptedException { if(text != null) { if(conversationsViewModel != null) { - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { Conversation conversation = new Conversation(); @@ -216,14 +147,11 @@ public void run() { conversation.setAddress(threadedConversations.getAddress()); conversation.setStatus(Telephony.Sms.STATUS_PENDING); try { - conversationsViewModel.insert(conversation); + conversationsViewModel.insert(getApplicationContext(), conversation); ThreadedConversations tc = ThreadedConversations.build(getApplicationContext(), conversation); - ThreadedConversationsDao threadedConversationsDao = - tc.getDaoInstance(getApplicationContext()); - threadedConversationsDao.insert(tc); - tc.close(); + databaseConnector.threadedConversationsDao().insert(tc); SMSDatabaseWrapper.saveDraft(getApplicationContext(), conversation); } catch (Exception e) { @@ -238,28 +166,6 @@ public void run() { } } - @Override - public void onDestroy() { - super.onDestroy(); - if(generateUpdateEventsBroadcastReceiver != null) - unregisterReceiver(generateUpdateEventsBroadcastReceiver); - - if(smsDeliverActionBroadcastReceiver != null) - unregisterReceiver(smsDeliverActionBroadcastReceiver); - - if(smsSentBroadcastIntent != null) - unregisterReceiver(smsSentBroadcastIntent); - - if(smsDeliveredBroadcastIntent != null) - unregisterReceiver(smsDeliveredBroadcastIntent); - - if(dataSentBroadcastIntent != null) - unregisterReceiver(dataSentBroadcastIntent); - - if(dataDeliveredBroadcastIntent != null) - unregisterReceiver(dataDeliveredBroadcastIntent); - } - protected void cancelNotifications(String threadId) { if (!threadId.isEmpty()) { NotificationManagerCompat notificationManager = NotificationManagerCompat.from( diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ConversationDao.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ConversationDao.java index 3d49b332..5a40d0e0 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ConversationDao.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ConversationDao.java @@ -15,6 +15,7 @@ import androidx.room.Update; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import java.util.List; diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java index 1d3c2452..89abf8ca 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DAO/ThreadedConversationsDao.java @@ -28,27 +28,45 @@ public interface ThreadedConversationsDao { @Query("SELECT * FROM ThreadedConversations WHERE is_archived = 1 ORDER BY date DESC") PagingSource getArchived(); + @Query("SELECT * FROM ThreadedConversations WHERE is_blocked = 1 ORDER BY date DESC") + PagingSource getBlocked(); + @Query("SELECT ThreadedConversations.thread_id FROM ThreadedConversations WHERE is_archived = 1") List getArchivedList(); - @Query("SELECT * FROM ThreadedConversations WHERE is_archived = 0 ORDER BY date DESC") + @Query("SELECT ThreadedConversations.thread_id FROM ThreadedConversations WHERE is_blocked = 1") + List getBlockedList(); + + @Query("SELECT * FROM ThreadedConversations WHERE is_archived = 0 AND is_blocked = 0 " + + "ORDER BY date DESC") PagingSource getAllWithoutArchived(); @Query("SELECT * FROM ThreadedConversations WHERE is_archived = 0 AND is_read = 0 ORDER BY date DESC") PagingSource getAllUnreadWithoutArchived(); + @Query("SELECT * FROM ThreadedConversations WHERE is_mute = 1 ORDER BY date DESC") + PagingSource getMuted(); + @Query("SELECT COUNT(Conversation.id) FROM Conversation, ThreadedConversations WHERE " + "Conversation.thread_id = ThreadedConversations.thread_id AND " + "is_archived = 0 AND read = 0") - int getAllUnreadWithoutArchivedCount(); + int getCountUnread(); @Query("SELECT COUNT(Conversation.id) FROM Conversation, ThreadedConversations WHERE " + "Conversation.thread_id = ThreadedConversations.thread_id AND " + "is_archived = 0 AND read = 0 AND ThreadedConversations.thread_id IN(:ids)") - int getAllUnreadWithoutArchivedCount(List ids); + int getCountUnread(List ids); @Query("SELECT COUNT(ConversationsThreadsEncryption.id) FROM ConversationsThreadsEncryption") - int getAllEncryptedCount(); + int getCountEncrypted(); + + @Query("SELECT COUNT(ThreadedConversations.thread_id) FROM ThreadedConversations " + + "WHERE is_blocked = 1") + int getCountBlocked(); + + @Query("SELECT COUNT(ThreadedConversations.thread_id) FROM ThreadedConversations " + + "WHERE is_mute = 1") + int getCountMuted(); @Query("SELECT Conversation.address, " + "Conversation.text as snippet, " + @@ -56,7 +74,8 @@ public interface ThreadedConversationsDao { "Conversation.date, Conversation.type, Conversation.read, " + "ThreadedConversations.msg_count, ThreadedConversations.is_archived, " + "ThreadedConversations.is_blocked, ThreadedConversations.is_read, " + - "ThreadedConversations.is_shortcode, ThreadedConversations.contact_name " + + "ThreadedConversations.is_shortcode, ThreadedConversations.contact_name, " + + "ThreadedConversations.is_mute, ThreadedConversations.is_secured " + "FROM Conversation, ThreadedConversations WHERE " + "Conversation.type = :type AND ThreadedConversations.thread_id = Conversation.thread_id " + "ORDER BY Conversation.date DESC") @@ -67,7 +86,8 @@ public interface ThreadedConversationsDao { "Conversation.thread_id, " + "Conversation.date, Conversation.type, Conversation.read, " + "0 as msg_count, ThreadedConversations.is_archived, ThreadedConversations.is_blocked, " + - "ThreadedConversations.is_read, ThreadedConversations.is_shortcode " + + "ThreadedConversations.is_read, ThreadedConversations.is_shortcode, " + + "ThreadedConversations.is_mute, ThreadedConversations.is_secured " + "FROM Conversation, ThreadedConversations WHERE " + "Conversation.type = :type AND ThreadedConversations.thread_id = Conversation.thread_id " + "ORDER BY Conversation.date DESC") @@ -100,9 +120,24 @@ default void clearDrafts(int type) { @Query("UPDATE ThreadedConversations SET is_read = :read WHERE thread_id IN(:ids)") int updateAllRead(int read, List ids); + @Query("UPDATE ThreadedConversations SET is_read = :read WHERE thread_id = :id") + int updateAllRead(int read, long id); + + @Query("UPDATE ThreadedConversations SET is_mute = :muted WHERE thread_id = :id") + int updateMuted(int muted, String id); + + @Query("UPDATE ThreadedConversations SET is_mute = :muted WHERE thread_id IN(:ids)") + int updateMuted(int muted, List ids); + + @Query("UPDATE ThreadedConversations SET is_mute = 0 WHERE is_mute = 1") + int updateUnMuteAll(); + @Query("UPDATE Conversation SET read = :read WHERE thread_id IN(:ids)") int updateAllReadConversation(int read, List ids); + @Query("UPDATE Conversation SET read = :read WHERE thread_id = :id") + int updateAllReadConversation(int read, long id); + @Transaction default void updateRead(int read) { updateAllRead(read); @@ -115,9 +150,21 @@ default void updateRead(int read, List ids) { updateAllReadConversation(read, ids); } + @Transaction + default void updateRead(int read, long id) { + updateAllRead(read, id); + updateAllReadConversation(read, id); + } + + @Insert(onConflict = OnConflictStrategy.REPLACE) + List insertAll(List threadedConversationsList); + @Query("SELECT * FROM ThreadedConversations WHERE thread_id =:thread_id") ThreadedConversations get(String thread_id); + @Query("SELECT * FROM ThreadedConversations WHERE thread_id IN (:threadIds)") + List getList(List threadIds); + @Query("SELECT * FROM ThreadedConversations WHERE address =:address") ThreadedConversations getByAddress(String address); @@ -143,8 +190,6 @@ default void updateRead(int read, List ids) { @Insert(onConflict = OnConflictStrategy.REPLACE) long insert(ThreadedConversations threadedConversations); - @Insert(onConflict = OnConflictStrategy.REPLACE) - List insertAll(List threadedConversationsList); @Update int update(ThreadedConversations threadedConversations); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DefaultCheckActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DefaultCheckActivity.java index 704f6e5c..d47278ff 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DefaultCheckActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DefaultCheckActivity.java @@ -4,6 +4,7 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; +import androidx.room.Room; import android.Manifest; import android.app.Activity; @@ -21,6 +22,9 @@ import android.util.Log; import android.view.View; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientHandler; import com.google.android.material.button.MaterialButton; @@ -44,6 +48,18 @@ public void onClick(View v) { makeDefault(v); } }); + + if(checkIsDefaultApp()) { + startUserActivities(); + } + + } + + private boolean checkIsDefaultApp() { + final String myPackageName = getPackageName(); + final String defaultPackage = Telephony.Sms.getDefaultSmsPackage(this); + + return myPackageName.equals(defaultPackage); } // public void quicktest() { @@ -80,64 +96,69 @@ public void makeDefault(View view) { startActivity(intent); } } - - private void checkIsDefaultApp() { - final String myPackageName = getPackageName(); - final String defaultPackage = Telephony.Sms.getDefaultSmsPackage(this); - - if (myPackageName.equals(defaultPackage)) { - startUserActivities(); + private void startServices() { + GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); + try { + gatewayClientHandler.startServices(getApplicationContext()); + } catch (InterruptedException e) { + e.printStackTrace(); } } + public void startMigrations() { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .addMigrations(new Migrations.Migration4To5()) + .addMigrations(new Migrations.Migration5To6()) + .addMigrations(new Migrations.Migration6To7()) + .addMigrations(new Migrations.Migration7To8()) + .addMigrations(new Migrations.Migration9To10()) + .addMigrations(new Migrations.Migration10To11(getApplicationContext())) + .addMigrations(new Migrations.MIGRATION_11_12()) + .build(); + } + private void startUserActivities() { - new Thread(new Runnable() { + startMigrations(); + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { - if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - createNotificationChannel(); - } + configureNotifications(); startServices(); - finish(); } - }).start(); + }); Intent intent = new Intent(this, ThreadedConversationsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); + finish(); } - @Override - public void onActivityResult(int reqCode, int resultCode, Intent data) { - super.onActivityResult(reqCode, resultCode, data); - - if (reqCode == 0) { - if (resultCode == Activity.RESULT_OK) { - SharedPreferences sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - sharedPreferences.edit() - .putBoolean(getString(R.string.configs_load_natives), true) - .apply(); - startUserActivities(); - } + private void configureNotifications(){ + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel(); } } - ArrayList notificationsChannelIds = new ArrayList<>(); ArrayList notificationsChannelNames = new ArrayList<>(); - private List clearOutOldNotificationChannels() { - NotificationManager notificationManager = getSystemService(NotificationManager.class); - List notificationChannelList = new ArrayList<>(); + private void createNotificationChannel() { + notificationsChannelIds.add(getString(R.string.incoming_messages_channel_id)); + notificationsChannelNames.add(getString(R.string.incoming_messages_channel_name)); - for(NotificationChannel notificationChannel : notificationManager.getNotificationChannels()) { - if(!notificationsChannelIds.contains(notificationChannel.getId())) - notificationManager.deleteNotificationChannel(notificationChannel.getId()); - else - notificationChannelList.add(notificationChannel.getId()); - } + notificationsChannelIds.add(getString(R.string.running_gateway_clients_channel_id)); + notificationsChannelNames.add(getString(R.string.running_gateway_clients_channel_name)); + + notificationsChannelIds.add(getString(R.string.foreground_service_failed_channel_id)); + notificationsChannelNames.add(getString(R.string.foreground_service_failed_channel_name)); + + createNotificationChannelIncomingMessage(); + + createNotificationChannelRunningGatewayListeners(); - return notificationChannelList; + createNotificationChannelReconnectGatewayListeners(); } private void createNotificationChannelIncomingMessage() { @@ -157,17 +178,17 @@ private void createNotificationChannelIncomingMessage() { } private void createNotificationChannelRunningGatewayListeners() { - int importance = NotificationManager.IMPORTANCE_DEFAULT; - NotificationChannel channel = new NotificationChannel( - notificationsChannelIds.get(1), notificationsChannelNames.get(1), importance); - channel.setDescription(getString(R.string.running_gateway_clients_channel_description)); - channel.setLightColor(R.color.logo_primary); - channel.setLockscreenVisibility(Notification.DEFAULT_ALL); - - // Register the channel with the system; you can't change the importance - // or other notification behaviors after this - NotificationManager notificationManager = getSystemService(NotificationManager.class); - notificationManager.createNotificationChannel(channel); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel( + notificationsChannelIds.get(1), notificationsChannelNames.get(1), importance); + channel.setDescription(getString(R.string.running_gateway_clients_channel_description)); + channel.setLightColor(R.color.logo_primary); + channel.setLockscreenVisibility(Notification.DEFAULT_ALL); + + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); } private void createNotificationChannelReconnectGatewayListeners() { @@ -184,21 +205,21 @@ private void createNotificationChannelReconnectGatewayListeners() { notificationManager.createNotificationChannel(channel); } - private void createNotificationChannel() { - notificationsChannelIds.add(getString(R.string.incoming_messages_channel_id)); - notificationsChannelNames.add(getString(R.string.incoming_messages_channel_name)); - - notificationsChannelIds.add(getString(R.string.running_gateway_clients_channel_id)); - notificationsChannelNames.add(getString(R.string.running_gateway_clients_channel_name)); - - notificationsChannelIds.add(getString(R.string.foreground_service_failed_channel_id)); - notificationsChannelNames.add(getString(R.string.foreground_service_failed_channel_name)); - - createNotificationChannelIncomingMessage(); - createNotificationChannelRunningGatewayListeners(); + @Override + public void onActivityResult(int reqCode, int resultCode, Intent data) { + super.onActivityResult(reqCode, resultCode, data); - createNotificationChannelReconnectGatewayListeners(); + if (reqCode == 0) { + if (resultCode == Activity.RESULT_OK) { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + sharedPreferences.edit() + .putBoolean(getString(R.string.configs_load_natives), true) + .apply(); + startUserActivities(); + } + } } public boolean checkPermissionToReadContacts() { @@ -207,22 +228,4 @@ public boolean checkPermissionToReadContacts() { return (check == PackageManager.PERMISSION_GRANTED); } - @Override - protected void onResume() { - super.onResume(); - checkIsDefaultApp(); - } - - private void startServices() { - GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); - try { - gatewayClientHandler.startServices(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - gatewayClientHandler.close(); - } - - } - } \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DualSIMConversationActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DualSIMConversationActivity.java index 73084784..4b9efa19 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/DualSIMConversationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/DualSIMConversationActivity.java @@ -3,6 +3,7 @@ import android.graphics.Bitmap; import android.os.Bundle; import android.telephony.SubscriptionInfo; +import android.util.Log; import android.view.View; import android.widget.ImageButton; import android.widget.ImageView; @@ -22,7 +23,6 @@ public class DualSIMConversationActivity extends AppCompatActivity { protected MutableLiveData defaultSubscriptionId = new MutableLiveData<>(); - protected int simCount = 0; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -36,36 +36,35 @@ protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); sendImageButton = findViewById(R.id.conversation_send_btn); currentSimcardTextView = findViewById(R.id.conversation_compose_dual_sim_send_sim_name); - simCount = SIMHandler.getActiveSimcardCount(getApplicationContext()); + final boolean dualSim = SIMHandler.isDualSim(getApplicationContext()); - if(sendImageButton != null) { - try { - defaultSubscriptionId.setValue(SIMHandler.getDefaultSimSubscription(getApplicationContext())); - } catch(Exception e ) { - e.printStackTrace(); - } - - sendImageButton.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (simCount > 1) { - showMultiDualSimAlert(); - return true; - } - return false; - } - }); - } defaultSubscriptionId.observe(this, new Observer() { @Override public void onChanged(Integer integer) { - if(simCount > 1) { + if(dualSim) { String subscriptionName = SIMHandler.getSubscriptionName(getApplicationContext(), integer); currentSimcardTextView.setText(subscriptionName); } } }); + if(sendImageButton != null) { + String subscriptionName = SIMHandler.getSubscriptionName(getApplicationContext(), + SIMHandler.getDefaultSimSubscription(getApplicationContext())); + + defaultSubscriptionId.setValue(SIMHandler.getDefaultSimSubscription(getApplicationContext())); + if(dualSim) { + currentSimcardTextView.setText(subscriptionName); + sendImageButton.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showMultiDualSimAlert(); + return true; + } + }); + } + } + } private void showMultiDualSimAlert() { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ArchivedFragments.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ArchivedFragments.java deleted file mode 100644 index 8dc98278..00000000 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ArchivedFragments.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.afkanerd.deku.DefaultSMS.Fragments; - -import android.os.Bundle; -import android.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.afkanerd.deku.DefaultSMS.Models.Archive; -import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; -import com.afkanerd.deku.DefaultSMS.Models.Conversations.ViewHolders.ThreadedConversationsTemplateViewHolder; -import com.afkanerd.deku.DefaultSMS.R; - -import java.util.ArrayList; -import java.util.List; - -public class ArchivedFragments extends ThreadedConversationsFragment { - - public ArchivedFragments() { - - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - setHasOptionsMenu(true); - Bundle bundle = new Bundle(); - bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_TYPE, ARCHIVED_MESSAGE_TYPES); - - super.setArguments(bundle); - actionModeMenu = R.menu.archive_menu_items_selected; - defaultMenu = R.menu.archive_menu; - - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setLabels(view, getString(R.string.conversations_navigation_view_archived), - getString(R.string.homepage_archive_no_message)); - } -} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/DraftsFragments.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/DraftsFragments.java deleted file mode 100644 index 4be72760..00000000 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/DraftsFragments.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.afkanerd.deku.DefaultSMS.Fragments; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import com.afkanerd.deku.DefaultSMS.R; -import com.google.android.material.navigation.NavigationView; - -public class DraftsFragments extends ThreadedConversationsFragment { - public DraftsFragments() { - - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - Bundle bundle = new Bundle(); - bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_TYPE, DRAFTS_MESSAGE_TYPES); - - super.setArguments(bundle); - defaultMenu = R.menu.drafts_menu; - - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setLabels(view, getString(R.string.conversations_navigation_view_drafts), - getString(R.string.homepage_draft_no_message)); - } -} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/EncryptionFragments.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/EncryptionFragments.java deleted file mode 100644 index 3b2d1f33..00000000 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/EncryptionFragments.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.afkanerd.deku.DefaultSMS.Fragments; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.afkanerd.deku.DefaultSMS.R; - -public class EncryptionFragments extends ThreadedConversationsFragment { - public EncryptionFragments() { - - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - Bundle bundle = new Bundle(); - bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_TYPE, ENCRYPTED_MESSAGES_THREAD_FRAGMENT); - super.setArguments(bundle); - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setLabels(view, getString(R.string.conversations_navigation_view_encryption), - getString(R.string.homepage_encryption_no_message)); - } -} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ThreadedConversationsFragment.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ThreadedConversationsFragment.java index 17c52c0c..f9c93967 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ThreadedConversationsFragment.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/ThreadedConversationsFragment.java @@ -1,9 +1,17 @@ package com.afkanerd.deku.DefaultSMS.Fragments; +import android.app.Activity; +import android.content.ContentValues; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Bundle; +import android.os.ParcelFileDescriptor; +import android.provider.BlockedNumberContract; +import android.provider.DocumentsContract; +import android.telecom.TelecomManager; import android.util.Log; import android.view.ActionMode; import android.view.LayoutInflater; @@ -13,6 +21,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -31,16 +40,21 @@ import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ThreadedConversationsViewModel; import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; import com.afkanerd.deku.DefaultSMS.Models.Archive; +import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ViewHolders.ThreadedConversationsTemplateViewHolder; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.DefaultSMS.R; import com.afkanerd.deku.DefaultSMS.SearchMessagesThreadsActivity; import com.afkanerd.deku.DefaultSMS.SettingsActivity; +import com.afkanerd.deku.DefaultSMS.ThreadedConversationsActivity; import com.afkanerd.deku.E2EE.E2EEHandler; import com.afkanerd.deku.Router.Router.RouterActivity; import com.google.i18n.phonenumbers.NumberParseException; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -61,12 +75,24 @@ public class ThreadedConversationsFragment extends Fragment { ThreadedConversationRecyclerAdapter threadedConversationRecyclerAdapter; RecyclerView messagesThreadRecyclerView; + public static final String MESSAGES_THREAD_FRAGMENT_DEFAULT_MENU = + "MESSAGES_THREAD_FRAGMENT_DEFAULT_MENU"; + + public static final String MESSAGES_THREAD_FRAGMENT_DEFAULT_ACTION_MODE_MENU = + "MESSAGES_THREAD_FRAGMENT_DEFAULT_ACTION_MODE_MENU"; + public static final String MESSAGES_THREAD_FRAGMENT_LABEL = + "MESSAGES_THREAD_FRAGMENT_LABEL"; + public static final String MESSAGES_THREAD_FRAGMENT_NO_CONTENT = + "MESSAGES_THREAD_FRAGMENT_NO_CONTENT"; + public static final String MESSAGES_THREAD_FRAGMENT_TYPE = "MESSAGES_THREAD_FRAGMENT_TYPE"; public static final String ALL_MESSAGES_THREAD_FRAGMENT = "ALL_MESSAGES_THREAD_FRAGMENT"; public static final String PLAIN_MESSAGES_THREAD_FRAGMENT = "PLAIN_MESSAGES_THREAD_FRAGMENT"; public static final String ENCRYPTED_MESSAGES_THREAD_FRAGMENT = "ENCRYPTED_MESSAGES_THREAD_FRAGMENT"; public static final String ARCHIVED_MESSAGE_TYPES = "ARCHIVED_MESSAGE_TYPES"; + public static final String BLOCKED_MESSAGE_TYPES = "BLOCKED_MESSAGE_TYPES"; + public static final String MUTED_MESSAGE_TYPE = "MUTED_MESSAGE_TYPE"; public static final String DRAFTS_MESSAGE_TYPES = "DRAFTS_MESSAGE_TYPES"; public static final String UNREAD_MESSAGE_TYPES = "UNREAD_MESSAGE_TYPES"; @@ -76,13 +102,10 @@ public class ThreadedConversationsFragment extends Fragment { public interface ViewModelsInterface { ThreadedConversationsViewModel getThreadedConversationsViewModel(); - ExecutorService getExecutorService(); } private ViewModelsInterface viewModelsInterface; - ExecutorService executorService; - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -117,7 +140,7 @@ public boolean onCreateActionMode(ActionMode mode, Menu menu) { if(menu.findItem(R.id.conversations_threads_main_menu_mark_all_read) != null && menu.findItem(R.id.conversations_threads_main_menu_mark_all_unread) != null) - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { boolean hasUnread = threadedConversationsViewModel.hasUnread(threadsIds); @@ -149,15 +172,11 @@ public Runnable getDeleteRunnable(List ids) { @Override public void run() { - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { - ThreadedConversations threadedConversations = new ThreadedConversations(); - ThreadedConversationsDao threadedConversationsDao = - threadedConversations.getDaoInstance(getContext()); - List foundList = - threadedConversationsDao.findAddresses(ids); - threadedConversations.close(); + List foundList = threadedConversationsViewModel. + databaseConnector.threadedConversationsDao().findAddresses(ids); threadedConversationsViewModel.delete(getContext(), ids); getActivity().runOnUiThread(new Runnable() { @Override @@ -169,9 +188,6 @@ public void run() { try { String keystoreAlias = E2EEHandler.deriveKeystoreAlias( address, 0); -// E2EEHandler.removeFromKeystore(getContext(), keystoreAlias); -// E2EEHandler.removeFromEncryptionDatabase(getContext(), -// keystoreAlias); E2EEHandler.clear(getContext(), keystoreAlias); } catch (KeyStoreException | NumberParseException | InterruptedException | @@ -237,7 +253,7 @@ else if(item.getItemId() == R.id.conversations_threads_main_menu_archive) { archive.is_archived = true; archiveList.add(archive); } - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { threadedConversationsViewModel.archive(archiveList); @@ -258,7 +274,7 @@ else if(item.getItemId() == R.id.archive_unarchive) { archive.is_archived = false; archiveList.add(archive); } - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { threadedConversationsViewModel.unarchive(archiveList); @@ -276,7 +292,7 @@ else if(item.getItemId() == R.id.conversations_threads_main_menu_mark_all_unread threadedConversationRecyclerAdapter.selectedItems.getValue().values()) { threadIds.add(viewHolder.id); } - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { threadedConversationsViewModel.markUnRead(getContext(), threadIds); @@ -295,7 +311,7 @@ else if(item.getItemId() == R.id.conversations_threads_main_menu_mark_all_read) threadedConversationRecyclerAdapter.selectedItems.getValue().values()) { threadIds.add(viewHolder.id); } - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { threadedConversationsViewModel.markRead(getContext(), threadIds); @@ -305,7 +321,59 @@ public void run() { return true; } } - + else if(item.getItemId() == R.id.blocked_main_menu_unblock) { + List threadIds = new ArrayList<>(); + for (ThreadedConversationsTemplateViewHolder viewHolder : + threadedConversationRecyclerAdapter.selectedItems.getValue().values()) { + threadIds.add(viewHolder.id); + } + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.unblock(getContext(), threadIds); + } + }); + threadedConversationRecyclerAdapter.resetAllSelectedItems(); + return true; + } + else if(item.getItemId() == R.id.conversations_threads_main_menu_mute) { + List threadIds = new ArrayList<>(); + for (ThreadedConversationsTemplateViewHolder viewHolder : + threadedConversationRecyclerAdapter.selectedItems.getValue().values()) { + threadIds.add(viewHolder.id); + } + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.mute(threadIds); + threadedConversationsViewModel.getCount(getContext()); + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + threadedConversationRecyclerAdapter.notifyDataSetChanged(); + } + }); + } + }); + threadedConversationRecyclerAdapter.resetAllSelectedItems(); + return true; + } + else if(item.getItemId() == R.id.conversation_threads_main_menu_unmute_selected) { + List threadIds = new ArrayList<>(); + for (ThreadedConversationsTemplateViewHolder viewHolder : + threadedConversationRecyclerAdapter.selectedItems.getValue().values()) { + threadIds.add(viewHolder.id); + } + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.unMute(threadIds); + threadedConversationsViewModel.getCount(getContext()); + } + }); + threadedConversationRecyclerAdapter.resetAllSelectedItems(); + return true; + } } return false; } @@ -324,7 +392,7 @@ public void onDestroyActionMode(ActionMode mode) { public void onResume() { super.onResume(); - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { if(getContext() != null) { @@ -335,36 +403,41 @@ public void run() { .apply(); threadedConversationsViewModel.reset(getContext()); } - - threadedConversationsViewModel.refresh(getContext()); } } }); } - ThreadedConversationsDao threadedConversationsDao; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { viewModelsInterface = (ViewModelsInterface) view.getContext(); - executorService = viewModelsInterface.getExecutorService(); setHasOptionsMenu(true); Bundle args = getArguments(); - String messageType = args == null ? ALL_MESSAGES_THREAD_FRAGMENT : - args.getString(MESSAGES_THREAD_FRAGMENT_TYPE); + + String messageType; + if(args != null) { + messageType = args.getString(MESSAGES_THREAD_FRAGMENT_TYPE); + setLabels(view, args.getString(MESSAGES_THREAD_FRAGMENT_LABEL), + args.getString(MESSAGES_THREAD_FRAGMENT_NO_CONTENT)); + defaultMenu = args.getInt(MESSAGES_THREAD_FRAGMENT_DEFAULT_MENU); + actionModeMenu = args.getInt(MESSAGES_THREAD_FRAGMENT_DEFAULT_ACTION_MODE_MENU); + } else { + messageType = ALL_MESSAGES_THREAD_FRAGMENT; + setLabels(view, getString(R.string.conversations_navigation_view_inbox), getString(R.string.homepage_no_message)); + } actionBar = ((AppCompatActivity)getActivity()).getSupportActionBar(); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); - setLabels(view, getString(R.string.conversations_navigation_view_inbox), getString(R.string.homepage_no_message)); threadedConversationsViewModel = viewModelsInterface.getThreadedConversationsViewModel(); - threadedConversationRecyclerAdapter = new ThreadedConversationRecyclerAdapter( getContext(), - threadedConversationsDao); + threadedConversationRecyclerAdapter = new ThreadedConversationRecyclerAdapter( + threadedConversationsViewModel.databaseConnector.threadedConversationsDao()); threadedConversationRecyclerAdapter.selectedItems.observe(getViewLifecycleOwner(), new Observer>() { @Override @@ -405,7 +478,7 @@ public Unit invoke() { case ENCRYPTED_MESSAGES_THREAD_FRAGMENT: Log.d(getClass().getName(), "Fragment at encrypted"); try { - threadedConversationsViewModel.getEncrypted(getContext()).observe(getViewLifecycleOwner(), + threadedConversationsViewModel.getEncrypted().observe(getViewLifecycleOwner(), new Observer>() { @Override public void onChanged(PagingData smsList) { @@ -447,6 +520,26 @@ public void onChanged(PagingData smsList) { } }); break; + case BLOCKED_MESSAGE_TYPES: + threadedConversationsViewModel.getBlocked().observe(getViewLifecycleOwner(), + new Observer>() { + @Override + public void onChanged(PagingData smsList) { + threadedConversationRecyclerAdapter.submitData(getLifecycle(), smsList); + view.findViewById(R.id.homepage_messages_loader).setVisibility(View.GONE); + } + }); + break; + case MUTED_MESSAGE_TYPE: + threadedConversationsViewModel.getMuted().observe(getViewLifecycleOwner(), + new Observer>() { + @Override + public void onChanged(PagingData smsList) { + threadedConversationRecyclerAdapter.submitData(getLifecycle(), smsList); + view.findViewById(R.id.homepage_messages_loader).setVisibility(View.GONE); + } + }); + break; case ALL_MESSAGES_THREAD_FRAGMENT: default: threadedConversationsViewModel.get().observe(getViewLifecycleOwner(), @@ -460,6 +553,67 @@ public void onChanged(PagingData smsList) { } } + private static final int CREATE_FILE = 777; + public void exportInbox() { + // Request code for creating a PDF document. + + String filename = "deku_sms_backup_" + System.currentTimeMillis() + ".json"; + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + intent.putExtra(Intent.EXTRA_TITLE, filename); + + // Optionally, specify a URI for the directory that should be opened in + // the system file picker when your app creates the document. +// intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); + + startActivityForResult(intent, CREATE_FILE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) { + // The result data contains a URI for the document or directory that + // the user selected. + if (resultData != null) { + Uri uri = resultData.getData(); + // Perform operations on the document using its URI. + + if(uri == null) + return; + + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + try { + ParcelFileDescriptor pfd = requireActivity().getContentResolver(). + openFileDescriptor(uri, "w"); + FileOutputStream fileOutputStream = + new FileOutputStream(pfd.getFileDescriptor()); + fileOutputStream.write(threadedConversationsViewModel.getAllExport() + .getBytes()); + // Let the document provider know you're done by closing the stream. + fileOutputStream.close(); + pfd.close(); + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(getContext(), + getString(R.string.conversations_exported_complete), + Toast.LENGTH_LONG).show(); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + } + } + + @Override public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { inflater.inflate(defaultMenu, menu); @@ -472,27 +626,23 @@ public boolean onOptionsItemSelected(MenuItem item) { Intent searchIntent = new Intent(getContext(), SearchMessagesThreadsActivity.class); searchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(searchIntent); - return true; } if (item.getItemId() == R.id.conversation_threads_main_menu_routed) { Intent routingIntent = new Intent(getContext(), RouterActivity.class); routingIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(routingIntent); - return true; } if (item.getItemId() == R.id.conversation_threads_main_menu_settings) { Intent settingsIntent = new Intent(getContext(), SettingsActivity.class); startActivity(settingsIntent); - return true; } if (item.getItemId() == R.id.conversation_threads_main_menu_about) { Intent aboutIntent = new Intent(getContext(), AboutActivity.class); aboutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(aboutIntent); - return true; } if(item.getItemId() == R.id.conversation_threads_main_menu_clear_drafts) { - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { @@ -502,19 +652,40 @@ public void run() { } } }); - return true; } if(item.getItemId() == R.id.conversation_threads_main_menu_mark_all_read) { - try { - threadedConversationsViewModel.markAllRead(getContext()); - } catch (Exception e) { - e.printStackTrace(); - return false; - } + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.markAllRead(getContext()); + } + }); + } + else if(item.getItemId() == R.id.conversation_threads_main_menu_unmute_all) { + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.unMuteAll(); + } + }); + } + else if(item.getItemId() == R.id.conversations_menu_export) { + exportInbox(); + } + else if(item.getItemId() == R.id.blocked_main_menu_unblock_manager_id) { + TelecomManager telecomManager = (TelecomManager) + getContext().getSystemService(Context.TELECOM_SERVICE); + startActivity(telecomManager.createManageBlockedNumbersIntent(), null); return true; } + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.getCount(getContext()); + } + }); - return false; + return true; } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/UnreadFragments.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/UnreadFragments.java deleted file mode 100644 index 6d0f3821..00000000 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Fragments/UnreadFragments.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.afkanerd.deku.DefaultSMS.Fragments; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.afkanerd.deku.DefaultSMS.R; - -public class UnreadFragments extends ThreadedConversationsFragment { - public UnreadFragments() { - - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - Bundle bundle = new Bundle(); - bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_TYPE, - UNREAD_MESSAGE_TYPES); - super.setArguments(bundle); - defaultMenu = R.menu.read_menu; - - return super.onCreateView(inflater, container, savedInstanceState); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setLabels(view, getString(R.string.conversations_navigation_view_unread), - getString(R.string.homepage_unread_no_message)); - } -} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Contacts.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Contacts.java index df17344f..c930f48d 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Contacts.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Contacts.java @@ -1,19 +1,24 @@ package com.afkanerd.deku.DefaultSMS.Models; import android.content.Context; +import android.content.SharedPreferences; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.provider.BlockedNumberContract; import android.provider.ContactsContract; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DiffUtil; import androidx.room.Ignore; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; public class Contacts { @@ -153,4 +158,12 @@ public static Bitmap getContactBitmapPhoto(Context context, String phoneNumber) } return null; } + + public static Cursor getBlocked(Context context) { + return context.getContentResolver().query(BlockedNumberContract.BlockedNumbers.CONTENT_URI, + new String[]{BlockedNumberContract.BlockedNumbers.COLUMN_ID, + BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER, + BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER}, + null, null, null); + } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/Conversation.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/Conversation.java index f51e2d64..4102db06 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/Conversation.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/Conversation.java @@ -69,23 +69,6 @@ public void set_mk(String _mk) { this._mk = _mk; } - @Ignore - private Datastore databaseConnector; - - public ConversationDao getDaoInstance(Context context) { - databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration8To9()) - .enableMultiInstanceInvalidation() - .build(); - return databaseConnector.conversationDao(); - } - - public void close() { - if(databaseConnector != null) - databaseConnector.close(); - } - public int getError_code() { return error_code; } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ConversationHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ConversationHandler.java new file mode 100644 index 00000000..d71daf87 --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ConversationHandler.java @@ -0,0 +1,32 @@ +package com.afkanerd.deku.DefaultSMS.Models.Conversations; + +import android.content.Context; +import android.provider.Telephony; + +import androidx.room.Room; + +import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; + +import java.util.List; + +public class ConversationHandler { + + public static ConversationDao conversationDao; + + public static Conversation buildConversationForSending(Context context, String body, int subscriptionId, + String address) { + long threadId = Telephony.Threads.getOrCreateThreadId(context, address); + Conversation conversation = new Conversation(); + conversation.setMessage_id(String.valueOf(System.currentTimeMillis())); + conversation.setText(body); + conversation.setSubscription_id(subscriptionId); + conversation.setType(Telephony.Sms.MESSAGE_TYPE_OUTBOX); + conversation.setDate(String.valueOf(System.currentTimeMillis())); + conversation.setAddress(address); + conversation.setThread_id(String.valueOf(threadId)); + conversation.setStatus(Telephony.Sms.STATUS_PENDING); + return conversation; + } + +} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java index 95cdad99..2415364f 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversations.java @@ -3,21 +3,16 @@ import android.content.Context; import android.database.Cursor; import android.provider.Telephony; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; +import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.PrimaryKey; -import androidx.room.Room; -import com.afkanerd.deku.DefaultSMS.Commons.Helpers; -import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; import com.afkanerd.deku.DefaultSMS.Models.Contacts; -import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; -import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; import com.afkanerd.deku.DefaultSMS.R; import java.util.ArrayList; @@ -28,8 +23,16 @@ @Entity public class ThreadedConversations { - @Ignore - public boolean secured = false; + public boolean isIs_secured() { + return is_secured; + } + + public void setIs_secured(boolean is_secured) { + this.is_secured = is_secured; + } + + @ColumnInfo(defaultValue = "0") + public boolean is_secured = false; @NonNull @PrimaryKey private String thread_id; @@ -62,25 +65,17 @@ public class ThreadedConversations { private String formatted_datetime; - @Ignore - public final static String nativeSMSContentUrl = Telephony.Threads.CONTENT_URI.toString(); - - @Ignore - Datastore databaseConnector; - public ThreadedConversationsDao getDaoInstance(Context context) { - databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration8To9()) - .addMigrations(new Migrations.Migration9To10()) - .build(); - return databaseConnector.threadedConversationsDao(); + public boolean isIs_mute() { + return is_mute; } - public void close() { - if(databaseConnector != null) - databaseConnector.close(); + public void setIs_mute(boolean is_mute) { + this.is_mute = is_mute; } + @ColumnInfo(defaultValue = "0") + private boolean is_mute = false; + public static ThreadedConversations build(Context context, Conversation conversation) { ThreadedConversations threadedConversations = new ThreadedConversations(); threadedConversations.setAddress(conversation.getAddress()); @@ -92,6 +87,8 @@ public static ThreadedConversations build(Context context, Conversation conversa threadedConversations.setDate(conversation.getDate()); threadedConversations.setType(conversation.getType()); threadedConversations.setIs_read(conversation.isRead()); + String contactName = Contacts.retrieveContactName(context, conversation.getAddress()); + threadedConversations.setContact_name(contactName); return threadedConversations; } @@ -300,6 +297,7 @@ public boolean equals(@Nullable Object obj) { threadedConversations.is_read == this.is_read && threadedConversations.type == this.type && threadedConversations.msg_count == this.msg_count && + threadedConversations.is_mute == this.is_mute && Objects.equals(threadedConversations.date, this.date) && Objects.equals(threadedConversations.address, this.address) && Objects.equals(threadedConversations.contact_name, this.contact_name) && diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java index 851da8a3..7aae872f 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ThreadedConversationsHandler.java @@ -17,16 +17,14 @@ public static ThreadedConversations get(Context context, String address) { return threadedConversations; } - public static ThreadedConversations get(Context context, ThreadedConversations threadedConversations) throws InterruptedException { + public static ThreadedConversations get(ThreadedConversationsDao threadedConversationsDao, + ThreadedConversations threadedConversations) throws InterruptedException { final ThreadedConversations[] threadedConversations1 = {threadedConversations}; Thread thread = new Thread(new Runnable() { @Override public void run() { - ThreadedConversationsDao threadedConversationsDao = - threadedConversations.getDaoInstance(context); threadedConversations1[0] = threadedConversationsDao .get(threadedConversations.getThread_id()); - threadedConversations.close(); } }); thread.start(); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ConversationSentViewHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ConversationSentViewHandler.java index ed0dd8ed..87f18cf5 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ConversationSentViewHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ConversationSentViewHandler.java @@ -101,12 +101,19 @@ public void bind(Conversation conversation, String searchString) { statusMessage = itemView.getContext().getString(R.string.sms_status_sending); else if(status == Telephony.TextBasedSmsColumns.STATUS_FAILED ) { statusMessage = itemView.getContext().getString(R.string.sms_status_failed); + sentMessageStatus.setVisibility(View.VISIBLE); this.date.setVisibility(View.VISIBLE); + sentMessageStatus.setTextAppearance(R.style.conversation_failed); this.date.setTextAppearance(R.style.conversation_failed); + lastKnownStateIsFailed = true; } + else { + sentMessageStatus.setTextAppearance(R.style.Theme_main); + this.date.setTextAppearance(R.style.Theme_main); + } if(lastKnownStateIsFailed && status != Telephony.TextBasedSmsColumns.STATUS_FAILED) { sentMessageStatus = itemView.findViewById(R.id.message_thread_sent_status_text); lastKnownStateIsFailed = false; @@ -122,16 +129,14 @@ else if(status == Telephony.TextBasedSmsColumns.STATUS_FAILED ) { sentMessageStatus.setText(statusMessage); - final String[] text = {conversation.getText()}; - if(searchString != null && !searchString.isEmpty() && text[0] != null) { + final String text = conversation.getText(); + if(searchString != null && !searchString.isEmpty() && text != null) { Spannable spannable = Helpers.highlightSubstringYellow(itemView.getContext(), - text[0], searchString, true); + text, searchString, true); sentMessage.setText(spannable); } else -// Helpers.highlightLinks(sentMessage, text[0], -// itemView.getContext().getColor(R.color.primary_text_color)); - Helpers.highlightLinks(sentMessage, text[0], Color.BLACK); + Helpers.highlightLinks(sentMessage, text, Color.BLACK); } @Override diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsSentViewHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsSentViewHandler.java index 68e293a9..bfc487ac 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsSentViewHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsSentViewHandler.java @@ -18,8 +18,9 @@ public SentViewHolderReadThreadedConversations(@NonNull View itemView) { } @Override - public void bind(ThreadedConversations conversation, View.OnClickListener onClickListener, View.OnLongClickListener onLongClickListener) { - super.bind(conversation, onClickListener, onLongClickListener); + public void bind(ThreadedConversations conversation, View.OnClickListener onClickListener, + View.OnLongClickListener onLongClickListener, String defaultRegion) { + super.bind(conversation, onClickListener, onLongClickListener, defaultRegion); if(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT) { this.date.setText(itemView.getContext().getString(R.string.thread_conversation_type_draft)); this.date.setTextAppearance(R.style.conversation_draft_style); @@ -37,8 +38,9 @@ public SentViewHolderUnreadThreadedConversations(@NonNull View itemView) { } @Override - public void bind(ThreadedConversations conversation, View.OnClickListener onClickListener, View.OnLongClickListener onLongClickListener) { - super.bind(conversation, onClickListener, onLongClickListener); + public void bind(ThreadedConversations conversation, View.OnClickListener onClickListener, + View.OnLongClickListener onLongClickListener, String defaultRegion) { + super.bind(conversation, onClickListener, onLongClickListener, defaultRegion); if(conversation.getType() == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_DRAFT) { this.date.setText(itemView.getContext().getString(R.string.thread_conversation_type_draft)); this.date.setTextAppearance(R.style.conversation_draft_style); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java index 16086f64..06665847 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Conversations/ViewHolders/ThreadedConversationsTemplateViewHolder.java @@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.afkanerd.deku.DefaultSMS.Commons.Helpers; +import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.R; import com.google.android.material.card.MaterialCardView; @@ -38,13 +39,14 @@ public class ThreadedConversationsTemplateViewHolder extends RecyclerView.ViewHo public TextView date; public AvatarView contactInitials; public ImageView contactAvatar; + public ImageView muteAvatar; public TextView youLabel; public ConstraintLayout layout; public MaterialCardView materialCardView; - View itemView; + public View itemView; public ThreadedConversationsTemplateViewHolder(@NonNull View itemView) { super(itemView); @@ -58,10 +60,11 @@ public ThreadedConversationsTemplateViewHolder(@NonNull View itemView) { contactInitials = itemView.findViewById(R.id.messages_threads_contact_initials); materialCardView = itemView.findViewById(R.id.messages_threads_cardview); contactAvatar = itemView.findViewById(R.id.messages_threads_contact_photo); + muteAvatar = itemView.findViewById(R.id.messages_threads_mute_icon); } public void bind(ThreadedConversations conversation, View.OnClickListener onClickListener, - View.OnLongClickListener onLongClickListener) { + View.OnLongClickListener onLongClickListener, String defaultRegion) { this.id = String.valueOf(conversation.getThread_id()); int contactColor = Helpers.getColor(itemView.getContext(), id); @@ -94,6 +97,12 @@ public void bind(ThreadedConversations conversation, View.OnClickListener onClic this.date.setText(date); this.materialCardView.setOnClickListener(onClickListener); this.materialCardView.setOnLongClickListener(onLongClickListener); + + if(conversation.isIs_mute()) + this.muteAvatar.setVisibility(View.VISIBLE); + else + this.muteAvatar.setVisibility(View.GONE); + // TODO: investigate new Avatar first before anything else // this.contactInitials.setPlaceholder(itemView.getContext().getDrawable(R.drawable.round_person_24)); } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java index 16770858..aa1de2ad 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Datastore.java @@ -1,9 +1,13 @@ package com.afkanerd.deku.DefaultSMS.Models.Database; +import android.content.Context; + import androidx.annotation.NonNull; +import androidx.room.AutoMigration; import androidx.room.Database; import androidx.room.DatabaseConfiguration; import androidx.room.InvalidationTracker; +import androidx.room.Room; import androidx.room.RoomDatabase; import androidx.sqlite.db.SupportSQLiteOpenHelper; @@ -18,6 +22,8 @@ import com.afkanerd.deku.E2EE.Security.CustomKeyStoreDao; import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient; import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientDAO; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjectDao; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjects; import com.afkanerd.deku.Router.GatewayServers.GatewayServer; import com.afkanerd.deku.Router.GatewayServers.GatewayServerDAO; //import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClient; @@ -28,14 +34,25 @@ //@Database(entities = {GatewayServer.class, Archive.class, GatewayClient.class, // ThreadedConversations.class, Conversation.class}, version = 9) -@Database(entities = {ThreadedConversations.class, CustomKeyStore.class, Archive.class, GatewayServer.class, - ConversationsThreadsEncryption.class, Conversation.class, GatewayClient.class}, version = 10) +@Database(entities = { + ThreadedConversations.class, + CustomKeyStore.class, + Archive.class, + GatewayServer.class, + GatewayClientProjects.class, + ConversationsThreadsEncryption.class, + Conversation.class, + GatewayClient.class}, + version = 12, autoMigrations = {@AutoMigration(from = 11, to = 12)}) public abstract class Datastore extends RoomDatabase { + public static Datastore datastore; + public static String databaseName = "SMSWithoutBorders-Messaging-DB"; public abstract GatewayServerDAO gatewayServerDAO(); public abstract GatewayClientDAO gatewayClientDAO(); + public abstract GatewayClientProjectDao gatewayClientProjectDao(); public abstract ThreadedConversationsDao threadedConversationsDao(); @@ -45,6 +62,7 @@ public abstract class Datastore extends RoomDatabase { public abstract ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao(); + @Override public void clearAllTables() { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Migrations.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Migrations.java index 457c9986..bc7025a8 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Migrations.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/Migrations.java @@ -1,9 +1,18 @@ package com.afkanerd.deku.DefaultSMS.Models.Database; +import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.room.migration.Migration; import androidx.sqlite.db.SupportSQLiteDatabase; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientHandler; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjects; + public class Migrations { // Define the migration class public static class Migration4To5 extends Migration { @@ -130,4 +139,41 @@ public void migrate(@NonNull SupportSQLiteDatabase database) { } } + public static class Migration10To11 extends Migration { + public Migration10To11(Context context) { + super(10, 11); + SharedPreferences sharedPreferences = + context.getSharedPreferences(GatewayClientHandler.MIGRATIONS, Context.MODE_PRIVATE); + if(!sharedPreferences.contains(GatewayClientHandler.MIGRATIONS_TO_11)) { + context.getSharedPreferences(GatewayClientHandler.MIGRATIONS, Context.MODE_PRIVATE) + .edit().putBoolean(GatewayClientHandler.MIGRATIONS_TO_11, true).apply(); + sharedPreferences = + context.getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); + for(String key : sharedPreferences.getAll().keySet()) { + sharedPreferences.edit().remove(key).apply(); + } + } + } + + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + } + } + + public static class MIGRATION_11_12 extends Migration { + + public MIGRATION_11_12() { + super(11, 12); + } + + @Override + public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) { + Log.d(getClass().getName(), "Migration to 12 happening"); + supportSQLiteDatabase.execSQL("ALTER TABLE ThreadedConversations " + + "ADD COLUMN is_mute INTEGER NOT NULL DEFAULT 0"); + supportSQLiteDatabase.execSQL("ALTER TABLE ThreadedConversations " + + "ADD COLUMN is_secured INTEGER NOT NULL DEFAULT 0"); + } + }; + } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/SemaphoreManager.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/SemaphoreManager.java new file mode 100644 index 00000000..2058491f --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/Database/SemaphoreManager.java @@ -0,0 +1,33 @@ +package com.afkanerd.deku.DefaultSMS.Models.Database; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Semaphore; + +public class SemaphoreManager { + + private static final Semaphore semaphore = new Semaphore(1); + + private static final Map semaphoreMap = new HashMap<>(); + public static void acquireSemaphore(int id) throws InterruptedException { + if(!semaphoreMap.containsKey(id) || semaphoreMap.get(id) == null) + semaphoreMap.put(id, new Semaphore(1)); + + Objects.requireNonNull(semaphoreMap.get(id)).acquire(); + } + + public static void releaseSemaphore(int id) throws InterruptedException { + if(!semaphoreMap.containsKey(id) || semaphoreMap.get(id) == null) + semaphoreMap.put(id, new Semaphore(1)); + + Objects.requireNonNull(semaphoreMap.get(id)).release(); + } + public static void acquireSemaphore() throws InterruptedException { + semaphore.acquire(); + } + + public static void releaseSemaphore() throws InterruptedException { + semaphore.release(); + } +} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java index 87be291c..4d8ffff5 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NativeSMSDB.java @@ -22,7 +22,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; +import java.util.Set; public class NativeSMSDB { public static String ID = "ID"; @@ -134,6 +136,8 @@ protected static int deleteAllType(Context context, String type) { */ private static String[] parseNewIncomingUriForThreadInformation(Context context, Uri uri) { + if(uri == null) + return null; Cursor cursor = context.getContentResolver().query( uri, new String[]{ @@ -142,8 +146,6 @@ private static String[] parseNewIncomingUriForThreadInformation(Context context, null, null, null); - Log.d(NativeSMSDB.class.getName(), "Parsing draft information: " + cursor.getCount()); - if (cursor.moveToFirst()) { String threadId = cursor.getString( cursor.getColumnIndexOrThrow(Telephony.TextBasedSmsColumns.THREAD_ID)); @@ -325,11 +327,9 @@ public static String[] register_delivered(@NonNull Context context, String messa } public static String[] register_sent(Context context, String messageId) { - Log.d(NativeSMSDB.class.getName(), "Registered sent message"); int numberChanged = update_status(context, Telephony.TextBasedSmsColumns.STATUS_NONE, messageId, -1); - Log.d(NativeSMSDB.class.getName(), "Registered sent message update: " + numberChanged); return broadcastStateChanged(context, String.valueOf(messageId)); } @@ -438,7 +438,6 @@ public static int update_read(Context context, int read, String thread_id, Strin contentValues, "thread_id=?", new String[]{thread_id}); - Log.d(NativeSMSDB.class.getName(), "Updated to read: " + updated); return updated; } catch (Exception e) { e.printStackTrace(); @@ -488,7 +487,6 @@ public static String[] register_incoming_text(Context context, Intent intent) th Uri uri = context.getContentResolver().insert( Telephony.Sms.CONTENT_URI, contentValues); - Log.d(NativeSMSDB.class.getName(), "URI: " + uri.toString()); String[] broadcastOutputs = parseNewIncomingUriForThreadInformation(context, uri); String[] returnString = new String[7]; returnString[THREAD_ID] = broadcastOutputs[THREAD_ID]; @@ -505,14 +503,24 @@ public static String[] register_incoming_text(Context context, Intent intent) th return null; } - public static String[] register_incoming_data(Context context, Intent intent) throws IOException { - long messageId = System.currentTimeMillis(); + /* + * Bundle: [ + * android.telephony.extra.SUBSCRIPTION_INDEX, + * messageId, + * format, + * android.telephony.extra.SLOT_INDEX, + * pdus, + * phone, + * subscription + * ] + */ ContentValues contentValues = new ContentValues(); Bundle bundle = intent.getExtras(); int subscriptionId = bundle.getInt("subscription", -1); + Set keySet = bundle.keySet(); String address = ""; ByteArrayOutputStream dataBodyBuffer = new ByteArrayOutputStream(); @@ -520,16 +528,15 @@ public static String[] register_incoming_data(Context context, Intent intent) th long date = System.currentTimeMillis(); for (SmsMessage currentSMS : Telephony.Sms.Intents.getMessagesFromIntent(intent)) { - address = currentSMS.getDisplayOriginatingAddress(); + address = currentSMS.getOriginatingAddress(); dataBodyBuffer.write(currentSMS.getUserData()); dateSent = currentSMS.getTimestampMillis(); } String body = Base64.encodeToString(dataBodyBuffer.toByteArray(), Base64.DEFAULT); - contentValues.put(Telephony.Sms._ID, messageId); + contentValues.put(Telephony.Sms._ID, System.currentTimeMillis()); contentValues.put(Telephony.TextBasedSmsColumns.ADDRESS, address); -// contentValues.put(Telephony.TextBasedSmsColumns.BODY, body); contentValues.put(Telephony.TextBasedSmsColumns.SUBSCRIPTION_ID, subscriptionId); contentValues.put(Telephony.TextBasedSmsColumns.TYPE, Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX); diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java index 7601e4ac..5ac95e28 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/NotificationsHandler.java @@ -260,6 +260,23 @@ public static NotificationCompat.MessagingStyle getMessagingStyle(Context contex builder.addAction(replyAction); } + else if(conversation.getThread_id() != null){ + Intent muteIntent = new Intent(context, IncomingTextSMSReplyActionBroadcastReceiver.class); + muteIntent.putExtra(Conversation.ADDRESS, conversation.getAddress()); + muteIntent.putExtra(Conversation.ID, conversation.getMessage_id()); + muteIntent.putExtra(Conversation.THREAD_ID, conversation.getThread_id()); + muteIntent.setAction(IncomingTextSMSReplyActionBroadcastReceiver.MUTE_BROADCAST_INTENT); + + PendingIntent mutePendingIntent = + PendingIntent.getBroadcast(context, Integer.parseInt(conversation.getThread_id()), + muteIntent, PendingIntent.FLAG_MUTABLE); + + NotificationCompat.Action muteAction = new NotificationCompat.Action.Builder(null, + context.getString(R.string.conversation_menu_mute), mutePendingIntent) + .build(); + + builder.addAction(muteAction); + } return builder; } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SIMHandler.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SIMHandler.java index d8b96646..a1370bd8 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SIMHandler.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SIMHandler.java @@ -1,6 +1,11 @@ package com.afkanerd.deku.DefaultSMS.Models; +import static android.content.Context.TELEPHONY_SERVICE; +import static androidx.core.content.ContextCompat.getSystemService; + import android.content.Context; +import android.telephony.CellInfo; +import android.telephony.SmsManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -11,15 +16,12 @@ public class SIMHandler { public static List getSimCardInformation(Context context) { SubscriptionManager subscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - int simCount = getActiveSimcardCount(context); - return subscriptionManager.getActiveSubscriptionInfoList(); } - public static int getActiveSimcardCount(Context context) { - SubscriptionManager subscriptionManager = - (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - return subscriptionManager.getActiveSubscriptionInfoCount(); + public static boolean isDualSim(Context context) { + TelephonyManager manager = (TelephonyManager)context.getSystemService(TELEPHONY_SERVICE); + return manager.getPhoneCount() > 1; } private static String getSimStateString(int simState) { @@ -40,10 +42,10 @@ private static String getSimStateString(int simState) { } } public static int getDefaultSimSubscription(Context context) { - int defaultSmsSubscriptionId = SubscriptionManager.getDefaultSmsSubscriptionId(); - SubscriptionInfo subscriptionInfo = SubscriptionManager.from(context).getActiveSubscriptionInfo(defaultSmsSubscriptionId); - - return subscriptionInfo.getSubscriptionId(); + int subId = SubscriptionManager.getDefaultSmsSubscriptionId(); + if(subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) + return getSimCardInformation(context).get(0).getSubscriptionId(); + return subId; } public static String getSubscriptionName(Context context, int subscriptionId) { @@ -52,28 +54,8 @@ public static String getSubscriptionName(Context context, int subscriptionId) { for(SubscriptionInfo subscriptionInfo : subscriptionInfos) if(subscriptionInfo.getSubscriptionId() == subscriptionId) { if(subscriptionInfo.getCarrierName() != null) - return subscriptionInfo.getCarrierName().toString(); + return subscriptionInfo.getDisplayName().toString(); } return ""; } - - public static String getOperatorName(Context context, String serviceCenterAddress) { - if(serviceCenterAddress == null) - return null; - - SubscriptionManager subscriptionManager = SubscriptionManager.from(context); - - if (subscriptionManager.getActiveSubscriptionInfoCount() > 0) { - for (SubscriptionInfo subscriptionInfo : subscriptionManager.getActiveSubscriptionInfoList()) { - String smscNumber = subscriptionInfo.getSubscriptionId() + ""; - - // Compare the serviceCenterAddress with the SMS center number - if (serviceCenterAddress.equals(smscNumber)) { - return subscriptionInfo.getCarrierName().toString(); - } - } - } - - return null; // Return null if operator name not found or no active subscriptions - } } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSPduLevel.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSPduLevel.java index 9c75bcad..7fb8761e 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSPduLevel.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/SMSPduLevel.java @@ -98,4 +98,5 @@ public static void parse_address_format(String SMSC_address_format) { Log.d(IncomingTextSMSBroadcastReceiver.class.getName(), "PDU SMSC_NPI: " + SMSC_NPI); } + // A function that takes a pdu string as input and returns an array of two strings: the OA and DA } diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/ThreadingPoolExecutor.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/ThreadingPoolExecutor.java new file mode 100644 index 00000000..88f6a5b2 --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/Models/ThreadingPoolExecutor.java @@ -0,0 +1,8 @@ +package com.afkanerd.deku.DefaultSMS.Models; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ThreadingPoolExecutor { + public static final ExecutorService executorService = Executors.newFixedThreadPool(4); +} diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/SearchMessagesThreadsActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/SearchMessagesThreadsActivity.java index 346a68ff..fecc06f7 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/SearchMessagesThreadsActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/SearchMessagesThreadsActivity.java @@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.room.Room; import android.content.Context; import android.content.Intent; @@ -33,6 +34,7 @@ import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; import com.afkanerd.deku.DefaultSMS.Models.Contacts; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.SearchViewModel; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import java.util.List; @@ -43,19 +45,30 @@ public class SearchMessagesThreadsActivity extends AppCompatActivity { ThreadedConversations threadedConversations = new ThreadedConversations(); + Datastore databaseConnector; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search_messages_threads); + + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + databaseConnector = Datastore.datastore; + searchViewModel = new ViewModelProvider(this).get( SearchViewModel.class); + searchViewModel.databaseConnector = Datastore.datastore; Toolbar myToolbar = (Toolbar) findViewById(R.id.search_messages_toolbar); setSupportActionBar(myToolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); SearchConversationRecyclerAdapter searchConversationRecyclerAdapter = - new SearchConversationRecyclerAdapter(getApplicationContext()); + new SearchConversationRecyclerAdapter(); CustomContactsCursorAdapter customContactsCursorAdapter = new CustomContactsCursorAdapter(getApplicationContext(), Contacts.filterContacts(getApplicationContext(), ""), 0); @@ -109,12 +122,8 @@ public void onChanged(String s) { } }); - ThreadedConversationsDao threadedConversationsDao = - threadedConversations.getDaoInstance(getApplicationContext()); - if(getIntent().hasExtra(Conversation.THREAD_ID)) { - searchViewModel.getByThreadId(threadedConversationsDao, - getIntent().getStringExtra(Conversation.THREAD_ID)).observe(this, + searchViewModel.getByThreadId(getIntent().getStringExtra(Conversation.THREAD_ID)).observe(this, new Observer,Integer>>() { @Override public void onChanged(Pair,Integer> smsList) { @@ -130,7 +139,7 @@ public void onChanged(Pair,Integer> smsList) { }); } else { - searchViewModel.get(threadedConversationsDao).observe(this, + searchViewModel.get().observe(this, new Observer,Integer>>() { @Override public void onChanged(Pair,Integer> smsList) { @@ -146,12 +155,6 @@ public void onChanged(Pair,Integer> smsList) { } } - @Override - protected void onDestroy() { - super.onDestroy(); - threadedConversations.close(); - } - public static class CustomContactsCursorAdapter extends CursorAdapter { public CustomContactsCursorAdapter(Context context, Cursor c, int flags) { diff --git a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsActivity.java b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsActivity.java index e419c2e5..02dfb34c 100644 --- a/app/src/main/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsActivity.java +++ b/app/src/main/java/com/afkanerd/deku/DefaultSMS/ThreadedConversationsActivity.java @@ -1,52 +1,47 @@ package com.afkanerd.deku.DefaultSMS; +import static com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment.ARCHIVED_MESSAGE_TYPES; +import static com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment.BLOCKED_MESSAGE_TYPES; +import static com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment.DRAFTS_MESSAGE_TYPES; +import static com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment.ENCRYPTED_MESSAGES_THREAD_FRAGMENT; +import static com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment.MUTED_MESSAGE_TYPE; +import static com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment.UNREAD_MESSAGE_TYPES; + import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; import androidx.core.app.NotificationManagerCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; +import androidx.room.Room; -import android.content.DialogInterface; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.Intent; -import android.content.SharedPreferences; +import android.os.Build; import android.os.Bundle; import android.provider.Telephony; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.TextView; import com.afkanerd.deku.DefaultSMS.DAO.ThreadedConversationsDao; -import com.afkanerd.deku.DefaultSMS.Fragments.ArchivedFragments; -import com.afkanerd.deku.DefaultSMS.Fragments.DraftsFragments; -import com.afkanerd.deku.DefaultSMS.Fragments.EncryptionFragments; import com.afkanerd.deku.DefaultSMS.Fragments.ThreadedConversationsFragment; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ThreadedConversationRecyclerAdapter; import com.afkanerd.deku.DefaultSMS.AdaptersViewModels.ThreadedConversationsViewModel; -import com.afkanerd.deku.DefaultSMS.Fragments.UnreadFragments; -import com.afkanerd.deku.DefaultSMS.Models.Archive; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; -import com.afkanerd.deku.DefaultSMS.Models.Conversations.ViewHolders.ThreadedConversationsTemplateViewHolder; -import com.afkanerd.deku.E2EE.E2EEHandler; -import com.afkanerd.deku.Router.Router.RouterActivity; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientHandler; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.navigation.NavigationView; -import com.google.i18n.phonenumbers.NumberParseException; -import java.io.IOException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Objects; import java.util.concurrent.ExecutorService; public class ThreadedConversationsActivity extends CustomAppCompactActivity implements ThreadedConversationsFragment.ViewModelsInterface { @@ -55,18 +50,10 @@ public class ThreadedConversationsActivity extends CustomAppCompactActivity impl ActionBar ab; - HashMap messagesThreadRecyclerAdapterHashMap = new HashMap<>(); - - String ITEM_TYPE = ""; - - ThreadedConversations threadedConversations = new ThreadedConversations(); - MaterialToolbar toolbar; NavigationView navigationView; - ThreadedConversationsDao threadedConversationsDao; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -76,20 +63,12 @@ protected void onCreate(Bundle savedInstanceState) { setSupportActionBar(toolbar); ab = getSupportActionBar(); - if(!checkIsDefaultApp()) { - startActivity(new Intent(this, DefaultCheckActivity.class)); - finish(); - } - - threadedConversationsDao = threadedConversations.getDaoInstance(getApplicationContext()); - threadedConversationsViewModel = new ViewModelProvider(this).get( ThreadedConversationsViewModel.class); - threadedConversationsViewModel.threadedConversationsDao = threadedConversationsDao; + threadedConversationsViewModel.databaseConnector = databaseConnector; fragmentManagement(); - configureBroadcastListeners(); configureNavigationBar(); } @@ -105,6 +84,8 @@ public void configureNavigationBar() { MenuItem draftMenuItem = navigationView.getMenu().findItem(R.id.navigation_view_menu_drafts); MenuItem encryptedMenuItem = navigationView.getMenu().findItem(R.id.navigation_view_menu_encrypted); MenuItem unreadMenuItem = navigationView.getMenu().findItem(R.id.navigation_view_menu_unread); + MenuItem blockedMenuItem = navigationView.getMenu().findItem(R.id.navigation_view_menu_blocked); + MenuItem mutedMenuItem = navigationView.getMenu().findItem(R.id.navigation_view_menu_muted); threadedConversationsViewModel.folderMetrics.observe(this, new Observer>() { @Override @@ -119,6 +100,12 @@ public void onChanged(List integers) { unreadMenuItem.setTitle(getString(R.string.conversations_navigation_view_unread) + "(" + integers.get(2) + ")"); + + blockedMenuItem.setTitle(getString(R.string.conversations_navigation_view_blocked) + + "(" + integers.get(3) + ")"); + + mutedMenuItem.setTitle(getString(R.string.conversation_menu_muted_label) + + "(" + integers.get(4) + ")"); } }); @@ -133,42 +120,74 @@ public void onClick(View v) { navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { + String messageType = ""; + String label = ""; + String noContent = ""; + int defaultMenu = -1; + int actionModeMenu = -1; if(item.getItemId() == R.id.navigation_view_menu_inbox) { fragmentManagement(); drawerLayout.close(); return true; } else if(item.getItemId() == R.id.navigation_view_menu_drafts) { - fragmentManager.beginTransaction().replace(R.id.view_fragment, - DraftsFragments.class, null, "DRAFT_TAG") - .setReorderingAllowed(true) - .commit(); - drawerLayout.close(); - return true; + messageType = DRAFTS_MESSAGE_TYPES; + label = getString(R.string.conversations_navigation_view_drafts); + noContent = getString(R.string.homepage_draft_no_message); + defaultMenu = R.menu.drafts_menu; + actionModeMenu = R.menu.conversations_threads_menu_items_selected; } else if(item.getItemId() == R.id.navigation_view_menu_encrypted) { - fragmentManager.beginTransaction().replace(R.id.view_fragment, - EncryptionFragments.class, null, "ENCRYPTED_TAG") - .setReorderingAllowed(true) - .commit(); - drawerLayout.close(); - return true; + messageType = ENCRYPTED_MESSAGES_THREAD_FRAGMENT; + label = getString(R.string.conversations_navigation_view_encryption); + noContent = getString(R.string.homepage_encryption_no_message); + defaultMenu = R.menu.conversations_threads_menu; + actionModeMenu = R.menu.conversations_threads_menu_items_selected; } else if(item.getItemId() == R.id.navigation_view_menu_unread) { - fragmentManager.beginTransaction().replace(R.id.view_fragment, - UnreadFragments.class, null, "UNREAD_TAG") - .setReorderingAllowed(true) - .commit(); - drawerLayout.close(); - return true; + messageType = UNREAD_MESSAGE_TYPES; + label = getString(R.string.conversations_navigation_view_unread); + noContent = getString(R.string.homepage_unread_no_message); + defaultMenu = R.menu.read_menu; + actionModeMenu = R.menu.conversations_threads_menu_items_selected; } else if(item.getItemId() == R.id.navigation_view_menu_archive) { - fragmentManager.beginTransaction().replace(R.id.view_fragment, - ArchivedFragments.class, null, "ARCHIVED_TAG") - .setReorderingAllowed(true) - .commit(); - drawerLayout.close(); - return true; + messageType = ARCHIVED_MESSAGE_TYPES; + label = getString(R.string.conversations_navigation_view_archived); + noContent = getString(R.string.homepage_archive_no_message); + defaultMenu = R.menu.archive_menu; + actionModeMenu = R.menu.archive_menu_items_selected; + } + else if(item.getItemId() == R.id.navigation_view_menu_blocked) { + messageType = BLOCKED_MESSAGE_TYPES; + label = getString(R.string.conversation_menu_block); + noContent = getString(R.string.homepage_blocked_no_message); + defaultMenu = R.menu.blocked_conversations; + actionModeMenu = R.menu.blocked_conversations_items_selected; + } + else if(item.getItemId() == R.id.navigation_view_menu_muted) { + messageType = MUTED_MESSAGE_TYPE; + label = getString(R.string.conversation_menu_muted_label); + noContent = getString(R.string.homepage_muted_no_muted); + defaultMenu = R.menu.muted_menu; + actionModeMenu = R.menu.muted_menu_items_selected; } - return false; + else return false; + + Bundle bundle = new Bundle(); + bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_TYPE, + messageType); + bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_LABEL, label); + bundle.putString(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_NO_CONTENT, + noContent); + bundle.putInt(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_DEFAULT_MENU, + defaultMenu); + bundle.putInt(ThreadedConversationsFragment.MESSAGES_THREAD_FRAGMENT_DEFAULT_ACTION_MODE_MENU, + actionModeMenu); + fragmentManager.beginTransaction().replace(R.id.view_fragment, + ThreadedConversationsFragment.class, bundle, null) + .setReorderingAllowed(true) + .commit(); + drawerLayout.close(); + return true; } }); } @@ -181,13 +200,6 @@ private void fragmentManagement() { } - private boolean checkIsDefaultApp() { - final String myPackageName = getPackageName(); - final String defaultPackage = Telephony.Sms.getDefaultSmsPackage(this); - - return myPackageName.equals(defaultPackage); - } - private void cancelAllNotifications() { NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext()); notificationManager.cancelAll(); @@ -198,38 +210,21 @@ public void onNewMessageClick(View view) { startActivity(intent); } -// @Override -// public boolean onCreateOptionsMenu(Menu menu){ -// getMenuInflater().inflate(R.menu.conversations_threads_menu, menu); -// return super.onCreateOptionsMenu(menu); -// } - - - @Override - protected void onPause() { - super.onPause(); -// setViewModel(null); - } @Override protected void onResume() { super.onResume(); - + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + threadedConversationsViewModel.getCount(getApplicationContext()); + } + }); } + @Override public ThreadedConversationsViewModel getThreadedConversationsViewModel() { return threadedConversationsViewModel; } - - @Override - public ExecutorService getExecutorService() { - return executorService; - } - - @Override - public void onDestroy() { - super.onDestroy(); -// threadedConversations.close(); - } } \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryption.java b/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryption.java index a3d70040..3d202bc8 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryption.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryption.java @@ -68,20 +68,4 @@ public long getExchangeDate() { public void setExchangeDate(long exchangeDate) { this.exchangeDate = exchangeDate; } - - @Ignore - Datastore databaseConnector; - public ConversationsThreadsEncryptionDao getDaoInstance(Context context) { - databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration8To9()) - .enableMultiInstanceInvalidation() - .build(); - return databaseConnector.conversationsThreadsEncryptionDao(); - } - - public void close() { - if(databaseConnector != null) - databaseConnector.close(); - } } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java index 068ae8d9..9d8c02eb 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EECompactActivity.java @@ -3,29 +3,23 @@ import android.os.Bundle; import android.provider.Telephony; import android.util.Base64; -import android.util.Log; import android.util.Pair; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; import com.afkanerd.deku.DefaultSMS.CustomAppCompactActivity; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversations; import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; -import com.afkanerd.deku.DefaultSMS.Models.SettingsHandler; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.DefaultSMS.R; -import com.afkanerd.smswithoutborders.libsignal_doubleratchet.libsignal.Ratchets; import com.google.android.material.textfield.TextInputLayout; import com.google.i18n.phonenumbers.NumberParseException; @@ -61,8 +55,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { public void sendTextMessage(final String text, int subscriptionId, ThreadedConversations threadedConversations, String messageId, final byte[] _mk) throws NumberParseException, InterruptedException { - if(threadedConversations.secured && !isEncrypted) { - executorService.execute(new Runnable() { + if(threadedConversations.is_secured && !isEncrypted) { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { @@ -89,7 +83,7 @@ public void informSecured(boolean secured) { runOnUiThread(new Runnable() { @Override public void run() { - threadedConversations.secured = secured; + threadedConversations.is_secured = secured; if(secured && securePopUpRequest != null) { securePopUpRequest.setVisibility(View.GONE); TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); @@ -102,15 +96,10 @@ public void run() { protected void sendDataMessage(ThreadedConversations threadedConversations) { final int subscriptionId = SIMHandler.getDefaultSimSubscription(getApplicationContext()); - executorService.execute(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { try { -// E2EEHandler.clear(getApplicationContext(), -// E2EEHandler.deriveKeystoreAlias( -// threadedConversations.getAddress(), -// 0)); - Pair transmissionRequestKeyPair = E2EEHandler.buildForEncryptionRequest(getApplicationContext(), threadedConversations.getAddress()); @@ -128,7 +117,7 @@ public void run() { conversation.setDate(String.valueOf(System.currentTimeMillis())); conversation.setStatus(Telephony.Sms.STATUS_PENDING); - long id = conversationsViewModel.insert(conversation); + long id = conversationsViewModel.insert(getApplicationContext(), conversation); SMSDatabaseWrapper.send_data(getApplicationContext(), conversation); } catch (Exception e) { e.printStackTrace(); @@ -211,26 +200,28 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { @Override protected void onResume() { super.onResume(); - executorService.execute(new Runnable() { - @Override - public void run() { - try { - keystoreAlias = E2EEHandler.deriveKeystoreAlias(threadedConversations.getAddress(), 0); - threadedConversations.secured = - E2EEHandler.canCommunicateSecurely(getApplicationContext(), keystoreAlias); - if(threadedConversations.secured) { - runOnUiThread(new Runnable() { - @Override - public void run() { - TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); - layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); - } - }); + if(threadedConversations != null) { + ThreadingPoolExecutor.executorService.execute(new Runnable() { + @Override + public void run() { + try { + keystoreAlias = E2EEHandler.deriveKeystoreAlias(threadedConversations.getAddress(), 0); + threadedConversations.is_secured = + E2EEHandler.canCommunicateSecurely(getApplicationContext(), keystoreAlias); + if(threadedConversations.is_secured) { + runOnUiThread(new Runnable() { + @Override + public void run() { + TextInputLayout layout = findViewById(R.id.conversations_send_text_layout); + layout.setPlaceholderText(getString(R.string.send_message_secured_text_box_hint)); + } + }); + } + } catch (IOException | GeneralSecurityException | NumberParseException e) { + e.printStackTrace(); } - } catch (IOException | GeneralSecurityException | NumberParseException e) { - e.printStackTrace(); } - } - }); + }); + } } } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java index c7d13412..548a0c8a 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/E2EEHandler.java @@ -8,8 +8,11 @@ import android.util.Pair; import androidx.annotation.NonNull; +import androidx.room.Room; import com.afkanerd.deku.DefaultSMS.Commons.Helpers; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.ThreadingPoolExecutor; import com.afkanerd.deku.E2EE.Security.CustomKeyStore; import com.afkanerd.deku.E2EE.Security.CustomKeyStoreDao; import com.afkanerd.smswithoutborders.libsignal_doubleratchet.KeystoreHelpers; @@ -91,13 +94,15 @@ public static boolean isAvailableInKeystore(String keystoreAlias) throws Certifi } public static boolean canCommunicateSecurely(Context context, String keystoreAlias) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); - - ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context, Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } return isAvailableInKeystore(keystoreAlias) && - conversationsThreadsEncryptionDao.findByKeystoreAlias(keystoreAlias) != null; + Datastore.datastore.conversationsThreadsEncryptionDao() + .findByKeystoreAlias(keystoreAlias) != null; } public static PublicKey createNewKeyPair(Context context, String keystoreAlias) @@ -111,45 +116,43 @@ public static PublicKey createNewKeyPair(Context context, String keystoreAlias) private static void storeInCustomKeystore(Context context, String keystoreAlias, KeyPair keyPair, byte[] encryptedPrivateKey) throws InterruptedException { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } CustomKeyStore customKeyStore = new CustomKeyStore(); customKeyStore.setPrivateKey(Base64.encodeToString(encryptedPrivateKey, Base64.NO_WRAP)); customKeyStore.setPublicKey(Base64.encodeToString(keyPair.getPublic().getEncoded(), Base64.NO_WRAP)); customKeyStore.setKeystoreAlias(keystoreAlias); - Thread thread = new Thread(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { - CustomKeyStoreDao customKeyStoreDao = customKeyStore.getDaoInstance(context); + CustomKeyStoreDao customKeyStoreDao = Datastore.datastore.customKeyStoreDao(); customKeyStoreDao.insert(customKeyStore); - customKeyStore.close(); } }); - thread.start(); - thread.join(); } public static void removeFromKeystore(Context context, String keystoreAlias) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, InterruptedException { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } KeystoreHelpers.removeFromKeystore(context, keystoreAlias); - CustomKeyStore customKeyStore = new CustomKeyStore(); - new Thread(new Runnable() { + ThreadingPoolExecutor.executorService.execute(new Runnable() { @Override public void run() { - CustomKeyStoreDao customKeyStoreDao = customKeyStore.getDaoInstance(context); + CustomKeyStoreDao customKeyStoreDao = Datastore.datastore.customKeyStoreDao(); customKeyStoreDao.delete(keystoreAlias); - customKeyStore.close(); } - }).start(); - } - - public static int removeFromEncryptionDatabase(Context context, String keystoreAlias) throws KeyStoreException, - CertificateException, IOException, NoSuchAlgorithmException, InterruptedException { - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); - ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); - return conversationsThreadsEncryptionDao.delete(keystoreAlias); + }); } public static boolean isValidDefaultPublicKey(byte[] publicKey) { @@ -263,6 +266,12 @@ public static Pair buildForEncryptionRequest(Context context, St * @throws NumberParseException */ public static long insertNewAgreementKeyDefault(Context context, byte[] publicKey, String keystoreAlias) throws GeneralSecurityException, IOException, InterruptedException, NumberParseException { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } ConversationsThreadsEncryption conversationsThreadsEncryption = new ConversationsThreadsEncryption(); conversationsThreadsEncryption.setPublicKey(Base64.encodeToString(publicKey, Base64.NO_WRAP)); @@ -270,28 +279,37 @@ public static long insertNewAgreementKeyDefault(Context context, byte[] publicKe conversationsThreadsEncryption.setKeystoreAlias(keystoreAlias); ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); + Datastore.datastore.conversationsThreadsEncryptionDao(); return conversationsThreadsEncryptionDao.insert(conversationsThreadsEncryption); } public static ConversationsThreadsEncryption fetchStoredPeerData(Context context, String keystoreAlias) { - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); + Datastore.datastore.conversationsThreadsEncryptionDao(); return conversationsThreadsEncryptionDao.fetch(keystoreAlias); } public static KeyPair getKeyPairBasedVersioning(Context context, String keystoreAlias) throws UnrecoverableEntryException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InterruptedException { final KeyPair[] keyPair = new KeyPair[1]; if(Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } Thread thread = new Thread(new Runnable() { @Override public void run() { - CustomKeyStore customKeyStore = new CustomKeyStore(); - CustomKeyStoreDao customKeyStoreDao = customKeyStore.getDaoInstance(context); - customKeyStore = customKeyStoreDao.find(keystoreAlias); + CustomKeyStoreDao customKeyStoreDao = Datastore.datastore.customKeyStoreDao(); + CustomKeyStore customKeyStore = customKeyStoreDao.find(keystoreAlias); try { if(customKeyStore != null) keyPair[0] = customKeyStore.getKeyPair(); @@ -317,12 +335,17 @@ protected static String getKeystoreForRatchets(String keystoreAlias) { } public static byte[][] encrypt(Context context, final String keystoreAlias, byte[] data) throws Throwable { - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); - conversationsThreadsEncryption = conversationsThreadsEncryptionDao - .findByKeystoreAlias(keystoreAlias); + Datastore.datastore.conversationsThreadsEncryptionDao(); + ConversationsThreadsEncryption conversationsThreadsEncryption = + conversationsThreadsEncryptionDao.findByKeystoreAlias(keystoreAlias); States states; final String keystoreAliasRatchet = getKeystoreForRatchets(keystoreAlias); @@ -359,11 +382,15 @@ public static byte[][] encrypt(Context context, final String keystoreAlias, byte public static byte[] decrypt(Context context, final String keystoreAlias, final byte[] cipherText, byte[] mk, byte[] _AD) throws Throwable { - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); - conversationsThreadsEncryption = + Datastore.datastore.conversationsThreadsEncryptionDao(); + ConversationsThreadsEncryption conversationsThreadsEncryption = conversationsThreadsEncryptionDao.findByKeystoreAlias(keystoreAlias); Headers header = new Headers(); @@ -442,12 +469,16 @@ public static int getKeyType(Context context, String keystoreAlias, byte[] publi } public static void clear(Context context, String keystoreAlias) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, InterruptedException { + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context.getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } removeFromKeystore(context, keystoreAlias); removeFromKeystore(context, getKeystoreForRatchets(keystoreAlias)); - ConversationsThreadsEncryption conversationsThreadsEncryption = - new ConversationsThreadsEncryption(); ConversationsThreadsEncryptionDao conversationsThreadsEncryptionDao = - conversationsThreadsEncryption.getDaoInstance(context); + Datastore.datastore.conversationsThreadsEncryptionDao(); conversationsThreadsEncryptionDao.delete(keystoreAlias); conversationsThreadsEncryptionDao.delete(getKeystoreForRatchets(keystoreAlias)); } diff --git a/app/src/main/java/com/afkanerd/deku/E2EE/Security/CustomKeyStore.java b/app/src/main/java/com/afkanerd/deku/E2EE/Security/CustomKeyStore.java index 4a69ef22..ba9908b7 100644 --- a/app/src/main/java/com/afkanerd/deku/E2EE/Security/CustomKeyStore.java +++ b/app/src/main/java/com/afkanerd/deku/E2EE/Security/CustomKeyStore.java @@ -106,30 +106,4 @@ public KeyPair getKeyPair() throws UnrecoverableKeyException, CertificateExcepti return new KeyPair(x509PublicKey, x509PrivateKey); } - @Ignore - Datastore databaseConnector; - - public CustomKeyStoreDao getDaoInstance(Context context) { - databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration8To9()) - .addMigrations(new Migrations.Migration9To10()) - .build(); - return databaseConnector.customKeyStoreDao(); - } - - public void close() { - if(databaseConnector != null) - databaseConnector.close(); - } - - public static CustomKeyStoreDao getDao(Context context) { - Datastore databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration8To9()) - .build(); - CustomKeyStoreDao customKeyStoreDao = databaseConnector.customKeyStoreDao(); - databaseConnector.close(); - return customKeyStoreDao; - } } diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.java index 5e36b99a..f481fd5e 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClient.java @@ -9,6 +9,11 @@ import com.afkanerd.deku.DefaultSMS.R; +import org.apache.commons.codec.digest.MurmurHash3; + +import java.nio.charset.StandardCharsets; +import java.util.Objects; + @Entity public class GatewayClient { public GatewayClient() {} @@ -175,22 +180,42 @@ public void setProtocol(String protocol) { this.protocol = protocol; } + public long[] getHashcode() { + String hashValues = protocol + hostUrl + port + virtualHost + username + password; + return MurmurHash3.hash128(hashValues.getBytes(StandardCharsets.UTF_8)); + } + + public boolean same(@Nullable Object obj) { + if(obj instanceof GatewayClient) { + GatewayClient gatewayClient = (GatewayClient) obj; + return Objects.equals(gatewayClient.hostUrl, this.hostUrl) && + Objects.equals(gatewayClient.protocol, this.protocol) && + Objects.equals(gatewayClient.virtualHost, this.virtualHost) && + gatewayClient.port == this.port; + } + return false; + } public boolean equals(@Nullable Object obj) { // return super.equals(obj); if(obj instanceof GatewayClient) { GatewayClient gatewayClient = (GatewayClient) obj; - return gatewayClient.id == this.id && - gatewayClient.hostUrl.equals(this.hostUrl) && - gatewayClient.protocol.equals(this.protocol) && - gatewayClient.port == this.port && - gatewayClient.projectBinding.equals(this.projectBinding) && - gatewayClient.projectName.equals(this.projectName) && - gatewayClient.connectionStatus.equals(this.connectionStatus) && - gatewayClient.date == this.date; +// return gatewayClient.id == this.id && +// Objects.equals(gatewayClient.hostUrl, this.hostUrl) && +// Objects.equals(gatewayClient.protocol, this.protocol) && +// gatewayClient.port == this.port && +// Objects.equals(gatewayClient.projectBinding, this.projectBinding) && +// Objects.equals(gatewayClient.projectName, this.projectName) && +// Objects.equals(gatewayClient.connectionStatus, this.connectionStatus) && +// gatewayClient.date == this.date; + return Objects.equals(gatewayClient.hostUrl, this.hostUrl) && + Objects.equals(gatewayClient.protocol, this.protocol) && + Objects.equals(gatewayClient.virtualHost, this.virtualHost) && + gatewayClient.port == this.port; } return false; } + public static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { @Override diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java index b7ab8ec7..f61bca48 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientAddActivity.java @@ -1,11 +1,17 @@ package com.afkanerd.deku.QueueListener.GatewayClients; +import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS; + import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.widget.RadioGroup; @@ -49,10 +55,11 @@ public void onClick(View v) { }); } + long id = -1; public void editGatewayClient() throws InterruptedException { - long gatewayClientId = getIntent().getLongExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, -1); + id = getIntent().getLongExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, -1); - if(gatewayClientId != -1 ) { + if(id != -1 ) { TextInputEditText url = findViewById(R.id.new_gateway_client_url_input); TextInputEditText username = findViewById(R.id.new_gateway_client_username); TextInputEditText password = findViewById(R.id.new_gateway_password); @@ -61,7 +68,7 @@ public void editGatewayClient() throws InterruptedException { TextInputEditText port = findViewById(R.id.new_gateway_client_port); GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); - GatewayClient gatewayClient = gatewayClientHandler.fetch(gatewayClientId); + GatewayClient gatewayClient = gatewayClientHandler.fetch(id); url.setText(gatewayClient.getHostUrl()); username.setText(gatewayClient.getUsername()); @@ -69,7 +76,6 @@ public void editGatewayClient() throws InterruptedException { friendlyName.setText(gatewayClient.getFriendlyConnectionName()); virtualHost.setText(gatewayClient.getVirtualHost()); port.setText(String.valueOf(gatewayClient.getPort())); - gatewayClientHandler.close(); } } @@ -131,11 +137,46 @@ public void onSaveGatewayClient(View view) throws InterruptedException { else { gatewayClientHandler.add(gatewayClient); } - gatewayClientHandler.close(); Intent intent = new Intent(this, GatewayClientListingActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if(id != -1) + getMenuInflater().inflate(R.menu.gateway_server_add_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.gateway_client_delete) { + SharedPreferences sharedPreferences = getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); + sharedPreferences.edit().remove(String.valueOf(id)) + .apply(); + + GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); +// GatewayClient gatewayClient = gatewayClientHandler.fetch(id); +// gatewayClientHandler.delete(gatewayClient); + new Thread(new Runnable() { + @Override + public void run() { + GatewayClient gatewayClient = gatewayClientHandler.databaseConnector + .gatewayClientDAO().fetch(id); + gatewayClientHandler.databaseConnector.gatewayClientDAO() + .delete(gatewayClient); + gatewayClientHandler.databaseConnector.gatewayClientProjectDao() + .deleteGatewayClientId(id); + } + }).start(); + + startActivity(new Intent(this, GatewayClientListingActivity.class)); + finish(); + return true; + } + return false; + } } \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java index 509a2497..9ce1e42b 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientDAO.java @@ -5,6 +5,7 @@ import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; +import androidx.room.Transaction; import androidx.room.Update; import java.util.List; @@ -18,9 +19,18 @@ public interface GatewayClientDAO { @Insert(onConflict = OnConflictStrategy.REPLACE) long insert(GatewayClient gatewayClient); + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(List gatewayClients); + @Delete int delete(GatewayClient gatewayClient); + @Delete + void delete(List gatewayClients); + + @Query("DELETE FROM GatewayClient") + void deleteAll(); + @Query("SELECT * FROM GatewayClient WHERE id=:id") GatewayClient fetch(long id); @@ -29,4 +39,10 @@ public interface GatewayClientDAO { @Update void update(GatewayClient gatewayClient); + +// @Transaction +// default void repentance(List sinFulGatewayClients, List afreshGatewayClient) { +// delete(sinFulGatewayClients); +// insert(afreshGatewayClient); +// } } diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.java index ea7f7d74..03212c4f 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientHandler.java @@ -14,6 +14,7 @@ import androidx.work.WorkManager; import com.afkanerd.deku.DefaultSMS.Commons.Helpers; +import com.afkanerd.deku.DefaultSMS.Models.Database.SemaphoreManager; import com.afkanerd.deku.DefaultSMS.ThreadedConversationsActivity; import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; @@ -23,21 +24,25 @@ import com.afkanerd.deku.DefaultSMS.R; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; public class GatewayClientHandler { - Datastore databaseConnector; - Context context; + public Datastore databaseConnector; public GatewayClientHandler(Context context) { - this.context = context; - databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration4To5()) - .addMigrations(new Migrations.Migration5To6()) - .build(); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context, Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; } public long add(GatewayClient gatewayClient) throws InterruptedException { @@ -75,9 +80,6 @@ public void update(GatewayClient gatewayClient) throws InterruptedException { @Override public void run() { GatewayClientDAO gatewayClientDAO = databaseConnector.gatewayClientDAO(); -// gatewayClientDAO.updateProjectNameAndProjectBinding( -// gatewayClient.getProjectName(), gatewayClient.getProjectBinding(), -// gatewayClient.getId()); gatewayClientDAO.update(gatewayClient); } }); @@ -116,30 +118,56 @@ public void run() { return gatewayClientList[0]; } - public Intent getIntent(int id) throws InterruptedException { - GatewayClient gatewayClient = fetch(id); - return getIntent(gatewayClient); - } + private void setMigrationsTo11() { + try { + SemaphoreManager.acquireSemaphore(); + GatewayClientDAO gatewayClientDAO = databaseConnector.gatewayClientDAO(); + Map> gatewayClientMaps = new HashMap<>(); + List gatewayClientList = new ArrayList<>(); + for(GatewayClient gatewayClient : gatewayClientDAO.getAll()) { + GatewayClientProjects gatewayClientProjects1 = new GatewayClientProjects(); + gatewayClientProjects1.name = gatewayClient.getProjectName(); + gatewayClientProjects1.binding1Name = gatewayClient.getProjectBinding(); + gatewayClientProjects1.binding2Name = gatewayClient.getProjectBinding2(); + gatewayClientProjects1.gatewayClientId = gatewayClient.getHashcode()[0]; + + if(!gatewayClientMaps.containsKey(gatewayClient.getHashcode()[0]) || + gatewayClientMaps.get(gatewayClient.getHashcode()[0]) == null) { + gatewayClientMaps.put(gatewayClient.getHashcode()[0], new HashSet<>()); + gatewayClient.setId(gatewayClient.getHashcode()[0]); + gatewayClientList.add(gatewayClient); + } + gatewayClientMaps.get(gatewayClient.getHashcode()[0]).add(gatewayClientProjects1); + } - public Intent getIntent(GatewayClient gatewayClient) throws InterruptedException { - Intent intent = new Intent(context, RMQConnectionService.class); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, gatewayClient.getId()); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_USERNAME, gatewayClient.getUsername()); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_PASSWORD, gatewayClient.getPassword()); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_HOST, gatewayClient.getHostUrl()); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_PORT, gatewayClient.getPort()); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_VIRTUAL_HOST, gatewayClient.getVirtualHost()); - intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_FRIENDLY_NAME, gatewayClient.getFriendlyConnectionName()); - - return intent; - } + gatewayClientDAO.deleteAll(); + gatewayClientDAO.insert(gatewayClientList); - public void close() { - databaseConnector.close(); + List projectsList = new ArrayList<>(); + for(Set gatewayClientProjects : gatewayClientMaps.values()) + projectsList.addAll(gatewayClientProjects); + + databaseConnector.gatewayClientProjectDao().insert(projectsList); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + SemaphoreManager.releaseSemaphore(); + } catch (InterruptedException e ) { + e.printStackTrace(); + } + } } + public final static String MIGRATIONS = "MIGRATIONS"; + public final static String MIGRATIONS_TO_11 = "MIGRATIONS_TO_11"; + public void startServices(Context context) throws InterruptedException { + SharedPreferences sharedPreferences = context.getSharedPreferences(MIGRATIONS, Context.MODE_PRIVATE); + if(sharedPreferences.getBoolean(MIGRATIONS_TO_11, false)) { + setMigrationsTo11(); + sharedPreferences.edit().putBoolean(MIGRATIONS_TO_11, false).apply(); + } - public void startServices() throws InterruptedException { Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) @@ -166,8 +194,8 @@ public void startServices() throws InterruptedException { } public static String getConnectionStatus(Context context, String gatewayClientId) { - SharedPreferences sharedPreferences = context.getSharedPreferences(GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS, - Context.MODE_PRIVATE); + SharedPreferences sharedPreferences = context.getSharedPreferences( + GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); if(sharedPreferences.contains(gatewayClientId)) { if(sharedPreferences.getBoolean(gatewayClientId, false)) { @@ -208,7 +236,7 @@ public static void setListening(Context context, GatewayClient gatewayClient) th public static void startListening(Context context, GatewayClient gatewayClient) throws InterruptedException { GatewayClientHandler.setListening(context, gatewayClient); - new GatewayClientHandler(context).startServices(); + new GatewayClientHandler(context).startServices(context); } } diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.java index 8a16acec..13c358d6 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientListingActivity.java @@ -14,13 +14,13 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import com.afkanerd.deku.DefaultSMS.LinkedDevicesQRActivity; import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; -import com.afkanerd.deku.DefaultSMS.Models.Database.Migrations; import com.afkanerd.deku.DefaultSMS.R; import java.util.List; @@ -28,6 +28,7 @@ public class GatewayClientListingActivity extends AppCompatActivity { public static String GATEWAY_CLIENT_ID = "GATEWAY_CLIENT_ID"; + public static String GATEWAY_CLIENT_ID_NEW = "GATEWAY_CLIENT_ID_NEW"; public static String GATEWAY_CLIENT_USERNAME = "GATEWAY_CLIENT_USERNAME"; public static String GATEWAY_CLIENT_PASSWORD = "GATEWAY_CLIENT_PASSWORD"; public static String GATEWAY_CLIENT_VIRTUAL_HOST = "GATEWAY_CLIENT_VIRTUAL_HOST"; @@ -58,6 +59,14 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gateway_client_listing); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; + sharedPreferences = getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); toolbar = findViewById(R.id.gateway_client_listing_toolbar); @@ -80,11 +89,6 @@ protected void onCreate(Bundle savedInstanceState) { gatewayClientViewModel = new ViewModelProvider(this).get( GatewayClientViewModel.class); - databaseConnector = Room.databaseBuilder(getApplicationContext(), Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration5To6()) - .build(); - gatewayClientDAO = databaseConnector.gatewayClientDAO(); gatewayClientViewModel.getGatewayClientList( @@ -127,7 +131,7 @@ public void run() { @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.gateway_client_add_menu, menu); + getMenuInflater().inflate(R.menu.gateway_client_listing_menu, menu); return super.onCreateOptionsMenu(menu); } @@ -157,10 +161,4 @@ private boolean removeListenerConfiguration(int id) { return editor.remove(String.valueOf(id)) .commit(); } - - @Override - protected void onDestroy() { - super.onDestroy(); - databaseConnector.close(); - } } \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientCustomizationActivity.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectAddActivity.java similarity index 57% rename from app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientCustomizationActivity.java rename to app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectAddActivity.java index c7ccfe3a..c7d6e493 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientCustomizationActivity.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectAddActivity.java @@ -3,10 +3,11 @@ import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_ID; import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS; -import androidx.appcompat.app.ActionBar; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.room.Room; import android.content.Context; import android.content.Intent; @@ -16,19 +17,25 @@ import android.telephony.SubscriptionInfo; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ThreadedConversationsHandler; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; import com.afkanerd.deku.DefaultSMS.Models.SIMHandler; import com.afkanerd.deku.DefaultSMS.R; import com.google.android.material.button.MaterialButton; import com.google.android.material.textfield.TextInputEditText; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; -public class GatewayClientCustomizationActivity extends AppCompatActivity { +public class GatewayClientProjectAddActivity extends AppCompatActivity { + public static final String GATEWAY_CLIENT_PROJECT_ID = "GATEWAY_CLIENT_PROJECT_ID"; GatewayClient gatewayClient; GatewayClientHandler gatewayClientHandler; @@ -36,10 +43,19 @@ public class GatewayClientCustomizationActivity extends AppCompatActivity { SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener; Toolbar toolbar; + + Datastore databaseConnector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gateway_client_customization); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; toolbar = findViewById(R.id.gateway_client_customization_toolbar); setSupportActionBar(toolbar); @@ -47,7 +63,9 @@ protected void onCreate(Bundle savedInstanceState) { try { getGatewayClient(); - getSupportActionBar().setTitle(gatewayClient.getHostUrl()); + getSupportActionBar().setTitle(gatewayClient == null ? + getString(R.string.add_new_gateway_server_toolbar_title) : + gatewayClient.getHostUrl()); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -83,27 +101,47 @@ public void checkForBatteryOptimization() { startActivity(intent); } + long id = -1; private void getGatewayClient() throws InterruptedException { TextInputEditText projectName = findViewById(R.id.new_gateway_client_project_name); TextInputEditText projectBinding = findViewById(R.id.new_gateway_client_project_binding_sim_1); TextInputEditText projectBinding2 = findViewById(R.id.new_gateway_client_project_binding_sim_2); gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); - long gatewayId = getIntent().getLongExtra(GATEWAY_CLIENT_ID, -1); gatewayClient = gatewayClientHandler.fetch(gatewayId); - if(gatewayClient.getProjectName() != null && !gatewayClient.getProjectName().isEmpty()) - projectName.setText(gatewayClient.getProjectName()); - - if(gatewayClient.getProjectBinding() != null && !gatewayClient.getProjectBinding().isEmpty()) - projectBinding.setText(gatewayClient.getProjectBinding()); + final boolean isDualSim = SIMHandler.isDualSim(getApplicationContext()); + if(isDualSim) { + findViewById(R.id.new_gateway_client_project_binding_sim_2_constraint) + .setVisibility(View.VISIBLE); + } - List simcards = SIMHandler.getSimCardInformation(getApplicationContext()); - if(simcards.size() > 1) { - findViewById(R.id.new_gateway_client_project_binding_sim_2_constraint).setVisibility(View.VISIBLE); - if(gatewayClient.getProjectBinding2() != null && !gatewayClient.getProjectBinding2().isEmpty()) - projectBinding2.setText(gatewayClient.getProjectBinding2()); + if(getIntent().hasExtra(GATEWAY_CLIENT_PROJECT_ID)) { + id = getIntent().getLongExtra(GATEWAY_CLIENT_PROJECT_ID, -1); + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + GatewayClientProjects gatewayClientProjects = + databaseConnector.gatewayClientProjectDao().fetch(id); + runOnUiThread(new Runnable() { + @Override + public void run() { + projectName.setText(gatewayClientProjects.name); + projectBinding.setText(gatewayClientProjects.binding1Name); + } + }); + + if (isDualSim) { + runOnUiThread(new Runnable() { + @Override + public void run() { + projectBinding2.setText(gatewayClientProjects.binding2Name); + } + }); + } + } + }); } projectName.addTextChangedListener(new TextWatcher() { @@ -155,84 +193,65 @@ public void onSaveGatewayClientConfiguration(View view) throws InterruptedExcept return; } - gatewayClient.setProjectName(projectName.getText().toString()); - gatewayClient.setProjectBinding(projectBinding.getText().toString()); - - if(projectBinding2.getVisibility() == View.VISIBLE && projectBinding2.getText() != null) - gatewayClient.setProjectBinding2(projectBinding2.getText().toString()); - - gatewayClientHandler.update(gatewayClient); + if(id == -1) { + GatewayClientProjects gatewayClientProjects = new GatewayClientProjects(); + gatewayClientProjects.name = projectName.getText().toString(); + gatewayClientProjects.binding1Name = projectBinding.getText().toString(); + gatewayClientProjects.binding2Name = projectBinding2.getText().toString(); + gatewayClientProjects.gatewayClientId = gatewayClient.getId(); + + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + databaseConnector.gatewayClientProjectDao().insert(gatewayClientProjects); + } + }); + } + else { + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + GatewayClientProjects gatewayClientProjects = + databaseConnector.gatewayClientProjectDao().fetch(id); + gatewayClientProjects.name = projectName.getText().toString(); + gatewayClientProjects.binding1Name = projectBinding.getText().toString(); + gatewayClientProjects.binding2Name = projectBinding2.getText().toString(); + gatewayClientProjects.gatewayClientId = gatewayClient.getId(); + databaseConnector.gatewayClientProjectDao().update(gatewayClientProjects); + } + }); + } - Intent intent = new Intent(this, GatewayClientListingActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); + finish(); } @Override public boolean onCreateOptionsMenu(Menu menu) { - boolean connected = sharedPreferences.contains(String.valueOf(gatewayClient.getId())); - getMenuInflater().inflate(R.menu.gateway_client_customization_menu, menu); - menu.findItem(R.id.gateway_client_connect).setVisible(!connected); - menu.findItem(R.id.gateway_client_disconnect).setVisible(connected); + if(getIntent().hasExtra(GATEWAY_CLIENT_PROJECT_ID)) + getMenuInflater().inflate(R.menu.gateway_client_customization_menu, menu); return super.onCreateOptionsMenu(menu); } + ExecutorService consumerExecutorService = Executors.newFixedThreadPool(2); // Create a pool of 5 worker threads @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch(item.getItemId()) { - case R.id.gateway_client_delete: - try { - deleteGatewayClient(); - return true; - } catch (InterruptedException e) { - e.printStackTrace(); + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (R.id.gateway_client_project_delete == item.getItemId()) { + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + databaseConnector.gatewayClientProjectDao().delete(id); + finish(); } - break; - - case R.id.gateway_client_connect: - try { - GatewayClientHandler.startListening(getApplicationContext(), gatewayClient); - return true; - } catch (InterruptedException e) { - e.printStackTrace(); - } - break; - - case R.id.gateway_client_disconnect: - stopListening(); - return true; - - case R.id.gateway_client_edit: - editGatewayClient(); - return true; + }); + return true; } return false; } - private void editGatewayClient() { - Intent intent = new Intent(this, GatewayClientAddActivity.class); - intent.putExtra(GATEWAY_CLIENT_ID, gatewayClient.getId()); - - startActivity(intent); - } - - public void stopListening() { - sharedPreferences.edit().remove(String.valueOf(gatewayClient.getId())) - .apply(); - } - - private void deleteGatewayClient() throws InterruptedException { - stopListening(); - gatewayClientHandler.delete(gatewayClient); - startActivity(new Intent(this, GatewayClientListingActivity.class)); - finish(); - } - @Override protected void onDestroy() { super.onDestroy(); - gatewayClientHandler.close(); sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); } } \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectDao.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectDao.java new file mode 100644 index 00000000..ff90af7d --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectDao.java @@ -0,0 +1,40 @@ +package com.afkanerd.deku.QueueListener.GatewayClients; + +import androidx.lifecycle.LiveData; +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface GatewayClientProjectDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(List gatewayClientProjectsList); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + long insert(GatewayClientProjects gatewayClientProjects); + + @Query("SELECT * FROM GatewayClientProjects WHERE id = :id") + GatewayClientProjects fetch(long id); + + @Query("SELECT * FROM GatewayClientProjects WHERE gatewayClientId = :gatewayClientId") + LiveData> fetchGatewayClientId(long gatewayClientId); + + @Query("SELECT * FROM GatewayClientProjects WHERE gatewayClientId = :gatewayClientId") + List fetchGatewayClientIdList(long gatewayClientId); + + @Update + void update(GatewayClientProjects gatewayClientProjects); + + @Query("DELETE FROM GatewayClientProjects WHERE gatewayClientId = :id") + void deleteGatewayClientId(long id); + + @Query("DELETE FROM GatewayClientProjects WHERE id = :id") + void delete(long id); + +} diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingActivity.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingActivity.java new file mode 100644 index 00000000..b708bba3 --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingActivity.java @@ -0,0 +1,134 @@ +package com.afkanerd.deku.QueueListener.GatewayClients; + +import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_ID; +import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.room.Room; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.R; + +import java.util.List; + +public class GatewayClientProjectListingActivity extends AppCompatActivity { + + long id; + SharedPreferences sharedPreferences; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_gateway_client_project_listing); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), + Datastore.class, Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + + Toolbar toolbar = findViewById(R.id.gateway_client_project_listing_toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + String username = getIntent().getStringExtra(GatewayClientListingActivity.GATEWAY_CLIENT_USERNAME); + String host = getIntent().getStringExtra(GatewayClientListingActivity.GATEWAY_CLIENT_HOST); + id = getIntent().getLongExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, -1); + sharedPreferences = getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); + + getSupportActionBar().setTitle(username); + getSupportActionBar().setSubtitle(host); + + GatewayClientProjectListingRecyclerAdapter gatewayClientProjectListingRecyclerAdapter = + new GatewayClientProjectListingRecyclerAdapter(); + + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); + RecyclerView recyclerView = findViewById(R.id.gateway_client_project_listing_recycler_view); + recyclerView.setLayoutManager(linearLayoutManager); + + recyclerView.setAdapter(gatewayClientProjectListingRecyclerAdapter); + + GatewayClientProjectListingViewModel gatewayClientProjectListingViewModel = + new ViewModelProvider(this).get(GatewayClientProjectListingViewModel.class); + + gatewayClientProjectListingViewModel.get(Datastore.datastore, id).observe(this, + new Observer>() { + @Override + public void onChanged(List gatewayClients) { + gatewayClientProjectListingRecyclerAdapter.mDiffer.submitList(gatewayClients); + if(gatewayClients == null || gatewayClients.isEmpty()) + findViewById(R.id.gateway_client_project_listing_no_projects).setVisibility(View.VISIBLE); + else + findViewById(R.id.gateway_client_project_listing_no_projects).setVisibility(View.GONE); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.gateway_client_project_listing_menu, menu); + boolean connected = sharedPreferences.contains(String.valueOf(id)); + menu.findItem(R.id.gateway_client_project_connect).setVisible(!connected); + menu.findItem(R.id.gateway_client_project_disconnect).setVisible(connected); + return super.onCreateOptionsMenu(menu); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.gateway_client_project_add) { + Intent intent = new Intent(getApplicationContext(), GatewayClientProjectAddActivity.class); + intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, id); + intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID_NEW, true); + startActivity(intent); + return true; + } + if(item.getItemId() == R.id.gateway_client_edit ) { + Intent intent = new Intent(this, GatewayClientAddActivity.class); + intent.putExtra(GATEWAY_CLIENT_ID, id); + + startActivity(intent); + return true; + } + if(item.getItemId() == R.id.gateway_client_project_connect) { + GatewayClientHandler gatewayClientHandler = + new GatewayClientHandler(getApplicationContext()); + new Thread(new Runnable() { + @Override + public void run() { + GatewayClient gatewayClient = + gatewayClientHandler.databaseConnector.gatewayClientDAO().fetch(id); + try { + GatewayClientHandler.startListening(getApplicationContext(), gatewayClient); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + return true; + } + if(item.getItemId() == R.id.gateway_client_project_disconnect) { + sharedPreferences.edit().remove(String.valueOf(id)) + .apply(); + finish(); + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingRecyclerAdapter.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingRecyclerAdapter.java new file mode 100644 index 00000000..d89159d3 --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingRecyclerAdapter.java @@ -0,0 +1,79 @@ +package com.afkanerd.deku.QueueListener.GatewayClients; + +import android.content.Intent; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.RecyclerView; + +import com.afkanerd.deku.DefaultSMS.R; + +import org.jetbrains.annotations.NotNull; + +public class GatewayClientProjectListingRecyclerAdapter extends RecyclerView.Adapter{ + public final AsyncListDiffer mDiffer = + new AsyncListDiffer<>(this, GatewayClientProjects.DIFF_CALLBACK); + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view = inflater.inflate(R.layout.gateway_client_project_listing_layout, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + GatewayClientProjects gatewayClientProjects = mDiffer.getCurrentList().get(position); + Log.d(getClass().getName(), "Binding object: " + gatewayClientProjects.name); + holder.projectNameTextView.setText(gatewayClientProjects.name); + holder.projectBinding1TextView.setText(gatewayClientProjects.binding1Name); + holder.projectBinding2TextView.setText(gatewayClientProjects.binding2Name); + + holder.cardView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(holder.itemView.getContext(), GatewayClientProjectAddActivity.class); + intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, + gatewayClientProjects.gatewayClientId); + intent.putExtra(GatewayClientProjectAddActivity.GATEWAY_CLIENT_PROJECT_ID, + gatewayClientProjects.id); + holder.itemView.getContext().startActivity(intent); + } + }); + } + + @Override + public int getItemCount() { + return mDiffer.getCurrentList().size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + CardView cardView; + TextView projectNameTextView; + TextView projectBinding1TextView, projectBinding2TextView; + public ViewHolder(@NonNull @NotNull View itemView) { + super(itemView); + + cardView = itemView.findViewById(R.id.gateway_client_project_listing_card ); + projectNameTextView = + itemView.findViewById(R.id.gateway_client_project_listing_project_name); + + projectBinding1TextView = + itemView.findViewById(R.id.gateway_client_project_listing_project_binding1); + + projectBinding2TextView = + itemView.findViewById(R.id.gateway_client_project_listing_project_binding2); + } + } + + @Override + public int getItemViewType(int position) { + return super.getItemViewType(position); + } +} diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java new file mode 100644 index 00000000..493cdd9b --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjectListingViewModel.java @@ -0,0 +1,27 @@ +package com.afkanerd.deku.QueueListener.GatewayClients; + +import android.content.Context; +import android.util.Log; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import androidx.room.Room; + +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GatewayClientProjectListingViewModel extends ViewModel { + + long id; + public LiveData> get(Datastore databaseConnector, long id) { + this.id = id; + GatewayClientProjectDao gatewayClientProjectDao = databaseConnector.gatewayClientProjectDao(); + return gatewayClientProjectDao.fetchGatewayClientId(id); + } + +} diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjects.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjects.java new file mode 100644 index 00000000..0236d83e --- /dev/null +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientProjects.java @@ -0,0 +1,51 @@ +package com.afkanerd.deku.QueueListener.GatewayClients; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.DiffUtil; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import java.util.Objects; + +@Entity +public class GatewayClientProjects { + + @PrimaryKey(autoGenerate = true) + public long id; + public long gatewayClientId; + + public String name; + public String binding1Name; + public String binding2Name; + + + @Override + public boolean equals(@Nullable Object obj) { + if(obj instanceof GatewayClientProjects) { + GatewayClientProjects gatewayClientProjects = (GatewayClientProjects) obj; + + return gatewayClientProjects.id == this.id && + Objects.equals(gatewayClientProjects.name, this.name) && + Objects.equals(gatewayClientProjects.binding1Name, this.binding1Name) && + Objects.equals(gatewayClientProjects.binding2Name, this.binding2Name) && + gatewayClientProjects.gatewayClientId == this.gatewayClientId; + } + return false; + } + + public static final DiffUtil.ItemCallback DIFF_CALLBACK = + new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull GatewayClientProjects oldItem, + @NonNull GatewayClientProjects newItem) { + return oldItem.id == newItem.id; + } + + @Override + public boolean areContentsTheSame(@NonNull GatewayClientProjects oldItem, + @NonNull GatewayClientProjects newItem) { + return oldItem.equals(newItem); + } + }; +} diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.java index f73f36ac..99068067 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientRecyclerAdapter.java @@ -30,12 +30,10 @@ public class GatewayClientRecyclerAdapter extends RecyclerView.Adapter runningServiceInfoList = new ArrayList<>(); - Context context; public static final String ADAPTER_POSITION = "ADAPTER_POSITION"; SharedPreferences sharedPreferences; public GatewayClientRecyclerAdapter(Context context) { - this.context = context; runningServiceInfoList = ServiceHandler.getRunningService(context); sharedPreferences = context.getSharedPreferences( GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); @@ -44,7 +42,7 @@ public GatewayClientRecyclerAdapter(Context context) { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from(this.context); + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); View view = inflater.inflate(R.layout.gateway_client_listing_layout, parent, false); return new ViewHolder(view); } @@ -63,7 +61,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.username.setText(gatewayClient.getUsername()); holder.connectionStatus.setText(gatewayClient.getConnectionStatus()); - String date = Helpers.formatDate(context, gatewayClient.getDate()); + String date = Helpers.formatDate(holder.itemView.getContext(), gatewayClient.getDate()); holder.date.setText(date); if(gatewayClient.getFriendlyConnectionName() == null || @@ -75,9 +73,13 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.cardView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(context, GatewayClientCustomizationActivity.class); + Intent intent = new Intent(holder.itemView.getContext(), GatewayClientProjectListingActivity.class); intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_ID, gatewayClient.getId()); - context.startActivity(intent); + intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_USERNAME, + gatewayClient.getUsername()); + intent.putExtra(GatewayClientListingActivity.GATEWAY_CLIENT_HOST, + gatewayClient.getHostUrl()); + holder.itemView.getContext().startActivity(intent); } }); } diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.java b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.java index 8fa0990f..d3bd264e 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/GatewayClients/GatewayClientViewModel.java @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import java.util.ArrayList; import java.util.List; public class GatewayClientViewModel extends ViewModel { @@ -30,17 +31,33 @@ private void loadGatewayClients(Context context) { new Thread(new Runnable() { @Override public void run() { - List gatewayClients = gatewayClientDAO.getAll(); - Log.d(getClass().getName(), "Number of items: " + gatewayClients.size()); - - if(gatewayClients != null) - for(GatewayClient gatewayClient : gatewayClients) - gatewayClient.setConnectionStatus( - GatewayClientHandler.getConnectionStatus(context, - String.valueOf(gatewayClient.getId()))); + List gatewayClients = normalizeGatewayClients(gatewayClientDAO.getAll()); + for(GatewayClient gatewayClient : gatewayClients) + gatewayClient.setConnectionStatus( + GatewayClientHandler.getConnectionStatus(context, + String.valueOf(gatewayClient.getId()))); gatewayClientList.postValue(gatewayClients); } }).start(); } + + private List normalizeGatewayClients(List gatewayClients) { + List filteredGatewayClients = new ArrayList<>(); + for(GatewayClient gatewayClient : gatewayClients) { + boolean contained = false; + for(GatewayClient gatewayClient1 : filteredGatewayClients) { + if(gatewayClient1.same(gatewayClient)) { + contained = true; + break; + } + } + if(!contained) { + filteredGatewayClients.add(gatewayClient); + } + } + + return filteredGatewayClients; + } + } diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.java b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.java index 9056939e..e68aa5d8 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnection.java @@ -7,8 +7,11 @@ import com.rabbitmq.client.DeliverCallback; import com.rabbitmq.client.ShutdownListener; import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.impl.ChannelN; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class RMQConnection { final boolean autoDelete = false; @@ -18,24 +21,24 @@ public class RMQConnection { public static final String MESSAGE_BODY_KEY = "text"; public static final String MESSAGE_MSISDN_KEY = "to"; - public static final String MESSAGE_GLOBAL_MESSAGE_ID_KEY = "id"; public static final String MESSAGE_SID = "sid"; - private String queueName, queueName2; + public static final String RMQ_DELIVERY_TAG = "RMQ_DELIVERY_TAG"; + public static final String RMQ_CONSUMER_TAG = "RMQ_CONSUMER_TAG"; - private Connection connection; + public Connection connection; - private Channel channel1; +// private Channel channel1; +// private Channel channel2; - public Channel getChannel2() { - return channel2; - } - - public void setChannel2(Channel channel2) { - this.channel2 = channel2; - } - - private Channel channel2; +// public Channel getChannel2() { +// return channel2; +// } +// +// public void setChannel2(Channel channel2) { +// this.channel2 = channel2; +// } +// private boolean reconnecting = false; @@ -43,24 +46,52 @@ public void setReconnecting(boolean reconnecting) { this.reconnecting = reconnecting; } - private DeliverCallback deliverCallback, deliverCallback2; +// private DeliverCallback deliverCallback, deliverCallback2; public RMQConnection(Connection connection) throws IOException { - this.setConnection(connection); + this.connection = connection; } public RMQConnection(){ } - public void setConnection(Connection connection) throws IOException { - this.connection = connection; +// public Channel[] getChannels() throws IOException { +// Channel channel1 = this.connection.createChannel(); +// Channel channel2 = this.connection.createChannel(); +// +// int prefetchCount = 1; +// channel1.basicQos(prefetchCount); +// channel2.basicQos(prefetchCount); +// +// return new Channel[]{channel1, channel2}; +// } - this.channel1 = this.connection.createChannel(); - this.channel2 = this.connection.createChannel(); +// public Channel[] setConnection(Connection connection) throws IOException { +// this.connection = connection; +// +// Channel channel1 = this.connection.createChannel(); +// channel1.basicRecover(true); +// Channel channel2 = this.connection.createChannel(); +// channel2.basicRecover(true); +// +// int prefetchCount = 1; +// channel1.basicQos(prefetchCount); +// channel2.basicQos(prefetchCount); +// +// return new Channel[]{channel1, channel2}; +// } + + public List channelList = new ArrayList<>(); + public void removeChannel(Channel channel) { + channelList.remove(channel); + } + public Channel createChannel() throws IOException { int prefetchCount = 1; - this.channel1.basicQos(prefetchCount); - this.channel2.basicQos(prefetchCount); + Channel channel = this.connection.createChannel(); + channel.basicQos(prefetchCount); + channelList.add(channel); + return channelList.get(channelList.size() -1); } public void close() throws IOException { @@ -72,47 +103,15 @@ public Connection getConnection() { return connection; } - public Channel getChannel1() { - return channel1; - } + public String createQueue(String exchangeName, String bindingKey, Channel channel, + String queueName) throws IOException { + if(queueName == null) + queueName = bindingKey.replaceAll("\\.", "_"); + + channel.queueDeclare(queueName, durable, exclusive, autoDelete, null); + channel.queueBind(queueName, exchangeName, bindingKey); - /** - * - * @param exchangeName - * @param deliverCallback - * @throws IOException - */ - public void createQueue(String exchangeName, String bindingKey1, String bindingKey2, DeliverCallback deliverCallback, DeliverCallback deliverCallback2) throws IOException { - this.queueName = bindingKey1.replaceAll("\\.", "_"); - this.deliverCallback = deliverCallback; - - ShutdownListener shutdownListener = new ShutdownListener() { - @Override - public void shutdownCompleted(ShutdownSignalException cause) { - Log.d(getClass().getName(), "CHannel shutdown listener called: " + cause.toString()); - if(connection.isOpen()) { - try { - // Hopefully this triggers the reconnect mechanisms - connection.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - }; - - this.channel1.queueDeclare(queueName, durable, exclusive, autoDelete, null); - this.channel1.queueBind(queueName, exchangeName, bindingKey1); - this.channel1.addShutdownListener(shutdownListener); - - if (bindingKey2 != null && deliverCallback2 != null) { - this.queueName2 = bindingKey2.replaceAll("\\.", "_"); - this.deliverCallback2 = deliverCallback2; - - this.channel2.queueDeclare(queueName2, durable, exclusive, autoDelete, null); - this.channel2.queueBind(queueName2, exchangeName, bindingKey2); - this.channel2.addShutdownListener(shutdownListener); - } + return queueName; } // public void createQueue1(String exchangeName, String bindingKey, DeliverCallback deliverCallback) throws IOException { @@ -137,7 +136,7 @@ public void shutdownCompleted(ShutdownSignalException cause) { // this.channel2.queueBind(queueName2, exchangeName, bindingKey); // } - public void consume() throws IOException { + public String consume(Channel channel, String queueName, DeliverCallback deliverCallback) throws IOException { /** * - Binding information dumb: * 1. .usd. = .usd. @@ -146,9 +145,21 @@ public void consume() throws IOException { * 4. Can all be used in combination with each * 5. We can translate this into managing multiple service providers */ - this.channel1.basicConsume(this.queueName, autoAck, deliverCallback, consumerTag -> {}); - if(this.queueName2 != null) - this.channel2.basicConsume(this.queueName2, autoAck, deliverCallback2, consumerTag -> {}); + +// ShutdownListener shutdownListener2 = new ShutdownListener() { +// @Override +// public void shutdownCompleted(ShutdownSignalException cause) { +// Log.d(getClass().getName(), "Channel shutdown listener called: " + cause.toString()); +// if(!cause.isInitiatedByApplication() && connection.isOpen()) { +// try { +// channels[1].basicConsume(queueName2, autoAck, deliverCallback, consumerTag -> {}); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// } +// }; + return channel.basicConsume(queueName, autoAck, deliverCallback, consumerTag -> {}); } // public void consume1() throws IOException { diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.java b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.java index 58f705a6..324b52b0 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQConnectionService.java @@ -1,8 +1,12 @@ package com.afkanerd.deku.QueueListener.RMQ; +import static com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver.SMS_DELIVERED_BROADCAST_INTENT; +import static com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver.SMS_SENT_BROADCAST_INTENT; import static com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver.SMS_UPDATED_BROADCAST_INTENT; import static com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity.GATEWAY_CLIENT_LISTENERS; +import static org.junit.Assert.assertTrue; + import android.app.Activity; import android.app.Notification; import android.app.PendingIntent; @@ -12,22 +16,33 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.ServiceInfo; import android.database.Cursor; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.provider.Telephony; +import android.telephony.SmsManager; import android.telephony.SubscriptionInfo; import android.util.Log; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import androidx.room.Room; import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSBroadcastReceiver; import com.afkanerd.deku.DefaultSMS.DAO.ConversationDao; import com.afkanerd.deku.DefaultSMS.Models.Conversations.Conversation; +import com.afkanerd.deku.DefaultSMS.Models.Conversations.ConversationHandler; +import com.afkanerd.deku.DefaultSMS.Models.Database.Datastore; +import com.afkanerd.deku.DefaultSMS.Models.Database.SemaphoreManager; import com.afkanerd.deku.DefaultSMS.Models.NativeSMSDB; import com.afkanerd.deku.DefaultSMS.Models.SMSDatabaseWrapper; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjectDao; +import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientProjects; +import com.afkanerd.deku.Router.GatewayServers.GatewayServerHandler; import com.afkanerd.deku.Router.Router.RouterHandler; import com.afkanerd.deku.DefaultSMS.BroadcastReceivers.IncomingTextSMSReplyActionBroadcastReceiver; import com.afkanerd.deku.QueueListener.GatewayClients.GatewayClientListingActivity; @@ -37,13 +52,16 @@ import com.afkanerd.deku.DefaultSMS.R; import com.afkanerd.deku.Router.Router.RouterItem; import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Command; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.ConsumerShutdownSignalCallback; import com.rabbitmq.client.DeliverCallback; import com.rabbitmq.client.Delivery; import com.rabbitmq.client.RecoveryDelayHandler; import com.rabbitmq.client.ShutdownListener; import com.rabbitmq.client.ShutdownSignalException; +import com.rabbitmq.client.TrafficListener; import com.rabbitmq.client.impl.DefaultExceptionHandler; import org.json.JSONException; @@ -51,68 +69,65 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeoutException; public class RMQConnectionService extends Service { final int NOTIFICATION_ID = 1234; - final long DELAY_TIMEOUT = 10000; - - public final static String RMQ_SUCCESS_BROADCAST_INTENT = "RMQ_SUCCESS_BROADCAST_INTENT"; - public final static String RMQ_STOP_BROADCAST_INTENT = "RMQ_STOP_BROADCAST_INTENT"; - - public final static String SMS_TYPE_STATUS = "SMS_TYPE_STATUS"; - public final static String SMS_STATUS_SENT = "SENT"; - public final static String SMS_STATUS_DELIVERED = "DELIVERED"; - public final static String SMS_STATUS_FAILED = "FAILED"; - private HashMap connectionList = new HashMap<>(); + private HashMap connectionList = new HashMap<>(); - private ExecutorService consumerExecutorService; + ExecutorService consumerExecutorService = Executors.newFixedThreadPool(10); // Create a pool of 5 worker threads private BroadcastReceiver messageStateChangedBroadcast; - private HashMap> channelList = new HashMap<>(); - private SharedPreferences sharedPreferences; private SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener; - Conversation conversation; - ConversationDao conversationDao; + public RMQConnectionService(Context context) { + attachBaseContext(context); + } + // DO NOT DELETE + public RMQConnectionService() { } + Datastore databaseConnector; @Override public void onCreate() { super.onCreate(); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) + Datastore.datastore = Room.databaseBuilder(getApplicationContext(), Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + databaseConnector = Datastore.datastore; - consumerExecutorService = Executors.newFixedThreadPool(5); // Create a pool of 5 worker threads handleBroadcast(); sharedPreferences = getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); registerListeners(); - - conversation = new Conversation(); - conversationDao = conversation.getDaoInstance(getApplicationContext()); } public int[] getGatewayClientNumbers() { - Map keys = sharedPreferences.getAll(); int running = 0; int reconnecting = 0; - for(String _key : keys.keySet()) { - if (sharedPreferences.getBoolean(_key, false)) + for(Long keys : connectionList.keySet()) { + Connection connection = connectionList.get(keys); + if(connection != null && connection.isOpen()) ++running; else ++reconnecting; } - return new int[]{running, reconnecting}; } @@ -124,7 +139,7 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin if(connectionList.containsKey(Long.parseLong(key))) { if(connectionList.get(Long.parseLong(key)) != null && !sharedPreferences.contains(key) ) { - new Thread(new Runnable() { + consumerExecutorService.execute(new Runnable() { @Override public void run() { try { @@ -133,27 +148,12 @@ public void run() { e.printStackTrace(); } } - }).start(); - } else if(connectionList.get(Long.parseLong(key)) != null && - sharedPreferences.contains(key) ){ + }); + } else { int[] states = getGatewayClientNumbers(); createForegroundNotification(states[0], states[1]); } } - else { - new Thread(new Runnable() { - @Override - public void run() { - GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); - try { - GatewayClient gatewayClient = gatewayClientHandler.fetch(Integer.parseInt(key)); - connectGatewayClient(gatewayClient); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }).start(); - } } }; sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); @@ -161,68 +161,63 @@ public void run() { private void handleBroadcast() { IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_SENT_BROADCAST_INTENT); - intentFilter.addAction(IncomingTextSMSBroadcastReceiver.SMS_DELIVERED_BROADCAST_INTENT); + intentFilter.addAction(SMS_SENT_BROADCAST_INTENT); +// intentFilter.addAction(SMS_DELIVERED_BROADCAST_INTENT); messageStateChangedBroadcast = new BroadcastReceiver() { @Override public void onReceive(Context context, @NonNull Intent intent) { - // TODO: in case this intent comes back but the internet connection broke to send back acknowledgement - // TODO: should store pending confirmations in a place - Log.d(getClass().getName(), "Got request for RMQ broadcast!"); - - if (intent.getAction() != null && - intentFilter.hasAction(intent.getAction())) { - RouterItem smsStatusReport = new RouterItem(); - - if(intent.hasExtra(RMQConnection.MESSAGE_SID)) { - Log.d(getClass().getName(), "RMQ Sid found!"); - String messageSid = intent.getStringExtra(RMQConnection.MESSAGE_SID); - if (intent.getAction().equals(IncomingTextSMSBroadcastReceiver.SMS_SENT_BROADCAST_INTENT)) { - Map deliveryChannel = channelList.get(messageSid); - final Long deliveryTag = deliveryChannel.keySet().iterator().next(); - Channel channel = deliveryChannel.get(deliveryTag); - - smsStatusReport.sid = messageSid; + if (intent.getAction() != null && intentFilter.hasAction(intent.getAction())) { + if(intent.hasExtra(RMQConnection.MESSAGE_SID) && + intent.hasExtra(RMQConnection.RMQ_DELIVERY_TAG)) { + + final String sid = intent.getStringExtra(RMQConnection.MESSAGE_SID); + final String messageId = intent.getStringExtra(NativeSMSDB.ID); + + final String consumerTag = + intent.getStringExtra(RMQConnection.RMQ_CONSUMER_TAG); + final long deliveryTag = + intent.getLongExtra(RMQConnection.RMQ_DELIVERY_TAG, -1); + assertTrue(deliveryTag != -1); + + Channel channel = activeConsumingChannels.get(consumerTag); + + if(intentFilter.hasAction(intent.getAction())) { + Log.d(getClass().getName(), "Received an ACK of the message..."); if(getResultCode() == Activity.RESULT_OK) { - if (channel != null && channel.isOpen()) { - new Thread(new Runnable() { - @Override - public void run() { - try { - channel.basicAck(deliveryTag, false); - } catch (IOException e) { - e.printStackTrace(); + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + try { + Log.i(getClass().getName(), + "Confirming message sent"); + if(channel == null || !channel.isOpen()) { + return; } + channel.basicAck(deliveryTag, false); + } catch (IOException e) { + e.printStackTrace(); } - }).start(); - } - smsStatusReport.reportedStatus = SMS_STATUS_SENT; + } + }); } else { - if (channel != null && channel.isOpen()) { - new Thread(new Runnable() { - @Override - public void run() { - try { - channel.basicReject(deliveryTag, true); - } catch (IOException e) { - e.printStackTrace(); + Log.w(getClass().getName(), "Rejecting message sent"); + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + try { + if(channel == null || !channel.isOpen()) { + return; } + channel.basicReject(deliveryTag, true); + } catch (IOException e) { + e.printStackTrace(); } - }).start(); - smsStatusReport.reportedStatus = SMS_STATUS_FAILED; - } + } + }); } - - } - else if (intent.getAction().equals(IncomingTextSMSBroadcastReceiver.SMS_DELIVERED_BROADCAST_INTENT)) { - smsStatusReport.sid = messageSid; - smsStatusReport.reportedStatus = SMS_STATUS_DELIVERED; } - - RouterHandler.route(getApplicationContext(), smsStatusReport); } - else Log.d(getClass().getName(), "Sid not found!"); } } }; @@ -233,175 +228,289 @@ else if (intent.getAction().equals(IncomingTextSMSBroadcastReceiver.SMS_DELIVERE registerReceiver(messageStateChangedBroadcast, intentFilter); } - private DeliverCallback getDeliverCallback(Channel channel, final int subscriptionId) { - return new DeliverCallback() { - @Override - public void handle(String consumerTag, Delivery delivery) throws IOException { + private DeliverCallback getDeliverCallback(final Channel channel, final int subscriptionId) { + return (consumerTag, delivery) -> { + try { String message = new String(delivery.getBody(), StandardCharsets.UTF_8); + JSONObject jsonObject = new JSONObject(message); + + final String body = jsonObject.getString(RMQConnection.MESSAGE_BODY_KEY); + final String msisdn = jsonObject.getString(RMQConnection.MESSAGE_MSISDN_KEY); + final String sid = jsonObject.getString(RMQConnection.MESSAGE_SID); + long threadId = Telephony.Threads.getOrCreateThreadId(getApplicationContext(), msisdn); + + Bundle bundle = new Bundle(); + bundle.putString(RMQConnection.MESSAGE_SID, sid); + bundle.putLong(RMQConnection.RMQ_DELIVERY_TAG, + delivery.getEnvelope().getDeliveryTag()); + bundle.putString(RMQConnection.RMQ_CONSUMER_TAG, consumerTag); + + SemaphoreManager.acquireSemaphore(subscriptionId); + long messageId = System.currentTimeMillis(); + Conversation conversation = new Conversation(); + conversation.setMessage_id(String.valueOf(messageId)); + conversation.setText(body); + conversation.setSubscription_id(subscriptionId); + conversation.setType(Telephony.Sms.MESSAGE_TYPE_OUTBOX); + conversation.setDate(String.valueOf(System.currentTimeMillis())); + conversation.setAddress(msisdn); + conversation.setThread_id(String.valueOf(threadId)); + conversation.setStatus(Telephony.Sms.STATUS_PENDING); + + databaseConnector.conversationDao().insert(conversation); + Log.d(getClass().getName(), "Sending RMQ SMS: " + subscriptionId + ":" + + conversation.getAddress()); + SMSDatabaseWrapper.send_text(getApplicationContext(), conversation, bundle); + } catch (JSONException e) { + e.printStackTrace(); + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + if(channel != null && channel.isOpen()) { + try { + channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + }); + } catch(Exception e) { + e.printStackTrace(); + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + try { + if(channel != null && channel.isOpen()) + channel.basicReject(delivery.getEnvelope().getDeliveryTag(), + true); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + }); + } finally { try { - JSONObject jsonObject = new JSONObject(message); - - String body = jsonObject.getString(RMQConnection.MESSAGE_BODY_KEY); - - String msisdn = jsonObject.getString(RMQConnection.MESSAGE_MSISDN_KEY); - String globalMessageKey = jsonObject.getString(RMQConnection.MESSAGE_GLOBAL_MESSAGE_ID_KEY); - String sid = jsonObject.getString(RMQConnection.MESSAGE_SID); - - Map deliveryChannelMap = new HashMap<>(); - deliveryChannelMap.put(delivery.getEnvelope().getDeliveryTag(), channel); - channelList.put(sid, deliveryChannelMap); - - Bundle bundle = new Bundle(); - bundle.putString(RMQConnection.MESSAGE_SID, sid); - String messageId = String.valueOf(System.currentTimeMillis()); - - Conversation conversation = new Conversation(); - conversation.setMessage_id(messageId); - conversation.setText(body); - conversation.setSubscription_id(subscriptionId); - conversation.setType(Telephony.Sms.MESSAGE_TYPE_OUTBOX); - conversation.setDate(String.valueOf(System.currentTimeMillis())); - conversation.setAddress(msisdn); - conversation.setStatus(Telephony.Sms.STATUS_PENDING); - - long id = conversationDao.insert(conversation); - SMSDatabaseWrapper.send_text(getApplicationContext(), conversation, bundle); - conversation.setId(id); - conversationDao.update(conversation); - } catch (JSONException e) { - e.printStackTrace(); - channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false); - } catch(Exception e) { + SemaphoreManager.releaseSemaphore(subscriptionId); + } catch (InterruptedException e) { e.printStackTrace(); } } }; } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { + private void startAllGatewayClientConnections() { + Log.d(getClass().getName(), "Starting all connections..."); +// connectionList.clear(); Map storedGatewayClients = sharedPreferences.getAll(); GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); + int[] states = getGatewayClientNumbers(); + createForegroundNotification(states[0], states[1]); + for (String gatewayClientIds : storedGatewayClients.keySet()) { - if(!connectionList.containsKey(Long.parseLong(gatewayClientIds))) { + if(!connectionList.containsKey(Long.parseLong(gatewayClientIds)) || + (connectionList.get(Long.parseLong(gatewayClientIds)) != null && + !connectionList.get(Long.parseLong(gatewayClientIds)).isOpen())) { try { - GatewayClient gatewayClient = gatewayClientHandler.fetch(Long.parseLong(gatewayClientIds)); + GatewayClient gatewayClient = + gatewayClientHandler.fetch(Long.parseLong(gatewayClientIds)); connectGatewayClient(gatewayClient); } catch (InterruptedException e) { e.printStackTrace(); } } } - gatewayClientHandler.close(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + startAllGatewayClientConnections(); return START_STICKY; } - public void connectGatewayClient(GatewayClient gatewayClient) throws InterruptedException { - Log.d(getClass().getName(), "Starting new service connection..."); - int[] states = getGatewayClientNumbers(); + public void startConnection(ConnectionFactory factory, GatewayClient gatewayClient) throws IOException, TimeoutException, InterruptedException { + Log.d(getClass().getName(), "Starting new connection..."); + + Connection connection = connectionList.get(gatewayClient.getId()); + if(connection == null || !connection.isOpen()) { + try { + connection = factory.newConnection(consumerExecutorService, + gatewayClient.getFriendlyConnectionName()); + } catch (Exception e) { + e.printStackTrace(); + Thread.sleep(5000); + startConnection(factory, gatewayClient); + } + } - ConnectionFactory factory = new ConnectionFactory(); + RMQConnection rmqConnection = new RMQConnection(connection); + connectionList.put(gatewayClient.getId(), connection); - factory.setRecoveryDelayHandler(new RecoveryDelayHandler() { + if(connection != null) + connection.addShutdownListener(new ShutdownListener() { @Override - public long getDelay(int recoveryAttempts) { - connectionList.get(gatewayClient.getId()).setConnected(DELAY_TIMEOUT); - return DELAY_TIMEOUT; + public void shutdownCompleted(ShutdownSignalException cause) { + Log.e(getClass().getName(), "Connection shutdown cause: " + cause.toString()); + if(sharedPreferences.getBoolean(String.valueOf(gatewayClient.getId()), false)) { + try { + connectionList.remove(gatewayClient.getId()); + int[] states = getGatewayClientNumbers(); + createForegroundNotification(states[0], states[1]); + startConnection(factory, gatewayClient); + } catch (IOException | TimeoutException | InterruptedException e) { + e.printStackTrace(); + } + } } }); + GatewayClientHandler gatewayClientHandler = new GatewayClientHandler(getApplicationContext()); + GatewayClientProjectDao gatewayClientProjectDao = + gatewayClientHandler.databaseConnector.gatewayClientProjectDao(); + + List subscriptionInfoList = SIMHandler + .getSimCardInformation(getApplicationContext()); + + List gatewayClientProjectsList = + gatewayClientProjectDao.fetchGatewayClientIdList(gatewayClient.getId()); + Log.d(getClass().getName(), "Subscription number: " + subscriptionInfoList.size()); + + for(int i=0;i 0 ? gatewayClientProjects.binding2Name : + gatewayClientProjects.binding1Name; + int subscriptionId = subscriptionInfoList.get(j).getSubscriptionId(); + + startChannelConsumption(rmqConnection, channel, subscriptionId, + gatewayClientProjects, bindingName); + } + } + + int[] states = getGatewayClientNumbers(); + createForegroundNotification(states[0], states[1]); + } + + public void startChannelConsumption(RMQConnection rmqConnection, final Channel channel, + final int subscriptionId, + final GatewayClientProjects gatewayClientProjects, + final String bindingName) throws IOException { + channel.basicRecover(true); + final DeliverCallback deliverCallback = getDeliverCallback(channel, subscriptionId); + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + try { + String queueName = rmqConnection.createQueue(gatewayClientProjects.name, bindingName, + channel, null); + long messagesCount = channel.messageCount(queueName); + + Log.d(getClass().getName(), "Created Queue: " + queueName + + " (" + messagesCount + ")"); + + String consumerTag = channel.basicConsume(queueName, false, deliverCallback, + new ConsumerShutdownSignalCallback() { + @Override + public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) { + Log.e(getClass().getName(), "Consumer error: " + sig.getMessage()); + if(rmqConnection.connection != null && rmqConnection.connection.isOpen()) { + try { + activeConsumingChannels.remove(consumerTag); + Channel channel = rmqConnection.createChannel(); + startChannelConsumption(rmqConnection, channel, subscriptionId, + gatewayClientProjects, bindingName); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }); + activeConsumingChannels.put(consumerTag, channel); + Log.i(getClass().getName(), "Adding tag: " + consumerTag); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + Map activeConsumingChannels = new HashMap<>(); + + boolean disconnected = false; + public void connectGatewayClient(GatewayClient gatewayClient) throws InterruptedException { + Log.d(getClass().getName(), "Starting new service connection..."); + + ConnectionFactory factory = new ConnectionFactory(); + factory.setUsername(gatewayClient.getUsername()); factory.setPassword(gatewayClient.getPassword()); factory.setVirtualHost(gatewayClient.getVirtualHost()); factory.setHost(gatewayClient.getHostUrl()); factory.setPort(gatewayClient.getPort()); - factory.setConnectionTimeout(15000); + factory.setAutomaticRecoveryEnabled(true); + factory.setNetworkRecoveryInterval(10000); factory.setExceptionHandler(new DefaultExceptionHandler()); - Thread thread = new Thread(new Runnable() { + factory.setRecoveryDelayHandler(new RecoveryDelayHandler() { @Override - public void run() { - try { - /** - * Avoid risk of :ForegroundServiceDidNotStartInTimeException - * - Put RMQ connection in list before connecting which could take a while - */ - - RMQConnection rmqConnection = new RMQConnection(); - - RMQMonitor rmqMonitor = new RMQMonitor(getApplicationContext(), - gatewayClient.getId(), - rmqConnection); - connectionList.put(gatewayClient.getId(), rmqMonitor); - - rmqMonitor.setConnected(DELAY_TIMEOUT); - Log.d(getClass().getName(), "Attempting to make connection..."); - - Connection connection = factory.newConnection(consumerExecutorService, - gatewayClient.getFriendlyConnectionName()); - Log.d(getClass().getName(), "Connection made.."); - - rmqMonitor.setConnected(0L); - connection.addShutdownListener(new ShutdownListener() { - @Override - public void shutdownCompleted(ShutdownSignalException cause) { - Log.d(getClass().getName(), "Connection shutdown cause: " + cause.toString()); - } - }); - - rmqMonitor.getRmqConnection().setConnection(connection); - - List subscriptionInfoList = SIMHandler - .getSimCardInformation(getApplicationContext()); + public long getDelay(int recoveryAttempts) { + Log.w(getClass().getName(), "Factory recovering...: " + recoveryAttempts); + int[] states = getGatewayClientNumbers(); + createForegroundNotification(states[0], states[1]); + disconnected = true; + return 10000; + } + }); + factory.setTrafficListener(new TrafficListener() { + @Override + public void write(Command outboundCommand) { + } - if(gatewayClient.getProjectName() != null && !gatewayClient.getProjectName().isEmpty()) { - SubscriptionInfo subscriptionInfo = subscriptionInfoList.get(0); - DeliverCallback deliverCallback1 = getDeliverCallback(rmqConnection.getChannel1(), - subscriptionInfo.getSubscriptionId()); - DeliverCallback deliverCallback2 = null; + @Override + public void read(Command inboundCommand) { + if(disconnected) { + Objects.requireNonNull(connectionList.get(gatewayClient.getId())).abort(); + connectionList.remove(gatewayClient.getId()); + startAllGatewayClientConnections(); + disconnected = false; + } + } + }); - boolean dualQueue = subscriptionInfoList.size() > 1 && gatewayClient.getProjectBinding2() != null - && !gatewayClient.getProjectBinding2().isEmpty(); - if(dualQueue) { - subscriptionInfo = subscriptionInfoList.get(1); - deliverCallback2 = getDeliverCallback(rmqConnection.getChannel2(), - subscriptionInfo.getSubscriptionId()); - } + consumerExecutorService.execute(new Runnable() { + @Override + public void run() { + /** + * Avoid risk of :ForegroundServiceDidNotStartInTimeException + * - Put RMQ connection in list before connecting which could take a while + */ - rmqConnection.createQueue(gatewayClient.getProjectName(), - gatewayClient.getProjectBinding(), gatewayClient.getProjectBinding2(), - deliverCallback1, deliverCallback2); - rmqConnection.consume(); - } - } catch (IOException | TimeoutException e) { + try { + startConnection(factory, gatewayClient); + } catch (IOException | TimeoutException | InterruptedException e) { e.printStackTrace(); - // TODO: send a notification indicating this, with options to retry the connection - int[] states = getGatewayClientNumbers(); - createForegroundNotification(states[0], states[1]); } } }); - thread.setName(getClass().getName() + ":connectGatewayClient_Thread"); - thread.start(); } - private void stop(long gatewayClientId) { - try { - if(connectionList.containsKey(gatewayClientId)) { - connectionList.remove(gatewayClientId) - .getRmqConnection().close(); - if(connectionList.isEmpty()) { - stopForeground(true); - stopSelf(); - } - else { - int[] states = getGatewayClientNumbers(); - createForegroundNotification(states[0], states[1]); - } + private void stop(long gatewayClientId) throws IOException { + if(connectionList.containsKey(gatewayClientId)) { + Connection connection = connectionList.get(gatewayClientId); + if(connection != null) + connection.close(); + + connectionList.remove(gatewayClientId); + if(connectionList.isEmpty()) { + stopForeground(true); + stopSelf(); + } + else { + int[] states = getGatewayClientNumbers(); + createForegroundNotification(states[0], states[1]); } - } catch (IOException e) { - e.printStackTrace(); } } @@ -426,14 +535,19 @@ public void createForegroundNotification(int runningGatewayClientCount, int reco PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); - String description = runningGatewayClientCount + " " + getString(R.string.gateway_client_running_description); + String description = runningGatewayClientCount + " " + + getString(R.string.gateway_client_running_description); if(reconnecting > 0) - description += "\n" + reconnecting + " " + getString(R.string.gateway_client_reconnecting_description); + description += "\n" + reconnecting + " " + + getString(R.string.gateway_client_reconnecting_description); + Notification notification = - new NotificationCompat.Builder(getApplicationContext(), getString(R.string.running_gateway_clients_channel_id)) - .setContentTitle(getString(R.string.gateway_client_running_title)) + new NotificationCompat.Builder(getApplicationContext(), + getString(R.string.running_gateway_clients_channel_id)) + .setContentTitle(getApplicationContext() + .getString(R.string.gateway_client_running_title)) .setSmallIcon(R.drawable.ic_stat_name) .setPriority(NotificationCompat.DEFAULT_ALL) .setSilent(true) @@ -442,6 +556,11 @@ public void createForegroundNotification(int runningGatewayClientCount, int reco .setContentIntent(pendingIntent) .build(); - startForeground(NOTIFICATION_ID, notification); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startForeground(NOTIFICATION_ID, notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); + } + else + startForeground(NOTIFICATION_ID, notification); } } diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQMonitor.kt b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQMonitor.kt index 0372f0cf..1e00092f 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQMonitor.kt +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQMonitor.kt @@ -38,10 +38,10 @@ class RMQMonitor(val context: Context, private val gatewayClientId: Long, } fun setConnected(delayTimeout : Long ) { - sharedPreferences.edit() - .putBoolean(this.gatewayClientId.toString(), (delayTimeout == 0L)) - .apply(); - +// sharedPreferences.edit() +// .putBoolean(this.gatewayClientId.toString(), (delayTimeout == 0L)) +// .apply(); +// if(delayTimeout > 0 && !activeThreads.containsKey(gatewayClientId.toString()) && rmqConnection.connection != null) setMonitorTimeout(delayTimeout) diff --git a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.java b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.java index 989c29d3..9c9354a4 100644 --- a/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.java +++ b/app/src/main/java/com/afkanerd/deku/QueueListener/RMQ/RMQWorkManager.java @@ -20,25 +20,26 @@ public class RMQWorkManager extends Worker { final int NOTIFICATION_ID = 12345; - Context context; SharedPreferences sharedPreferences; public RMQWorkManager(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); - this.context = context; - sharedPreferences = context.getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); } @NonNull @Override public Result doWork() { Intent intent = new Intent(getApplicationContext(), RMQConnectionService.class); + sharedPreferences = getApplicationContext() + .getSharedPreferences(GATEWAY_CLIENT_LISTENERS, Context.MODE_PRIVATE); if(!sharedPreferences.getAll().isEmpty()) { try { - context.startForegroundService(intent); - new RMQConnectionService().createForegroundNotification(0, - sharedPreferences.getAll().size()); + getApplicationContext().startForegroundService(intent); + RMQConnectionService rmqConnectionService = + new RMQConnectionService(getApplicationContext()); +// rmqConnectionService.createForegroundNotification(0, +// sharedPreferences.getAll().size()); } catch (Exception e) { e.printStackTrace(); if (e instanceof ForegroundServiceStartNotAllowedException) { @@ -58,16 +59,19 @@ private void notifyUserToReconnectSMSServices(){ Notification notification = new NotificationCompat.Builder(getApplicationContext(), - context.getString(R.string.foreground_service_failed_channel_id)) - .setContentTitle(context.getString(R.string.foreground_service_failed_channel_name)) + getApplicationContext().getString(R.string.foreground_service_failed_channel_id)) + .setContentTitle(getApplicationContext() + .getString(R.string.foreground_service_failed_channel_name)) .setSmallIcon(R.drawable.ic_stat_name) .setPriority(NotificationCompat.DEFAULT_ALL) .setAutoCancel(true) - .setContentText(context.getString(R.string.foreground_service_failed_channel_description)) + .setContentText(getApplicationContext() + .getString(R.string.foreground_service_failed_channel_description)) .setContentIntent(pendingIntent) .build(); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); + NotificationManagerCompat notificationManager = + NotificationManagerCompat.from(getApplicationContext()); notificationManager.notify(NOTIFICATION_ID, notification); } diff --git a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerAddActivity.java b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerAddActivity.java index eed438bc..f7bd4ca3 100644 --- a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerAddActivity.java +++ b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerAddActivity.java @@ -167,11 +167,4 @@ public boolean onOptionsItemSelected(MenuItem item) { } return false; } - - @Override - protected void onDestroy() { - gatewayServerHandler.close(); - - super.onDestroy(); - } } \ No newline at end of file diff --git a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerHandler.java b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerHandler.java index f6ee4d35..4ac7e957 100644 --- a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerHandler.java +++ b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerHandler.java @@ -16,12 +16,13 @@ public class GatewayServerHandler { Datastore databaseConnector; public GatewayServerHandler(Context context){ - databaseConnector = Room.databaseBuilder(context, Datastore.class, - Datastore.databaseName) - .addMigrations(new Migrations.Migration4To5()) - .addMigrations(new Migrations.Migration5To6()) - .addMigrations(new Migrations.Migration6To7()) - .build(); + if(Datastore.datastore == null || !Datastore.datastore.isOpen()) { + Datastore.datastore = Room.databaseBuilder(context, Datastore.class, + Datastore.databaseName) + .enableMultiInstanceInvalidation() + .build(); + } + databaseConnector = Datastore.datastore; } public LiveData> getAllLiveData() throws InterruptedException { @@ -38,7 +39,8 @@ public void run() { return liveData[0]; } - public List getAll() throws InterruptedException { + + public synchronized List getAll() throws InterruptedException { final List[] gatewayServerList = new List[]{new ArrayList<>()}; Thread thread = new Thread(new Runnable() { @Override @@ -110,10 +112,6 @@ public void run() { thread.join(); } - public void close() { - databaseConnector.close(); - } - // public static List fetchAll(Context context) throws InterruptedException { // Datastore databaseConnector = Room.databaseBuilder(context, Datastore.class, // Datastore.databaseName).build(); diff --git a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerListingActivity.java b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerListingActivity.java index 485014e4..6a1910b3 100644 --- a/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerListingActivity.java +++ b/app/src/main/java/com/afkanerd/deku/Router/GatewayServers/GatewayServerListingActivity.java @@ -17,7 +17,6 @@ import android.view.MenuItem; import android.view.View; -import com.afkanerd.deku.DefaultSMS.LinkedDevicesQRActivity; import com.afkanerd.deku.DefaultSMS.R; import java.util.List; @@ -85,7 +84,7 @@ public void run() { @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.gateway_client_add_menu, menu); + getMenuInflater().inflate(R.menu.gateway_client_listing_menu, menu); return super.onCreateOptionsMenu(menu); } diff --git a/app/src/main/java/com/afkanerd/deku/Router/Router/RouterHandler.java b/app/src/main/java/com/afkanerd/deku/Router/Router/RouterHandler.java index 49e9b95b..c5ee310d 100644 --- a/app/src/main/java/com/afkanerd/deku/Router/Router/RouterHandler.java +++ b/app/src/main/java/com/afkanerd/deku/Router/Router/RouterHandler.java @@ -17,6 +17,7 @@ import androidx.work.WorkQuery; import com.afkanerd.deku.DefaultSMS.Commons.Helpers; +import com.afkanerd.deku.Router.GatewayServers.GatewayServerHandler; import com.android.volley.DefaultRetryPolicy; import com.android.volley.Request; import com.android.volley.RequestQueue; @@ -78,7 +79,8 @@ public static void routeJsonMessages(Context context, String jsonStringBody, Str } - public static void route(Context context, RouterItem routerItem) { + public static void route(Context context, RouterItem routerItem, + GatewayServerHandler gatewayServerHandler) throws InterruptedException { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setPrettyPrinting().serializeNulls(); Gson gson = gsonBuilder.create(); @@ -89,52 +91,48 @@ public static void route(Context context, RouterItem routerItem) { boolean isBase64 = Helpers.isBase64Encoded(routerItem.getText()); - executorService.execute(new Runnable() { - @Override - public void run() { - GatewayServer gatewayServer = new GatewayServer(); - GatewayServerDAO gatewayServerDAO = gatewayServer.getDaoInstance(context); - List gatewayServerList = gatewayServerDAO.getAllList(); - - for (GatewayServer gatewayServer1 : gatewayServerList) { - if(gatewayServer1.getFormat() != null && - gatewayServer1.getFormat().equals(GatewayServer.BASE64_FORMAT) && !isBase64) - continue; - - routerItem.tag = gatewayServer1.getTag(); - final String jsonStringBody = gson.toJson(routerItem); - - try { - OneTimeWorkRequest routeMessageWorkRequest = new OneTimeWorkRequest.Builder(RouterWorkManager.class) - .setConstraints(constraints) - .setBackoffCriteria( - BackoffPolicy.LINEAR, - OneTimeWorkRequest.MIN_BACKOFF_MILLIS, - TimeUnit.MILLISECONDS - ) - .addTag(TAG_NAME) - .addTag(getTagForMessages(routerItem.getMessage_id())) - .addTag(getTagForGatewayServers(gatewayServer1.getURL())) - .setInputData( - new Data.Builder() - .putString(RouterWorkManager.SMS_JSON_OBJECT, jsonStringBody) - .putString(RouterWorkManager.SMS_JSON_ROUTING_URL, gatewayServer1.getURL()) - .build() - ) - .build(); - - String uniqueWorkName = routerItem.getMessage_id() + ":" + gatewayServer1.getURL(); - WorkManager workManager = WorkManager.getInstance(context); - workManager.enqueueUniqueWork( - uniqueWorkName, - ExistingWorkPolicy.KEEP, - routeMessageWorkRequest); - } catch (Exception e) { - e.printStackTrace(); - } - } +// GatewayServer gatewayServer = new GatewayServer(); +// GatewayServerDAO gatewayServerDAO = gatewayServer.getDaoInstance(context); +// List gatewayServerList = gatewayServerDAO.getAllList(); + List gatewayServerList = gatewayServerHandler.getAll(); + + for (GatewayServer gatewayServer1 : gatewayServerList) { + if(gatewayServer1.getFormat() != null && + gatewayServer1.getFormat().equals(GatewayServer.BASE64_FORMAT) && !isBase64) + continue; + + routerItem.tag = gatewayServer1.getTag(); + final String jsonStringBody = gson.toJson(routerItem); + + try { + OneTimeWorkRequest routeMessageWorkRequest = new OneTimeWorkRequest.Builder(RouterWorkManager.class) + .setConstraints(constraints) + .setBackoffCriteria( + BackoffPolicy.LINEAR, + OneTimeWorkRequest.MIN_BACKOFF_MILLIS, + TimeUnit.MILLISECONDS + ) + .addTag(TAG_NAME) + .addTag(getTagForMessages(routerItem.getMessage_id())) + .addTag(getTagForGatewayServers(gatewayServer1.getURL())) + .setInputData( + new Data.Builder() + .putString(RouterWorkManager.SMS_JSON_OBJECT, jsonStringBody) + .putString(RouterWorkManager.SMS_JSON_ROUTING_URL, gatewayServer1.getURL()) + .build() + ) + .build(); + + String uniqueWorkName = routerItem.getMessage_id() + ":" + gatewayServer1.getURL(); + WorkManager workManager = WorkManager.getInstance(context); + workManager.enqueueUniqueWork( + uniqueWorkName, + ExistingWorkPolicy.KEEP, + routeMessageWorkRequest); + } catch (Exception e) { + e.printStackTrace(); } - }); + } } private static String getTagForMessages(String messageId) { diff --git a/app/src/main/res/drawable/round_add_circle_outline_24.xml b/app/src/main/res/drawable/round_add_circle_outline_24.xml new file mode 100644 index 00000000..2e61465b --- /dev/null +++ b/app/src/main/res/drawable/round_add_circle_outline_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/round_notifications_off_24.xml b/app/src/main/res/drawable/round_notifications_off_24.xml new file mode 100644 index 00000000..60fa7c53 --- /dev/null +++ b/app/src/main/res/drawable/round_notifications_off_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_gateway_client_project_listing.xml b/app/src/main/res/layout/activity_gateway_client_project_listing.xml new file mode 100644 index 00000000..7eaef1ae --- /dev/null +++ b/app/src/main/res/layout/activity_gateway_client_project_listing.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/conversations_threads_layout.xml b/app/src/main/res/layout/conversations_threads_layout.xml index e561d966..09f5358d 100644 --- a/app/src/main/res/layout/conversations_threads_layout.xml +++ b/app/src/main/res/layout/conversations_threads_layout.xml @@ -1,6 +1,7 @@ + app:layout_constraintTop_toTopOf="parent" + tools:text="TextView" /> + app:layout_constraintTop_toBottomOf="@+id/messages_thread_address_text" + tools:text="TextView" /> + app:layout_constraintTop_toTopOf="@+id/messages_thread_text" + tools:visibility="visible" /> + + diff --git a/app/src/main/res/layout/gateway_client_project_listing_layout.xml b/app/src/main/res/layout/gateway_client_project_listing_layout.xml new file mode 100644 index 00000000..c54317dc --- /dev/null +++ b/app/src/main/res/layout/gateway_client_project_listing_layout.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/blocked_conversations.xml b/app/src/main/res/menu/blocked_conversations.xml new file mode 100644 index 00000000..83fadddc --- /dev/null +++ b/app/src/main/res/menu/blocked_conversations.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/blocked_conversations_items_selected.xml b/app/src/main/res/menu/blocked_conversations_items_selected.xml new file mode 100644 index 00000000..0a49b545 --- /dev/null +++ b/app/src/main/res/menu/blocked_conversations_items_selected.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/conversations_menu.xml b/app/src/main/res/menu/conversations_menu.xml index 2bb528a7..16c8e89d 100644 --- a/app/src/main/res/menu/conversations_menu.xml +++ b/app/src/main/res/menu/conversations_menu.xml @@ -16,5 +16,18 @@ android:icon="@drawable/ic_outline_search_24" android:id="@+id/conversation_main_menu_search" android:title="@string/conversations_menu_search_title"/> + + + \ No newline at end of file diff --git a/app/src/main/res/menu/conversations_menu_item_selected.xml b/app/src/main/res/menu/conversations_menu_item_selected.xml index a9ef7c5f..f444a005 100644 --- a/app/src/main/res/menu/conversations_menu_item_selected.xml +++ b/app/src/main/res/menu/conversations_menu_item_selected.xml @@ -5,12 +5,12 @@ android:id="@+id/conversations_menu_copy" android:icon="@drawable/round_content_copy_24" android:title="@string/conversation_menu_copy" - app:showAsAction="always" /> + app:showAsAction="ifRoom" /> + app:showAsAction="ifRoom" /> + + diff --git a/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml b/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml index 41b0d6ac..943e284d 100644 --- a/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml +++ b/app/src/main/res/menu/conversations_threads_navigation_view_menu.xml @@ -27,11 +27,18 @@ android:id="@+id/navigation_view_menu_drafts" android:icon="@drawable/twotone_folder_24" android:title="@string/conversations_navigation_view_drafts" /> - + + diff --git a/app/src/main/res/menu/gateway_client_customization_menu.xml b/app/src/main/res/menu/gateway_client_customization_menu.xml index 957d925c..34aa63ae 100644 --- a/app/src/main/res/menu/gateway_client_customization_menu.xml +++ b/app/src/main/res/menu/gateway_client_customization_menu.xml @@ -2,23 +2,9 @@ - - - - - + android:icon="@drawable/round_delete_24" + app:showAsAction="ifRoom" /> + diff --git a/app/src/main/res/menu/gateway_client_add_menu.xml b/app/src/main/res/menu/gateway_client_listing_menu.xml similarity index 100% rename from app/src/main/res/menu/gateway_client_add_menu.xml rename to app/src/main/res/menu/gateway_client_listing_menu.xml diff --git a/app/src/main/res/menu/gateway_client_project_add_menu.xml b/app/src/main/res/menu/gateway_client_project_add_menu.xml new file mode 100644 index 00000000..4b75dd7d --- /dev/null +++ b/app/src/main/res/menu/gateway_client_project_add_menu.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/gateway_client_project_listing_menu.xml b/app/src/main/res/menu/gateway_client_project_listing_menu.xml new file mode 100644 index 00000000..2943d99d --- /dev/null +++ b/app/src/main/res/menu/gateway_client_project_listing_menu.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/muted_menu.xml b/app/src/main/res/menu/muted_menu.xml new file mode 100644 index 00000000..5bc057b4 --- /dev/null +++ b/app/src/main/res/menu/muted_menu.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/muted_menu_items_selected.xml b/app/src/main/res/menu/muted_menu_items_selected.xml new file mode 100644 index 00000000..a4f6955b --- /dev/null +++ b/app/src/main/res/menu/muted_menu_items_selected.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index c4a603d4..036d09bc 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index c4a603d4..036d09bc 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..463e624e Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index 43d607ac..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..2030ea31 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp deleted file mode 100644 index a2ad2ed2..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..9ba9a0d3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index a1ac0f72..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..8dfb4e62 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 9107525b..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..ad6a3532 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp deleted file mode 100644 index 63cdf0b9..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..5c1ccd31 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 3f2e3e0b..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..f5006cdb Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 3c05bd6a..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..f76c76a6 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp deleted file mode 100644 index bf1c5b4d..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..d69158b6 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index b737e9a4..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..a4078e48 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 9e1eb920..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..13f42d6c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 3a0f935d..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..88653340 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index bde7eb0a..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..35e5f9fc Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index 7c4924de..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..c20059cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 9e5a9e35..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..0b7cca24 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b5a08ac..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/raw/.gitignore b/app/src/main/res/raw/.gitignore new file mode 100644 index 00000000..9db2cfd6 --- /dev/null +++ b/app/src/main/res/raw/.gitignore @@ -0,0 +1 @@ +app.properties diff --git a/app/src/main/res/raw/example_app.properties b/app/src/main/res/raw/example_app.properties new file mode 100644 index 00000000..dfc5ea93 --- /dev/null +++ b/app/src/main/res/raw/example_app.properties @@ -0,0 +1,6 @@ +username= +password= +host= +virtualhost= +port= +exchange= diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 98f9514d..e4ccbaec 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -145,4 +145,22 @@ Tout marquer comme lu Marquer comme non lu Marquer comme lu + Bloquer + Aucun contact bloqué + Bloquée + Gestionnaire bloqué + Débloquer + Contact bloqué avec succès + Muets + Le contact est désormais désactivé ! + Unmute + Le contact est désormais réactivé ! + Messages masqués + Aucun contact mis en sourdine + Réactiver tout le son + Exporter + Exportation terminée + Nom du projet + Aucun projet ajouté + Activer tout \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..4262b1fe --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #E6E6E7 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7fdb716..18067698 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,11 +30,14 @@ Message Forwarding Clear all drafts Mark all Read + Unmute all Mark as unread Mark as read Send your first message Nothing in Drafts Nothing in Archives + No blocked contacts + No Muted contacts No unread message No Encrypted Communications contacts No Gateway server Added @@ -85,6 +88,7 @@ Scan QR code Add Manually + Activate All Base64 @@ -154,6 +158,9 @@ Text Message (secured) Call Encrypt + Blocked Manager + Unblock + Contact blocked succcessfully Search results founds @@ -164,7 +171,14 @@ copy delete unarchive - share + Share + Block + Export + Mute + Muted + Unmute + Contact is now muted! + Contact is now unmuted! View details Message details @@ -210,8 +224,12 @@ Archived Unread Encrypted + Blocked Folders Inbox LOAD NATIVES + Export Complete! + Project name + No Projects added \ No newline at end of file diff --git a/app/src/test/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java b/app/src/test/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java deleted file mode 100644 index 2d81d1e3..00000000 --- a/app/src/test/java/com/afkanerd/deku/E2EE/ConversationsThreadsEncryptionTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.afkanerd.deku.E2EE; - -import static org.junit.Assert.assertEquals; - -import com.google.i18n.phonenumbers.NumberParseException; - -import org.junit.Test; - -public class ConversationsThreadsEncryptionTest { - -} diff --git a/app/src/test/java/com/afkanerd/deku/E2EE/Security/LibSignal/RandomSecTest.java b/app/src/test/java/com/afkanerd/deku/E2EE/Security/LibSignal/RandomSecTest.java deleted file mode 100644 index f1bb41e2..00000000 --- a/app/src/test/java/com/afkanerd/deku/E2EE/Security/LibSignal/RandomSecTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.afkanerd.deku.E2EE.Security.LibSignal; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import android.util.Base64; - -import com.google.crypto.tink.shaded.protobuf.InvalidProtocolBufferException; -import com.google.crypto.tink.subtle.Hkdf; - -import org.junit.Test; - -import java.security.GeneralSecurityException; - -import javax.crypto.Mac; - -public class RandomSecTest { - - @Test - public void HKDFTest() throws GeneralSecurityException { - String algo = "HMACSHA512"; - - byte[] ikm = "b863650041e1183fe909920e5d3c82e43393824b0274c2690b68b5d955070075".getBytes(); - byte[] salt = "43f8097fe98fbd2a051a625308f38e376781f012f25888c4733536a3244cb7ab".getBytes(); - - byte[] infoRk = "kdf_rk".getBytes(), infoCk = "kdf_ck".getBytes(); - byte[] HkdfRkOut = Hkdf.computeHkdf(algo, ikm, salt, infoRk, 32); - byte[] HkdfCkOut = Hkdf.computeHkdf(algo, ikm, salt, infoCk, 32); - - byte[] expectedOutRk = "xYRlJ2/Am68jgq+vW2Q+iEQhvE0BarTYrsxvq3tg/xQ=".getBytes(); - byte[] expectedOutCk = "FxE8p7KnF8f+0lh2NuTDQE4l8cblrDsCaI3q7ouDPIg=".getBytes(); - - assertArrayEquals(expectedOutRk, - com.google.crypto.tink.subtle.Base64.encode(HkdfRkOut, - Base64.NO_WRAP)); - - assertArrayEquals(expectedOutCk, - com.google.crypto.tink.subtle.Base64.encode(HkdfCkOut, - Base64.NO_WRAP)); - } - - @Test - public void customHKDFTest() throws GeneralSecurityException { - String algo = "HMACSHA512"; - byte[] ikm = "b863650041e1183fe909920e5d3c82e43393824b0274c2690b68b5d955070075".getBytes(); - byte[] salt = "43f8097fe98fbd2a051a625308f38e376781f012f25888c4733536a3244cb7ab".getBytes(); - - int len = 32; - int num = 2; - - byte[] info = "kdf_ck".getBytes(); - byte[][] hkdfOutput = EncryptionHandlers.HKDF(algo, ikm, salt, info, len, num); - - byte[][] expectedOut = new byte[num][len]; - expectedOut[0] = com.google.crypto.tink.subtle.Base64.decode( - "FxE8p7KnF8f+0lh2NuTDQE4l8cblrDsCaI3q7ouDPIg=".getBytes(), Base64.NO_WRAP); - expectedOut[1] = com.google.crypto.tink.subtle.Base64.decode( - "dQ6vtJ394Y4OhPM4iiLXw0vVjCPoDMzd288BNHJ64gE=".getBytes(), Base64.NO_WRAP); - assertArrayEquals(expectedOut, hkdfOutput); - } - - @Test - public void HMACTest() throws GeneralSecurityException, InvalidProtocolBufferException { - String helloWorldB64Digest = "Dei+5df5xdIJ+Mb6vtDqhMs/yhJE6O04B5phtZmoTEc="; - Mac mac = EncryptionHandlers.HMAC("hello world".getBytes()); - byte[] macOutput = mac.doFinal(); - - String output = java.util.Base64.getEncoder().encodeToString(macOutput); - assertEquals(helloWorldB64Digest, output); - - String helloWorldB64DigestWithUpdate1 = "0J0pwZbLRrifdO0NSkg+ih613V5eK8cO5GGQwkfkEl4="; - String helloWorldB64DigestWithUpdate2 = "bHujacd1S7gcfJw7ypJhcvtFuKgyopCGJNX5GMxpfPc="; - mac = EncryptionHandlers.HMAC("hello world".getBytes()); - byte[] macOutput1 = mac.doFinal(new byte[]{0x01}); - byte[] macOutput2 = mac.doFinal(new byte[]{0x02}); - - assertArrayEquals( - java.util.Base64.getDecoder().decode( - helloWorldB64DigestWithUpdate1), macOutput1); - assertArrayEquals( - java.util.Base64.getDecoder().decode( - helloWorldB64DigestWithUpdate2), macOutput2); - } -} diff --git a/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityAESRandomTest.java b/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityAESRandomTest.java deleted file mode 100644 index b337e109..00000000 --- a/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityAESRandomTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.afkanerd.deku.E2EE.Security; - -import static org.junit.Assert.assertArrayEquals; - -import org.junit.Test; - -import javax.crypto.spec.SecretKeySpec; - -public class SecurityAESRandomTest { - - @Test - public void canEncryptDecryptAES256CBC() throws Throwable { - byte[] plainText = EncryptionHandlers.generateRandomBytes(140); - byte[] sharedSecret = EncryptionHandlers.generateRandomBytes(32); - - byte[] cipherText = SecurityAES.encryptAES256CBC(plainText, sharedSecret, null); - byte[] plain = SecurityAES.decryptAES256CBC(cipherText, sharedSecret); - - assertArrayEquals(plain, plainText); - } - - @Test - public void canEncryptDecryptAESGCM() throws Throwable { - byte[] plainText = EncryptionHandlers.generateRandomBytes(140); - byte[] sharedSecret = EncryptionHandlers.generateRandomBytes(32); - - byte[] cipherText = SecurityAES.encryptAESGCM(plainText, - new SecretKeySpec(sharedSecret, "AES")); - - byte[] plain = SecurityAES.decryptAESGCM(cipherText, - new SecretKeySpec(sharedSecret, "AES")); - - assertArrayEquals(plain, plainText); - } -} diff --git a/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityECDHTest.java b/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityECDHTest.java deleted file mode 100644 index d3e02841..00000000 --- a/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityECDHTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.afkanerd.deku.E2EE.Security; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PublicKey; - -public class SecurityECDHTest { - -} diff --git a/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityRSARandomTest.java b/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityRSARandomTest.java deleted file mode 100644 index 29d9e8ac..00000000 --- a/app/src/test/java/com/afkanerd/deku/E2EE/Security/SecurityRSARandomTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.afkanerd.deku.E2EE.Security; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import android.security.keystore.KeyProperties; - -import org.junit.Test; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -public class SecurityRSARandomTest { - - @Test - public void testCanEncrypt() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException { - KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA); - keyGenerator.initialize(2048); - KeyPair keyPair = keyGenerator.generateKeyPair(); - - SecretKey secretKey = SecurityAES.generateSecretKey(256); - byte[] cipherText = SecurityRSA.encrypt(keyPair.getPublic(), secretKey.getEncoded()); - byte[] plainText = SecurityRSA.decrypt(keyPair.getPrivate(), cipherText); - assertArrayEquals(secretKey.getEncoded(), plainText); - } -} diff --git a/app/src/test/java/com/afkanerd/deku/RandomTest.java b/app/src/test/java/com/afkanerd/deku/RandomTest.java index 675134cc..f0a14214 100644 --- a/app/src/test/java/com/afkanerd/deku/RandomTest.java +++ b/app/src/test/java/com/afkanerd/deku/RandomTest.java @@ -1,10 +1,16 @@ package com.afkanerd.deku; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import android.util.Log; + import org.junit.Test; +import java.util.ArrayList; +import java.util.List; + public class RandomTest { class RandomClass { @@ -22,4 +28,39 @@ public void ObjectBehaviourTest() { randomClassUpdate(randomClass); assertEquals(1, randomClass.value); } + + List output =new ArrayList<>(); + + public void sum(int i) { + output.add(i); + } + + public void runSum(int i) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + sum(i); + try { + Thread.sleep(1000L - (i*100L)); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + thread.start(); + } + + @Test + public void SyncingThreadsTest() { + for(int i=0;i<5;++i) + runSum(i); + List expected = new ArrayList<>(); + expected.add(0); + expected.add(1); + expected.add(2); + expected.add(3); + expected.add(4); + assertEquals(expected, output); + } + } diff --git a/build.gradle b/build.gradle index 51742e3e..ef9258fb 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.1' + classpath 'com.android.tools.build:gradle:8.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/0.17.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/17.txt rename to fastlane/metadata/android/en-US/changelogs/0.17.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/22.txt b/fastlane/metadata/android/en-US/changelogs/0.22.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/22.txt rename to fastlane/metadata/android/en-US/changelogs/0.22.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/33.txt b/fastlane/metadata/android/en-US/changelogs/0.33.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/33.txt rename to fastlane/metadata/android/en-US/changelogs/0.33.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/34.txt b/fastlane/metadata/android/en-US/changelogs/0.34.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/34.txt rename to fastlane/metadata/android/en-US/changelogs/0.34.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/35.txt b/fastlane/metadata/android/en-US/changelogs/0.35.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/35.txt rename to fastlane/metadata/android/en-US/changelogs/0.35.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/36.txt b/fastlane/metadata/android/en-US/changelogs/0.36.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/36.txt rename to fastlane/metadata/android/en-US/changelogs/0.36.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/37.txt b/fastlane/metadata/android/en-US/changelogs/0.37.0.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/37.txt rename to fastlane/metadata/android/en-US/changelogs/0.37.0.txt diff --git a/fastlane/metadata/android/en-US/changelogs/0.38.0.txt b/fastlane/metadata/android/en-US/changelogs/0.38.0.txt new file mode 100644 index 00000000..848cc857 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.38.0.txt @@ -0,0 +1,2 @@ +- update: blocking added +- update: added option to mute messages diff --git a/fastlane/metadata/android/en-US/changelogs/0.39.0.txt b/fastlane/metadata/android/en-US/changelogs/0.39.0.txt new file mode 100644 index 00000000..b43e0024 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.39.0.txt @@ -0,0 +1,3 @@ +- fix: RMQ connections now shoes messages in SMS inbox +- fix: Routing to custom cloud breaks when accessing database now fixed +- update: can export messages to a json file diff --git a/fastlane/metadata/android/en-US/changelogs/0.40.0.txt b/fastlane/metadata/android/en-US/changelogs/0.40.0.txt new file mode 100644 index 00000000..c0228177 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.40.0.txt @@ -0,0 +1,3 @@ +- update: fixed broken issues with RMQ connections + +- update: once add GatewayClients and use that for all instances diff --git a/fastlane/metadata/android/en-US/changelogs/0.41.0.txt b/fastlane/metadata/android/en-US/changelogs/0.41.0.txt new file mode 100644 index 00000000..1dcfa5b8 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.41.0.txt @@ -0,0 +1,5 @@ +- update: fix issue with dualsim in pixel devices + +- update: fix broken RMQ connections and reduce channel and connection loads + +- update: optimized for speed and better thread handling diff --git a/version.properties b/version.properties index e8605028..d7966af3 100644 --- a/version.properties +++ b/version.properties @@ -1,5 +1,5 @@ releaseVersion=0 -stagingVersion=37 +stagingVersion=41 nightlyVersion=0 -versionName=0.37.0 -tagVersion=49 \ No newline at end of file +versionName=0.41.0 +tagVersion=53 \ No newline at end of file