aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/pixart/messenger/persistance
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java2510
-rw-r--r--src/main/java/de/pixart/messenger/persistance/FileBackend.java1828
-rw-r--r--src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java2
3 files changed, 2170 insertions, 2170 deletions
diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
index 4ce66edf8..993a42c21 100644
--- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
+++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java
@@ -52,1261 +52,1261 @@ import de.pixart.messenger.xmpp.jid.Jid;
public class DatabaseBackend extends SQLiteOpenHelper {
- private static DatabaseBackend instance = null;
-
- public static final String DATABASE_NAME = "history";
- public static final int DATABASE_VERSION = 31;
-
- private static String CREATE_CONTATCS_STATEMENT = "create table "
- + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
- + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
- + Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
- + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
- + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
- + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
- + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
- + Account.TABLENAME + "(" + Account.UUID
- + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
- + Contact.JID + ") ON CONFLICT REPLACE);";
-
- private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
- + ServiceDiscoveryResult.TABLENAME + "("
- + ServiceDiscoveryResult.HASH + " TEXT, "
- + ServiceDiscoveryResult.VER + " TEXT, "
- + ServiceDiscoveryResult.RESULT + " TEXT, "
- + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
- + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
-
- private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE "
- + PresenceTemplate.TABELNAME + "("
- + PresenceTemplate.UUID + " TEXT, "
- + PresenceTemplate.LAST_USED + " NUMBER,"
- + PresenceTemplate.MESSAGE + " TEXT,"
- + PresenceTemplate.STATUS + " TEXT,"
- + "UNIQUE("+PresenceTemplate.MESSAGE + "," +PresenceTemplate.STATUS+") ON CONFLICT REPLACE);";
-
- private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
- + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
- + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + SQLiteAxolotlStore.ID + " INTEGER, "
- + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + SQLiteAxolotlStore.ACCOUNT
- + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
- + SQLiteAxolotlStore.ID
- + ") ON CONFLICT REPLACE"
- + ");";
-
- private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
- + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
- + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + SQLiteAxolotlStore.ID + " INTEGER, "
- + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + SQLiteAxolotlStore.ACCOUNT
- + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
- + SQLiteAxolotlStore.ID
- + ") ON CONFLICT REPLACE" +
- ");";
-
- private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
- + SQLiteAxolotlStore.SESSION_TABLENAME + "("
- + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + SQLiteAxolotlStore.NAME + " TEXT, "
- + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
- + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + SQLiteAxolotlStore.ACCOUNT
- + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
- + SQLiteAxolotlStore.NAME + ", "
- + SQLiteAxolotlStore.DEVICE_ID
- + ") ON CONFLICT REPLACE"
- + ");";
-
- private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
- + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
- + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
- + SQLiteAxolotlStore.NAME + " TEXT, "
- + SQLiteAxolotlStore.OWN + " INTEGER, "
- + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
- + SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
- + SQLiteAxolotlStore.TRUST + " TEXT, "
- + SQLiteAxolotlStore.ACTIVE + " NUMBER, "
- + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
- + SQLiteAxolotlStore.ACCOUNT
- + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
- + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
- + SQLiteAxolotlStore.NAME + ", "
- + SQLiteAxolotlStore.FINGERPRINT
- + ") ON CONFLICT IGNORE"
- + ");";
-
- private static String START_TIMES_TABLE = "start_times";
- private static String CREATE_START_TIMES_TABLE = "create table "+START_TIMES_TABLE+" (timestamp NUMBER);";
-
- private DatabaseBackend(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("PRAGMA foreign_keys=ON;");
- db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID+ " TEXT PRIMARY KEY,"
- + Account.USERNAME + " TEXT,"
- + Account.SERVER + " TEXT,"
- + Account.PASSWORD + " TEXT,"
- + Account.DISPLAY_NAME + " TEXT, "
- + Account.STATUS + " TEXT,"
- + Account.STATUS_MESSAGE + " TEXT,"
- + Account.ROSTERVERSION + " TEXT,"
- + Account.OPTIONS + " NUMBER, "
- + Account.AVATAR + " TEXT, "
- + Account.KEYS + " TEXT, "
- + Account.HOSTNAME + " TEXT, "
- + Account.PORT + " NUMBER DEFAULT 5222)");
- db.execSQL("create table " + Conversation.TABLENAME + " ("
- + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
- + " TEXT, " + Conversation.CONTACT + " TEXT, "
- + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
- + " TEXT, " + Conversation.CREATED + " NUMBER, "
- + Conversation.STATUS + " NUMBER, " + Conversation.MODE
- + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
- + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
- + "(" + Account.UUID + ") ON DELETE CASCADE);");
- db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
- + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
- + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
- + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
- + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
- + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
- + Message.RELATIVE_FILE_PATH + " TEXT, "
- + Message.SERVER_MSG_ID + " TEXT, "
- + Message.FINGERPRINT + " TEXT, "
- + Message.CARBON + " INTEGER, "
- + Message.EDITED + " TEXT, "
- + Message.READ + " NUMBER DEFAULT 1, "
- + Message.OOB + " INTEGER, "
- + Message.ERROR_MESSAGE + " TEXT,"
- + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
- + Message.CONVERSATION + ") REFERENCES "
- + Conversation.TABLENAME + "(" + Conversation.UUID
- + ") ON DELETE CASCADE);");
-
- db.execSQL(CREATE_CONTATCS_STATEMENT);
- db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
- db.execSQL(CREATE_SESSIONS_STATEMENT);
- db.execSQL(CREATE_PREKEYS_STATEMENT);
- db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
- db.execSQL(CREATE_IDENTITIES_STATEMENT);
- db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
- db.execSQL(CREATE_START_TIMES_TABLE);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion < 2 && newVersion >= 2) {
- db.execSQL("update " + Account.TABLENAME + " set "
- + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
- }
- if (oldVersion < 3 && newVersion >= 3) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.TYPE + " NUMBER");
- }
- if (oldVersion < 5 && newVersion >= 5) {
- db.execSQL("DROP TABLE " + Contact.TABLENAME);
- db.execSQL(CREATE_CONTATCS_STATEMENT);
- db.execSQL("UPDATE " + Account.TABLENAME + " SET "
- + Account.ROSTERVERSION + " = NULL");
- }
- if (oldVersion < 6 && newVersion >= 6) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.TRUE_COUNTERPART + " TEXT");
- }
- if (oldVersion < 7 && newVersion >= 7) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.REMOTE_MSG_ID + " TEXT");
- db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
- + Contact.AVATAR + " TEXT");
- db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
- + Account.AVATAR + " TEXT");
- }
- if (oldVersion < 8 && newVersion >= 8) {
- db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
- + Conversation.ATTRIBUTES + " TEXT");
- }
- if (oldVersion < 9 && newVersion >= 9) {
- db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
- + Contact.LAST_TIME + " NUMBER");
- db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
- + Contact.LAST_PRESENCE + " TEXT");
- }
- if (oldVersion < 10 && newVersion >= 10) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.RELATIVE_FILE_PATH + " TEXT");
- }
- if (oldVersion < 11 && newVersion >= 11) {
- db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
- + Contact.GROUPS + " TEXT");
- db.execSQL("delete from " + Contact.TABLENAME);
- db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
- }
- if (oldVersion < 12 && newVersion >= 12) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.SERVER_MSG_ID + " TEXT");
- }
- if (oldVersion < 13 && newVersion >= 13) {
- db.execSQL("delete from " + Contact.TABLENAME);
- db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
- }
- if (oldVersion < 14 && newVersion >= 14) {
- canonicalizeJids(db);
- }
- if (oldVersion < 15 && newVersion >= 15) {
- recreateAxolotlDb(db);
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.FINGERPRINT + " TEXT");
- } else if (oldVersion < 22 && newVersion >= 22) {
- db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
- }
- if (oldVersion < 16 && newVersion >= 16) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
- + Message.CARBON + " INTEGER");
- }
- if (oldVersion < 19 && newVersion >= 19) {
- db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
- }
- if (oldVersion < 20 && newVersion >= 20) {
- db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
- db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
- }
- if (oldVersion < 26 && newVersion >= 26) {
- db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
- db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT");
- }
- /* Any migrations that alter the Account table need to happen BEFORE this migration, as it
+ private static DatabaseBackend instance = null;
+
+ public static final String DATABASE_NAME = "history";
+ public static final int DATABASE_VERSION = 31;
+
+ private static String CREATE_CONTATCS_STATEMENT = "create table "
+ + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
+ + Contact.SERVERNAME + " TEXT, " + Contact.SYSTEMNAME + " TEXT,"
+ + Contact.JID + " TEXT," + Contact.KEYS + " TEXT,"
+ + Contact.PHOTOURI + " TEXT," + Contact.OPTIONS + " NUMBER,"
+ + Contact.SYSTEMACCOUNT + " NUMBER, " + Contact.AVATAR + " TEXT, "
+ + Contact.LAST_PRESENCE + " TEXT, " + Contact.LAST_TIME + " NUMBER, "
+ + Contact.GROUPS + " TEXT, FOREIGN KEY(" + Contact.ACCOUNT + ") REFERENCES "
+ + Account.TABLENAME + "(" + Account.UUID
+ + ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
+ + Contact.JID + ") ON CONFLICT REPLACE);";
+
+ private static String CREATE_DISCOVERY_RESULTS_STATEMENT = "create table "
+ + ServiceDiscoveryResult.TABLENAME + "("
+ + ServiceDiscoveryResult.HASH + " TEXT, "
+ + ServiceDiscoveryResult.VER + " TEXT, "
+ + ServiceDiscoveryResult.RESULT + " TEXT, "
+ + "UNIQUE(" + ServiceDiscoveryResult.HASH + ", "
+ + ServiceDiscoveryResult.VER + ") ON CONFLICT REPLACE);";
+
+ private static String CREATE_PRESENCE_TEMPLATES_STATEMENT = "CREATE TABLE "
+ + PresenceTemplate.TABELNAME + "("
+ + PresenceTemplate.UUID + " TEXT, "
+ + PresenceTemplate.LAST_USED + " NUMBER,"
+ + PresenceTemplate.MESSAGE + " TEXT,"
+ + PresenceTemplate.STATUS + " TEXT,"
+ + "UNIQUE(" + PresenceTemplate.MESSAGE + "," + PresenceTemplate.STATUS + ") ON CONFLICT REPLACE);";
+
+ private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
+ + SQLiteAxolotlStore.PREKEY_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.ID + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.ID
+ + ") ON CONFLICT REPLACE"
+ + ");";
+
+ private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
+ + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.ID + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.ID
+ + ") ON CONFLICT REPLACE" +
+ ");";
+
+ private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
+ + SQLiteAxolotlStore.SESSION_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.NAME + " TEXT, "
+ + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.NAME + ", "
+ + SQLiteAxolotlStore.DEVICE_ID
+ + ") ON CONFLICT REPLACE"
+ + ");";
+
+ private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
+ + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
+ + SQLiteAxolotlStore.ACCOUNT + " TEXT, "
+ + SQLiteAxolotlStore.NAME + " TEXT, "
+ + SQLiteAxolotlStore.OWN + " INTEGER, "
+ + SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
+ + SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
+ + SQLiteAxolotlStore.TRUST + " TEXT, "
+ + SQLiteAxolotlStore.ACTIVE + " NUMBER, "
+ + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ + SQLiteAxolotlStore.ACCOUNT
+ + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
+ + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
+ + SQLiteAxolotlStore.NAME + ", "
+ + SQLiteAxolotlStore.FINGERPRINT
+ + ") ON CONFLICT IGNORE"
+ + ");";
+
+ private static String START_TIMES_TABLE = "start_times";
+ private static String CREATE_START_TIMES_TABLE = "create table " + START_TIMES_TABLE + " (timestamp NUMBER);";
+
+ private DatabaseBackend(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("PRAGMA foreign_keys=ON;");
+ db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID + " TEXT PRIMARY KEY,"
+ + Account.USERNAME + " TEXT,"
+ + Account.SERVER + " TEXT,"
+ + Account.PASSWORD + " TEXT,"
+ + Account.DISPLAY_NAME + " TEXT, "
+ + Account.STATUS + " TEXT,"
+ + Account.STATUS_MESSAGE + " TEXT,"
+ + Account.ROSTERVERSION + " TEXT,"
+ + Account.OPTIONS + " NUMBER, "
+ + Account.AVATAR + " TEXT, "
+ + Account.KEYS + " TEXT, "
+ + Account.HOSTNAME + " TEXT, "
+ + Account.PORT + " NUMBER DEFAULT 5222)");
+ db.execSQL("create table " + Conversation.TABLENAME + " ("
+ + Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
+ + " TEXT, " + Conversation.CONTACT + " TEXT, "
+ + Conversation.ACCOUNT + " TEXT, " + Conversation.CONTACTJID
+ + " TEXT, " + Conversation.CREATED + " NUMBER, "
+ + Conversation.STATUS + " NUMBER, " + Conversation.MODE
+ + " NUMBER, " + Conversation.ATTRIBUTES + " TEXT, FOREIGN KEY("
+ + Conversation.ACCOUNT + ") REFERENCES " + Account.TABLENAME
+ + "(" + Account.UUID + ") ON DELETE CASCADE);");
+ db.execSQL("create table " + Message.TABLENAME + "( " + Message.UUID
+ + " TEXT PRIMARY KEY, " + Message.CONVERSATION + " TEXT, "
+ + Message.TIME_SENT + " NUMBER, " + Message.COUNTERPART
+ + " TEXT, " + Message.TRUE_COUNTERPART + " TEXT,"
+ + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, "
+ + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
+ + Message.RELATIVE_FILE_PATH + " TEXT, "
+ + Message.SERVER_MSG_ID + " TEXT, "
+ + Message.FINGERPRINT + " TEXT, "
+ + Message.CARBON + " INTEGER, "
+ + Message.EDITED + " TEXT, "
+ + Message.READ + " NUMBER DEFAULT 1, "
+ + Message.OOB + " INTEGER, "
+ + Message.ERROR_MESSAGE + " TEXT,"
+ + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ + Message.CONVERSATION + ") REFERENCES "
+ + Conversation.TABLENAME + "(" + Conversation.UUID
+ + ") ON DELETE CASCADE);");
+
+ db.execSQL(CREATE_CONTATCS_STATEMENT);
+ db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
+ db.execSQL(CREATE_SESSIONS_STATEMENT);
+ db.execSQL(CREATE_PREKEYS_STATEMENT);
+ db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
+ db.execSQL(CREATE_IDENTITIES_STATEMENT);
+ db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
+ db.execSQL(CREATE_START_TIMES_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 2 && newVersion >= 2) {
+ db.execSQL("update " + Account.TABLENAME + " set "
+ + Account.OPTIONS + " = " + Account.OPTIONS + " | 8");
+ }
+ if (oldVersion < 3 && newVersion >= 3) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.TYPE + " NUMBER");
+ }
+ if (oldVersion < 5 && newVersion >= 5) {
+ db.execSQL("DROP TABLE " + Contact.TABLENAME);
+ db.execSQL(CREATE_CONTATCS_STATEMENT);
+ db.execSQL("UPDATE " + Account.TABLENAME + " SET "
+ + Account.ROSTERVERSION + " = NULL");
+ }
+ if (oldVersion < 6 && newVersion >= 6) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.TRUE_COUNTERPART + " TEXT");
+ }
+ if (oldVersion < 7 && newVersion >= 7) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.REMOTE_MSG_ID + " TEXT");
+ db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
+ + Contact.AVATAR + " TEXT");
+ db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "
+ + Account.AVATAR + " TEXT");
+ }
+ if (oldVersion < 8 && newVersion >= 8) {
+ db.execSQL("ALTER TABLE " + Conversation.TABLENAME + " ADD COLUMN "
+ + Conversation.ATTRIBUTES + " TEXT");
+ }
+ if (oldVersion < 9 && newVersion >= 9) {
+ db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
+ + Contact.LAST_TIME + " NUMBER");
+ db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
+ + Contact.LAST_PRESENCE + " TEXT");
+ }
+ if (oldVersion < 10 && newVersion >= 10) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.RELATIVE_FILE_PATH + " TEXT");
+ }
+ if (oldVersion < 11 && newVersion >= 11) {
+ db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN "
+ + Contact.GROUPS + " TEXT");
+ db.execSQL("delete from " + Contact.TABLENAME);
+ db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
+ }
+ if (oldVersion < 12 && newVersion >= 12) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.SERVER_MSG_ID + " TEXT");
+ }
+ if (oldVersion < 13 && newVersion >= 13) {
+ db.execSQL("delete from " + Contact.TABLENAME);
+ db.execSQL("update " + Account.TABLENAME + " set " + Account.ROSTERVERSION + " = NULL");
+ }
+ if (oldVersion < 14 && newVersion >= 14) {
+ canonicalizeJids(db);
+ }
+ if (oldVersion < 15 && newVersion >= 15) {
+ recreateAxolotlDb(db);
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.FINGERPRINT + " TEXT");
+ } else if (oldVersion < 22 && newVersion >= 22) {
+ db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.CERTIFICATE);
+ }
+ if (oldVersion < 16 && newVersion >= 16) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
+ + Message.CARBON + " INTEGER");
+ }
+ if (oldVersion < 19 && newVersion >= 19) {
+ db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
+ }
+ if (oldVersion < 20 && newVersion >= 20) {
+ db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
+ db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
+ }
+ if (oldVersion < 26 && newVersion >= 26) {
+ db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS + " TEXT");
+ db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.STATUS_MESSAGE + " TEXT");
+ }
+ /* Any migrations that alter the Account table need to happen BEFORE this migration, as it
* depends on account de-serialization.
*/
- if (oldVersion < 17 && newVersion >= 17) {
- List<Account> accounts = getAccounts(db);
- for (Account account : accounts) {
- String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
- if (ownDeviceIdString == null) {
- continue;
- }
- int ownDeviceId = Integer.valueOf(ownDeviceIdString);
- AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId);
- deleteSession(db, account, ownAddress);
- IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
- if (identityKeyPair != null) {
- String[] selectionArgs = {
- account.getUuid(),
- identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", "")
- };
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.TRUSTED, 2);
- db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
- SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
- selectionArgs);
- } else {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair");
- }
- }
- }
- if (oldVersion < 18 && newVersion >= 18) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
- }
-
- if (oldVersion < 21 && newVersion >= 21) {
- List<Account> accounts = getAccounts(db);
- for (Account account : accounts) {
- account.unsetPgpSignature();
- db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
- + "=?", new String[]{account.getUuid()});
- }
- }
-
- if (oldVersion < 23 && newVersion >= 23) {
- db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
- }
-
- if (oldVersion < 24 && newVersion >= 24) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
- }
-
- if (oldVersion < 25 && newVersion >= 25) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
- }
-
- if (oldVersion < 26 && newVersion >= 26) {
- db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
- }
-
- if (oldVersion < 27 && newVersion >= 27) {
- db.execSQL("DELETE FROM "+ServiceDiscoveryResult.TABLENAME);
- }
-
- if (oldVersion < 28 && newVersion >= 28) {
- canonicalizeJids(db);
- }
-
- if (oldVersion < 29 && newVersion >= 29) {
- db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT");
- }
-
- if (oldVersion < 30 && newVersion >= 30) {
- db.execSQL(CREATE_START_TIMES_TABLE);
- }
- if (oldVersion < 31 && newVersion >= 31) {
- db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.TRUST + " TEXT");
- db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.ACTIVE + " NUMBER");
- HashMap<Integer,ContentValues> migration = new HashMap<>();
- migration.put(0,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED,true));
- migration.put(1,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
- migration.put(2,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true));
- migration.put(3,createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false));
- migration.put(4,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
- migration.put(5,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, false));
- migration.put(6,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false));
- migration.put(7,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true));
- migration.put(8,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false));
- for(Map.Entry<Integer,ContentValues> entry : migration.entrySet()) {
- String whereClause = SQLiteAxolotlStore.TRUSTED+"=?";
- String[] where = {String.valueOf(entry.getKey())};
- db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,entry.getValue(),whereClause,where);
- }
- }
- }
-
- private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) {
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.TRUST,trust.toString());
- values.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0);
- return values;
- }
-
- private void canonicalizeJids(SQLiteDatabase db) {
- // migrate db to new, canonicalized JID domainpart representation
-
- // Conversation table
- Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
- while (cursor.moveToNext()) {
- String newJid;
- try {
- newJid = Jid.fromString(
- cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
- ).toPreppedString();
- } catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
- + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
- + ": " + ignored + ". Skipping...");
- continue;
- }
-
- String updateArgs[] = {
- newJid,
- cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
- };
- db.execSQL("update " + Conversation.TABLENAME
- + " set " + Conversation.CONTACTJID + " = ? "
- + " where " + Conversation.UUID + " = ?", updateArgs);
- }
- cursor.close();
-
- // Contact table
- cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
- while (cursor.moveToNext()) {
- String newJid;
- try {
- newJid = Jid.fromString(
- cursor.getString(cursor.getColumnIndex(Contact.JID))
- ).toPreppedString();
- } catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
- + cursor.getString(cursor.getColumnIndex(Contact.JID))
- + ": " + ignored + ". Skipping...");
- continue;
- }
-
- String updateArgs[] = {
- newJid,
- cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
- cursor.getString(cursor.getColumnIndex(Contact.JID)),
- };
- db.execSQL("update " + Contact.TABLENAME
- + " set " + Contact.JID + " = ? "
- + " where " + Contact.ACCOUNT + " = ? "
- + " AND " + Contact.JID + " = ?", updateArgs);
- }
- cursor.close();
-
- // Account table
- cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
- while (cursor.moveToNext()) {
- String newServer;
- try {
- newServer = Jid.fromParts(
- cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
- cursor.getString(cursor.getColumnIndex(Account.SERVER)),
- "mobile"
- ).getDomainpart();
- } catch (InvalidJidException ignored) {
- Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
- + cursor.getString(cursor.getColumnIndex(Account.SERVER))
- + ": " + ignored + ". Skipping...");
- continue;
- }
-
- String updateArgs[] = {
- newServer,
- cursor.getString(cursor.getColumnIndex(Account.UUID)),
- };
- db.execSQL("update " + Account.TABLENAME
- + " set " + Account.SERVER + " = ? "
- + " where " + Account.UUID + " = ?", updateArgs);
- }
- cursor.close();
- }
-
- public static synchronized DatabaseBackend getInstance(Context context) {
- if (instance == null) {
- instance = new DatabaseBackend(context);
- }
- return instance;
- }
-
- public void createConversation(Conversation conversation) {
- SQLiteDatabase db = this.getWritableDatabase();
- db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
- }
-
- public void createMessage(Message message) {
- SQLiteDatabase db = this.getWritableDatabase();
- db.insert(Message.TABLENAME, null, message.getContentValues());
- }
-
- public void createAccount(Account account) {
- SQLiteDatabase db = this.getWritableDatabase();
- db.insert(Account.TABLENAME, null, account.getContentValues());
- }
-
- public void insertDiscoveryResult(ServiceDiscoveryResult result) {
- SQLiteDatabase db = this.getWritableDatabase();
- db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues());
- }
-
- public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = {hash, ver};
- Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null,
- ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?",
- selectionArgs, null, null, null);
- if (cursor.getCount() == 0) {
- cursor.close();
- return null;
- }
- cursor.moveToFirst();
-
- ServiceDiscoveryResult result = null;
- try {
- result = new ServiceDiscoveryResult(cursor);
- } catch (JSONException e) { /* result is still null */ }
-
- cursor.close();
- return result;
- }
-
- public void insertPresenceTemplate(PresenceTemplate template) {
- SQLiteDatabase db = this.getWritableDatabase();
- db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues());
- }
-
- public List<PresenceTemplate> getPresenceTemplates() {
- ArrayList<PresenceTemplate> templates = new ArrayList<>();
- SQLiteDatabase db = this.getReadableDatabase();
- Cursor cursor = db.query(PresenceTemplate.TABELNAME,null,null,null,null,null,PresenceTemplate.LAST_USED+" desc");
- while (cursor.moveToNext()) {
- templates.add(PresenceTemplate.fromCursor(cursor));
- }
- cursor.close();
- return templates;
- }
-
- public void deletePresenceTemplate(PresenceTemplate template) {
- Log.d(Config.LOGTAG,"deleting presence template with uuid "+template.getUuid());
- SQLiteDatabase db = this.getWritableDatabase();
- String where = PresenceTemplate.UUID+"=?";
- String[] whereArgs = {template.getUuid()};
- db.delete(PresenceTemplate.TABELNAME,where,whereArgs);
- }
-
- public CopyOnWriteArrayList<Conversation> getConversations(int status) {
- CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
- SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = {Integer.toString(status)};
- Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
- + " where " + Conversation.STATUS + " = ? order by "
- + Conversation.CREATED + " desc", selectionArgs);
- while (cursor.moveToNext()) {
- list.add(Conversation.fromCursor(cursor));
- }
- cursor.close();
- return list;
- }
-
- public ArrayList<Message> getMessages(Conversation conversations, int limit) {
- return getMessages(conversations, limit, -1);
- }
-
- public ArrayList<Message> getMessages(Conversation conversation, int limit,
- long timestamp) {
- ArrayList<Message> list = new ArrayList<>();
- SQLiteDatabase db = this.getReadableDatabase();
- Cursor cursor;
- if (timestamp == -1) {
- String[] selectionArgs = {conversation.getUuid()};
- cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
- + "=?", selectionArgs, null, null, Message.TIME_SENT
- + " DESC", String.valueOf(limit));
- } else {
- String[] selectionArgs = {conversation.getUuid(),
- Long.toString(timestamp)};
- cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
- + "=? and " + Message.TIME_SENT + "<?", selectionArgs,
- null, null, Message.TIME_SENT + " DESC",
- String.valueOf(limit));
- }
- if (cursor.getCount() > 0) {
- cursor.moveToLast();
- do {
- Message message = Message.fromCursor(cursor);
- message.setConversation(conversation);
- list.add(message);
- } while (cursor.moveToPrevious());
- }
- cursor.close();
- return list;
- }
-
- public Iterable<Message> getMessagesIterable(final Conversation conversation) {
- return new Iterable<Message>() {
- @Override
- public Iterator<Message> iterator() {
- class MessageIterator implements Iterator<Message> {
- SQLiteDatabase db = getReadableDatabase();
- String[] selectionArgs = {conversation.getUuid()};
- Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
- + "=?", selectionArgs, null, null, Message.TIME_SENT
- + " ASC", null);
-
- public MessageIterator() {
- cursor.moveToFirst();
- }
-
- @Override
- public boolean hasNext() {
- return !cursor.isAfterLast();
- }
-
- @Override
- public Message next() {
- Message message = Message.fromCursor(cursor);
- cursor.moveToNext();
- return message;
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- }
- return new MessageIterator();
- }
- };
- }
-
- public Conversation findConversation(final Account account, final Jid contactJid) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = {account.getUuid(),
- contactJid.toBareJid().toPreppedString() + "/%",
- contactJid.toBareJid().toPreppedString()
- };
- Cursor cursor = db.query(Conversation.TABLENAME, null,
- Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
- + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
- if (cursor.getCount() == 0) {
- cursor.close();
- return null;
- }
- cursor.moveToFirst();
- Conversation conversation = Conversation.fromCursor(cursor);
- cursor.close();
- return conversation;
- }
-
- public void updateConversation(final Conversation conversation) {
- final SQLiteDatabase db = this.getWritableDatabase();
- final String[] args = {conversation.getUuid()};
- db.update(Conversation.TABLENAME, conversation.getContentValues(),
- Conversation.UUID + "=?", args);
- }
-
- public List<Account> getAccounts() {
- SQLiteDatabase db = this.getReadableDatabase();
- return getAccounts(db);
- }
-
- private List<Account> getAccounts(SQLiteDatabase db) {
- List<Account> list = new ArrayList<>();
- Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
- null, null);
- while (cursor.moveToNext()) {
- list.add(Account.fromCursor(cursor));
- }
- cursor.close();
- return list;
- }
-
- public boolean updateAccount(Account account) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {account.getUuid()};
- final int rows = db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + "=?", args);
- return rows == 1;
- }
-
- public boolean deleteAccount(Account account) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {account.getUuid()};
- final int rows = db.delete(Account.TABLENAME, Account.UUID + "=?", args);
- return rows == 1;
- }
-
- public boolean hasEnabledAccounts() {
- SQLiteDatabase db = this.getReadableDatabase();
- Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
- + Account.TABLENAME + " where not options & (1 <<1)", null);
- try {
- cursor.moveToFirst();
- int count = cursor.getInt(0);
- return (count > 0);
- } catch (SQLiteCantOpenDatabaseException e) {
- return true; // better safe than sorry
- } catch (RuntimeException e) {
- return true; // better safe than sorry
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- @Override
- public SQLiteDatabase getWritableDatabase() {
- SQLiteDatabase db = super.getWritableDatabase();
- db.execSQL("PRAGMA foreign_keys=ON;");
- return db;
- }
-
- public void updateMessage(Message message) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {message.getUuid()};
- db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
- + "=?", args);
- }
-
- public void updateMessage(Message message, String uuid) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {uuid};
- db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
- + "=?", args);
- }
-
- public void readRoster(Roster roster) {
- SQLiteDatabase db = this.getReadableDatabase();
- Cursor cursor;
- String args[] = {roster.getAccount().getUuid()};
- cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
- while (cursor.moveToNext()) {
- roster.initContact(Contact.fromCursor(cursor));
- }
- cursor.close();
- }
-
- public void writeRoster(final Roster roster) {
- final Account account = roster.getAccount();
- final SQLiteDatabase db = this.getWritableDatabase();
- db.beginTransaction();
- for (Contact contact : roster.getContacts()) {
- if (contact.getOption(Contact.Options.IN_ROSTER)) {
- db.insert(Contact.TABLENAME, null, contact.getContentValues());
- } else {
- String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
- String[] whereArgs = {account.getUuid(), contact.getJid().toPreppedString()};
- db.delete(Contact.TABLENAME, where, whereArgs);
- }
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- account.setRosterVersion(roster.getVersion());
- updateAccount(account);
- }
-
- public void deleteMessagesInConversation(Conversation conversation) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {conversation.getUuid()};
- db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
- }
-
- public Pair<Long, String> getLastMessageReceived(Account account) {
- Cursor cursor = null;
- try {
- SQLiteDatabase db = this.getReadableDatabase();
- String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
- String[] args = {account.getUuid()};
- cursor = db.rawQuery(sql, args);
- if (cursor.getCount() == 0) {
- return null;
- } else {
- cursor.moveToFirst();
- return new Pair<>(cursor.getLong(0), cursor.getString(1));
- }
- } catch (Exception e) {
- return null;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- public Pair<Long, String> getLastClearDate(Account account) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {Conversation.ATTRIBUTES};
- String selection = Conversation.ACCOUNT + "=?";
- String[] args = {account.getUuid()};
- Cursor cursor = db.query(Conversation.TABLENAME, columns, selection, args, null, null, null);
- long maxClearDate = 0;
- while (cursor.moveToNext()) {
- try {
- final JSONObject jsonObject = new JSONObject(cursor.getString(0));
- maxClearDate = Math.max(maxClearDate, jsonObject.getLong(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY));
- } catch (Exception e) {
- //ignored
- }
- }
- cursor.close();
- return new Pair<>(maxClearDate, null);
- }
-
- private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
- final SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = {account.getUuid(),
- contact.getName(),
- Integer.toString(contact.getDeviceId())};
- return db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
- null,
- SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + SQLiteAxolotlStore.NAME + " = ? AND "
- + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
- selectionArgs,
- null, null, null);
- }
-
- public SessionRecord loadSession(Account account, AxolotlAddress contact) {
- SessionRecord session = null;
- Cursor cursor = getCursorForSession(account, contact);
- if (cursor.getCount() != 0) {
- cursor.moveToFirst();
- try {
- session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
- } catch (IOException e) {
- cursor.close();
- throw new AssertionError(e);
- }
- }
- cursor.close();
- return session;
- }
-
- public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
- final SQLiteDatabase db = this.getReadableDatabase();
- return getSubDeviceSessions(db, account, contact);
- }
-
- private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
- List<Integer> devices = new ArrayList<>();
- String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
- String[] selectionArgs = {account.getUuid(),
- contact.getName()};
- Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
- columns,
- SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + SQLiteAxolotlStore.NAME + " = ?",
- selectionArgs,
- null, null, null);
-
- while (cursor.moveToNext()) {
- devices.add(cursor.getInt(
- cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
- }
-
- cursor.close();
- return devices;
- }
-
- public boolean containsSession(Account account, AxolotlAddress contact) {
- Cursor cursor = getCursorForSession(account, contact);
- int count = cursor.getCount();
- cursor.close();
- return count != 0;
- }
-
- public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
- SQLiteDatabase db = this.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.NAME, contact.getName());
- values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
- values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
- values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
- }
-
- public void deleteSession(Account account, AxolotlAddress contact) {
- SQLiteDatabase db = this.getWritableDatabase();
- deleteSession(db, account, contact);
- }
-
- private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
- String[] args = {account.getUuid(),
- contact.getName(),
- Integer.toString(contact.getDeviceId())};
- db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + SQLiteAxolotlStore.NAME + " = ? AND "
- + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
- args);
- }
-
- public void deleteAllSessions(Account account, AxolotlAddress contact) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {account.getUuid(), contact.getName()};
- db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + SQLiteAxolotlStore.NAME + " = ?",
- args);
- }
-
- private Cursor getCursorForPreKey(Account account, int preKeyId) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {SQLiteAxolotlStore.KEY};
- String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
- Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
- columns,
- SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + SQLiteAxolotlStore.ID + "=?",
- selectionArgs,
- null, null, null);
-
- return cursor;
- }
-
- public PreKeyRecord loadPreKey(Account account, int preKeyId) {
- PreKeyRecord record = null;
- Cursor cursor = getCursorForPreKey(account, preKeyId);
- if (cursor.getCount() != 0) {
- cursor.moveToFirst();
- try {
- record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
- } catch (IOException e) {
- throw new AssertionError(e);
- }
- }
- cursor.close();
- return record;
- }
-
- public boolean containsPreKey(Account account, int preKeyId) {
- Cursor cursor = getCursorForPreKey(account, preKeyId);
- int count = cursor.getCount();
- cursor.close();
- return count != 0;
- }
-
- public void storePreKey(Account account, PreKeyRecord record) {
- SQLiteDatabase db = this.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.ID, record.getId());
- values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
- values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
- }
-
- public void deletePreKey(Account account, int preKeyId) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {account.getUuid(), Integer.toString(preKeyId)};
- db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + SQLiteAxolotlStore.ID + "=?",
- args);
- }
-
- private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {SQLiteAxolotlStore.KEY};
- String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
- Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
- columns,
- SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
- selectionArgs,
- null, null, null);
-
- return cursor;
- }
-
- public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
- SignedPreKeyRecord record = null;
- Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
- if (cursor.getCount() != 0) {
- cursor.moveToFirst();
- try {
- record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
- } catch (IOException e) {
- throw new AssertionError(e);
- }
- }
- cursor.close();
- return record;
- }
-
- public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
- List<SignedPreKeyRecord> prekeys = new ArrayList<>();
- SQLiteDatabase db = this.getReadableDatabase();
- String[] columns = {SQLiteAxolotlStore.KEY};
- String[] selectionArgs = {account.getUuid()};
- Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
- columns,
- SQLiteAxolotlStore.ACCOUNT + "=?",
- selectionArgs,
- null, null, null);
-
- while (cursor.moveToNext()) {
- try {
- prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
- } catch (IOException ignored) {
- }
- }
- cursor.close();
- return prekeys;
- }
-
- public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
- Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
- int count = cursor.getCount();
- cursor.close();
- return count != 0;
- }
-
- public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
- SQLiteDatabase db = this.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.ID, record.getId());
- values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
- values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
- }
-
- public void deleteSignedPreKey(Account account, int signedPreKeyId) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
- db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + "=? AND "
- + SQLiteAxolotlStore.ID + "=?",
- args);
- }
-
- private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
- final SQLiteDatabase db = this.getReadableDatabase();
- return getIdentityKeyCursor(db, account, name, own);
- }
-
- private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
- return getIdentityKeyCursor(db, account, name, own, null);
- }
-
- private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
- final SQLiteDatabase db = this.getReadableDatabase();
- return getIdentityKeyCursor(db, account, fingerprint);
- }
-
- private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
- return getIdentityKeyCursor(db, account, null, null, fingerprint);
- }
-
- private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
- String[] columns = {SQLiteAxolotlStore.TRUST,
- SQLiteAxolotlStore.ACTIVE,
- SQLiteAxolotlStore.KEY};
- ArrayList<String> selectionArgs = new ArrayList<>(4);
- selectionArgs.add(account.getUuid());
- String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
- if (name != null) {
- selectionArgs.add(name);
- selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
- }
- if (fingerprint != null) {
- selectionArgs.add(fingerprint);
- selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
- }
- if (own != null) {
- selectionArgs.add(own ? "1" : "0");
- selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
- }
- Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
- columns,
- selectionString,
- selectionArgs.toArray(new String[selectionArgs.size()]),
- null, null, null);
-
- return cursor;
- }
-
- public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
- SQLiteDatabase db = getReadableDatabase();
- return loadOwnIdentityKeyPair(db, account);
- }
-
- private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
- String name = account.getJid().toBareJid().toPreppedString();
- IdentityKeyPair identityKeyPair = null;
- Cursor cursor = getIdentityKeyCursor(db, account, name, true);
- if (cursor.getCount() != 0) {
- cursor.moveToFirst();
- try {
- identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
- }
- }
- cursor.close();
-
- return identityKeyPair;
- }
-
- public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
- return loadIdentityKeys(account, name, null);
- }
-
- public Set<IdentityKey> loadIdentityKeys(Account account, String name, FingerprintStatus status) {
- Set<IdentityKey> identityKeys = new HashSet<>();
- Cursor cursor = getIdentityKeyCursor(account, name, false);
-
- while (cursor.moveToNext()) {
- if (status != null && !FingerprintStatus.fromCursor(cursor).equals(status)) {
- continue;
- }
- try {
- String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY));
- if (key != null) {
- identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0));
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().toBareJid() + ", address: " + name);
- }
- } catch (InvalidKeyException e) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
- }
- }
- cursor.close();
-
- return identityKeys;
- }
-
- public long numTrustedKeys(Account account, String name) {
- SQLiteDatabase db = getReadableDatabase();
- String[] args = {
- account.getUuid(),
- name,
- FingerprintStatus.Trust.TRUSTED.toString(),
- FingerprintStatus.Trust.VERIFIED.toString(),
- FingerprintStatus.Trust.VERIFIED_X509.toString()
- };
- return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + " = ?"
- + " AND " + SQLiteAxolotlStore.NAME + " = ?"
- + " AND (" + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ? OR " +SQLiteAxolotlStore.TRUST +" = ?)"
- + " AND " +SQLiteAxolotlStore.ACTIVE + " > 0",
- args
- );
- }
-
- private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) {
- SQLiteDatabase db = this.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- values.put(SQLiteAxolotlStore.NAME, name);
- values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
- values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
- values.put(SQLiteAxolotlStore.KEY, base64Serialized);
- values.putAll(status.toContentValues());
- String where = SQLiteAxolotlStore.ACCOUNT+"=? AND "+SQLiteAxolotlStore.NAME+"=? AND "+SQLiteAxolotlStore.FINGERPRINT+" =?";
- String[] whereArgs = {account.getUuid(),name,fingerprint};
- int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,values,where,whereArgs);
- if (rows == 0) {
- db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
- }
- }
-
- public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) {
- SQLiteDatabase db = this.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
- values.put(SQLiteAxolotlStore.NAME, name);
- values.put(SQLiteAxolotlStore.OWN, 0);
- values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
- values.putAll(status.toContentValues());
- db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
- }
-
- public FingerprintStatus getFingerprintStatus(Account account, String fingerprint) {
- Cursor cursor = getIdentityKeyCursor(account, fingerprint);
- final FingerprintStatus status;
- if (cursor.getCount() > 0) {
- cursor.moveToFirst();
- status = FingerprintStatus.fromCursor(cursor);
- } else {
- status = null;
- }
- cursor.close();
- return status;
- }
-
- public boolean setIdentityKeyTrust(Account account, String fingerprint, FingerprintStatus fingerprintStatus) {
- SQLiteDatabase db = this.getWritableDatabase();
- return setIdentityKeyTrust(db, account, fingerprint, fingerprintStatus);
- }
-
- private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) {
- String[] selectionArgs = {
- account.getUuid(),
- fingerprint
- };
- int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, status.toContentValues(),
- SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
- selectionArgs);
- return rows == 1;
- }
-
- public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
- SQLiteDatabase db = this.getWritableDatabase();
- String[] selectionArgs = {
- account.getUuid(),
- fingerprint
- };
- try {
- ContentValues values = new ContentValues();
- values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
- return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
- SQLiteAxolotlStore.ACCOUNT + " = ? AND "
- + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
- selectionArgs) == 1;
- } catch (CertificateEncodingException e) {
- Log.d(Config.LOGTAG, "could not encode certificate");
- return false;
- }
- }
-
- public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
- SQLiteDatabase db = this.getReadableDatabase();
- String[] selectionArgs = {
- account.getUuid(),
- fingerprint
- };
- String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
- String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
- Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
- if (cursor.getCount() < 1) {
- return null;
- } else {
- cursor.moveToFirst();
- byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
- cursor.close();
- if (certificate == null || certificate.length == 0) {
- return null;
- }
- try {
- CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
- return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
- } catch (CertificateException e) {
- Log.d(Config.LOGTAG,"certificate exception "+e.getMessage());
- return null;
- }
- }
- }
-
- public void storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) {
- storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status);
- }
-
- public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
- storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false));
- }
-
- public void recreateAxolotlDb(SQLiteDatabase db) {
- Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
- db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
- db.execSQL(CREATE_SESSIONS_STATEMENT);
- db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
- db.execSQL(CREATE_PREKEYS_STATEMENT);
- db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
- db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
- db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
- db.execSQL(CREATE_IDENTITIES_STATEMENT);
- }
-
- public void wipeAxolotlDb(Account account) {
- String accountName = account.getUuid();
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
- SQLiteDatabase db = this.getWritableDatabase();
- String[] deleteArgs = {
- accountName
- };
- db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + " = ?",
- deleteArgs);
- db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + " = ?",
- deleteArgs);
- db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + " = ?",
- deleteArgs);
- db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
- SQLiteAxolotlStore.ACCOUNT + " = ?",
- deleteArgs);
- }
-
- public boolean startTimeCountExceedsThreshold() {
- SQLiteDatabase db = this.getWritableDatabase();
- long cleanBeforeTimestamp = System.currentTimeMillis() - Config.FREQUENT_RESTARTS_DETECTION_WINDOW;
- db.execSQL("delete from "+START_TIMES_TABLE+" where timestamp < "+cleanBeforeTimestamp);
- ContentValues values = new ContentValues();
- values.put("timestamp",System.currentTimeMillis());
- db.insert(START_TIMES_TABLE,null,values);
- String[] columns = new String[]{"count(timestamp)"};
- Cursor cursor = db.query(START_TIMES_TABLE,columns,null,null,null,null,null);
- int count;
- if (cursor.moveToFirst()) {
- count = cursor.getInt(0);
- } else {
- count = 0;
- }
- cursor.close();
- Log.d(Config.LOGTAG,"start time counter reached "+count);
- return count >= Config.FREQUENT_RESTARTS_THRESHOLD;
- }
-
- public void clearStartTimeCounter() {
- Log.d(Config.LOGTAG,"resetting start time counter");
- SQLiteDatabase db = this.getWritableDatabase();
- db.execSQL("delete from " + START_TIMES_TABLE);
- }
+ if (oldVersion < 17 && newVersion >= 17) {
+ List<Account> accounts = getAccounts(db);
+ for (Account account : accounts) {
+ String ownDeviceIdString = account.getKey(SQLiteAxolotlStore.JSONKEY_REGISTRATION_ID);
+ if (ownDeviceIdString == null) {
+ continue;
+ }
+ int ownDeviceId = Integer.valueOf(ownDeviceIdString);
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownDeviceId);
+ deleteSession(db, account, ownAddress);
+ IdentityKeyPair identityKeyPair = loadOwnIdentityKeyPair(db, account);
+ if (identityKeyPair != null) {
+ String[] selectionArgs = {
+ account.getUuid(),
+ identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", "")
+ };
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.TRUSTED, 2);
+ db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+ selectionArgs);
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not load own identity key pair");
+ }
+ }
+ }
+ if (oldVersion < 18 && newVersion >= 18) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.READ + " NUMBER DEFAULT 1");
+ }
+
+ if (oldVersion < 21 && newVersion >= 21) {
+ List<Account> accounts = getAccounts(db);
+ for (Account account : accounts) {
+ account.unsetPgpSignature();
+ db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
+ + "=?", new String[]{account.getUuid()});
+ }
+ }
+
+ if (oldVersion < 23 && newVersion >= 23) {
+ db.execSQL(CREATE_DISCOVERY_RESULTS_STATEMENT);
+ }
+
+ if (oldVersion < 24 && newVersion >= 24) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.EDITED + " TEXT");
+ }
+
+ if (oldVersion < 25 && newVersion >= 25) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.OOB + " INTEGER");
+ }
+
+ if (oldVersion < 26 && newVersion >= 26) {
+ db.execSQL(CREATE_PRESENCE_TEMPLATES_STATEMENT);
+ }
+
+ if (oldVersion < 27 && newVersion >= 27) {
+ db.execSQL("DELETE FROM " + ServiceDiscoveryResult.TABLENAME);
+ }
+
+ if (oldVersion < 28 && newVersion >= 28) {
+ canonicalizeJids(db);
+ }
+
+ if (oldVersion < 29 && newVersion >= 29) {
+ db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.ERROR_MESSAGE + " TEXT");
+ }
+
+ if (oldVersion < 30 && newVersion >= 30) {
+ db.execSQL(CREATE_START_TIMES_TABLE);
+ }
+ if (oldVersion < 31 && newVersion >= 31) {
+ db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.TRUST + " TEXT");
+ db.execSQL("ALTER TABLE " + SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN " + SQLiteAxolotlStore.ACTIVE + " NUMBER");
+ HashMap<Integer, ContentValues> migration = new HashMap<>();
+ migration.put(0, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, true));
+ migration.put(1, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
+ migration.put(2, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true));
+ migration.put(3, createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false));
+ migration.put(4, createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
+ migration.put(5, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, false));
+ migration.put(6, createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false));
+ migration.put(7, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true));
+ migration.put(8, createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false));
+ for (Map.Entry<Integer, ContentValues> entry : migration.entrySet()) {
+ String whereClause = SQLiteAxolotlStore.TRUSTED + "=?";
+ String[] where = {String.valueOf(entry.getKey())};
+ db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, entry.getValue(), whereClause, where);
+ }
+ }
+ }
+
+ private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) {
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.TRUST, trust.toString());
+ values.put(SQLiteAxolotlStore.ACTIVE, active ? 1 : 0);
+ return values;
+ }
+
+ private void canonicalizeJids(SQLiteDatabase db) {
+ // migrate db to new, canonicalized JID domainpart representation
+
+ // Conversation table
+ Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME, new String[0]);
+ while (cursor.moveToNext()) {
+ String newJid;
+ try {
+ newJid = Jid.fromString(
+ cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
+ ).toPreppedString();
+ } catch (InvalidJidException ignored) {
+ Log.e(Config.LOGTAG, "Failed to migrate Conversation CONTACTJID "
+ + cursor.getString(cursor.getColumnIndex(Conversation.CONTACTJID))
+ + ": " + ignored + ". Skipping...");
+ continue;
+ }
+
+ String updateArgs[] = {
+ newJid,
+ cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
+ };
+ db.execSQL("update " + Conversation.TABLENAME
+ + " set " + Conversation.CONTACTJID + " = ? "
+ + " where " + Conversation.UUID + " = ?", updateArgs);
+ }
+ cursor.close();
+
+ // Contact table
+ cursor = db.rawQuery("select * from " + Contact.TABLENAME, new String[0]);
+ while (cursor.moveToNext()) {
+ String newJid;
+ try {
+ newJid = Jid.fromString(
+ cursor.getString(cursor.getColumnIndex(Contact.JID))
+ ).toPreppedString();
+ } catch (InvalidJidException ignored) {
+ Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
+ + cursor.getString(cursor.getColumnIndex(Contact.JID))
+ + ": " + ignored + ". Skipping...");
+ continue;
+ }
+
+ String updateArgs[] = {
+ newJid,
+ cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
+ cursor.getString(cursor.getColumnIndex(Contact.JID)),
+ };
+ db.execSQL("update " + Contact.TABLENAME
+ + " set " + Contact.JID + " = ? "
+ + " where " + Contact.ACCOUNT + " = ? "
+ + " AND " + Contact.JID + " = ?", updateArgs);
+ }
+ cursor.close();
+
+ // Account table
+ cursor = db.rawQuery("select * from " + Account.TABLENAME, new String[0]);
+ while (cursor.moveToNext()) {
+ String newServer;
+ try {
+ newServer = Jid.fromParts(
+ cursor.getString(cursor.getColumnIndex(Account.USERNAME)),
+ cursor.getString(cursor.getColumnIndex(Account.SERVER)),
+ "mobile"
+ ).getDomainpart();
+ } catch (InvalidJidException ignored) {
+ Log.e(Config.LOGTAG, "Failed to migrate Account SERVER "
+ + cursor.getString(cursor.getColumnIndex(Account.SERVER))
+ + ": " + ignored + ". Skipping...");
+ continue;
+ }
+
+ String updateArgs[] = {
+ newServer,
+ cursor.getString(cursor.getColumnIndex(Account.UUID)),
+ };
+ db.execSQL("update " + Account.TABLENAME
+ + " set " + Account.SERVER + " = ? "
+ + " where " + Account.UUID + " = ?", updateArgs);
+ }
+ cursor.close();
+ }
+
+ public static synchronized DatabaseBackend getInstance(Context context) {
+ if (instance == null) {
+ instance = new DatabaseBackend(context);
+ }
+ return instance;
+ }
+
+ public void createConversation(Conversation conversation) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.insert(Conversation.TABLENAME, null, conversation.getContentValues());
+ }
+
+ public void createMessage(Message message) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.insert(Message.TABLENAME, null, message.getContentValues());
+ }
+
+ public void createAccount(Account account) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.insert(Account.TABLENAME, null, account.getContentValues());
+ }
+
+ public void insertDiscoveryResult(ServiceDiscoveryResult result) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.insert(ServiceDiscoveryResult.TABLENAME, null, result.getContentValues());
+ }
+
+ public ServiceDiscoveryResult findDiscoveryResult(final String hash, final String ver) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = {hash, ver};
+ Cursor cursor = db.query(ServiceDiscoveryResult.TABLENAME, null,
+ ServiceDiscoveryResult.HASH + "=? AND " + ServiceDiscoveryResult.VER + "=?",
+ selectionArgs, null, null, null);
+ if (cursor.getCount() == 0) {
+ cursor.close();
+ return null;
+ }
+ cursor.moveToFirst();
+
+ ServiceDiscoveryResult result = null;
+ try {
+ result = new ServiceDiscoveryResult(cursor);
+ } catch (JSONException e) { /* result is still null */ }
+
+ cursor.close();
+ return result;
+ }
+
+ public void insertPresenceTemplate(PresenceTemplate template) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.insert(PresenceTemplate.TABELNAME, null, template.getContentValues());
+ }
+
+ public List<PresenceTemplate> getPresenceTemplates() {
+ ArrayList<PresenceTemplate> templates = new ArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.query(PresenceTemplate.TABELNAME, null, null, null, null, null, PresenceTemplate.LAST_USED + " desc");
+ while (cursor.moveToNext()) {
+ templates.add(PresenceTemplate.fromCursor(cursor));
+ }
+ cursor.close();
+ return templates;
+ }
+
+ public void deletePresenceTemplate(PresenceTemplate template) {
+ Log.d(Config.LOGTAG, "deleting presence template with uuid " + template.getUuid());
+ SQLiteDatabase db = this.getWritableDatabase();
+ String where = PresenceTemplate.UUID + "=?";
+ String[] whereArgs = {template.getUuid()};
+ db.delete(PresenceTemplate.TABELNAME, where, whereArgs);
+ }
+
+ public CopyOnWriteArrayList<Conversation> getConversations(int status) {
+ CopyOnWriteArrayList<Conversation> list = new CopyOnWriteArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = {Integer.toString(status)};
+ Cursor cursor = db.rawQuery("select * from " + Conversation.TABLENAME
+ + " where " + Conversation.STATUS + " = ? order by "
+ + Conversation.CREATED + " desc", selectionArgs);
+ while (cursor.moveToNext()) {
+ list.add(Conversation.fromCursor(cursor));
+ }
+ cursor.close();
+ return list;
+ }
+
+ public ArrayList<Message> getMessages(Conversation conversations, int limit) {
+ return getMessages(conversations, limit, -1);
+ }
+
+ public ArrayList<Message> getMessages(Conversation conversation, int limit,
+ long timestamp) {
+ ArrayList<Message> list = new ArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor;
+ if (timestamp == -1) {
+ String[] selectionArgs = {conversation.getUuid()};
+ cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ + "=?", selectionArgs, null, null, Message.TIME_SENT
+ + " DESC", String.valueOf(limit));
+ } else {
+ String[] selectionArgs = {conversation.getUuid(),
+ Long.toString(timestamp)};
+ cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ + "=? and " + Message.TIME_SENT + "<?", selectionArgs,
+ null, null, Message.TIME_SENT + " DESC",
+ String.valueOf(limit));
+ }
+ if (cursor.getCount() > 0) {
+ cursor.moveToLast();
+ do {
+ Message message = Message.fromCursor(cursor);
+ message.setConversation(conversation);
+ list.add(message);
+ } while (cursor.moveToPrevious());
+ }
+ cursor.close();
+ return list;
+ }
+
+ public Iterable<Message> getMessagesIterable(final Conversation conversation) {
+ return new Iterable<Message>() {
+ @Override
+ public Iterator<Message> iterator() {
+ class MessageIterator implements Iterator<Message> {
+ SQLiteDatabase db = getReadableDatabase();
+ String[] selectionArgs = {conversation.getUuid()};
+ Cursor cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ + "=?", selectionArgs, null, null, Message.TIME_SENT
+ + " ASC", null);
+
+ public MessageIterator() {
+ cursor.moveToFirst();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !cursor.isAfterLast();
+ }
+
+ @Override
+ public Message next() {
+ Message message = Message.fromCursor(cursor);
+ cursor.moveToNext();
+ return message;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+ return new MessageIterator();
+ }
+ };
+ }
+
+ public Conversation findConversation(final Account account, final Jid contactJid) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = {account.getUuid(),
+ contactJid.toBareJid().toPreppedString() + "/%",
+ contactJid.toBareJid().toPreppedString()
+ };
+ Cursor cursor = db.query(Conversation.TABLENAME, null,
+ Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
+ + " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
+ if (cursor.getCount() == 0) {
+ cursor.close();
+ return null;
+ }
+ cursor.moveToFirst();
+ Conversation conversation = Conversation.fromCursor(cursor);
+ cursor.close();
+ return conversation;
+ }
+
+ public void updateConversation(final Conversation conversation) {
+ final SQLiteDatabase db = this.getWritableDatabase();
+ final String[] args = {conversation.getUuid()};
+ db.update(Conversation.TABLENAME, conversation.getContentValues(),
+ Conversation.UUID + "=?", args);
+ }
+
+ public List<Account> getAccounts() {
+ SQLiteDatabase db = this.getReadableDatabase();
+ return getAccounts(db);
+ }
+
+ private List<Account> getAccounts(SQLiteDatabase db) {
+ List<Account> list = new ArrayList<>();
+ Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
+ null, null);
+ while (cursor.moveToNext()) {
+ list.add(Account.fromCursor(cursor));
+ }
+ cursor.close();
+ return list;
+ }
+
+ public boolean updateAccount(Account account) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {account.getUuid()};
+ final int rows = db.update(Account.TABLENAME, account.getContentValues(), Account.UUID + "=?", args);
+ return rows == 1;
+ }
+
+ public boolean deleteAccount(Account account) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {account.getUuid()};
+ final int rows = db.delete(Account.TABLENAME, Account.UUID + "=?", args);
+ return rows == 1;
+ }
+
+ public boolean hasEnabledAccounts() {
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("select count(" + Account.UUID + ") from "
+ + Account.TABLENAME + " where not options & (1 <<1)", null);
+ try {
+ cursor.moveToFirst();
+ int count = cursor.getInt(0);
+ return (count > 0);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ return true; // better safe than sorry
+ } catch (RuntimeException e) {
+ return true; // better safe than sorry
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ @Override
+ public SQLiteDatabase getWritableDatabase() {
+ SQLiteDatabase db = super.getWritableDatabase();
+ db.execSQL("PRAGMA foreign_keys=ON;");
+ return db;
+ }
+
+ public void updateMessage(Message message) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {message.getUuid()};
+ db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
+ + "=?", args);
+ }
+
+ public void updateMessage(Message message, String uuid) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {uuid};
+ db.update(Message.TABLENAME, message.getContentValues(), Message.UUID
+ + "=?", args);
+ }
+
+ public void readRoster(Roster roster) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor;
+ String args[] = {roster.getAccount().getUuid()};
+ cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
+ while (cursor.moveToNext()) {
+ roster.initContact(Contact.fromCursor(cursor));
+ }
+ cursor.close();
+ }
+
+ public void writeRoster(final Roster roster) {
+ final Account account = roster.getAccount();
+ final SQLiteDatabase db = this.getWritableDatabase();
+ db.beginTransaction();
+ for (Contact contact : roster.getContacts()) {
+ if (contact.getOption(Contact.Options.IN_ROSTER)) {
+ db.insert(Contact.TABLENAME, null, contact.getContentValues());
+ } else {
+ String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";
+ String[] whereArgs = {account.getUuid(), contact.getJid().toPreppedString()};
+ db.delete(Contact.TABLENAME, where, whereArgs);
+ }
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ account.setRosterVersion(roster.getVersion());
+ updateAccount(account);
+ }
+
+ public void deleteMessagesInConversation(Conversation conversation) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {conversation.getUuid()};
+ db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
+ }
+
+ public Pair<Long, String> getLastMessageReceived(Account account) {
+ Cursor cursor = null;
+ try {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
+ String[] args = {account.getUuid()};
+ cursor = db.rawQuery(sql, args);
+ if (cursor.getCount() == 0) {
+ return null;
+ } else {
+ cursor.moveToFirst();
+ return new Pair<>(cursor.getLong(0), cursor.getString(1));
+ }
+ } catch (Exception e) {
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ public Pair<Long, String> getLastClearDate(Account account) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] columns = {Conversation.ATTRIBUTES};
+ String selection = Conversation.ACCOUNT + "=?";
+ String[] args = {account.getUuid()};
+ Cursor cursor = db.query(Conversation.TABLENAME, columns, selection, args, null, null, null);
+ long maxClearDate = 0;
+ while (cursor.moveToNext()) {
+ try {
+ final JSONObject jsonObject = new JSONObject(cursor.getString(0));
+ maxClearDate = Math.max(maxClearDate, jsonObject.getLong(Conversation.ATTRIBUTE_LAST_CLEAR_HISTORY));
+ } catch (Exception e) {
+ //ignored
+ }
+ }
+ cursor.close();
+ return new Pair<>(maxClearDate, null);
+ }
+
+ private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
+ final SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = {account.getUuid(),
+ contact.getName(),
+ Integer.toString(contact.getDeviceId())};
+ return db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
+ null,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.NAME + " = ? AND "
+ + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
+ selectionArgs,
+ null, null, null);
+ }
+
+ public SessionRecord loadSession(Account account, AxolotlAddress contact) {
+ SessionRecord session = null;
+ Cursor cursor = getCursorForSession(account, contact);
+ if (cursor.getCount() != 0) {
+ cursor.moveToFirst();
+ try {
+ session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
+ } catch (IOException e) {
+ cursor.close();
+ throw new AssertionError(e);
+ }
+ }
+ cursor.close();
+ return session;
+ }
+
+ public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
+ final SQLiteDatabase db = this.getReadableDatabase();
+ return getSubDeviceSessions(db, account, contact);
+ }
+
+ private List<Integer> getSubDeviceSessions(SQLiteDatabase db, Account account, AxolotlAddress contact) {
+ List<Integer> devices = new ArrayList<>();
+ String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
+ String[] selectionArgs = {account.getUuid(),
+ contact.getName()};
+ Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
+ columns,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.NAME + " = ?",
+ selectionArgs,
+ null, null, null);
+
+ while (cursor.moveToNext()) {
+ devices.add(cursor.getInt(
+ cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
+ }
+
+ cursor.close();
+ return devices;
+ }
+
+ public boolean containsSession(Account account, AxolotlAddress contact) {
+ Cursor cursor = getCursorForSession(account, contact);
+ int count = cursor.getCount();
+ cursor.close();
+ return count != 0;
+ }
+
+ public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.NAME, contact.getName());
+ values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
+ values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(), Base64.DEFAULT));
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
+ }
+
+ public void deleteSession(Account account, AxolotlAddress contact) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ deleteSession(db, account, contact);
+ }
+
+ private void deleteSession(SQLiteDatabase db, Account account, AxolotlAddress contact) {
+ String[] args = {account.getUuid(),
+ contact.getName(),
+ Integer.toString(contact.getDeviceId())};
+ db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.NAME + " = ? AND "
+ + SQLiteAxolotlStore.DEVICE_ID + " = ? ",
+ args);
+ }
+
+ public void deleteAllSessions(Account account, AxolotlAddress contact) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {account.getUuid(), contact.getName()};
+ db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.NAME + " = ?",
+ args);
+ }
+
+ private Cursor getCursorForPreKey(Account account, int preKeyId) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] columns = {SQLiteAxolotlStore.KEY};
+ String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
+ Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
+ columns,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.ID + "=?",
+ selectionArgs,
+ null, null, null);
+
+ return cursor;
+ }
+
+ public PreKeyRecord loadPreKey(Account account, int preKeyId) {
+ PreKeyRecord record = null;
+ Cursor cursor = getCursorForPreKey(account, preKeyId);
+ if (cursor.getCount() != 0) {
+ cursor.moveToFirst();
+ try {
+ record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+ cursor.close();
+ return record;
+ }
+
+ public boolean containsPreKey(Account account, int preKeyId) {
+ Cursor cursor = getCursorForPreKey(account, preKeyId);
+ int count = cursor.getCount();
+ cursor.close();
+ return count != 0;
+ }
+
+ public void storePreKey(Account account, PreKeyRecord record) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.ID, record.getId());
+ values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
+ }
+
+ public void deletePreKey(Account account, int preKeyId) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {account.getUuid(), Integer.toString(preKeyId)};
+ db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.ID + "=?",
+ args);
+ }
+
+ private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] columns = {SQLiteAxolotlStore.KEY};
+ String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
+ Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ columns,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
+ selectionArgs,
+ null, null, null);
+
+ return cursor;
+ }
+
+ public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
+ SignedPreKeyRecord record = null;
+ Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
+ if (cursor.getCount() != 0) {
+ cursor.moveToFirst();
+ try {
+ record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+ cursor.close();
+ return record;
+ }
+
+ public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
+ List<SignedPreKeyRecord> prekeys = new ArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] columns = {SQLiteAxolotlStore.KEY};
+ String[] selectionArgs = {account.getUuid()};
+ Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ columns,
+ SQLiteAxolotlStore.ACCOUNT + "=?",
+ selectionArgs,
+ null, null, null);
+
+ while (cursor.moveToNext()) {
+ try {
+ prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
+ } catch (IOException ignored) {
+ }
+ }
+ cursor.close();
+ return prekeys;
+ }
+
+ public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
+ Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
+ int count = cursor.getCount();
+ cursor.close();
+ return count != 0;
+ }
+
+ public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.ID, record.getId());
+ values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(), Base64.DEFAULT));
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
+ }
+
+ public void deleteSignedPreKey(Account account, int signedPreKeyId) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
+ db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + "=? AND "
+ + SQLiteAxolotlStore.ID + "=?",
+ args);
+ }
+
+ private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
+ final SQLiteDatabase db = this.getReadableDatabase();
+ return getIdentityKeyCursor(db, account, name, own);
+ }
+
+ private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, boolean own) {
+ return getIdentityKeyCursor(db, account, name, own, null);
+ }
+
+ private Cursor getIdentityKeyCursor(Account account, String fingerprint) {
+ final SQLiteDatabase db = this.getReadableDatabase();
+ return getIdentityKeyCursor(db, account, fingerprint);
+ }
+
+ private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String fingerprint) {
+ return getIdentityKeyCursor(db, account, null, null, fingerprint);
+ }
+
+ private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
+ String[] columns = {SQLiteAxolotlStore.TRUST,
+ SQLiteAxolotlStore.ACTIVE,
+ SQLiteAxolotlStore.KEY};
+ ArrayList<String> selectionArgs = new ArrayList<>(4);
+ selectionArgs.add(account.getUuid());
+ String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
+ if (name != null) {
+ selectionArgs.add(name);
+ selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
+ }
+ if (fingerprint != null) {
+ selectionArgs.add(fingerprint);
+ selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
+ }
+ if (own != null) {
+ selectionArgs.add(own ? "1" : "0");
+ selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
+ }
+ Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+ columns,
+ selectionString,
+ selectionArgs.toArray(new String[selectionArgs.size()]),
+ null, null, null);
+
+ return cursor;
+ }
+
+ public IdentityKeyPair loadOwnIdentityKeyPair(Account account) {
+ SQLiteDatabase db = getReadableDatabase();
+ return loadOwnIdentityKeyPair(db, account);
+ }
+
+ private IdentityKeyPair loadOwnIdentityKeyPair(SQLiteDatabase db, Account account) {
+ String name = account.getJid().toBareJid().toPreppedString();
+ IdentityKeyPair identityKeyPair = null;
+ Cursor cursor = getIdentityKeyCursor(db, account, name, true);
+ if (cursor.getCount() != 0) {
+ cursor.moveToFirst();
+ try {
+ identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT));
+ } catch (InvalidKeyException e) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
+ }
+ }
+ cursor.close();
+
+ return identityKeyPair;
+ }
+
+ public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
+ return loadIdentityKeys(account, name, null);
+ }
+
+ public Set<IdentityKey> loadIdentityKeys(Account account, String name, FingerprintStatus status) {
+ Set<IdentityKey> identityKeys = new HashSet<>();
+ Cursor cursor = getIdentityKeyCursor(account, name, false);
+
+ while (cursor.moveToNext()) {
+ if (status != null && !FingerprintStatus.fromCursor(cursor).equals(status)) {
+ continue;
+ }
+ try {
+ String key = cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY));
+ if (key != null) {
+ identityKeys.add(new IdentityKey(Base64.decode(key, Base64.DEFAULT), 0));
+ } else {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Missing key (possibly preverified) in database for account" + account.getJid().toBareJid() + ", address: " + name);
+ }
+ } catch (InvalidKeyException e) {
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
+ }
+ }
+ cursor.close();
+
+ return identityKeys;
+ }
+
+ public long numTrustedKeys(Account account, String name) {
+ SQLiteDatabase db = getReadableDatabase();
+ String[] args = {
+ account.getUuid(),
+ name,
+ FingerprintStatus.Trust.TRUSTED.toString(),
+ FingerprintStatus.Trust.VERIFIED.toString(),
+ FingerprintStatus.Trust.VERIFIED_X509.toString()
+ };
+ return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?"
+ + " AND " + SQLiteAxolotlStore.NAME + " = ?"
+ + " AND (" + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ? OR " + SQLiteAxolotlStore.TRUST + " = ?)"
+ + " AND " + SQLiteAxolotlStore.ACTIVE + " > 0",
+ args
+ );
+ }
+
+ private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, FingerprintStatus status) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ values.put(SQLiteAxolotlStore.NAME, name);
+ values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
+ values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
+ values.put(SQLiteAxolotlStore.KEY, base64Serialized);
+ values.putAll(status.toContentValues());
+ String where = SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.NAME + "=? AND " + SQLiteAxolotlStore.FINGERPRINT + " =?";
+ String[] whereArgs = {account.getUuid(), name, fingerprint};
+ int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, where, whereArgs);
+ if (rows == 0) {
+ db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
+ }
+ }
+
+ public void storePreVerification(Account account, String name, String fingerprint, FingerprintStatus status) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
+ values.put(SQLiteAxolotlStore.NAME, name);
+ values.put(SQLiteAxolotlStore.OWN, 0);
+ values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
+ values.putAll(status.toContentValues());
+ db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
+ }
+
+ public FingerprintStatus getFingerprintStatus(Account account, String fingerprint) {
+ Cursor cursor = getIdentityKeyCursor(account, fingerprint);
+ final FingerprintStatus status;
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ status = FingerprintStatus.fromCursor(cursor);
+ } else {
+ status = null;
+ }
+ cursor.close();
+ return status;
+ }
+
+ public boolean setIdentityKeyTrust(Account account, String fingerprint, FingerprintStatus fingerprintStatus) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ return setIdentityKeyTrust(db, account, fingerprint, fingerprintStatus);
+ }
+
+ private boolean setIdentityKeyTrust(SQLiteDatabase db, Account account, String fingerprint, FingerprintStatus status) {
+ String[] selectionArgs = {
+ account.getUuid(),
+ fingerprint
+ };
+ int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, status.toContentValues(),
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+ selectionArgs);
+ return rows == 1;
+ }
+
+ public boolean setIdentityKeyCertificate(Account account, String fingerprint, X509Certificate x509Certificate) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] selectionArgs = {
+ account.getUuid(),
+ fingerprint
+ };
+ try {
+ ContentValues values = new ContentValues();
+ values.put(SQLiteAxolotlStore.CERTIFICATE, x509Certificate.getEncoded());
+ return db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
+ SQLiteAxolotlStore.ACCOUNT + " = ? AND "
+ + SQLiteAxolotlStore.FINGERPRINT + " = ? ",
+ selectionArgs) == 1;
+ } catch (CertificateEncodingException e) {
+ Log.d(Config.LOGTAG, "could not encode certificate");
+ return false;
+ }
+ }
+
+ public X509Certificate getIdentityKeyCertifcate(Account account, String fingerprint) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = {
+ account.getUuid(),
+ fingerprint
+ };
+ String[] colums = {SQLiteAxolotlStore.CERTIFICATE};
+ String selection = SQLiteAxolotlStore.ACCOUNT + " = ? AND " + SQLiteAxolotlStore.FINGERPRINT + " = ? ";
+ Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, colums, selection, selectionArgs, null, null, null);
+ if (cursor.getCount() < 1) {
+ return null;
+ } else {
+ cursor.moveToFirst();
+ byte[] certificate = cursor.getBlob(cursor.getColumnIndex(SQLiteAxolotlStore.CERTIFICATE));
+ cursor.close();
+ if (certificate == null || certificate.length == 0) {
+ return null;
+ }
+ try {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate));
+ } catch (CertificateException e) {
+ Log.d(Config.LOGTAG, "certificate exception " + e.getMessage());
+ return null;
+ }
+ }
+ }
+
+ public void storeIdentityKey(Account account, String name, IdentityKey identityKey, FingerprintStatus status) {
+ storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT), status);
+ }
+
+ public void storeOwnIdentityKeyPair(Account account, IdentityKeyPair identityKeyPair) {
+ storeIdentityKey(account, account.getJid().toBareJid().toPreppedString(), true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), FingerprintStatus.createActiveVerified(false));
+ }
+
+ public void recreateAxolotlDb(SQLiteDatabase db) {
+ Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + ">>> (RE)CREATING AXOLOTL DATABASE <<<");
+ db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
+ db.execSQL(CREATE_SESSIONS_STATEMENT);
+ db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
+ db.execSQL(CREATE_PREKEYS_STATEMENT);
+ db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
+ db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
+ db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
+ db.execSQL(CREATE_IDENTITIES_STATEMENT);
+ }
+
+ public void wipeAxolotlDb(Account account) {
+ String accountName = account.getUuid();
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] deleteArgs = {
+ accountName
+ };
+ db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
+ deleteArgs);
+ db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
+ deleteArgs);
+ db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
+ deleteArgs);
+ db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
+ SQLiteAxolotlStore.ACCOUNT + " = ?",
+ deleteArgs);
+ }
+
+ public boolean startTimeCountExceedsThreshold() {
+ SQLiteDatabase db = this.getWritableDatabase();
+ long cleanBeforeTimestamp = System.currentTimeMillis() - Config.FREQUENT_RESTARTS_DETECTION_WINDOW;
+ db.execSQL("delete from " + START_TIMES_TABLE + " where timestamp < " + cleanBeforeTimestamp);
+ ContentValues values = new ContentValues();
+ values.put("timestamp", System.currentTimeMillis());
+ db.insert(START_TIMES_TABLE, null, values);
+ String[] columns = new String[]{"count(timestamp)"};
+ Cursor cursor = db.query(START_TIMES_TABLE, columns, null, null, null, null, null);
+ int count;
+ if (cursor.moveToFirst()) {
+ count = cursor.getInt(0);
+ } else {
+ count = 0;
+ }
+ cursor.close();
+ Log.d(Config.LOGTAG, "start time counter reached " + count);
+ return count >= Config.FREQUENT_RESTARTS_THRESHOLD;
+ }
+
+ public void clearStartTimeCounter() {
+ Log.d(Config.LOGTAG, "resetting start time counter");
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.execSQL("delete from " + START_TIMES_TABLE);
+ }
}
diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java
index 9babae1de..893834ad7 100644
--- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java
+++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java
@@ -58,918 +58,918 @@ import de.pixart.messenger.utils.FileWriterException;
import de.pixart.messenger.xmpp.pep.Avatar;
public class FileBackend {
- private static final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
-
- public static final String CONVERSATIONS_FILE_PROVIDER = "de.pixart.messenger.files";
-
- private XmppConnectionService mXmppConnectionService;
-
- public FileBackend(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- private void createNoMedia() {
- final File nomedia_files = new File(getConversationsFileDirectory()+".nomedia");
- final File nomedia_audios = new File(getConversationsAudioDirectory()+".nomedia");
- if (!nomedia_files.exists()) {
- try {
- nomedia_files.createNewFile();
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "could not create nomedia file for files directory");
- }
- }
- if (!nomedia_audios.exists()) {
- try {
- nomedia_audios.createNewFile();
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "could not create nomedia file for audio directory");
- }
- }
- }
-
- public void updateMediaScanner(File file) {
- if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())
- || file.getAbsolutePath().startsWith(getConversationsVideoDirectory())) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- intent.setData(Uri.fromFile(file));
- mXmppConnectionService.sendBroadcast(intent);
- } else {
- createNoMedia();
- }
- }
-
- public boolean deleteFile(Message message) {
- File file = getFile(message);
- if (file.delete()) {
- updateMediaScanner(file);
- return true;
- } else {
- return false;
- }
- }
-
- public DownloadableFile getFile(Message message) {
- return getFile(message, true);
- }
-
- public DownloadableFile getFile(Message message, boolean decrypted) {
- final boolean encrypted = !decrypted
- && (message.getEncryption() == Message.ENCRYPTION_PGP
- || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
- final DownloadableFile file;
- String path = message.getRelativeFilePath();
- if (path == null) {
- String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4);
- path = filename;
- }
- if (path.startsWith("/")) {
- file = new DownloadableFile(path);
- } else {
- String mime = message.getMimeType();
- if (mime != null && mime.startsWith("image")) {
- file = new DownloadableFile(getConversationsImageDirectory() + path);
- } else if (mime != null && mime.startsWith("video")) {
- file = new DownloadableFile(getConversationsVideoDirectory() + path);
- } else if (mime != null && mime.startsWith("audio")) {
- file = new DownloadableFile(getConversationsAudioDirectory() + path);
- } else {
- file = new DownloadableFile(getConversationsFileDirectory() + path);
- }
- }
- if (encrypted) {
- return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp");
- } else {
- return file;
- }
- }
-
- private static long getFileSize(Context context, Uri uri) {
- Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
- if (cursor != null && cursor.moveToFirst()) {
- return cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE));
- } else {
- return -1;
- }
- }
-
- public static boolean allFilesUnderSize(Context context, List<Uri> uris, long max) {
- if (max <= 0) {
- Log.d(Config.LOGTAG,"server did not report max file size for http upload");
- return true; //exception to be compatible with HTTP Upload < v0.2
- }
- for(Uri uri : uris) {
- if (FileBackend.getFileSize(context, uri) > max) {
- Log.d(Config.LOGTAG,"not all files are under "+max+" bytes. suggesting falling back to jingle");
- return false;
- }
- }
- return true;
- }
-
- public static String getConversationsFileDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/files/";
- }
-
- public static String getConversationsImageDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/images/";
- }
-
- public static String getConversationsVideoDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/videos/";
- }
-
- public static String getConversationsAudioDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/audios/";
- }
-
- public static String getConversationsDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Pix-Art Messenger/";
- }
-
- public Bitmap resize(Bitmap originalBitmap, int size) {
- int w = originalBitmap.getWidth();
- int h = originalBitmap.getHeight();
- if (Math.max(w, h) > size) {
- int scalledW;
- int scalledH;
- if (w <= h) {
- scalledW = (int) (w / ((double) h / size));
- scalledH = size;
- } else {
- scalledW = size;
- scalledH = (int) (h / ((double) w / size));
- }
- Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
- if (originalBitmap != null && !originalBitmap.isRecycled()) {
- originalBitmap.recycle();
- }
- return result;
- } else {
- return originalBitmap;
- }
- }
-
- public static Bitmap rotate(Bitmap bitmap, int degree) {
- if (degree == 0) {
- return bitmap;
- }
- int w = bitmap.getWidth();
- int h = bitmap.getHeight();
- Matrix mtx = new Matrix();
- mtx.postRotate(degree);
- Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
- if (bitmap != null && !bitmap.isRecycled()) {
- bitmap.recycle();
- }
- return result;
- }
-
- public boolean useImageAsIs(Uri uri) {
- String path = getOriginalPath(uri);
- if (path == null) {
- return false;
- }
- File file = new File(path);
- long size = file.length();
- if (size == 0 || size >= Config.IMAGE_MAX_SIZE ) {
- return false;
- }
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- try {
- BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options);
- if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) {
- return false;
- }
- return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
- } catch (FileNotFoundException e) {
- return false;
- }
- }
-
- public String getOriginalPath(Uri uri) {
- return FileUtils.getPath(mXmppConnectionService,uri);
- }
-
- public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
- Log.d(Config.LOGTAG,"copy file ("+uri.toString()+") to private storage "+file.getAbsolutePath());
- file.getParentFile().mkdirs();
- OutputStream os = null;
- InputStream is = null;
- try {
- file.createNewFile();
- os = new FileOutputStream(file);
- is = mXmppConnectionService.getContentResolver().openInputStream(uri);
- byte[] buffer = new byte[1024];
- int length;
- while ((length = is.read(buffer)) > 0) {
- try {
- os.write(buffer, 0, length);
- } catch (IOException e) {
- throw new FileWriterException();
- }
- }
- try {
- os.flush();
- } catch (IOException e) {
- throw new FileWriterException();
- }
- } catch(FileNotFoundException e) {
- throw new FileCopyException(R.string.error_file_not_found);
- } catch(FileWriterException e) {
- throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
- } catch (IOException e) {
- e.printStackTrace();
- throw new FileCopyException(R.string.error_io_exception);
- } finally {
- close(os);
- close(is);
- }
- }
-
- public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
- String mime = mXmppConnectionService.getContentResolver().getType(uri);
- Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime="+mime+")");
- String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
- if (extension == null) {
- extension = getExtensionFromUri(uri);
- }
- String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4);
- message.setRelativeFilePath(filename + "." + extension);
- copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
- }
-
- private String getExtensionFromUri(Uri uri) {
- String[] projection = {MediaStore.MediaColumns.DATA};
- String filename = null;
- Cursor cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- filename = cursor.getString(0);
- }
- } catch (Exception e) {
- filename = null;
- } finally {
- cursor.close();
- }
- }
- int pos = filename == null ? -1 : filename.lastIndexOf('.');
- return pos > 0 ? filename.substring(pos+1) : null;
- }
-
- private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException {
- file.getParentFile().mkdirs();
- InputStream is = null;
- OutputStream os = null;
- try {
- if (!file.exists() && !file.createNewFile()) {
- throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
- }
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- throw new FileCopyException(R.string.error_not_an_image_file);
- }
- Bitmap originalBitmap;
- BitmapFactory.Options options = new BitmapFactory.Options();
- int inSampleSize = (int) Math.pow(2, sampleSize);
- Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
- options.inSampleSize = inSampleSize;
- originalBitmap = BitmapFactory.decodeStream(is, null, options);
- is.close();
- if (originalBitmap == null) {
- throw new FileCopyException(R.string.error_not_an_image_file);
- }
- Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE);
- int rotation = getRotation(image);
- scaledBitmap = rotate(scaledBitmap, rotation);
- boolean targetSizeReached = false;
- int quality = Config.IMAGE_QUALITY;
- while(!targetSizeReached) {
- os = new FileOutputStream(file);
- boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os);
- if (!success) {
- throw new FileCopyException(R.string.error_compressing_image);
- }
- os.flush();
- targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50;
- quality -= 5;
- }
- scaledBitmap.recycle();
- } catch (FileNotFoundException e) {
- throw new FileCopyException(R.string.error_file_not_found);
- } catch (IOException e) {
- e.printStackTrace();
- throw new FileCopyException(R.string.error_io_exception);
- } catch (SecurityException e) {
- throw new FileCopyException(R.string.error_security_exception_during_image_copy);
- } catch (OutOfMemoryError e) {
- ++sampleSize;
- if (sampleSize <= 3) {
- copyImageToPrivateStorage(file, image, sampleSize);
- } else {
- throw new FileCopyException(R.string.error_out_of_memory);
- }
- } finally {
- close(os);
- close(is);
- }
- }
-
- public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException {
- Log.d(Config.LOGTAG,"copy image ("+image.toString()+") to private storage "+file.getAbsolutePath());
- copyImageToPrivateStorage(file, image, 0);
- }
-
- public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException {
- String filename = fileDateFormat.format(new Date(message.getTimeSent()))+"_"+message.getUuid().substring(0,4);
- switch(Config.IMAGE_FORMAT) {
- case JPEG:
- message.setRelativeFilePath(filename+".jpg");
- break;
- case PNG:
- message.setRelativeFilePath(filename+".png");
- break;
- case WEBP:
- message.setRelativeFilePath(filename+".webp");
- break;
- }
- copyImageToPrivateStorage(getFile(message), image);
- updateFileParams(message);
- }
-
- private int getRotation(File file) {
- return getRotation(Uri.parse("file://"+file.getAbsolutePath()));
- }
-
- private int getRotation(Uri image) {
- InputStream is = null;
- try {
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- return ExifHelper.getOrientation(is);
- } catch (FileNotFoundException e) {
- return 0;
- } finally {
- close(is);
- }
- }
-
- public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException {
- final String uuid = message.getUuid();
- final LruCache<String,Bitmap> cache = mXmppConnectionService.getBitmapCache();
- Bitmap thumbnail = cache.get(uuid);
- if ((thumbnail == null) && (!cacheOnly)) {
- synchronized (cache) {
- thumbnail = cache.get(uuid);
- if (thumbnail != null) {
- return thumbnail;
- }
- DownloadableFile file = getFile(message);
- if (file.getMimeType().startsWith("video/")) {
- thumbnail = getVideoPreview(file, size);
- } else {
- Bitmap fullsize = getFullsizeImagePreview(file, size);
- if (fullsize == null) {
- throw new FileNotFoundException();
- }
- thumbnail = resize(fullsize, size);
- thumbnail = rotate(thumbnail, getRotation(file));
- }
- this.mXmppConnectionService.getBitmapCache().put(uuid, thumbnail);
- }
- }
- return thumbnail;
- }
-
- private Bitmap getFullsizeImagePreview(File file, int size) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(file, size);
- try {
- return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- } catch (OutOfMemoryError e) {
- options.inSampleSize *= 2;
- return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- }
- }
-
- private Bitmap getVideoPreview(File file, int size) {
- MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
- Bitmap frame;
- try {
- metadataRetriever.setDataSource(file.getAbsolutePath());
- frame = metadataRetriever.getFrameAtTime(0);
- metadataRetriever.release();
- frame = resize(frame, size);
- } catch(IllegalArgumentException | NullPointerException e) {
- frame = Bitmap.createBitmap(size,size, Bitmap.Config.ARGB_8888);
- frame.eraseColor(0xff000000);
- }
- Canvas canvas = new Canvas(frame);
- Bitmap play = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), R.drawable.play_video);
- float x = (frame.getWidth() - play.getWidth()) / 2.0f;
- float y = (frame.getHeight() - play.getHeight()) / 2.0f;
- canvas.drawBitmap(play,x,y,null);
- return frame;
- }
-
- private static String getTakePhotoPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)+"/Camera/";
- }
-
- public Uri getTakePhotoUri() {
- File file = new File(getTakePhotoPath()+"IMG_" + fileDateFormat.format(new Date()) + ".jpg");
- file.getParentFile().mkdirs();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file);
- } else {
- return Uri.fromFile(file);
- }
- }
-
- public static Uri getIndexableTakePhotoUri(Uri original) {
- if ("file".equals(original.getScheme())) {
- return original;
- } else {
- List<String> segments = original.getPathSegments();
- return Uri.parse("file://"+getTakePhotoPath()+segments.get(segments.size() - 1));
- }
- }
-
- public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
- try {
- Avatar avatar = new Avatar();
- Bitmap bm = cropCenterSquare(image, size);
- if (bm == null) {
- return null;
- }
- ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
- Base64OutputStream mBase64OutputStream = new Base64OutputStream(
- mByteArrayOutputStream, Base64.DEFAULT);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(
- mBase64OutputStream, digest);
- if (!bm.compress(format, 75, mDigestOutputStream)) {
- return null;
- }
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
- avatar.image = new String(mByteArrayOutputStream.toByteArray());
- return avatar;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } catch (IOException e) {
- return null;
- }
- }
-
- public Avatar getStoredPepAvatar(String hash) {
- if (hash == null) {
- return null;
- }
- Avatar avatar = new Avatar();
- File file = new File(getAvatarPath(hash));
- FileInputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- is = new FileInputStream(file);
- ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
- Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest);
- byte[] buffer = new byte[4096];
- int length;
- while ((length = is.read(buffer)) > 0) {
- os.write(buffer, 0, length);
- }
- os.flush();
- os.close();
- avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
- avatar.image = new String(mByteArrayOutputStream.toByteArray());
- avatar.height = options.outHeight;
- avatar.width = options.outWidth;
- return avatar;
- } catch (IOException e) {
- return null;
- } catch (NoSuchAlgorithmException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public boolean isAvatarCached(Avatar avatar) {
- File file = new File(getAvatarPath(avatar.getFilename()));
- return file.exists();
- }
-
- public boolean save(Avatar avatar) {
- File file;
- if (isAvatarCached(avatar)) {
- file = new File(getAvatarPath(avatar.getFilename()));
- } else {
- String filename = getAvatarPath(avatar.getFilename());
- file = new File(filename + ".tmp");
- file.getParentFile().mkdirs();
- OutputStream os = null;
- try {
- file.createNewFile();
- os = new FileOutputStream(file);
- MessageDigest digest = MessageDigest.getInstance("SHA-1");
- digest.reset();
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
- mDigestOutputStream.write(avatar.getImageAsBytes());
- mDigestOutputStream.flush();
- mDigestOutputStream.close();
- String sha1sum = CryptoHelper.bytesToHex(digest.digest());
- if (sha1sum.equals(avatar.sha1sum)) {
- file.renameTo(new File(filename));
- } else {
- Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
- file.delete();
- return false;
- }
- } catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) {
- return false;
- } finally {
- close(os);
- }
- }
- avatar.size = file.length();
- return true;
- }
-
- public String getAvatarPath(String avatar) {
- return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
- }
-
- public Uri getAvatarUri(String avatar) {
- return Uri.parse("file:" + getAvatarPath(avatar));
- }
-
- public Bitmap cropCenterSquare(Uri image, int size) {
- if (image == null) {
- return null;
- }
- InputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(image, size);
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- return null;
- }
- Bitmap input = BitmapFactory.decodeStream(is, null, options);
- if (input == null) {
- return null;
- } else {
- input = rotate(input, getRotation(image));
- return cropCenterSquare(input, size);
- }
- } catch (SecurityException e) {
- return null; // happens for example on Android 6.0 if contacts permissions get revoked
- } catch (FileNotFoundException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
- if (image == null) {
- return null;
- }
- InputStream is = null;
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = calcSampleSize(image, Math.max(newHeight, newWidth));
- is = mXmppConnectionService.getContentResolver().openInputStream(image);
- if (is == null) {
- return null;
- }
- Bitmap source = BitmapFactory.decodeStream(is, null, options);
- if (source == null) {
- return null;
- }
- int sourceWidth = source.getWidth();
- int sourceHeight = source.getHeight();
- float xScale = (float) newWidth / sourceWidth;
- float yScale = (float) newHeight / sourceHeight;
- float scale = Math.max(xScale, yScale);
- float scaledWidth = scale * sourceWidth;
- float scaledHeight = scale * sourceHeight;
- float left = (newWidth - scaledWidth) / 2;
- float top = (newHeight - scaledHeight) / 2;
-
- RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
- Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(dest);
- canvas.drawBitmap(source, null, targetRect, null);
- if (source != null && !source.isRecycled()) {
- source.recycle();
- }
- return dest;
- } catch (SecurityException e) {
- return null; //android 6.0 with revoked permissions for example
- } catch (FileNotFoundException e) {
- return null;
- } finally {
- close(is);
- }
- }
-
- public Bitmap cropCenterSquare(Bitmap input, int size) {
- int w = input.getWidth();
- int h = input.getHeight();
-
- float scale = Math.max((float) size / h, (float) size / w);
-
- float outWidth = scale * w;
- float outHeight = scale * h;
- float left = (size - outWidth) / 2;
- float top = (size - outHeight) / 2;
- RectF target = new RectF(left, top, left + outWidth, top + outHeight);
-
- Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- canvas.drawBitmap(input, null, target, null);
- if (input != null && !input.isRecycled()) {
- input.recycle();
- }
- return output;
- }
-
- private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options);
- return calcSampleSize(options, size);
- }
-
- private static int calcSampleSize(File image, int size) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(image.getAbsolutePath(), options);
- return calcSampleSize(options, size);
- }
-
- public static int calcSampleSize(BitmapFactory.Options options, int size) {
- int height = options.outHeight;
- int width = options.outWidth;
- int inSampleSize = 1;
-
- if (height > size || width > size) {
- int halfHeight = height / 2;
- int halfWidth = width / 2;
-
- while ((halfHeight / inSampleSize) > size
- && (halfWidth / inSampleSize) > size) {
- inSampleSize *= 2;
- }
- }
- return inSampleSize;
- }
-
- public Uri getJingleFileUri(Message message) {
- File file = getFile(message);
- return Uri.parse("file://" + file.getAbsolutePath());
- }
-
- public void updateFileParams(Message message) {
- updateFileParams(message,null);
- }
-
- public void updateFileParams(Message message, URL url) {
- DownloadableFile file = getFile(message);
- final String mime = file.getMimeType();
- boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/"));
- boolean video = mime != null && mime.startsWith("video/");
- if (image || video) {
- try {
- Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file);
- if (url == null) {
- message.setBody(Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height);
- } else {
- message.setBody(url.toString() + "|" + Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height);
- }
- return;
- } catch (NotAVideoFile notAVideoFile) {
- Log.d(Config.LOGTAG,"file with mime type "+file.getMimeType()+" was not a video file");
- //fall threw
- }
- }
- if (url != null) {
- message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
- } else {
- message.setBody(Long.toString(file.getSize()));
- }
-
- }
-
- private Dimensions getImageDimensions(File file) {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(file.getAbsolutePath(), options);
- int rotation = getRotation(file);
- boolean rotated = rotation == 90 || rotation == 270;
- int imageHeight = rotated ? options.outWidth : options.outHeight;
- int imageWidth = rotated ? options.outHeight : options.outWidth;
- return new Dimensions(imageHeight, imageWidth);
- }
-
- private Dimensions getVideoDimensions(File file) throws NotAVideoFile {
- MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
- try {
- metadataRetriever.setDataSource(file.getAbsolutePath());
- } catch (Exception e) {
- throw new NotAVideoFile();
- }
- String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
- if (hasVideo == null) {
- throw new NotAVideoFile();
- }
- int rotation = extractRotationFromMediaRetriever(metadataRetriever);
- boolean rotated = rotation == 90 || rotation == 270;
- int height;
- try {
- String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
- height = Integer.parseInt(h);
- } catch (Exception e) {
- height = -1;
- }
- int width;
- try {
- String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
- width = Integer.parseInt(w);
- } catch (Exception e) {
- width = -1;
- }
- metadataRetriever.release();
- Log.d(Config.LOGTAG,"extracted video dims "+width+"x"+height);
- return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
- }
-
- private int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
- int rotation;
- if (Build.VERSION.SDK_INT >= 17) {
- String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
- try {
- rotation = Integer.parseInt(r);
- } catch (Exception e) {
- rotation = 0;
- }
- } else {
- rotation = 0;
- }
- return rotation;
- }
-
- private class Dimensions {
- public final int width;
- public final int height;
-
- public Dimensions(int height, int width) {
- this.width = width;
- this.height = height;
- }
- }
-
- private class NotAVideoFile extends Exception {
-
- }
-
- public class FileCopyException extends Exception {
- private static final long serialVersionUID = -1010013599132881427L;
- private int resId;
-
- public FileCopyException(int resId) {
- this.resId = resId;
- }
-
- public int getResId() {
- return resId;
- }
- }
-
- public Bitmap getAvatar(String avatar, int size) {
- if (avatar == null) {
- return null;
- }
- Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
- if (bm == null) {
- return null;
- }
- return bm;
- }
-
- public boolean isFileAvailable(Message message) {
- return getFile(message).exists();
- }
-
- public static void close(Closeable stream) {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) {
- }
- }
- }
-
- public static void close(Socket socket) {
- if (socket != null) {
- try {
- socket.close();
- } catch (IOException e) {
- }
- }
- }
-
-
- public static boolean weOwnFile(Context context, Uri uri) {
- if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
- return false;
- } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return fileIsInFilesDir(context, uri);
- } else {
- return weOwnFileLollipop(uri);
- }
- }
-
-
- /**
- * This is more than hacky but probably way better than doing nothing
- * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
- * and check against those as well
- */
- private static boolean fileIsInFilesDir(Context context, Uri uri) {
- try {
- final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
- final String needle = new File(uri.getPath()).getCanonicalPath();
- return needle.startsWith(haystack);
- } catch (IOException e) {
- return false;
- }
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private static boolean weOwnFileLollipop(Uri uri) {
- try {
- File file = new File(uri.getPath());
- FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor();
- StructStat st = Os.fstat(fd);
- return st.st_uid == android.os.Process.myUid();
- } catch (FileNotFoundException e) {
- return false;
- } catch (Exception e) {
- return true;
- }
- }
-
- public static Bitmap rotateBitmap(File file, Bitmap bitmap, int orientation) {
-
- if (orientation == 1) {
- return bitmap;
- }
-
- Matrix matrix = new Matrix();
- switch (orientation) {
- case 2:
- matrix.setScale(-1, 1);
- break;
- case 3:
- matrix.setRotate(180);
- break;
- case 4:
- matrix.setRotate(180);
- matrix.postScale(-1, 1);
- break;
- case 5:
- matrix.setRotate(90);
- matrix.postScale(-1, 1);
- break;
- case 6:
- matrix.setRotate(90);
- break;
- case 7:
- matrix.setRotate(-90);
- matrix.postScale(-1, 1);
- break;
- case 8:
- matrix.setRotate(-90);
- break;
- default:
- return bitmap;
- }
-
- try {
- Bitmap oriented = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
- bitmap.recycle();
- return oriented;
- } catch (OutOfMemoryError e) {
- e.printStackTrace();
- return bitmap;
- }
- }
+ private static final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
+
+ public static final String CONVERSATIONS_FILE_PROVIDER = "de.pixart.messenger.files";
+
+ private XmppConnectionService mXmppConnectionService;
+
+ public FileBackend(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ private void createNoMedia() {
+ final File nomedia_files = new File(getConversationsFileDirectory() + ".nomedia");
+ final File nomedia_audios = new File(getConversationsAudioDirectory() + ".nomedia");
+ if (!nomedia_files.exists()) {
+ try {
+ nomedia_files.createNewFile();
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "could not create nomedia file for files directory");
+ }
+ }
+ if (!nomedia_audios.exists()) {
+ try {
+ nomedia_audios.createNewFile();
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "could not create nomedia file for audio directory");
+ }
+ }
+ }
+
+ public void updateMediaScanner(File file) {
+ if (file.getAbsolutePath().startsWith(getConversationsImageDirectory())
+ || file.getAbsolutePath().startsWith(getConversationsVideoDirectory())) {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ intent.setData(Uri.fromFile(file));
+ mXmppConnectionService.sendBroadcast(intent);
+ } else {
+ createNoMedia();
+ }
+ }
+
+ public boolean deleteFile(Message message) {
+ File file = getFile(message);
+ if (file.delete()) {
+ updateMediaScanner(file);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public DownloadableFile getFile(Message message) {
+ return getFile(message, true);
+ }
+
+ public DownloadableFile getFile(Message message, boolean decrypted) {
+ final boolean encrypted = !decrypted
+ && (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
+ final DownloadableFile file;
+ String path = message.getRelativeFilePath();
+ if (path == null) {
+ String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4);
+ path = filename;
+ }
+ if (path.startsWith("/")) {
+ file = new DownloadableFile(path);
+ } else {
+ String mime = message.getMimeType();
+ if (mime != null && mime.startsWith("image")) {
+ file = new DownloadableFile(getConversationsImageDirectory() + path);
+ } else if (mime != null && mime.startsWith("video")) {
+ file = new DownloadableFile(getConversationsVideoDirectory() + path);
+ } else if (mime != null && mime.startsWith("audio")) {
+ file = new DownloadableFile(getConversationsAudioDirectory() + path);
+ } else {
+ file = new DownloadableFile(getConversationsFileDirectory() + path);
+ }
+ }
+ if (encrypted) {
+ return new DownloadableFile(getConversationsFileDirectory() + file.getName() + ".pgp");
+ } else {
+ return file;
+ }
+ }
+
+ private static long getFileSize(Context context, Uri uri) {
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE));
+ } else {
+ return -1;
+ }
+ }
+
+ public static boolean allFilesUnderSize(Context context, List<Uri> uris, long max) {
+ if (max <= 0) {
+ Log.d(Config.LOGTAG, "server did not report max file size for http upload");
+ return true; //exception to be compatible with HTTP Upload < v0.2
+ }
+ for (Uri uri : uris) {
+ if (FileBackend.getFileSize(context, uri) > max) {
+ Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static String getConversationsFileDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/files/";
+ }
+
+ public static String getConversationsImageDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/images/";
+ }
+
+ public static String getConversationsVideoDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/videos/";
+ }
+
+ public static String getConversationsAudioDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/audios/";
+ }
+
+ public static String getConversationsDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/";
+ }
+
+ public Bitmap resize(Bitmap originalBitmap, int size) {
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ if (Math.max(w, h) > size) {
+ int scalledW;
+ int scalledH;
+ if (w <= h) {
+ scalledW = (int) (w / ((double) h / size));
+ scalledH = size;
+ } else {
+ scalledW = size;
+ scalledH = (int) (h / ((double) w / size));
+ }
+ Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
+ if (originalBitmap != null && !originalBitmap.isRecycled()) {
+ originalBitmap.recycle();
+ }
+ return result;
+ } else {
+ return originalBitmap;
+ }
+ }
+
+ public static Bitmap rotate(Bitmap bitmap, int degree) {
+ if (degree == 0) {
+ return bitmap;
+ }
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ Matrix mtx = new Matrix();
+ mtx.postRotate(degree);
+ Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
+ if (bitmap != null && !bitmap.isRecycled()) {
+ bitmap.recycle();
+ }
+ return result;
+ }
+
+ public boolean useImageAsIs(Uri uri) {
+ String path = getOriginalPath(uri);
+ if (path == null) {
+ return false;
+ }
+ File file = new File(path);
+ long size = file.length();
+ if (size == 0 || size >= Config.IMAGE_MAX_SIZE) {
+ return false;
+ }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ try {
+ BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri), null, options);
+ if (options == null || options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) {
+ return false;
+ }
+ return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
+ } catch (FileNotFoundException e) {
+ return false;
+ }
+ }
+
+ public String getOriginalPath(Uri uri) {
+ return FileUtils.getPath(mXmppConnectionService, uri);
+ }
+
+ public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
+ Log.d(Config.LOGTAG, "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
+ file.getParentFile().mkdirs();
+ OutputStream os = null;
+ InputStream is = null;
+ try {
+ file.createNewFile();
+ os = new FileOutputStream(file);
+ is = mXmppConnectionService.getContentResolver().openInputStream(uri);
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = is.read(buffer)) > 0) {
+ try {
+ os.write(buffer, 0, length);
+ } catch (IOException e) {
+ throw new FileWriterException();
+ }
+ }
+ try {
+ os.flush();
+ } catch (IOException e) {
+ throw new FileWriterException();
+ }
+ } catch (FileNotFoundException e) {
+ throw new FileCopyException(R.string.error_file_not_found);
+ } catch (FileWriterException e) {
+ throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new FileCopyException(R.string.error_io_exception);
+ } finally {
+ close(os);
+ close(is);
+ }
+ }
+
+ public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
+ String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
+ if (extension == null) {
+ extension = getExtensionFromUri(uri);
+ }
+ String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4);
+ message.setRelativeFilePath(filename + "." + extension);
+ copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
+ }
+
+ private String getExtensionFromUri(Uri uri) {
+ String[] projection = {MediaStore.MediaColumns.DATA};
+ String filename = null;
+ Cursor cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ filename = cursor.getString(0);
+ }
+ } catch (Exception e) {
+ filename = null;
+ } finally {
+ cursor.close();
+ }
+ }
+ int pos = filename == null ? -1 : filename.lastIndexOf('.');
+ return pos > 0 ? filename.substring(pos + 1) : null;
+ }
+
+ private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException {
+ file.getParentFile().mkdirs();
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ if (!file.exists() && !file.createNewFile()) {
+ throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
+ }
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ if (is == null) {
+ throw new FileCopyException(R.string.error_not_an_image_file);
+ }
+ Bitmap originalBitmap;
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ int inSampleSize = (int) Math.pow(2, sampleSize);
+ Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
+ options.inSampleSize = inSampleSize;
+ originalBitmap = BitmapFactory.decodeStream(is, null, options);
+ is.close();
+ if (originalBitmap == null) {
+ throw new FileCopyException(R.string.error_not_an_image_file);
+ }
+ Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE);
+ int rotation = getRotation(image);
+ scaledBitmap = rotate(scaledBitmap, rotation);
+ boolean targetSizeReached = false;
+ int quality = Config.IMAGE_QUALITY;
+ while (!targetSizeReached) {
+ os = new FileOutputStream(file);
+ boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os);
+ if (!success) {
+ throw new FileCopyException(R.string.error_compressing_image);
+ }
+ os.flush();
+ targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50;
+ quality -= 5;
+ }
+ scaledBitmap.recycle();
+ } catch (FileNotFoundException e) {
+ throw new FileCopyException(R.string.error_file_not_found);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new FileCopyException(R.string.error_io_exception);
+ } catch (SecurityException e) {
+ throw new FileCopyException(R.string.error_security_exception_during_image_copy);
+ } catch (OutOfMemoryError e) {
+ ++sampleSize;
+ if (sampleSize <= 3) {
+ copyImageToPrivateStorage(file, image, sampleSize);
+ } else {
+ throw new FileCopyException(R.string.error_out_of_memory);
+ }
+ } finally {
+ close(os);
+ close(is);
+ }
+ }
+
+ public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException {
+ Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath());
+ copyImageToPrivateStorage(file, image, 0);
+ }
+
+ public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException {
+ String filename = fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4);
+ switch (Config.IMAGE_FORMAT) {
+ case JPEG:
+ message.setRelativeFilePath(filename + ".jpg");
+ break;
+ case PNG:
+ message.setRelativeFilePath(filename + ".png");
+ break;
+ case WEBP:
+ message.setRelativeFilePath(filename + ".webp");
+ break;
+ }
+ copyImageToPrivateStorage(getFile(message), image);
+ updateFileParams(message);
+ }
+
+ private int getRotation(File file) {
+ return getRotation(Uri.parse("file://" + file.getAbsolutePath()));
+ }
+
+ private int getRotation(Uri image) {
+ InputStream is = null;
+ try {
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ return ExifHelper.getOrientation(is);
+ } catch (FileNotFoundException e) {
+ return 0;
+ } finally {
+ close(is);
+ }
+ }
+
+ public Bitmap getThumbnail(Message message, int size, boolean cacheOnly) throws FileNotFoundException {
+ final String uuid = message.getUuid();
+ final LruCache<String, Bitmap> cache = mXmppConnectionService.getBitmapCache();
+ Bitmap thumbnail = cache.get(uuid);
+ if ((thumbnail == null) && (!cacheOnly)) {
+ synchronized (cache) {
+ thumbnail = cache.get(uuid);
+ if (thumbnail != null) {
+ return thumbnail;
+ }
+ DownloadableFile file = getFile(message);
+ if (file.getMimeType().startsWith("video/")) {
+ thumbnail = getVideoPreview(file, size);
+ } else {
+ Bitmap fullsize = getFullsizeImagePreview(file, size);
+ if (fullsize == null) {
+ throw new FileNotFoundException();
+ }
+ thumbnail = resize(fullsize, size);
+ thumbnail = rotate(thumbnail, getRotation(file));
+ }
+ this.mXmppConnectionService.getBitmapCache().put(uuid, thumbnail);
+ }
+ }
+ return thumbnail;
+ }
+
+ private Bitmap getFullsizeImagePreview(File file, int size) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(file, size);
+ try {
+ return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ } catch (OutOfMemoryError e) {
+ options.inSampleSize *= 2;
+ return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ }
+ }
+
+ private Bitmap getVideoPreview(File file, int size) {
+ MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
+ Bitmap frame;
+ try {
+ metadataRetriever.setDataSource(file.getAbsolutePath());
+ frame = metadataRetriever.getFrameAtTime(0);
+ metadataRetriever.release();
+ frame = resize(frame, size);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ frame.eraseColor(0xff000000);
+ }
+ Canvas canvas = new Canvas(frame);
+ Bitmap play = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), R.drawable.play_video);
+ float x = (frame.getWidth() - play.getWidth()) / 2.0f;
+ float y = (frame.getHeight() - play.getHeight()) / 2.0f;
+ canvas.drawBitmap(play, x, y, null);
+ return frame;
+ }
+
+ private static String getTakePhotoPath() {
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/";
+ }
+
+ public Uri getTakePhotoUri() {
+ File file = new File(getTakePhotoPath() + "IMG_" + fileDateFormat.format(new Date()) + ".jpg");
+ file.getParentFile().mkdirs();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file);
+ } else {
+ return Uri.fromFile(file);
+ }
+ }
+
+ public static Uri getIndexableTakePhotoUri(Uri original) {
+ if ("file".equals(original.getScheme())) {
+ return original;
+ } else {
+ List<String> segments = original.getPathSegments();
+ return Uri.parse("file://" + getTakePhotoPath() + segments.get(segments.size() - 1));
+ }
+ }
+
+ public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
+ try {
+ Avatar avatar = new Avatar();
+ Bitmap bm = cropCenterSquare(image, size);
+ if (bm == null) {
+ return null;
+ }
+ ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
+ Base64OutputStream mBase64OutputStream = new Base64OutputStream(
+ mByteArrayOutputStream, Base64.DEFAULT);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(
+ mBase64OutputStream, digest);
+ if (!bm.compress(format, 75, mDigestOutputStream)) {
+ return null;
+ }
+ mDigestOutputStream.flush();
+ mDigestOutputStream.close();
+ avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ avatar.image = new String(mByteArrayOutputStream.toByteArray());
+ return avatar;
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ public Avatar getStoredPepAvatar(String hash) {
+ if (hash == null) {
+ return null;
+ }
+ Avatar avatar = new Avatar();
+ File file = new File(getAvatarPath(hash));
+ FileInputStream is = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ is = new FileInputStream(file);
+ ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
+ Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest);
+ byte[] buffer = new byte[4096];
+ int length;
+ while ((length = is.read(buffer)) > 0) {
+ os.write(buffer, 0, length);
+ }
+ os.flush();
+ os.close();
+ avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ avatar.image = new String(mByteArrayOutputStream.toByteArray());
+ avatar.height = options.outHeight;
+ avatar.width = options.outWidth;
+ return avatar;
+ } catch (IOException e) {
+ return null;
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ } finally {
+ close(is);
+ }
+ }
+
+ public boolean isAvatarCached(Avatar avatar) {
+ File file = new File(getAvatarPath(avatar.getFilename()));
+ return file.exists();
+ }
+
+ public boolean save(Avatar avatar) {
+ File file;
+ if (isAvatarCached(avatar)) {
+ file = new File(getAvatarPath(avatar.getFilename()));
+ } else {
+ String filename = getAvatarPath(avatar.getFilename());
+ file = new File(filename + ".tmp");
+ file.getParentFile().mkdirs();
+ OutputStream os = null;
+ try {
+ file.createNewFile();
+ os = new FileOutputStream(file);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
+ mDigestOutputStream.write(avatar.getImageAsBytes());
+ mDigestOutputStream.flush();
+ mDigestOutputStream.close();
+ String sha1sum = CryptoHelper.bytesToHex(digest.digest());
+ if (sha1sum.equals(avatar.sha1sum)) {
+ file.renameTo(new File(filename));
+ } else {
+ Log.d(Config.LOGTAG, "sha1sum mismatch for " + avatar.owner);
+ file.delete();
+ return false;
+ }
+ } catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) {
+ return false;
+ } finally {
+ close(os);
+ }
+ }
+ avatar.size = file.length();
+ return true;
+ }
+
+ public String getAvatarPath(String avatar) {
+ return mXmppConnectionService.getFilesDir().getAbsolutePath() + "/avatars/" + avatar;
+ }
+
+ public Uri getAvatarUri(String avatar) {
+ return Uri.parse("file:" + getAvatarPath(avatar));
+ }
+
+ public Bitmap cropCenterSquare(Uri image, int size) {
+ if (image == null) {
+ return null;
+ }
+ InputStream is = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(image, size);
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ if (is == null) {
+ return null;
+ }
+ Bitmap input = BitmapFactory.decodeStream(is, null, options);
+ if (input == null) {
+ return null;
+ } else {
+ input = rotate(input, getRotation(image));
+ return cropCenterSquare(input, size);
+ }
+ } catch (SecurityException e) {
+ return null; // happens for example on Android 6.0 if contacts permissions get revoked
+ } catch (FileNotFoundException e) {
+ return null;
+ } finally {
+ close(is);
+ }
+ }
+
+ public Bitmap cropCenter(Uri image, int newHeight, int newWidth) {
+ if (image == null) {
+ return null;
+ }
+ InputStream is = null;
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(image, Math.max(newHeight, newWidth));
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ if (is == null) {
+ return null;
+ }
+ Bitmap source = BitmapFactory.decodeStream(is, null, options);
+ if (source == null) {
+ return null;
+ }
+ int sourceWidth = source.getWidth();
+ int sourceHeight = source.getHeight();
+ float xScale = (float) newWidth / sourceWidth;
+ float yScale = (float) newHeight / sourceHeight;
+ float scale = Math.max(xScale, yScale);
+ float scaledWidth = scale * sourceWidth;
+ float scaledHeight = scale * sourceHeight;
+ float left = (newWidth - scaledWidth) / 2;
+ float top = (newHeight - scaledHeight) / 2;
+
+ RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
+ Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(dest);
+ canvas.drawBitmap(source, null, targetRect, null);
+ if (source != null && !source.isRecycled()) {
+ source.recycle();
+ }
+ return dest;
+ } catch (SecurityException e) {
+ return null; //android 6.0 with revoked permissions for example
+ } catch (FileNotFoundException e) {
+ return null;
+ } finally {
+ close(is);
+ }
+ }
+
+ public Bitmap cropCenterSquare(Bitmap input, int size) {
+ int w = input.getWidth();
+ int h = input.getHeight();
+
+ float scale = Math.max((float) size / h, (float) size / w);
+
+ float outWidth = scale * w;
+ float outHeight = scale * h;
+ float left = (size - outWidth) / 2;
+ float top = (size - outHeight) / 2;
+ RectF target = new RectF(left, top, left + outWidth, top + outHeight);
+
+ Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(output);
+ canvas.drawBitmap(input, null, target, null);
+ if (input != null && !input.isRecycled()) {
+ input.recycle();
+ }
+ return output;
+ }
+
+ private int calcSampleSize(Uri image, int size) throws FileNotFoundException, SecurityException {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options);
+ return calcSampleSize(options, size);
+ }
+
+ private static int calcSampleSize(File image, int size) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(image.getAbsolutePath(), options);
+ return calcSampleSize(options, size);
+ }
+
+ public static int calcSampleSize(BitmapFactory.Options options, int size) {
+ int height = options.outHeight;
+ int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > size || width > size) {
+ int halfHeight = height / 2;
+ int halfWidth = width / 2;
+
+ while ((halfHeight / inSampleSize) > size
+ && (halfWidth / inSampleSize) > size) {
+ inSampleSize *= 2;
+ }
+ }
+ return inSampleSize;
+ }
+
+ public Uri getJingleFileUri(Message message) {
+ File file = getFile(message);
+ return Uri.parse("file://" + file.getAbsolutePath());
+ }
+
+ public void updateFileParams(Message message) {
+ updateFileParams(message, null);
+ }
+
+ public void updateFileParams(Message message, URL url) {
+ DownloadableFile file = getFile(message);
+ final String mime = file.getMimeType();
+ boolean image = message.getType() == Message.TYPE_IMAGE || (mime != null && mime.startsWith("image/"));
+ boolean video = mime != null && mime.startsWith("video/");
+ if (image || video) {
+ try {
+ Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file);
+ if (url == null) {
+ message.setBody(Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height);
+ } else {
+ message.setBody(url.toString() + "|" + Long.toString(file.getSize()) + '|' + dimensions.width + '|' + dimensions.height);
+ }
+ return;
+ } catch (NotAVideoFile notAVideoFile) {
+ Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was not a video file");
+ //fall threw
+ }
+ }
+ if (url != null) {
+ message.setBody(url.toString() + "|" + Long.toString(file.getSize()));
+ } else {
+ message.setBody(Long.toString(file.getSize()));
+ }
+
+ }
+
+ private Dimensions getImageDimensions(File file) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ int rotation = getRotation(file);
+ boolean rotated = rotation == 90 || rotation == 270;
+ int imageHeight = rotated ? options.outWidth : options.outHeight;
+ int imageWidth = rotated ? options.outHeight : options.outWidth;
+ return new Dimensions(imageHeight, imageWidth);
+ }
+
+ private Dimensions getVideoDimensions(File file) throws NotAVideoFile {
+ MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
+ try {
+ metadataRetriever.setDataSource(file.getAbsolutePath());
+ } catch (Exception e) {
+ throw new NotAVideoFile();
+ }
+ String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
+ if (hasVideo == null) {
+ throw new NotAVideoFile();
+ }
+ int rotation = extractRotationFromMediaRetriever(metadataRetriever);
+ boolean rotated = rotation == 90 || rotation == 270;
+ int height;
+ try {
+ String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+ height = Integer.parseInt(h);
+ } catch (Exception e) {
+ height = -1;
+ }
+ int width;
+ try {
+ String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+ width = Integer.parseInt(w);
+ } catch (Exception e) {
+ width = -1;
+ }
+ metadataRetriever.release();
+ Log.d(Config.LOGTAG, "extracted video dims " + width + "x" + height);
+ return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
+ }
+
+ private int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
+ int rotation;
+ if (Build.VERSION.SDK_INT >= 17) {
+ String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ try {
+ rotation = Integer.parseInt(r);
+ } catch (Exception e) {
+ rotation = 0;
+ }
+ } else {
+ rotation = 0;
+ }
+ return rotation;
+ }
+
+ private class Dimensions {
+ public final int width;
+ public final int height;
+
+ public Dimensions(int height, int width) {
+ this.width = width;
+ this.height = height;
+ }
+ }
+
+ private class NotAVideoFile extends Exception {
+
+ }
+
+ public class FileCopyException extends Exception {
+ private static final long serialVersionUID = -1010013599132881427L;
+ private int resId;
+
+ public FileCopyException(int resId) {
+ this.resId = resId;
+ }
+
+ public int getResId() {
+ return resId;
+ }
+ }
+
+ public Bitmap getAvatar(String avatar, int size) {
+ if (avatar == null) {
+ return null;
+ }
+ Bitmap bm = cropCenter(getAvatarUri(avatar), size, size);
+ if (bm == null) {
+ return null;
+ }
+ return bm;
+ }
+
+ public boolean isFileAvailable(Message message) {
+ return getFile(message).exists();
+ }
+
+ public static void close(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ public static void close(Socket socket) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+
+ public static boolean weOwnFile(Context context, Uri uri) {
+ if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+ return false;
+ } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return fileIsInFilesDir(context, uri);
+ } else {
+ return weOwnFileLollipop(uri);
+ }
+ }
+
+
+ /**
+ * This is more than hacky but probably way better than doing nothing
+ * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
+ * and check against those as well
+ */
+ private static boolean fileIsInFilesDir(Context context, Uri uri) {
+ try {
+ final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
+ final String needle = new File(uri.getPath()).getCanonicalPath();
+ return needle.startsWith(haystack);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static boolean weOwnFileLollipop(Uri uri) {
+ try {
+ File file = new File(uri.getPath());
+ FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor();
+ StructStat st = Os.fstat(fd);
+ return st.st_uid == android.os.Process.myUid();
+ } catch (FileNotFoundException e) {
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ public static Bitmap rotateBitmap(File file, Bitmap bitmap, int orientation) {
+
+ if (orientation == 1) {
+ return bitmap;
+ }
+
+ Matrix matrix = new Matrix();
+ switch (orientation) {
+ case 2:
+ matrix.setScale(-1, 1);
+ break;
+ case 3:
+ matrix.setRotate(180);
+ break;
+ case 4:
+ matrix.setRotate(180);
+ matrix.postScale(-1, 1);
+ break;
+ case 5:
+ matrix.setRotate(90);
+ matrix.postScale(-1, 1);
+ break;
+ case 6:
+ matrix.setRotate(90);
+ break;
+ case 7:
+ matrix.setRotate(-90);
+ matrix.postScale(-1, 1);
+ break;
+ case 8:
+ matrix.setRotate(-90);
+ break;
+ default:
+ return bitmap;
+ }
+
+ try {
+ Bitmap oriented = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ bitmap.recycle();
+ return oriented;
+ } catch (OutOfMemoryError e) {
+ e.printStackTrace();
+ return bitmap;
+ }
+ }
}
diff --git a/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java b/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java
index 85e852b5d..d5253f227 100644
--- a/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java
+++ b/src/main/java/de/pixart/messenger/persistance/OnPhoneContactsMerged.java
@@ -1,5 +1,5 @@
package de.pixart.messenger.persistance;
public interface OnPhoneContactsMerged {
- public void phoneContactsMerged();
+ public void phoneContactsMerged();
}