aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/thedevstack/conversationsplus/persistance
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/persistance')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java484
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java549
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/OnPhoneContactsMerged.java5
3 files changed, 1038 insertions, 0 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
new file mode 100644
index 00000000..21eaf476
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
@@ -0,0 +1,484 @@
+package de.thedevstack.conversationsplus.persistance;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Contact;
+import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.entities.Roster;
+import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+public class DatabaseBackend extends SQLiteOpenHelper {
+
+ private static DatabaseBackend instance = null;
+
+ private static final String DATABASE_NAME = "history";
+ private static final int DATABASE_VERSION = 14;
+
+ 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 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.ROSTERVERSION + " TEXT," + Account.OPTIONS
+ + " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
+ + " TEXT)");
+ 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.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ + Message.CONVERSATION + ") REFERENCES "
+ + Conversation.TABLENAME + "(" + Conversation.UUID
+ + ") ON DELETE CASCADE);");
+
+ db.execSQL(CREATE_CONTATCS_STATEMENT);
+ }
+
+ @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) {
+ // 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))
+ ).toString();
+ } 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))
+ ).toString();
+ } 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 createContact(Contact contact) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.insert(Contact.TABLENAME, null, contact.getContentValues());
+ }
+
+ public int getConversationCount() {
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("select count(uuid) as count from "
+ + Conversation.TABLENAME + " where " + Conversation.STATUS
+ + "=" + Conversation.STATUS_AVAILABLE, null);
+ cursor.moveToFirst();
+ int count = cursor.getInt(0);
+ cursor.close();
+ return count;
+ }
+
+ 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 Conversation findConversation(final Account account, final Jid contactJid) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = { account.getUuid(),
+ contactJid.toBareJid().toString() + "/%",
+ contactJid.toBareJid().toString()
+ };
+ Cursor cursor = db.query(Conversation.TABLENAME, null,
+ Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
+ + " like ? OR "+Conversation.CONTACTJID+"=?)", selectionArgs, null, null, null);
+ if (cursor.getCount() == 0)
+ return null;
+ 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() {
+ List<Account> list = new ArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ 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 void updateAccount(Account account) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = { account.getUuid() };
+ db.update(Account.TABLENAME, account.getContentValues(), Account.UUID
+ + "=?", args);
+ }
+
+ public void deleteAccount(Account account) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = { account.getUuid() };
+ db.delete(Account.TABLENAME, Account.UUID + "=?", args);
+ }
+
+ 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);
+ cursor.close();
+ return (count > 0);
+ } catch (SQLiteCantOpenDatabaseException e) {
+ return true; // better safe than sorry
+ } catch (RuntimeException e) {
+ return true; // better safe than sorry
+ }
+ }
+
+ @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 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();
+ 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().toString() };
+ db.delete(Contact.TABLENAME, where, whereArgs);
+ }
+ }
+ account.setRosterVersion(roster.getVersion());
+ updateAccount(account);
+ }
+
+ public void deleteMessage(Message message) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = { message.getUuid() };
+ db.delete(Message.TABLENAME, Message.UUID + "=?", args);
+ }
+
+ public void deleteMessagesInConversation(Conversation conversation) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ String[] args = { conversation.getUuid() };
+ db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
+ }
+
+ public Conversation findConversationByUuid(String conversationUuid) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = { conversationUuid };
+ Cursor cursor = db.query(Conversation.TABLENAME, null,
+ Conversation.UUID + "=?", selectionArgs, null, null, null);
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ cursor.moveToFirst();
+ Conversation conversation = Conversation.fromCursor(cursor);
+ cursor.close();
+ return conversation;
+ }
+
+ public Message findMessageByUuid(String messageUuid) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = { messageUuid };
+ Cursor cursor = db.query(Message.TABLENAME, null, Message.UUID + "=?",
+ selectionArgs, null, null, null);
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ cursor.moveToFirst();
+ Message message = Message.fromCursor(cursor);
+ cursor.close();
+ return message;
+ }
+
+ public Account findAccountByUuid(String accountUuid) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ String[] selectionArgs = { accountUuid };
+ Cursor cursor = db.query(Account.TABLENAME, null, Account.UUID + "=?",
+ selectionArgs, null, null, null);
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ cursor.moveToFirst();
+ Account account = Account.fromCursor(cursor);
+ cursor.close();
+ return account;
+ }
+
+ public List<Message> getImageMessages(Conversation conversation) {
+ ArrayList<Message> list = new ArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor;
+ String[] selectionArgs = { conversation.getUuid(), String.valueOf(Message.TYPE_IMAGE) };
+ cursor = db.query(Message.TABLENAME, null, Message.CONVERSATION
+ + "=? AND "+Message.TYPE+"=?", selectionArgs, null, null,null);
+ 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;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java
new file mode 100644
index 00000000..3a245d62
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/FileBackend.java
@@ -0,0 +1,549 @@
+package de.thedevstack.conversationsplus.persistance;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.util.Base64;
+import android.util.Base64OutputStream;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.R;
+import de.thedevstack.conversationsplus.entities.Transferable;
+import de.thedevstack.conversationsplus.entities.DownloadableFile;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.services.XmppConnectionService;
+import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.ExifHelper;
+import de.thedevstack.conversationsplus.xmpp.pep.Avatar;
+
+public class FileBackend {
+
+ private static int IMAGE_SIZE = 1920;
+
+ private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
+
+ private XmppConnectionService mXmppConnectionService;
+
+ public FileBackend(XmppConnectionService service) {
+ this.mXmppConnectionService = service;
+ }
+
+ public DownloadableFile getFile(Message message) {
+ return getFile(message, true);
+ }
+
+ public DownloadableFile getFile(Message message, boolean decrypted) {
+ String path = message.getRelativeFilePath();
+ String extension;
+ if (path != null && !path.isEmpty()) {
+ String[] parts = path.split("\\.");
+ extension = "."+parts[parts.length - 1];
+ } else {
+ if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_TEXT) {
+ extension = ".webp";
+ } else {
+ extension = "";
+ }
+ path = message.getUuid()+extension;
+ }
+ final boolean encrypted = !decrypted
+ && (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
+ if (encrypted) {
+ return new DownloadableFile(getConversationsFileDirectory()+message.getUuid()+extension+".pgp");
+ } else {
+ if (path.startsWith("/")) {
+ return new DownloadableFile(path);
+ } else {
+ if (Arrays.asList(Transferable.VALID_IMAGE_EXTENSIONS).contains(extension)) {
+ return new DownloadableFile(getConversationsFileDirectory() + path);
+ } else {
+ return new DownloadableFile(getConversationsImageDirectory() + path);
+ }
+ }
+ }
+ }
+
+ public static String getConversationsFileDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
+ }
+
+ public static String getConversationsImageDirectory() {
+ return Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ + "/Conversations/";
+ }
+
+ 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));
+ }
+ return Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
+ } else {
+ return originalBitmap;
+ }
+ }
+
+ public Bitmap rotate(Bitmap bitmap, int degree) {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ Matrix mtx = new Matrix();
+ mtx.postRotate(degree);
+ return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
+ }
+
+ public String getOriginalPath(Uri uri) {
+ String path = null;
+ if (uri.getScheme().equals("file")) {
+ return uri.getPath();
+ } else if (uri.toString().startsWith("content://media/")) {
+ String[] projection = {MediaStore.MediaColumns.DATA};
+ Cursor metaCursor = mXmppConnectionService.getContentResolver().query(uri,
+ projection, null, null, null);
+ if (metaCursor != null) {
+ try {
+ if (metaCursor.moveToFirst()) {
+ path = metaCursor.getString(0);
+ }
+ } finally {
+ metaCursor.close();
+ }
+ }
+ }
+ return path;
+ }
+
+ public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
+ Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
+ String mime = mXmppConnectionService.getContentResolver().getType(uri);
+ String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
+ message.setRelativeFilePath(message.getUuid() + "." + extension);
+ DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
+ 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) {
+ os.write(buffer, 0, length);
+ }
+ os.flush();
+ } 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);
+ } finally {
+ close(os);
+ close(is);
+ }
+ Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
+ return file;
+ }
+
+ public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
+ throws FileCopyException {
+ return this.copyImageToPrivateStorage(message, image, 0);
+ }
+
+ private DownloadableFile copyImageToPrivateStorage(Message message,
+ Uri image, int sampleSize) throws FileCopyException {
+ DownloadableFile file = getFile(message);
+ file.getParentFile().mkdirs();
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ file.createNewFile();
+ is = mXmppConnectionService.getContentResolver().openInputStream(image);
+ os = new FileOutputStream(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, IMAGE_SIZE);
+ int rotation = getRotation(image);
+ if (rotation > 0) {
+ scaledBitmap = rotate(scaledBitmap, rotation);
+ }
+
+ boolean success = scaledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
+ if (!success) {
+ throw new FileCopyException(R.string.error_compressing_image);
+ }
+ os.flush();
+ long size = file.getSize();
+ int width = scaledBitmap.getWidth();
+ int height = scaledBitmap.getHeight();
+ message.setBody(Long.toString(size) + '|' + width + '|' + height);
+ return file;
+ } 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) {
+ return copyImageToPrivateStorage(message, image, sampleSize);
+ } else {
+ throw new FileCopyException(R.string.error_out_of_memory);
+ }
+ } catch (NullPointerException e) {
+ throw new FileCopyException(R.string.error_io_exception);
+ } finally {
+ close(os);
+ close(is);
+ }
+ }
+
+ 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 {
+ Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
+ message.getUuid());
+ if ((thumbnail == null) && (!cacheOnly)) {
+ File file = getFile(message);
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inSampleSize = calcSampleSize(file, size);
+ Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
+ if (fullsize == null) {
+ throw new FileNotFoundException();
+ }
+ thumbnail = resize(fullsize, size);
+ this.mXmppConnectionService.getBitmapCache().put(message.getUuid(),
+ thumbnail);
+ }
+ return thumbnail;
+ }
+
+ public Uri getTakePhotoUri() {
+ StringBuilder pathBuilder = new StringBuilder();
+ pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
+ pathBuilder.append('/');
+ pathBuilder.append("Camera");
+ pathBuilder.append('/');
+ pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg");
+ Uri uri = Uri.parse("file://" + pathBuilder.toString());
+ File file = new File(uri.toString());
+ file.getParentFile().mkdirs();
+ return uri;
+ }
+
+ 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 mBase64OutputSttream = new Base64OutputStream(
+ mByteArrayOutputStream, Base64.DEFAULT);
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ DigestOutputStream mDigestOutputStream = new DigestOutputStream(
+ mBase64OutputSttream, 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 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 (FileNotFoundException e) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ } catch (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);
+ Bitmap input = BitmapFactory.decodeStream(is, null, options);
+ if (input == null) {
+ return null;
+ } else {
+ int rotation = getRotation(image);
+ if (rotation > 0) {
+ input = rotate(input, rotation);
+ }
+ return cropCenterSquare(input, size);
+ }
+ } 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);
+ 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);
+ return dest;
+ } 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);
+ return output;
+ }
+
+ private int calcSampleSize(Uri image, int size) throws FileNotFoundException {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(image), null, options);
+ return calcSampleSize(options, size);
+ }
+
+ private int calcSampleSize(File image, int size) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(image.getAbsolutePath(), options);
+ return calcSampleSize(options, size);
+ }
+
+ private 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);
+ if (message.getType() == Message.TYPE_IMAGE || file.getMimeType().startsWith("image/")) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+ int imageHeight = options.outHeight;
+ int imageWidth = options.outWidth;
+ if (url == null) {
+ message.setBody(Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
+ } else {
+ message.setBody(url.toString()+"|"+Long.toString(file.getSize()) + '|' + imageWidth + '|' + imageHeight);
+ }
+ } else {
+ if (url != null) {
+ message.setBody(url.toString()+"|"+Long.toString(file.getSize()));
+ } else {
+ message.setBody(Long.toString(file.getSize()));
+ }
+ }
+
+ }
+
+ 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) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/OnPhoneContactsMerged.java b/src/main/java/de/thedevstack/conversationsplus/persistance/OnPhoneContactsMerged.java
new file mode 100644
index 00000000..cfbb6ef1
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/OnPhoneContactsMerged.java
@@ -0,0 +1,5 @@
+package de.thedevstack.conversationsplus.persistance;
+
+public interface OnPhoneContactsMerged {
+ public void phoneContactsMerged();
+}