diff options
Diffstat (limited to 'src/main/java')
40 files changed, 1585 insertions, 1024 deletions
diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java index 0adbbe51f..f9b2539c3 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java @@ -21,6 +21,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import de.pixart.messenger.Config; +import de.pixart.messenger.utils.Compatibility; import de.pixart.messenger.xml.Element; import rocks.xmpp.addr.Jid; @@ -177,7 +178,7 @@ public class XmppAxolotlMessage { try { SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes()); if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) { @@ -293,7 +294,7 @@ public class XmppAxolotlMessage { key = newKey; } - Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index 94710b86e..365f8fa87 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -35,6 +35,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import de.pixart.messenger.Config; @@ -787,6 +788,29 @@ public class DatabaseBackend extends SQLiteOpenHelper { }; } + public List<FilePath> getRelativeFilePaths(String account, Jid jid, int limit) { + SQLiteDatabase db = this.getReadableDatabase(); + final String SQL = "select uuid,relativeFilePath from messages where type in (1,2) and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; + final String[] args = {account, jid.toEscapedString(), jid.toEscapedString() + "/%"}; + Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + String.valueOf(limit) : ""), args); + List<FilePath> filesPaths = new ArrayList<>(); + while (cursor.moveToNext()) { + filesPaths.add(new FilePath(cursor.getString(0), cursor.getString(1))); + } + cursor.close(); + return filesPaths; + } + + public static class FilePath { + public final UUID uuid; + public final String path; + + private FilePath(String uuid, String path) { + this.uuid = UUID.fromString(uuid); + this.path = path; + } + } + public Conversation findConversation(final Account account, final Jid contactJid) { SQLiteDatabase db = this.getReadableDatabase(); String[] selectionArgs = {account.getUuid(), diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java index 0c3028feb..822fa6318 100644 --- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java @@ -46,6 +46,7 @@ import java.security.DigestOutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; @@ -56,6 +57,7 @@ import de.pixart.messenger.R; import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.entities.Message; import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.util.Attachment; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.ExifHelper; import de.pixart.messenger.utils.FileUtils; @@ -80,8 +82,8 @@ public class FileBackend { } private void createNoMedia() { - final File nomedia_files = new File(getConversationsDirectory("Files", true) + ".nomedia"); - final File nomedia_audios = new File(getConversationsDirectory("Audios", true) + ".nomedia"); + final File nomedia_files = new File(getConversationsDirectory("Files") + ".nomedia"); + final File nomedia_audios = new File(getConversationsDirectory("Audios") + ".nomedia"); if (!nomedia_files.exists()) { try { nomedia_files.createNewFile(); @@ -99,8 +101,8 @@ public class FileBackend { } public void updateMediaScanner(File file) { - if (file.getAbsolutePath().startsWith(getConversationsDirectory("Images", true)) - || file.getAbsolutePath().startsWith(getConversationsDirectory("Videos", true))) { + if (file.getAbsolutePath().startsWith(getConversationsDirectory("Images")) + || file.getAbsolutePath().startsWith(getConversationsDirectory("Videos"))) { Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); intent.setData(Uri.fromFile(file)); mXmppConnectionService.sendBroadcast(intent); @@ -129,13 +131,13 @@ public class FileBackend { file = new DownloadableFile(path); } else { if (mime != null && mime.startsWith("image")) { - file = new DownloadableFile(getConversationsDirectory("Images", true) + path); + file = new DownloadableFile(getConversationsDirectory("Images") + path); } else if (mime != null && mime.startsWith("video")) { - file = new DownloadableFile(getConversationsDirectory("Videos", true) + path); + file = new DownloadableFile(getConversationsDirectory("Videos") + path); } else if (mime != null && mime.startsWith("audio")) { - file = new DownloadableFile(getConversationsDirectory("Audios", true) + path); + file = new DownloadableFile(getConversationsDirectory("Audios") + path); } else { - file = new DownloadableFile(getConversationsDirectory("Files", true) + path); + file = new DownloadableFile(getConversationsDirectory("Files") + path); } } return file; @@ -151,7 +153,7 @@ public class FileBackend { } final DownloadableFile file = getFileForPath(path, message.getMimeType()); if (encrypted) { - return new DownloadableFile(getConversationsDirectory("Files", true) + file.getName() + ".pgp"); + return new DownloadableFile(getConversationsDirectory("Files") + file.getName() + ".pgp"); } else { return file; } @@ -172,16 +174,19 @@ public class FileBackend { } } - public static boolean allFilesUnderSize(Context context, List<Uri> uris, long max) { + public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, 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) { - String mime = context.getContentResolver().getType(uri); + for (Attachment attachment : attachments) { + if (attachment.getType() != Attachment.Type.FILE) { + continue; + } + String mime = attachment.getMime(); if (mime != null && mime.startsWith("video/")) { try { - Dimensions dimensions = FileBackend.getVideoDimensions(context, uri); + Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri()); if (dimensions.getMin() >= 720) { Log.d(Config.LOGTAG, "do not consider video file with min width larger or equal than 720 for size check"); continue; @@ -190,7 +195,7 @@ public class FileBackend { //ignore and fall through } } - if (FileBackend.getFileSize(context, uri) > max) { + if (FileBackend.getFileSize(context, attachment.getUri()) > max) { Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle"); return false; } @@ -198,31 +203,43 @@ public class FileBackend { return true; } - public static String getDirectoryName(final String type, final boolean isMedia) { - String media = ""; - if (isMedia) { - media = "Media/Pix-Art Messenger "; + public List<Attachment> convertToAttachments(List<DatabaseBackend.FilePath> relativeFilePaths) { + List<Attachment> attachments = new ArrayList<>(); + for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) { + final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path)); + Log.d(Config.LOGTAG, "mime=" + mime); + File file = getFileForPath(relativeFilePath.path, mime); + if (file.exists()) { + attachments.add(Attachment.of(relativeFilePath.uuid, file, mime)); + } else { + Log.d(Config.LOGTAG, "file " + file.getAbsolutePath() + " doesn't exist"); + } } - if (type == "null" || type == null) { - return "/Pix-Art Messenger/"; + return attachments; + } + + public static String getConversationsDirectory(final String type) { + if (type.equalsIgnoreCase("null") || type == null) { + return getAppMediaDirectory() + "Pix-Art Messenger" + "/"; } else { - return "/Pix-Art Messenger" + "/" + media + type + "/"; + return getAppMediaDirectory() + "Pix-Art Messenger" + " " + type + "/"; } } - public static String getConversationsDirectory(final String type, final boolean isMedia) { - String DirName = null; - if (type != "null" || type != null) { - DirName = type; - } - String path = Environment.getExternalStorageDirectory().getAbsolutePath() + getDirectoryName(DirName, isMedia); - File createFolders = new File(path); - if (!createFolders.exists()) { - Log.d(Config.LOGTAG, "creating directory " + createFolders); - createFolders.mkdirs(); - } - return path; + public static String getAppMediaDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Media/"; + } + public static String getBackupDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Database/"; + } + + public static String getAppLogsDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Chats/"; + } + + public static String getAppUpdateDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Update/"; } private Bitmap resize(final Bitmap originalBitmap, int size) throws IOException { @@ -294,7 +311,7 @@ public class FileBackend { Log.d(Config.LOGTAG, "File path = null"); return false; } - if (path.contains(getDirectoryName("null", true))) { + if (path.contains(getConversationsDirectory("null"))) { Log.d(Config.LOGTAG, "File " + path + " is in our directory"); return true; } @@ -554,6 +571,21 @@ public class FileBackend { return paint; } + private Bitmap cropCenterSquareVideo(Uri uri, int size) { + MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); + Bitmap frame; + try { + metadataRetriever.setDataSource(mXmppConnectionService, uri); + frame = metadataRetriever.getFrameAtTime(0); + metadataRetriever.release(); + return cropCenterSquare(frame, size); + } catch (Exception e) { + frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + frame.eraseColor(0xff000000); + return frame; + } + } + private Bitmap getVideoPreview(File file, int size) throws IOException { MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); Bitmap frame; @@ -1041,6 +1073,30 @@ public class FileBackend { return getVideoDimensions(metadataRetriever); } + public Bitmap getPreviewForUri(Attachment attachment, int size, boolean cacheOnly) { + final String key = "attachment_" + attachment.getUuid().toString() + "_" + String.valueOf(size); + final LruCache<String, Bitmap> cache = mXmppConnectionService.getBitmapCache(); + Bitmap bitmap = cache.get(key); + if (bitmap != null || cacheOnly) { + return bitmap; + } + if (attachment.getMime() != null && attachment.getMime().startsWith("video/")) { + bitmap = cropCenterSquareVideo(attachment.getUri(), size); + drawOverlay(bitmap, R.drawable.play_video, 0.75f); + } else { + bitmap = cropCenterSquare(attachment.getUri(), size); + if ("image/gif".equals(attachment.getMime())) { + Bitmap withGifOverlay = bitmap.copy(Bitmap.Config.ARGB_8888, true); + drawOverlay(withGifOverlay, R.drawable.play_gif, 1.0f); + bitmap.recycle(); + bitmap = withGifOverlay; + } + } + if (bitmap != null) { + cache.put(key, bitmap); + } + return bitmap; + } private static Dimensions getVideoDimensions(Context context, Uri uri) throws NotAVideoFile { MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); diff --git a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java index 1c4c6ca19..65711ef42 100644 --- a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java +++ b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java @@ -1,85 +1,39 @@ package de.pixart.messenger.services; -import android.Manifest; import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; import android.os.PowerManager; import android.os.SystemClock; -import android.util.Log; import android.util.Pair; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.util.concurrent.atomic.AtomicLong; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; -import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.entities.DownloadableFile; +import de.pixart.messenger.utils.Compatibility; public class AbstractConnectionManager { - protected XmppConnectionService mXmppConnectionService; + private static final String KEYTYPE = "AES"; + private static final String CIPHERMODE = "AES/GCM/NoPadding"; + private static final String PROVIDER = "BC"; private static final int UI_REFRESH_THRESHOLD = 250; private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0); + protected XmppConnectionService mXmppConnectionService; public AbstractConnectionManager(XmppConnectionService service) { this.mXmppConnectionService = service; } - public XmppConnectionService getXmppConnectionService() { - return this.mXmppConnectionService; - } - - public long getAutoAcceptFileSize() { - long defaultValue_wifi = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_wifi); - long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile); - long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming); - - String config = "0"; - if (mXmppConnectionService.isWIFI()) { - config = this.mXmppConnectionService.getPreferences().getString( - "auto_accept_file_size_wifi", String.valueOf(defaultValue_wifi)); - } else if (mXmppConnectionService.isMobile()) { - config = this.mXmppConnectionService.getPreferences().getString( - "auto_accept_file_size_mobile", String.valueOf(defaultValue_mobile)); - } else if (mXmppConnectionService.isMobileRoaming()) { - config = this.mXmppConnectionService.getPreferences().getString( - "auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming)); - } - try { - return Long.parseLong(config); - } catch (NumberFormatException e) { - return defaultValue_mobile; - } - } - - public boolean hasStoragePermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - } else { - return true; - } - } - public static Pair<InputStream, Integer> createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { FileInputStream is; int size; @@ -90,15 +44,15 @@ public class AbstractConnectionManager { } try { if (gcm) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); - return new Pair<>(cis, cipher.getOutputSize(size)); + Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(file.getIv()); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + return new Pair<>(new CipherInputStream(is, cipher), cipher.getOutputSize(size)); } else { IvParameterSpec ips = new IvParameterSpec(file.getIv()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted input stream"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips); return new Pair<>(new CipherInputStream(is, cipher), (size / 16 + 1) * 16); } } catch (Exception e) { @@ -126,27 +80,53 @@ public class AbstractConnectionManager { } try { if (gcm) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); + Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(file.getIv()); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + return new CipherOutputStream(os, cipher); } else { IvParameterSpec ips = new IvParameterSpec(file.getIv()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted output stream"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips); return new CipherOutputStream(os, cipher); } - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; + } catch (Exception e) { + throw new AssertionError(e); } } + public XmppConnectionService getXmppConnectionService() { + return this.mXmppConnectionService; + } + + public long getAutoAcceptFileSize() { + long defaultValue_wifi = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_wifi); + long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile); + long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming); + + String config = "0"; + if (mXmppConnectionService.isWIFI()) { + config = this.mXmppConnectionService.getPreferences().getString( + "auto_accept_file_size_wifi", String.valueOf(defaultValue_wifi)); + } else if (mXmppConnectionService.isMobile()) { + config = this.mXmppConnectionService.getPreferences().getString( + "auto_accept_file_size_mobile", String.valueOf(defaultValue_mobile)); + } else if (mXmppConnectionService.isMobileRoaming()) { + config = this.mXmppConnectionService.getPreferences().getString( + "auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming)); + } + try { + return Long.parseLong(config); + } catch (NumberFormatException e) { + return defaultValue_mobile; + } + } + + public boolean hasStoragePermission() { + return Compatibility.hasStoragePermission(mXmppConnectionService); + } + public void updateConversationUi(boolean force) { synchronized (LAST_UI_UPDATE_CALL) { if (force || SystemClock.elapsedRealtime() - LAST_UI_UPDATE_CALL.get() >= UI_REFRESH_THRESHOLD) { diff --git a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java index d20f9c1f3..4c0aec3ca 100644 --- a/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java +++ b/src/main/java/de/pixart/messenger/services/ContactChooserTargetService.java @@ -17,7 +17,7 @@ import java.util.ArrayList; import java.util.List; import de.pixart.messenger.entities.Conversation; -import de.pixart.messenger.ui.ShareWithActivity; +import de.pixart.messenger.ui.ConversationsActivity; @TargetApi(Build.VERSION_CODES.M) public class ContactChooserTargetService extends ChooserTargetService implements ServiceConnection { @@ -41,7 +41,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements return chooserTargets; } mXmppConnectionService.populateWithOrderedConversations(conversations, false); - final ComponentName componentName = new ComponentName(this, ShareWithActivity.class); + final ComponentName componentName = new ComponentName(this, ConversationsActivity.class); final int pixel = (int) (48 * getResources().getDisplayMetrics().density); for (Conversation conversation : conversations) { if (conversation.sentMessagesCount() == 0) { @@ -51,7 +51,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements final Icon icon = Icon.createWithBitmap(mXmppConnectionService.getAvatarService().get(conversation, pixel)); final float score = 1 - (1.0f / MAX_TARGETS) * chooserTargets.size(); final Bundle extras = new Bundle(); - extras.putString("uuid", conversation.getUuid()); + extras.putString(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras)); if (chooserTargets.size() >= MAX_TARGETS) { break; diff --git a/src/main/java/de/pixart/messenger/services/ExportLogsService.java b/src/main/java/de/pixart/messenger/services/ExportLogsService.java index b0da108ba..e66873acc 100644 --- a/src/main/java/de/pixart/messenger/services/ExportLogsService.java +++ b/src/main/java/de/pixart/messenger/services/ExportLogsService.java @@ -41,7 +41,7 @@ import static de.pixart.messenger.ui.SettingsActivity.USE_MULTI_ACCOUNTS; public class ExportLogsService extends XmppConnectionService { private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsDirectory("Chats", false) + "%s"; + private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s"; private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n"; private static AtomicBoolean running = new AtomicBoolean(false); boolean ReadableLogsEnabled = false; @@ -171,7 +171,7 @@ public class ExportLogsService extends XmppConnectionService { // Get hold of the db: FileInputStream InputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME)); // Set the output folder on the SDcard - File directory = new File(FileBackend.getConversationsDirectory("Database", false)); + File directory = new File(FileBackend.getBackupDirectory()); // Create the folder if it doesn't exist: if (!directory.exists()) { boolean directory_created = directory.mkdirs(); diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index b6d23668c..525354957 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -116,6 +116,7 @@ import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.UiCallback; import de.pixart.messenger.ui.interfaces.OnAvatarPublication; +import de.pixart.messenger.ui.interfaces.OnMediaLoaded; import de.pixart.messenger.ui.interfaces.OnSearchResultsAvailable; import de.pixart.messenger.utils.Compatibility; import de.pixart.messenger.utils.ConversationsFileObserver; @@ -431,7 +432,6 @@ public class XmppConnectionService extends Service { public void stopForcingForegroundNotification() { mForceForegroundService.set(false); toggleForegroundService(); - mNotificationService.dismissForcedForegroundNotification(); } public boolean areMessagesInitialized() { @@ -727,6 +727,7 @@ public class XmppConnectionService extends Service { } private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet<Account> pingCandidates) { + storeNumberOfAccounts(this.getAccounts().size()); boolean pingNow = false; if (account.getStatus().isAttemptReconnect()) { if (!hasInternetConnection()) { @@ -802,6 +803,14 @@ public class XmppConnectionService extends Service { return pingNow; } + private void storeNumberOfAccounts(int accounts) { + //write No of accounts to file + final SharedPreferences.Editor editor = getPreferences().edit(); + Log.d(Config.LOGTAG, "Number of accounts is " + accounts); + editor.putInt(SettingsActivity.NUMBER_OF_ACCOUNTS, accounts); + editor.apply(); + } + public boolean isDataSaverDisabled() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); @@ -1126,7 +1135,7 @@ public class XmppConnectionService extends Service { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { startContactObserver(); } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + if (Compatibility.hasStoragePermission(this)) { Log.d(Config.LOGTAG, "starting file observer"); new Thread(fileObserver::startWatching).start(); } @@ -1224,19 +1233,21 @@ public class XmppConnectionService extends Service { } public void toggleForegroundService() { - if (mForceForegroundService.get() || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) { + final boolean status; + if (mForceForegroundService.get() || (Compatibility.keepForegroundService(this)/* && hasEnabledAccounts()*/)) { startForeground(NotificationService.FOREGROUND_NOTIFICATION_ID, this.mNotificationService.createForegroundNotification()); - Log.d(Config.LOGTAG, "started foreground service"); + status = true; } else { stopForeground(true); - Log.d(Config.LOGTAG, "stopped foreground service"); + status = false; } + mNotificationService.dismissForcedForegroundNotification(); //if the channel was changed the previous call might fail + Log.d(Config.LOGTAG, "ForegroundService: " + (status ? "on" : "off")); } @Override public void onTaskRemoved(final Intent rootIntent) { super.onTaskRemoved(rootIntent); - //TODO check for accounts enabled if ((Compatibility.keepForegroundService(this) && hasEnabledAccounts()) || mForceForegroundService.get()) { Log.d(Config.LOGTAG, "ignoring onTaskRemoved because foreground service is activated"); } else { @@ -2661,7 +2672,7 @@ public class XmppConnectionService extends Service { private boolean hasEnabledAccounts() { if (this.accounts == null) { - return true; // set to true if accounts could not be fetched - used for notifications + return false; // set to false if accounts could not be fetched - used for notifications } for (Account account : this.accounts) { if (account.isEnabled()) { @@ -2671,6 +2682,18 @@ public class XmppConnectionService extends Service { return false; } + public void getAttachments(final Conversation conversation, int limit, final OnMediaLoaded onMediaLoaded) { + getAttachments(conversation.getAccount(), conversation.getJid().asBareJid(), limit, onMediaLoaded); + } + + public void getAttachments(final Account account, final Jid jid, final int limit, final OnMediaLoaded onMediaLoaded) { + getAttachments(account.getUuid(), jid.asBareJid(), limit, onMediaLoaded); + } + + public void getAttachments(final String account, final Jid jid, final int limit, final OnMediaLoaded onMediaLoaded) { + new Thread(() -> onMediaLoaded.onMediaLoaded(fileBackend.convertToAttachments(databaseBackend.getRelativeFilePaths(account, jid, limit)))).start(); + } + public void persistSelfNick(MucOptions.User self) { final Conversation conversation = self.getConversation(); Jid full = self.getFullJid(); diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index 38479b459..070914bb8 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -32,6 +32,7 @@ import org.openintents.openpgp.util.OpenPgpUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; @@ -49,9 +50,14 @@ import de.pixart.messenger.entities.MucOptions.User; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate; import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate; +import de.pixart.messenger.ui.adapter.MediaAdapter; +import de.pixart.messenger.ui.interfaces.OnMediaLoaded; +import de.pixart.messenger.ui.util.Attachment; +import de.pixart.messenger.ui.util.GridManager; import de.pixart.messenger.ui.util.MucDetailsContextMenuHelper; import de.pixart.messenger.ui.util.MyLinkify; import de.pixart.messenger.ui.util.SoftKeyboardUtils; +import de.pixart.messenger.utils.Compatibility; import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.utils.StringUtils; @@ -64,7 +70,7 @@ import rocks.xmpp.addr.Jid; import static de.pixart.messenger.entities.Bookmark.printableValue; import static de.pixart.messenger.utils.StringUtils.changed; -public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher { +public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded { public static final String ACTION_VIEW_MUC = "view_muc"; private static final float INACTIVE_ALPHA = 0.4684f; //compromise between dark and light theme private Conversation mConversation; @@ -96,6 +102,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; private ActivityMucDetailsBinding binding; + private MediaAdapter mMediaAdapter; private String uuid = null; private User mSelectedUser = null; @@ -319,6 +326,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.mucEditTitle.addTextChangedListener(this); this.binding.mucEditSubject.addTextChangedListener(this); this.binding.mucEditSubject.addTextChangedListener(new StylingHelper.MessageEditorStyler(this.binding.mucEditSubject)); + mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); + this.binding.media.setAdapter(mMediaAdapter); + GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size); } @Override @@ -328,6 +338,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (this.mTheme != theme) { recreate(); } + binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE); } @Override @@ -475,6 +486,15 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers return true; } + @Override + public void onMediaLoaded(List<Attachment> attachments) { + runOnUiThread(() -> { + int limit = GridManager.getCurrentColumnCount(binding.media); + mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size()))); + binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE); + }); + } + protected void saveAsBookmark() { xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName()); updateView(); @@ -503,6 +523,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers if (uuid != null) { this.mConversation = xmppConnectionService.findConversationByUuid(uuid); if (this.mConversation != null) { + if (Compatibility.hasStoragePermission(this)) { + final int limit = GridManager.getCurrentColumnCount(this.binding.media); + xmppConnectionService.getAttachments(this.mConversation, limit, this); + this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, mConversation)); + } updateView(); } } diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java index 5dce3fca2..1dfc80e3c 100644 --- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java @@ -30,6 +30,8 @@ import android.widget.Toast; import org.openintents.openpgp.util.OpenPgpUtils; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -45,6 +47,11 @@ import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate; import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate; +import de.pixart.messenger.ui.adapter.MediaAdapter; +import de.pixart.messenger.ui.interfaces.OnMediaLoaded; +import de.pixart.messenger.ui.util.Attachment; +import de.pixart.messenger.ui.util.GridManager; +import de.pixart.messenger.utils.Compatibility; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.MenuDoubleTabUtil; @@ -57,12 +64,13 @@ import de.pixart.messenger.xmpp.OnUpdateBlocklist; import de.pixart.messenger.xmpp.XmppConnection; import rocks.xmpp.addr.Jid; -public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated { +public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnMediaLoaded { public static final String ACTION_VIEW_CONTACT = "view_contact"; private Contact contact; private Conversation mConversation; ActivityContactDetailsBinding binding; + private MediaAdapter mMediaAdapter; private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() { @Override @@ -260,6 +268,9 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp this.mNotifyStatusButton = findViewById(R.id.notification_status_button); this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener); this.mNotifyStatusText = findViewById(R.id.notification_status_text); + mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); + this.binding.media.setAdapter(mMediaAdapter); + GridManager.setupLayoutManager(this, this.binding.media, R.dimen.media_size); } @Override @@ -279,6 +290,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp this.showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, false); this.showLastSeen = preferences.getBoolean("last_activity", false); } + binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE); + mMediaAdapter.setAttachments(Collections.emptyList()); } @Override @@ -552,12 +565,20 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } final AxolotlService axolotlService = contact.getAccount().getAxolotlService(); if (Config.supportOmemo() && axolotlService != null) { + final Collection<XmppAxolotlSession> sessions = axolotlService.findSessionsForContact(contact); + boolean anyActive = false; + for(XmppAxolotlSession session : sessions) { + anyActive = session.getTrust().isActive(); + if (anyActive) { + break; + } + } boolean skippedInactive = false; boolean showsInactive = false; - for (final XmppAxolotlSession session : axolotlService.findSessionsForContact(contact)) { + for (final XmppAxolotlSession session : sessions) { final FingerprintStatus trust = session.getTrust(); hasKeys |= !trust.isCompromised(); - if (!trust.isActive()) { + if (!trust.isActive() && anyActive) { if (showInactiveOmemo) { showsInactive = true; } else { @@ -657,6 +678,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp processFingerprintVerification(mPendingFingerprintVerificationUri); mPendingFingerprintVerificationUri = null; } + if (Compatibility.hasStoragePermission(this)) { + final int limit = GridManager.getCurrentColumnCount(this.binding.media); + xmppConnectionService.getAttachments(account, contact.getJid().asBareJid(), limit, this); + this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, contact)); + } populateView(); } } @@ -676,4 +702,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp Toast.makeText(this, R.string.invalid_barcode, Toast.LENGTH_SHORT).show(); } } + + @Override + public void onMediaLoaded(List<Attachment> attachments) { + runOnUiThread(() -> { + int limit = GridManager.getCurrentColumnCount(binding.media); + mMediaAdapter.setAttachments(attachments.subList(0, Math.min(limit, attachments.size()))); + binding.mediaWrapper.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE); + }); + } } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index 0d4610c8e..d326058e7 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -15,8 +15,6 @@ import android.content.IntentSender.SendIntentException; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.databinding.DataBindingUtil; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -27,7 +25,6 @@ import android.provider.MediaStore; import android.support.annotation.IdRes; import android.support.annotation.NonNull; import android.support.annotation.StringRes; -import android.support.media.ExifInterface; import android.support.v13.view.inputmethod.InputConnectionCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.support.v7.app.AlertDialog; @@ -54,16 +51,12 @@ import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView.OnEditorActionListener; import android.widget.Toast; import net.java.otr4j.session.SessionStatus; -import java.io.File; -import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -98,9 +91,10 @@ import de.pixart.messenger.http.HttpDownloadConnection; import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.adapter.MediaPreviewAdapter; import de.pixart.messenger.ui.adapter.MessageAdapter; import de.pixart.messenger.ui.util.ActivityResult; -import de.pixart.messenger.ui.util.AttachmentTool; +import de.pixart.messenger.ui.util.Attachment; import de.pixart.messenger.ui.util.ConversationMenuConfigurator; import de.pixart.messenger.ui.util.DateSeparator; import de.pixart.messenger.ui.util.EditMessageActionModeCallback; @@ -113,7 +107,6 @@ import de.pixart.messenger.ui.util.SendButtonAction; import de.pixart.messenger.ui.util.SendButtonTool; import de.pixart.messenger.ui.util.ShareUtil; import de.pixart.messenger.ui.widget.EditMessage; -import de.pixart.messenger.utils.FileUtils; import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.utils.MessageUtils; import de.pixart.messenger.utils.NickValidityChecker; @@ -129,7 +122,6 @@ import rocks.xmpp.addr.Jid; import static de.pixart.messenger.ui.XmppActivity.EXTRA_ACCOUNT; import static de.pixart.messenger.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION; -import static de.pixart.messenger.ui.util.SendButtonAction.TEXT; import static de.pixart.messenger.ui.util.SoftKeyboardUtils.hideSoftKeyboard; import static de.pixart.messenger.xmpp.Patches.ENCRYPTION_EXCEPTIONS; @@ -139,55 +131,56 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke public static final int REQUEST_DECRYPT_PGP = 0x0202; public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207; public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208; - public static final int REQUEST_TRUST_KEYS_MENU = 0x0209; + public static final int REQUEST_TRUST_KEYS_ATTACHMENTS = 0x0209; public static final int REQUEST_START_DOWNLOAD = 0x0210; public static final int REQUEST_ADD_EDITOR_CONTENT = 0x0211; public static final int ATTACHMENT_CHOICE = 0x0300; public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301; - public static final int ATTACHMENT_CHOICE_TAKE_FROM_CAMERA = 0x0302; + public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302; public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303; public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304; public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305; public static final int ATTACHMENT_CHOICE_CHOOSE_VIDEO = 0x0306; + public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307; public static final int ATTACHMENT_CHOICE_INVALID = 0x0399; public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action"; public static final String STATE_CONVERSATION_UUID = ConversationFragment.class.getName() + ".uuid"; public static final String STATE_SCROLL_POSITION = ConversationFragment.class.getName() + ".scroll_position"; - public static final String STATE_PHOTO_URI = ConversationFragment.class.getName() + ".take_photo_uri"; - public static final String STATE_VIDEO_URI = ConversationFragment.class.getName() + ".take_video_uri"; + public static final String STATE_PHOTO_URI = ConversationFragment.class.getName() + ".media_previews"; + public static final String STATE_MEDIA_PREVIEWS = ConversationFragment.class.getName() + ".take_photo_uri"; private static final String STATE_LAST_MESSAGE_UUID = "state_last_message_uuid"; private final List<Message> messageList = new ArrayList<>(); - final private List<Uri> mPendingImageUris = new ArrayList<>(); - private String lastMessageUuid = null; - public Uri mPendingEditorContent = null; private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>(); private final PendingItem<String> pendingConversationsUuid = new PendingItem<>(); + private final PendingItem<ArrayList<Attachment>> pendingMediaPreviews = new PendingItem<>(); private final PendingItem<Bundle> pendingExtras = new PendingItem<>(); private final PendingItem<Uri> pendingTakePhotoUri = new PendingItem<>(); private final PendingItem<Uri> pendingTakeVideoUri = new PendingItem<>(); private final PendingItem<ScrollState> pendingScrollState = new PendingItem<>(); private final PendingItem<String> pendingLastMessageUuid = new PendingItem<>(); private final PendingItem<Message> pendingMessage = new PendingItem<>(); - protected MessageAdapter messageListAdapter; - private Conversation conversation; + public Uri mPendingEditorContent = null; public FragmentConversationBinding binding; + protected MessageAdapter messageListAdapter; protected Message lastHistoryMessage = null; SimpleDateFormat sdf = new SimpleDateFormat("EEEE, dd. MMM yyyy", Locale.getDefault()); + private String lastMessageUuid = null; + private Conversation conversation; private Toast messageLoaderToast; private ConversationsActivity activity; - private boolean reInitRequiredOnStart = true; - - private SimpleFingerGestures gesturesDetector = new SimpleFingerGestures(); - + private Menu mOptionsMenu; protected OnClickListener clickToVerify = new OnClickListener() { @Override public void onClick(View v) { activity.verifyOtrSessionDialog(conversation, v); } }; + private boolean reInitRequiredOnStart = true; + private MediaPreviewAdapter mediaPreviewAdapter; + private SimpleFingerGestures gesturesDetector = new SimpleFingerGestures(); private OnClickListener clickToMuc = new OnClickListener() { @Override @@ -466,7 +459,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke switch (action) { case CHOOSE_ATTACHMENT: choose_attachment(v); - case TAKE_FROM_CAMERA: + case TAKE_PHOTO: + case RECORD_VIDEO: case SEND_LOCATION: case RECORD_VOICE: case CHOOSE_PICTURE: @@ -494,32 +488,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } }; - - @SuppressLint("RestrictedApi") - private void choose_attachment(View v) { - SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); - final boolean hideVoice = p.getBoolean("show_record_voice_btn", activity.getResources().getBoolean(R.bool.show_record_voice_btn)); - PopupMenu popup = new PopupMenu(activity, v); - popup.inflate(R.menu.choose_attachment); - Menu menu = popup.getMenu(); - ConversationMenuConfigurator.configureQuickShareAttachmentMenu(conversation, menu, hideVoice); - popup.setOnMenuItemClickListener(attachmentItem -> { - switch (attachmentItem.getItemId()) { - case R.id.attach_choose_picture: - case R.id.attach_take_picture: - case R.id.attach_choose_file: - case R.id.attach_record_voice: - case R.id.attach_location: - handleAttachmentSelection(attachmentItem); - default: - return false; - } - }); - MenuPopupHelper menuHelper = new MenuPopupHelper(getActivity(), (MenuBuilder) menu, v); - menuHelper.setForceShowIcon(true); - menuHelper.show(); - } - private View.OnLongClickListener mSendButtonLongListener = new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { @@ -651,6 +619,32 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + @SuppressLint("RestrictedApi") + private void choose_attachment(View v) { + SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); + final boolean hideVoice = p.getBoolean("show_record_voice_btn", activity.getResources().getBoolean(R.bool.show_record_voice_btn)); + PopupMenu popup = new PopupMenu(activity, v); + popup.inflate(R.menu.choose_attachment); + Menu menu = popup.getMenu(); + ConversationMenuConfigurator.configureQuickShareAttachmentMenu(conversation, menu, hideVoice); + popup.setOnMenuItemClickListener(attachmentItem -> { + switch (attachmentItem.getItemId()) { + case R.id.attach_choose_picture: + case R.id.attach_take_picture: + case R.id.attach_record_video: + case R.id.attach_choose_file: + case R.id.attach_record_voice: + case R.id.attach_location: + handleAttachmentSelection(attachmentItem); + default: + return false; + } + }); + MenuPopupHelper menuHelper = new MenuPopupHelper(getActivity(), (MenuBuilder) menu, v); + menuHelper.setForceShowIcon(true); + menuHelper.show(); + } + private void toggleScrollDownButton() { toggleScrollDownButton(binding.messagesView); } @@ -851,111 +845,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } public void attachEditorContentToConversation(Uri uri) { - this.attachFileToConversation(conversation, uri, null); + mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uri, Attachment.Type.FILE)); + toggleInputMethod(); } - private void attachImageToConversation(Conversation conversation, Uri uri, boolean sendAsIs) { + private void attachImageToConversation(Conversation conversation, Uri uri) { if (conversation == null) { return; } - if (sendAsIs) { - sendImage(conversation, uri); - return; - } - final Conversation conversation_preview = conversation; - final Uri uri_preview = uri; - Bitmap bitmap = null; - try { - bitmap = BitmapFactory.decodeFile(FileUtils.getPath(activity, uri)); - } catch (Exception e) { - e.printStackTrace(); - } - File file = null; - ExifInterface exif = null; - int orientation = 0; - try { - file = new File(FileUtils.getPath(activity, uri)); - } catch (Exception e) { - e.printStackTrace(); - } - if (file != null) { - try { - exif = new ExifInterface(file.toString()); - } catch (IOException e) { - e.printStackTrace(); - } - orientation = exif != null ? exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) : 0; - } - Log.d(Config.LOGTAG, "EXIF: " + orientation); - Bitmap rotated_image = null; - Log.d(Config.LOGTAG, "Rotate image"); - try { - rotated_image = FileBackend.rotateBitmap(file, bitmap, orientation); - } catch (Exception e) { - e.printStackTrace(); - } - if (rotated_image != null) { - int scaleSize = 600; - int originalWidth = rotated_image.getWidth(); - int originalHeight = rotated_image.getHeight(); - int newWidth = -1; - int newHeight = -1; - float multFactor; - if (originalHeight > originalWidth) { - newHeight = scaleSize; - multFactor = (float) originalWidth / (float) originalHeight; - newWidth = (int) (newHeight * multFactor); - } else if (originalWidth > originalHeight) { - newWidth = scaleSize; - multFactor = (float) originalHeight / (float) originalWidth; - newHeight = (int) (newWidth * multFactor); - } else if (originalHeight == originalWidth) { - newHeight = scaleSize; - newWidth = scaleSize; - } - Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px"); - Bitmap preview = null; - - try { - preview = Bitmap.createScaledBitmap(rotated_image, newWidth, newHeight, false); - } catch (Exception e) { - e.printStackTrace(); - } - if (preview != null) { - ImageView ImagePreview = new ImageView(activity); - LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); - ImagePreview.setLayoutParams(vp); - ImagePreview.setMaxWidth(newWidth); - ImagePreview.setMaxHeight(newHeight); - ImagePreview.setPadding(5, 5, 5, 5); - ImagePreview.setImageBitmap(preview); - getActivity().runOnUiThread(() -> { - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setView(ImagePreview); - builder.setTitle(R.string.send_image); - builder.setPositiveButton(R.string.ok, (dialog, which) -> sendImage(conversation_preview, uri_preview)); - builder.setOnCancelListener(dialog -> mPendingImageUris.clear()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - builder.setOnDismissListener(dialog -> mPendingImageUris.clear()); - } - AlertDialog alertDialog = builder.create(); - alertDialog.show(); - }); - } else { - getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show()); - } - } else { - getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show()); - } - } - - private void sendImage(Conversation conversation, Uri uri) { final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG); prepareFileToast.show(); activity.delegateUriPermissionsToService(uri); activity.xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback<Message>() { - @Override public void userInputRequried(PendingIntent pi, Message object) { hidePrepareFileToast(prepareFileToast); @@ -981,6 +883,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } private void sendMessage() { + if (mediaPreviewAdapter.hasAttachments()) { + commitAttachments(); + return; + } final String body = binding.textinput.getText().toString(); final Conversation conversation = this.conversation; if (body.length() == 0 || conversation == null) { @@ -1021,10 +927,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } protected boolean trustKeysIfNeeded(int requestCode) { - return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID); - } - - protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); final List<Jid> targets = axolotlService.getCryptoTargets(conversation); boolean hasUnaccepted = !conversation.getAcceptedCryptoTargets().containsAll(targets); @@ -1042,7 +944,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } intent.putExtra("contacts", contacts); intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString()); - intent.putExtra("choice", attachmentChoice); intent.putExtra("conversation", conversation.getUuid()); startActivityForResult(intent, requestCode); return true; @@ -1086,63 +987,43 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } private void handlePositiveActivityResult(int requestCode, final Intent data) { - final String type = data == null ? null : data.getType(); switch (requestCode) { case REQUEST_TRUST_KEYS_TEXT: final String body = binding.textinput.getText().toString(); Message message = new Message(conversation, body, conversation.getNextEncryption()); sendMessage(message); break; - case REQUEST_TRUST_KEYS_MENU: - int choice = data.getIntExtra("choice", ATTACHMENT_CHOICE_INVALID); - selectPresenceToAttachFile(choice); + case REQUEST_TRUST_KEYS_ATTACHMENTS: + commitAttachments(); break; case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - final List<Uri> imageUris = AttachmentTool.extractUriFromIntent(data); - final int ImageUrisCount = imageUris.size(); - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image - number of uris: " + ImageUrisCount); - if (ImageUrisCount == 1) { - Uri uri = imageUris.get(0); - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE"); - attachImageToConversation(conversation, uri, false); - } else { - for (Iterator<Uri> i = imageUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching images to conversations. CHOOSE_IMAGES"); - attachImagesToConversation(conversation, i.next()); - } - } + final List<Attachment> imageUris = Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE); + mediaPreviewAdapter.addMediaPreviews(imageUris); + toggleInputMethod(); break; - case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: + case ATTACHMENT_CHOICE_TAKE_PHOTO: final Uri takePhotoUri = pendingTakePhotoUri.pop(); - final Uri takeVideoUri = pendingTakeVideoUri.pop(); if (takePhotoUri != null) { - attachPhotoToConversation(conversation, takePhotoUri); - } else if (takeVideoUri != null) { - attachFileToConversation(conversation, takeVideoUri, type); + mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), takePhotoUri, Attachment.Type.IMAGE)); + toggleInputMethod(); } else { - Log.d(Config.LOGTAG, "lost take uri. unable to to attach"); + Log.d(Config.LOGTAG, "lost take photo uri. unable to to attach"); } break; case ATTACHMENT_CHOICE_CHOOSE_FILE: + case ATTACHMENT_CHOICE_RECORD_VIDEO: case ATTACHMENT_CHOICE_RECORD_VOICE: - final List<Uri> fileUris = AttachmentTool.extractUriFromIntent(data); - final PresenceSelector.OnPresenceSelected callback = () -> { - for (Iterator<Uri> i = fileUris.iterator(); i.hasNext(); i.remove()) { - Log.d(Config.LOGTAG, "ConversationsActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE"); - attachFileToConversation(conversation, i.next(), type); - } - }; - if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), fileUris, getMaxHttpUploadSize(conversation))) { - callback.onPresenceSelected(); - } else { - activity.selectPresence(conversation, callback); - } + final Attachment.Type type = requestCode == ATTACHMENT_CHOICE_RECORD_VOICE ? Attachment.Type.RECORDING : Attachment.Type.FILE; + final List<Attachment> fileUris = Attachment.extractAttachments(getActivity(), data, type); + mediaPreviewAdapter.addMediaPreviews(fileUris); + toggleInputMethod(); break; case ATTACHMENT_CHOICE_LOCATION: double latitude = data.getDoubleExtra("latitude", 0); double longitude = data.getDoubleExtra("longitude", 0); Uri geo = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude)); - attachLocationToConversation(conversation, geo); + mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), geo, Attachment.Type.LOCATION)); + toggleInputMethod(); break; case REQUEST_INVITE_TO_CONVERSATION: XmppActivity.ConferenceInvite invite = XmppActivity.ConferenceInvite.parse(data); @@ -1156,9 +1037,51 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + private void commitAttachments() { + if (conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(REQUEST_TRUST_KEYS_ATTACHMENTS)) { + return; + } + final List<Attachment> attachments = mediaPreviewAdapter.getAttachments(); + final PresenceSelector.OnPresenceSelected callback = () -> { + for (Iterator<Attachment> i = attachments.iterator(); i.hasNext(); i.remove()) { + final Attachment attachment = i.next(); + if (attachment.getType() == Attachment.Type.LOCATION) { + attachLocationToConversation(conversation, attachment.getUri()); + } else if (attachment.getType() == Attachment.Type.IMAGE) { + Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE"); + attachImageToConversation(conversation, attachment.getUri()); + } else { + Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO"); + attachFileToConversation(conversation, attachment.getUri(), attachment.getMime()); + } + } + mediaPreviewAdapter.notifyDataSetChanged(); + toggleInputMethod(); + }; + if (conversation == null || conversation.getMode() == Conversation.MODE_MULTI || FileBackend.allFilesUnderSize(getActivity(), attachments, getMaxHttpUploadSize(conversation))) { + callback.onPresenceSelected(); + } else { + activity.selectPresence(conversation, callback); + } + } + + public void toggleInputMethod() { + boolean hasAttachments = mediaPreviewAdapter.hasAttachments(); + binding.textinput.setVisibility(hasAttachments ? View.GONE : View.VISIBLE); + binding.mediaPreview.setVisibility(hasAttachments ? View.VISIBLE : View.GONE); + if (mOptionsMenu != null) { + ConversationMenuConfigurator.configureAttachmentMenu(conversation, mOptionsMenu, activity.xmppConnectionService.getAttachmentChoicePreference(), hasAttachments); + } + updateSendButton(); + } + private void handleNegativeActivityResult(int requestCode) { switch (requestCode) { - //nothing to do for now + case ATTACHMENT_CHOICE_TAKE_PHOTO: + if (pendingTakePhotoUri.clear()) { + Log.d(Config.LOGTAG, "cleared pending photo uri after negative activity result"); + } + break; } } @@ -1202,27 +1125,38 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke @Override public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { + mOptionsMenu = menu; + boolean hasAttachments = mediaPreviewAdapter != null && mediaPreviewAdapter.hasAttachments(); menuInflater.inflate(R.menu.fragment_conversation, menu); final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); final MenuItem menuNeedHelp = menu.findItem(R.id.action_create_issue); final MenuItem menuSearchUpdates = menu.findItem(R.id.action_check_updates); final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat); + final MenuItem menuGroupDetails = menu.findItem(R.id.action_group_details); + final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details); if (conversation != null) { if (conversation.getMode() == Conversation.MODE_MULTI) { menuInviteContact.setVisible(true); menuArchiveChat.setTitle(R.string.action_end_conversation_muc); + menuGroupDetails.setVisible(true); + menuContactDetails.setVisible(false); } else { menuInviteContact.setVisible(false); menuArchiveChat.setTitle(R.string.action_end_conversation); + menuGroupDetails.setVisible(false); + menuContactDetails.setVisible(true); } menuNeedHelp.setVisible(true); menuSearchUpdates.setVisible(false); - ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu, activity.xmppConnectionService.getAttachmentChoicePreference()); + ConversationMenuConfigurator.configureAttachmentMenu(conversation, menu, activity.xmppConnectionService.getAttachmentChoicePreference(), hasAttachments); ConversationMenuConfigurator.configureEncryptionMenu(conversation, menu); } else { menuNeedHelp.setVisible(false); menuSearchUpdates.setVisible(true); + menuInviteContact.setVisible(false); + menuGroupDetails.setVisible(false); + menuContactDetails.setVisible(false); } super.onCreateOptionsMenu(menu, menuInflater); } @@ -1235,7 +1169,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke binding.textinput.addTextChangedListener(new StylingHelper.MessageEditorStyler(binding.textinput)); binding.textinput.setOnEditorActionListener(mEditorActionListener); binding.textinput.setRichContentListener(new String[]{"image/*"}, mEditorContentListener); - binding.textinput.setBackgroundResource(activity.isDarkTheme() ? R.drawable.message_bubble_sent_blue_dark : R.drawable.message_bubble_sent_blue); + binding.textinput.setBackgroundResource(messageInputBubble()); binding.textSendButton.setOnClickListener(this.mSendButtonListener); binding.textSendButton.setOnLongClickListener(this.mSendButtonLongListener); @@ -1244,6 +1178,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke binding.messagesView.setOnScrollListener(mOnScrollListener); binding.messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); + mediaPreviewAdapter = new MediaPreviewAdapter(this); + binding.mediaPreview.setAdapter(mediaPreviewAdapter); + binding.mediaPreview.setBackgroundResource(messageInputBubble()); messageListAdapter = new MessageAdapter((XmppActivity) getActivity(), this.messageList); messageListAdapter.setOnContactPictureClicked(message -> { String fingerprint; @@ -1439,7 +1376,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (m.isFileOrImage() && !deleted) { String path = m.getRelativeFilePath(); Log.d(Config.LOGTAG, "Path = " + path); - if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null", false))) { + if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null"))) { deleteFile.setVisible(true); deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m))); } @@ -1513,6 +1450,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke break; case R.id.attach_choose_picture: case R.id.attach_take_picture: + case R.id.attach_record_video: case R.id.attach_choose_file: case R.id.attach_record_voice: case R.id.attach_location: @@ -1541,6 +1479,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case R.id.action_clear_history: clearHistoryDialog(conversation); break; + case R.id.action_group_details: + Intent intent = new Intent(activity, ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + startActivity(intent); + break; + case R.id.action_contact_details: + activity.switchToContactDetails(conversation.getContact()); + break; case R.id.action_block: case R.id.action_unblock: final Activity activity = getActivity(); @@ -1560,7 +1507,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); break; case R.id.attach_take_picture: - attachFile(ATTACHMENT_CHOICE_TAKE_FROM_CAMERA); + attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO); + break; + case R.id.attach_record_video: + attachFile(ATTACHMENT_CHOICE_RECORD_VIDEO); break; case R.id.attach_choose_file: attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); @@ -1620,7 +1570,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO)) { return; } - } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_FROM_CAMERA) { + } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) { if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)) { return; } @@ -1684,9 +1634,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke activity.showInstallPgpDialog(); } } else { - if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) { - selectPresenceToAttachFile(attachmentChoice); - } + selectPresenceToAttachFile(attachmentChoice); } } @@ -1806,7 +1754,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final int encryption = conversation.getNextEncryption(); final Account account = conversation.getAccount(); final PresenceSelector.OnPresenceSelected callback = () -> { - final Intent intent = new Intent(); + Intent intent = new Intent(); boolean chooser = false; switch (attachmentChoice) { case ATTACHMENT_CHOICE_CHOOSE_IMAGE: @@ -1823,46 +1771,29 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setAction(Intent.ACTION_GET_CONTENT); break; - case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setTitle(getString(R.string.attach_take_from_camera)); - builder.setNegativeButton(getString(R.string.action_take_photo), - (dialog, which) -> { - final Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri(); - pendingTakePhotoUri.push(uri); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); - startActivityForResult(intent, attachmentChoice); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - }); - builder.setPositiveButton(getString(R.string.action_take_video), - (dialog, which) -> { - final Uri uri = activity.xmppConnectionService.getFileBackend().getTakeVideoUri(); - pendingTakeVideoUri.push(uri); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); - startActivityForResult(intent, attachmentChoice); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - }); - builder.create().show(); + case ATTACHMENT_CHOICE_RECORD_VIDEO: + intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); + break; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + final Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri(); + pendingTakePhotoUri.push(uri); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); break; case ATTACHMENT_CHOICE_CHOOSE_FILE: chooser = true; intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setAction(Intent.ACTION_GET_CONTENT); break; case ATTACHMENT_CHOICE_RECORD_VOICE: - startActivityForResult(new Intent(getActivity(), RecordingActivity.class), attachmentChoice); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + intent = new Intent(getActivity(), RecordingActivity.class); break; case ATTACHMENT_CHOICE_LOCATION: - startActivityForResult(new Intent(getActivity(), ShareLocationActivity.class), attachmentChoice); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + intent = new Intent(getActivity(), ShareLocationActivity.class); break; } if (intent.resolveActivity(getActivity().getPackageManager()) != null) { @@ -1871,8 +1802,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke startActivityForResult( Intent.createChooser(intent, getString(R.string.perform_action_with)), attachmentChoice); + activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); } else { startActivityForResult(intent, attachmentChoice); + activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); } } }; @@ -2079,23 +2012,32 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } @Override + public void startActivityForResult(Intent intent, int requestCode) { + final Activity activity = getActivity(); + if (activity instanceof ConversationsActivity) { + ((ConversationsActivity) activity).clearPendingViewIntent(); + } + super.startActivityForResult(intent, requestCode); + } + + @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (conversation != null) { outState.putString(STATE_CONVERSATION_UUID, conversation.getUuid()); outState.putString(STATE_LAST_MESSAGE_UUID, lastMessageUuid); - final Uri PhotoUri = pendingTakePhotoUri.peek(); - final Uri VideoUri = pendingTakeVideoUri.peek(); - if (PhotoUri != null) { - outState.putString(STATE_PHOTO_URI, PhotoUri.toString()); - } - if (VideoUri != null) { - outState.putString(STATE_VIDEO_URI, VideoUri.toString()); + final Uri uri = pendingTakePhotoUri.peek(); + if (uri != null) { + outState.putString(STATE_PHOTO_URI, uri.toString()); } final ScrollState scrollState = getScrollPosition(); if (scrollState != null) { outState.putParcelable(STATE_SCROLL_POSITION, scrollState); } + final ArrayList<Attachment> attachments = mediaPreviewAdapter.getAttachments(); + if (attachments.size() > 0) { + outState.putParcelableArrayList(STATE_MEDIA_PREVIEWS, attachments); + } } } @@ -2106,18 +2048,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } String uuid = savedInstanceState.getString(STATE_CONVERSATION_UUID); + ArrayList<Attachment> attachments = savedInstanceState.getParcelableArrayList(STATE_MEDIA_PREVIEWS); pendingLastMessageUuid.push(savedInstanceState.getString(STATE_LAST_MESSAGE_UUID, null)); if (uuid != null) { QuickLoader.set(uuid); this.pendingConversationsUuid.push(uuid); + if (attachments != null && attachments.size() > 0) { + this.pendingMediaPreviews.push(attachments); + } String takePhotoUri = savedInstanceState.getString(STATE_PHOTO_URI); if (takePhotoUri != null) { pendingTakePhotoUri.push(Uri.parse(takePhotoUri)); } - String takeVideoUri = savedInstanceState.getString(STATE_VIDEO_URI); - if (takeVideoUri != null) { - pendingTakeVideoUri.push(Uri.parse(takeVideoUri)); - } pendingScrollState.push(savedInstanceState.getParcelable(STATE_SCROLL_POSITION)); } } @@ -2151,10 +2093,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } if (this.conversation != null) { final String msg = this.binding.textinput.getText().toString(); - final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); - if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && participating && this.conversation.setNextMessage(msg)) { - this.activity.xmppConnectionService.updateConversation(this.conversation); - } + storeNextMessage(msg); updateChatState(this.conversation, msg); this.activity.xmppConnectionService.getNotificationService().setOpenConversation(null); } @@ -2176,17 +2115,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } Log.d(Config.LOGTAG, "ConversationFragment.saveMessageDraftStopAudioPlayer()"); final String msg = this.binding.textinput.getText().toString(); - final boolean participating = previousConversation.getMode() == Conversational.MODE_SINGLE || previousConversation.getMucOptions().participating(); - if (participating && previousConversation.setNextMessage(msg)) { - activity.xmppConnectionService.updateConversation(previousConversation); - } + storeNextMessage(msg); updateChatState(this.conversation, msg); messageListAdapter.stopAudioPlayer(); + mediaPreviewAdapter.clearPreviews(); + toggleInputMethod(); } public void reInit(Conversation conversation, Bundle extras) { QuickLoader.set(conversation.getUuid()); this.saveMessageDraftStopAudioPlayer(); + this.clearPending(); if (this.reInit(conversation, extras != null)) { if (extras != null) { processExtras(extras); @@ -2343,6 +2282,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final String nick = extras.getString(ConversationsActivity.EXTRA_NICK); final boolean asQuote = extras.getBoolean(ConversationsActivity.EXTRA_AS_QUOTE); final boolean pm = extras.getBoolean(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, false); + final List<Uri> uris = extractUris(extras); + if (uris != null && uris.size() > 0) { + mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uris)); + toggleInputMethod(); + return; + } if (nick != null) { if (pm) { Jid jid = conversation.getJid(); @@ -2371,6 +2316,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + private List<Uri> extractUris(Bundle extras) { + final List<Uri> uris = extras.getParcelableArrayList(Intent.EXTRA_STREAM); + if (uris != null) { + return uris; + } + final Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); + if (uri != null) { + return Collections.singletonList(uri); + } else { + return null; + } + } + private boolean showBlockSubmenu(View view) { final Jid jid = conversation.getJid(); if (jid.getLocal() == null) { @@ -2551,10 +2509,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.textinput.append(conversation.getDraftMessage()); conversation.setDraftMessage(null); } - final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); - if (participating && conversation.setNextMessage(this.binding.textinput.getText().toString())) { - activity.xmppConnectionService.databaseBackend.updateConversation(conversation); - } + storeNextMessage(); updateChatMsgHint(); SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity); final boolean prefScrollToBottom = p.getBoolean("scroll_to_bottom", activity.getResources().getBoolean(R.bool.scroll_to_bottom)); @@ -2566,6 +2521,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + private boolean storeNextMessage() { + return storeNextMessage(this.binding.textinput.getText().toString()); + } + + private boolean storeNextMessage(String msg) { + final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating(); + if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && participating && this.conversation.setNextMessage(msg)) { + this.activity.xmppConnectionService.updateConversation(this.conversation); + return true; + } + return false; + } + public void doneSendingPgpMessage() { mSendingPgpMessage.set(false); } @@ -2585,11 +2553,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } public void updateSendButton() { + boolean hasAttachments = mediaPreviewAdapter != null && mediaPreviewAdapter.hasAttachments(); boolean useSendButtonToIndicateStatus = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status)); final Conversation c = this.conversation; final Presence.Status status; final String text = this.binding.textinput == null ? "" : this.binding.textinput.getText().toString(); - SendButtonAction action = SendButtonTool.getAction(getActivity(), c, text); + final SendButtonAction action; + if (hasAttachments) { + action = SendButtonAction.TEXT; + } else { + action = SendButtonTool.getAction(getActivity(), c, text); + } if (useSendButtonToIndicateStatus && c.getAccount().getStatus() == Account.State.ONLINE) { if (activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) { status = Presence.Status.OFFLINE; @@ -2601,9 +2575,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } else { status = Presence.Status.OFFLINE; } - if (action.toString().equals("CHOOSE_ATTACHMENT") && !activity.xmppConnectionService.getAttachmentChoicePreference()) { - action = TEXT; - } this.binding.textSendButton.setTag(action); this.binding.textSendButton.setImageResource(SendButtonTool.getSendButtonImageResource(getActivity(), action, status)); } @@ -2925,6 +2896,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { service.sendChatState(conversation); } + if (storeNextMessage()) { + activity.onConversationsListItemUpdated(); + } updateSendButton(); } @@ -3084,23 +3058,33 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke reInit(conversation); ScrollState scrollState = pendingScrollState.pop(); String lastMessageUuid = pendingLastMessageUuid.pop(); + List<Attachment> attachments = pendingMediaPreviews.pop(); if (scrollState != null) { setScrollPosition(scrollState, lastMessageUuid); } + if (attachments != null && attachments.size() > 0) { + Log.d(Config.LOGTAG, "had attachments on restore"); + mediaPreviewAdapter.addMediaPreviews(attachments); + toggleInputMethod(); + } return true; } private void clearPending() { - if (postponedActivityResult.pop() != null) { + if (postponedActivityResult.clear()) { Log.e(Config.LOGTAG, "cleared pending intent with unhandled result left"); } - pendingScrollState.pop(); - if (pendingTakePhotoUri.pop() != null) { + if (pendingScrollState.clear()) { + Log.e(Config.LOGTAG,"cleared scroll state"); + } + if (pendingTakePhotoUri.clear()) { Log.e(Config.LOGTAG, "cleared pending photo uri"); } } - + private int messageInputBubble() { + return activity.isDarkTheme() ? R.drawable.message_bubble_sent_blue_dark : R.drawable.message_bubble_sent_blue; + } public Conversation getConversation() { return conversation; diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java index 11d4cc31f..8d8e5386a 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java @@ -63,6 +63,7 @@ import net.java.otr4j.session.SessionStatus; import org.openintents.openpgp.util.OpenPgpApi; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -111,6 +112,11 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio public static final String ACTION_DESTROY_MUC = "de.pixart.messenger.DESTROY_MUC"; public static final int REQUEST_OPEN_MESSAGE = 0x9876; public static final int REQUEST_PLAY_PAUSE = 0x5432; + private static List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList( + ACTION_VIEW_CONVERSATION, + Intent.ACTION_SEND, + Intent.ACTION_SEND_MULTIPLE + ); private boolean showLastSeen = false; @@ -126,8 +132,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private boolean mActivityPaused = true; private AtomicBoolean mRedirectInProcess = new AtomicBoolean(false); - private static boolean isViewIntent(Intent i) { - return i != null && ACTION_VIEW_CONVERSATION.equals(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION); + private static boolean isViewOrShareIntent(Intent i) { + Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction())); + return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION); } private static Intent createLauncherIntent(Context context) { @@ -451,7 +458,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } else { intent = savedInstanceState.getParcelable("intent"); } - if (isViewIntent(intent)) { + if (isViewOrShareIntent(intent)) { pendingViewIntent.push(intent); setIntent(createLauncherIntent(this)); } @@ -483,6 +490,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override public void onConversationSelected(Conversation conversation) { + clearPendingViewIntent(); if (ConversationFragment.getConversation(this) == conversation) { Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open"); return; @@ -490,6 +498,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio openConversation(conversation, null); } + public void clearPendingViewIntent() { + if (pendingViewIntent.clear()) { + Log.e(Config.LOGTAG, "cleared pending view intent"); + } + } + private void displayToast(final String msg) { runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show()); } @@ -626,7 +640,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio @Override protected void onNewIntent(final Intent intent) { - if (isViewIntent(intent)) { + if (isViewOrShareIntent(intent)) { if (xmppConnectionService != null) { processViewIntent(intent); } else { diff --git a/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java b/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java new file mode 100644 index 000000000..4b32b42ab --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/MediaBrowserActivity.java @@ -0,0 +1,77 @@ +package de.pixart.messenger.ui; + +import android.content.Context; +import android.content.Intent; +import android.databinding.DataBindingUtil; +import android.os.Bundle; +import android.support.v7.widget.Toolbar; + +import java.util.List; + +import de.pixart.messenger.R; +import de.pixart.messenger.databinding.ActivityMediaBrowserBinding; +import de.pixart.messenger.entities.Account; +import de.pixart.messenger.entities.Contact; +import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.ui.adapter.MediaAdapter; +import de.pixart.messenger.ui.interfaces.OnMediaLoaded; +import de.pixart.messenger.ui.util.Attachment; +import de.pixart.messenger.ui.util.GridManager; +import rocks.xmpp.addr.Jid; + + +public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded { + + private ActivityMediaBrowserBinding binding; + + private MediaAdapter mMediaAdapter; + + public static void launch(Context context, Contact contact) { + launch(context, contact.getAccount(), contact.getJid().asBareJid().toEscapedString()); + } + + public static void launch(Context context, Conversation conversation) { + launch(context, conversation.getAccount(), conversation.getJid().asBareJid().toEscapedString()); + } + + private static void launch(Context context, Account account, String jid) { + final Intent intent = new Intent(context, MediaBrowserActivity.class); + intent.putExtra("account", account.getUuid()); + intent.putExtra("jid", jid); + context.startActivity(intent); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_media_browser); + setSupportActionBar((Toolbar) binding.toolbar); + configureActionBar(getSupportActionBar()); + mMediaAdapter = new MediaAdapter(this, R.dimen.media_size); + this.binding.media.setAdapter(mMediaAdapter); + GridManager.setupLayoutManager(this, this.binding.media, R.dimen.browser_media_size); + + } + + @Override + protected void refreshUiReal() { + + } + + @Override + void onBackendConnected() { + Intent intent = getIntent(); + String account = intent == null ? null : intent.getStringExtra("account"); + String jid = intent == null ? null : intent.getStringExtra("jid"); + if (account != null && jid != null) { + xmppConnectionService.getAttachments(account, Jid.of(jid), 0, this); + } + } + + @Override + public void onMediaLoaded(List<Attachment> attachments) { + runOnUiThread(() -> { + mMediaAdapter.setAttachments(attachments); + }); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java index 455bbf3c3..31dc1681a 100644 --- a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java +++ b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java @@ -127,7 +127,7 @@ public class RecordingActivity extends Activity implements View.OnClickListener private static File generateOutputFilename(Context context) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); - return new File(FileBackend.getConversationsDirectory("Audios", true) + "/" + return new File(FileBackend.getConversationsDirectory("Audios") + dateFormat.format(new Date()) + ".m4a"); } diff --git a/src/main/java/de/pixart/messenger/ui/SearchActivity.java b/src/main/java/de/pixart/messenger/ui/SearchActivity.java index cc3172670..e9ba1d426 100644 --- a/src/main/java/de/pixart/messenger/ui/SearchActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SearchActivity.java @@ -56,12 +56,11 @@ import de.pixart.messenger.services.MessageSearchTask; import de.pixart.messenger.ui.adapter.MessageAdapter; import de.pixart.messenger.ui.interfaces.OnSearchResultsAvailable; import de.pixart.messenger.ui.util.ChangeWatcher; -import de.pixart.messenger.ui.util.Color; import de.pixart.messenger.ui.util.DateSeparator; -import de.pixart.messenger.ui.util.Drawable; import de.pixart.messenger.ui.util.ListViewUtils; import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.ui.util.ShareUtil; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.utils.FtsUtils; import de.pixart.messenger.utils.MessageUtils; @@ -213,12 +212,12 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc private void changeBackground(boolean hasSearch, boolean hasResults) { if (hasSearch) { if (hasResults) { - binding.searchResults.setBackgroundColor(Color.get(this, R.attr.color_background_secondary)); + binding.searchResults.setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_secondary)); } else { - binding.searchResults.setBackground(Drawable.get(this, R.attr.activity_background_no_results)); + binding.searchResults.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_background_no_results)); } } else { - binding.searchResults.setBackground(Drawable.get(this, R.attr.activity_background_search)); + binding.searchResults.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_background_search)); } } diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java index 4bf9d26cc..1c0992cf1 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java @@ -38,7 +38,7 @@ import de.pixart.messenger.crypto.OmemoSetting; import de.pixart.messenger.entities.Account; import de.pixart.messenger.services.ExportLogsService; import de.pixart.messenger.services.MemorizingTrustManager; -import de.pixart.messenger.ui.util.Color; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.utils.TimeframeUtils; import rocks.xmpp.addr.Jid; @@ -60,6 +60,7 @@ public class SettingsActivity extends XmppActivity implements public static final String USE_BUNDLED_EMOJIS = "use_bundled_emoji"; public static final String USE_MULTI_ACCOUNTS = "use_multi_accounts"; public static final String QUICK_SHARE_ATTACHMENT_CHOICE = "quick_share_attachment_choice"; + public static final String NUMBER_OF_ACCOUNTS = "number_of_accounts"; public static final int REQUEST_WRITE_LOGS = 0xbf8701; Preference multiAccountPreference; @@ -75,6 +76,7 @@ public class SettingsActivity extends XmppActivity implements super.onCreate(savedInstanceState); this.mTheme = findTheme(); setTheme(this.mTheme); + updateTheme(); setContentView(R.layout.activity_settings); FragmentManager fm = getFragmentManager(); mSettingsFragment = (SettingsFragment) fm.findFragmentById(R.id.settings_content); @@ -83,7 +85,7 @@ public class SettingsActivity extends XmppActivity implements fm.beginTransaction().replace(R.id.settings_content, mSettingsFragment).commit(); } mSettingsFragment.setActivityIntent(getIntent()); - getWindow().getDecorView().setBackgroundColor(Color.get(this, R.attr.color_background_secondary)); + getWindow().getDecorView().setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_secondary)); setSupportActionBar(findViewById(R.id.toolbar)); configureActionBar(getSupportActionBar()); } @@ -96,12 +98,11 @@ public class SettingsActivity extends XmppActivity implements @Override public void onStart() { super.onStart(); - updateTheme(); - PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); multiAccountPreference = mSettingsFragment.findPreference("enable_multi_accounts"); if (multiAccountPreference != null) { isMultiAccountChecked = ((CheckBoxPreference) multiAccountPreference).isChecked(); + handleMultiAccountChanges(); } BundledEmojiPreference = mSettingsFragment.findPreference("use_bundled_emoji"); @@ -112,7 +113,7 @@ public class SettingsActivity extends XmppActivity implements QuickShareAttachmentChoicePreference = mSettingsFragment.findPreference("quick_share_attachment_choice"); if (QuickShareAttachmentChoicePreference != null) { QuickShareAttachmentChoicePreference.setOnPreferenceChangeListener((preference, newValue) -> { - recreate(); + refreshUiReal(); return true; }); isQuickShareAttachmentChoiceChecked = ((CheckBoxPreference) QuickShareAttachmentChoicePreference).isChecked(); @@ -287,21 +288,20 @@ public class SettingsActivity extends XmppActivity implements Log.d(Config.LOGTAG, "Multi account checkbox checked: " + isMultiAccountChecked); if (isMultiAccountChecked) { enableMultiAccountsPreference.setEnabled(false); - if (xmppConnectionService != null) { - final List<Account> accounts = xmppConnectionService.getAccounts(); - Log.d(Config.LOGTAG, "Disabled multi account: Number of accounts " + accounts.size()); - if (accounts.size() > 1) { - Log.d(Config.LOGTAG, "Disabled multi account not possible because you have more than one account"); + int accounts = getNumberOfAccounts(); + Log.d(Config.LOGTAG, "Disabled multi account: Number of accounts " + accounts); + if (accounts > 1) { + Log.d(Config.LOGTAG, "Disabling multi account not possible because you have more than one account"); enableMultiAccountsPreference.setEnabled(false); } else { - Log.d(Config.LOGTAG, "Disabled multi account possible because you have one account"); + Log.d(Config.LOGTAG, "Disabling multi account possible because you have only one account"); enableMultiAccountsPreference.setEnabled(true); + enableMultiAccountsPreference.setOnPreferenceClickListener(preference -> { + refreshUiReal(); + return true; + }); } } else { - Log.d(Config.LOGTAG, "Disabled multi account not possible because XmppConnectionService == null"); - enableMultiAccountsPreference.setEnabled(false); - } - } else { enableMultiAccountsPreference.setEnabled(true); enableMultiAccountsPreference.setOnPreferenceClickListener(preference -> { enableMultiAccounts(); @@ -314,7 +314,7 @@ public class SettingsActivity extends XmppActivity implements private void updateTheme() { final int theme = findTheme(); if (this.mTheme != theme) { - recreate(); + refreshUiReal(); } } @@ -478,12 +478,17 @@ public class SettingsActivity extends XmppActivity implements SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE); SharedPreferences.Editor editor = multiaccount_prefs.edit(); editor.putString("BackupPW", pw1); - editor.commit(); + boolean passwordstored = editor.commit(); + Log.d(Config.LOGTAG, "saving multiaccount password " + passwordstored); + if (passwordstored) { + recreate(); + } else { + handleMultiAccountChanges(); + } } }) .setNegativeButton(R.string.cancel, null); alertDialogBuilder.create().show(); - } @@ -558,6 +563,39 @@ public class SettingsActivity extends XmppActivity implements } public void refreshUiReal() { - //nothing to do. This Activity doesn't implement any listeners + recreate(); + handleMultiAccountChanges(); + } + + private void handleMultiAccountChanges() { + multiAccountPreference = mSettingsFragment.findPreference("enable_multi_accounts"); + if (multiAccountPreference != null) { + //check if password = null + final SharedPreferences multiaccount_prefs = getApplicationContext().getSharedPreferences(USE_MULTI_ACCOUNTS, Context.MODE_PRIVATE); + if (multiaccount_prefs != null && multiaccount_prefs.getString("BackupPW", null) == null) { + Log.d(Config.LOGTAG, "uncheck multiaccount because password = null"); + if (multiAccountPreference != null) { + ((CheckBoxPreference) multiAccountPreference).setChecked(false); + } + } + //if multiAccountDisabled reset password + final Preference enableMultiAccountsPreference = mSettingsFragment.findPreference("enable_multi_accounts"); + if (enableMultiAccountsPreference != null && !isMultiAccountChecked) { + SharedPreferences.Editor editor = multiaccount_prefs.edit(); + editor.putString("BackupPW", null); + if (editor.commit()) { + Log.d(Config.LOGTAG, "resetting multiaccount password because multiaccount = unchecked"); + } else { + Log.d(Config.LOGTAG, "resetting multiaccount password failed"); + } + } + } + } + + private int getNumberOfAccounts() { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + int NumberOfAccounts = preferences.getInt(NUMBER_OF_ACCOUNTS, 0); + Log.d(Config.LOGTAG, "Get number of accounts from file: " + NumberOfAccounts); + return NumberOfAccounts; } } diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java index a820378a3..da3d2ff8f 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java @@ -1,6 +1,5 @@ package de.pixart.messenger.ui; -import android.app.PendingIntent; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -12,116 +11,32 @@ import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; -import java.net.URLConnection; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Conversation; -import de.pixart.messenger.entities.Message; -import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.ui.adapter.ConversationAdapter; -import de.pixart.messenger.ui.util.PresenceSelector; -import de.pixart.messenger.xmpp.XmppConnection; import rocks.xmpp.addr.Jid; import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; -import static java.lang.String.format; public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate { private static final int REQUEST_STORAGE_PERMISSION = 0x733f32; - private boolean mReturnToPrevious = false; + private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; private Conversation mPendingConversation = null; - - @Override - public void onConversationUpdate() { - refreshUi(); - } - - private class Share { - public List<Uri> uris = new ArrayList<>(); - public boolean image; - public String account; - public String contact; - public String text; - public String uuid; - public boolean multiple = false; - public String type; - } - private Share share; - - private static final int REQUEST_START_NEW_CONVERSATION = 0x0501; - private RecyclerView mListView; private ConversationAdapter mAdapter; private List<Conversation> mConversations = new ArrayList<>(); - private Toast mToast; - private AtomicInteger attachmentCounter = new AtomicInteger(0); - - private UiInformableCallback<Message> attachFileCallback = new UiInformableCallback<Message>() { - - @Override - public void inform(final String text) { - runOnUiThread(() -> replaceToast(text)); - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - // TODO Auto-generated method stub - - } - - @Override - public void success(final Message message) { - runOnUiThread(() -> { - if (attachmentCounter.decrementAndGet() <= 0) { - int resId; - if (share.image && share.multiple) { - resId = R.string.shared_images_with_x; - } else if (share.image) { - resId = R.string.shared_image_with_x; - } else { - resId = R.string.shared_file_with_x; - } - Conversation conversation = (Conversation) message.getConversation(); - replaceToast(getString(resId, conversation.getName())); - if (mReturnToPrevious) { - finish(); - } else { - switchToConversation(conversation); - } - } - }); - } - - @Override - public void error(final int errorCode, Message object) { - runOnUiThread(() -> { - replaceToast(getString(errorCode)); - if (attachmentCounter.decrementAndGet() <= 0) { - finish(); - } - }); - } - }; - - protected void hideToast() { - if (mToast != null) { - mToast.cancel(); - } - } - protected void replaceToast(String msg) { - hideToast(); - mToast = Toast.makeText(this, msg, Toast.LENGTH_LONG); - mToast.show(); + @Override + public void onConversationUpdate() { + refreshUi(); } protected void onActivityResult(int requestCode, int resultCode, final Intent data) { @@ -160,20 +75,16 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer super.onCreate(savedInstanceState); boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); new EmojiService(this).init(useBundledEmoji); - setContentView(R.layout.activity_share_with); - setSupportActionBar(findViewById(R.id.toolbar)); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setHomeButtonEnabled(false); } - setTitle(getString(R.string.title_activity_sharewith)); - - mListView = findViewById(R.id.choose_conversation_list); + RecyclerView mListView = findViewById(R.id.choose_conversation_list); mAdapter = new ConversationAdapter(this, this.mConversations); - mListView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)); + mListView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); mListView.setAdapter(mAdapter); mAdapter.setConversationClickListener((view, conversation) -> share(conversation)); this.share = new Share(); @@ -203,57 +114,28 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer if (intent == null) { return; } - this.mReturnToPrevious = getBooleanPreference("return_to_previous", R.bool.return_to_previous); final String type = intent.getType(); final String action = intent.getAction(); - Log.d(Config.LOGTAG, "action: " + action + ", type:" + type); - share.uuid = intent.getStringExtra("uuid"); if (Intent.ACTION_SEND.equals(action)) { - final String subject = intent.getStringExtra(Intent.EXTRA_SUBJECT); final String text = intent.getStringExtra(Intent.EXTRA_TEXT); final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); if (type != null && uri != null && (text == null || !type.equals("text/plain"))) { this.share.uris.clear(); this.share.uris.add(uri); - this.share.image = type.startsWith("image/") || isImage(uri); - this.share.type = type; } else { - if (subject != null) { - this.share.text = format("[%s]%n%s", subject, text); - } else { - this.share.text = text; - } + this.share.text = text; } } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) { - this.share.image = type != null && type.startsWith("image/"); - if (!this.share.image) { - return; - } this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); } if (xmppConnectionServiceBound) { - if (share.uuid != null) { - share(); - } else { - xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); - } - } - - } - - protected boolean isImage(Uri uri) { - try { - String guess = URLConnection.guessContentTypeFromName(uri.toString()); - return (guess != null && guess.startsWith("image/")); - } catch (final StringIndexOutOfBoundsException ignored) { - return false; + xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0); } } @Override void onBackendConnected() { - if (xmppConnectionServiceBound && share != null - && ((share.contact != null && share.account != null) || share.uuid != null)) { + if (xmppConnectionServiceBound && share != null && ((share.contact != null && share.account != null))) { share(); return; } @@ -262,28 +144,19 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer private void share() { final Conversation conversation; - if (share.uuid != null) { - conversation = xmppConnectionService.findConversationByUuid(share.uuid); - if (conversation == null) { - return; - } - } else { - Account account; - try { - account = xmppConnectionService.findAccountByJid(Jid.of(share.account)); - } catch (final IllegalArgumentException e) { - account = null; - } - if (account == null) { - return; - } - - try { - conversation = xmppConnectionService - .findOrCreateConversation(account, Jid.of(share.contact), false, true); - } catch (final IllegalArgumentException e) { - return; - } + Account account; + try { + account = xmppConnectionService.findAccountByJid(Jid.of(share.account)); + } catch (final IllegalArgumentException e) { + account = null; + } + if (account == null) { + return; + } + try { + conversation = xmppConnectionService.findOrCreateConversation(account, Jid.of(share.contact), false, true); + } catch (final IllegalArgumentException e) { + return; } share(conversation); } @@ -293,110 +166,18 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer mPendingConversation = conversation; return; } - final Account account = conversation.getAccount(); - final XmppConnection connection = account.getXmppConnection(); - final long max = connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize(); - mListView.setEnabled(false); - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) { - if (share.uuid == null) { - showInstallPgpDialog(); - } else { - Toast.makeText(this, R.string.openkeychain_not_installed, Toast.LENGTH_SHORT).show(); - finish(); - } - return; + Intent intent = new Intent(this, ConversationsActivity.class); + intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); + if (share.uris.size() > 0) { + intent.setAction(Intent.ACTION_SEND_MULTIPLE); + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else if (share.text != null) { + intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION); + intent.putExtra(ConversationsActivity.EXTRA_TEXT, share.text); } - if (share.uris.size() != 0) { - PresenceSelector.OnPresenceSelected callback = () -> { - attachmentCounter.set(share.uris.size()); - if (share.image) { - Log.d(Config.LOGTAG, "ShareWithActivity share() image " + share.uris.size() + " uri(s) " + share.uris.toString()); - share.multiple = share.uris.size() > 1; - replaceToast(getString(share.multiple ? R.string.preparing_images : R.string.preparing_image)); - for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) { - final Uri uri = i.next(); - delegateUriPermissionsToService(uri); - xmppConnectionService.attachImageToConversation(conversation, uri, attachFileCallback); - } - } else { - Log.d(Config.LOGTAG, "ShareWithActivity share() file " + share.uris.size() + " uri(s) " + share.uris.toString()); - replaceToast(getString(R.string.preparing_file)); - final Uri uri = share.uris.get(0); - delegateUriPermissionsToService(uri); - xmppConnectionService.attachFileToConversation(conversation, uri, share.type, attachFileCallback); - finish(); - } - }; - if (account.httpUploadAvailable() - && ((share.image && !neverCompressPictures()) - || conversation.getMode() == Conversation.MODE_MULTI - || FileBackend.allFilesUnderSize(this, share.uris, max)) - && conversation.getNextEncryption() != Message.ENCRYPTION_OTR) { - callback.onPresenceSelected(); - } else { - selectPresence(conversation, callback); - } - } else { - if (mReturnToPrevious && this.share.text != null && !this.share.text.isEmpty()) { - final PresenceSelector.OnPresenceSelected callback = new PresenceSelector.OnPresenceSelected() { - - private void finishAndSend(Message message) { - replaceToast(getString(R.string.shared_text_with_x, conversation.getName())); - finish(); - } - - private UiCallback<Message> messageEncryptionCallback = new UiCallback<Message>() { - @Override - public void success(final Message message) { - runOnUiThread(() -> finishAndSend(message)); - } - - @Override - public void error(final int errorCode, Message object) { - runOnUiThread(() -> { - replaceToast(getString(errorCode)); - finish(); - }); - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - finish(); - } - }; - - @Override - public void onPresenceSelected() { - - final int encryption = conversation.getNextEncryption(); - - Message message = new Message(conversation,share.text, encryption); - - Log.d(Config.LOGTAG,"on presence selected encrpytion="+encryption); - - if (encryption == Message.ENCRYPTION_PGP) { - replaceToast(getString(R.string.encrypting_message)); - xmppConnectionService.getPgpEngine().encrypt(message,messageEncryptionCallback); - return; - } - - if (encryption == Message.ENCRYPTION_OTR) { - message.setCounterpart(conversation.getNextCounterpart()); - } - xmppConnectionService.sendMessage(message); - finishAndSend(message); - } - }; - if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) { - selectPresence(conversation, callback); - } else { - callback.onPresenceSelected(); - } - } else { - switchToConversation(conversation, this.share.text, true); - } - } - + startActivity(intent); + finish(); } public void refreshUiReal() { @@ -404,12 +185,10 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer mAdapter.notifyDataSetChanged(); } - @Override - public void onBackPressed() { - if (attachmentCounter.get() >= 1) { - replaceToast(getString(R.string.sharing_files_please_wait)); - } else { - super.onBackPressed(); - } + private class Share { + public String account; + public String contact; + public String text; + ArrayList<Uri> uris = new ArrayList<>(); } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java index 959060101..9e35d7ebf 100644 --- a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java +++ b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java @@ -114,7 +114,7 @@ public class UpdaterActivity extends XmppActivity { store = null; } //delete old downloaded localVersion files - File dir = new File(FileBackend.getConversationsDirectory("Update", false)); + File dir = new File(FileBackend.getAppUpdateDirectory()); if (dir.isDirectory()) { String[] children = dir.list(); for (String aChildren : children) { @@ -291,7 +291,7 @@ public class UpdaterActivity extends XmppActivity { private class DownloadTask extends AsyncTask<String, Integer, String> { - File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + FileBackend.getDirectoryName("Update", false)); + File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + FileBackend.getAppUpdateDirectory()); File file = new File(dir, FileName); private Context context; private PowerManager.WakeLock mWakeLock; diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java index 9d5b76c1c..e2bf9d8e9 100644 --- a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java +++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java @@ -193,7 +193,7 @@ public class WelcomeActivity extends XmppActivity { private boolean BackupAvailable() { // Set the folder on the SDcard - File filePath = new File(FileBackend.getConversationsDirectory("Database", false) + "database.db.crypt"); + File filePath = new File(FileBackend.getBackupDirectory() + "database.db.crypt"); Log.d(Config.LOGTAG, "DB Path: " + filePath.toString()); if (filePath.exists()) { Log.d(Config.LOGTAG, "DB Path existing"); @@ -206,11 +206,11 @@ public class WelcomeActivity extends XmppActivity { private void checkDatabase(String DecryptionKey) throws IOException { // Set the folder on the SDcard - File directory = new File(FileBackend.getConversationsDirectory("Database", false)); + File directory = new File(FileBackend.getBackupDirectory()); // Set the input file stream up: - FileInputStream InputFile = new FileInputStream(directory.getPath() + "/database.db.crypt"); + FileInputStream InputFile = new FileInputStream(directory.getPath() + "database.db.crypt"); // Temp output for DB checks - File TempFile = new File(directory.getPath() + "/database.bak"); + File TempFile = new File(directory.getPath() + "database.bak"); FileOutputStream OutputTemp = new FileOutputStream(TempFile); try { @@ -286,11 +286,11 @@ public class WelcomeActivity extends XmppActivity { // Set location for the db: final OutputStream OutputFile = new FileOutputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME)); // Set the folder on the SDcard - File directory = new File(FileBackend.getConversationsDirectory("Database", false)); + File directory = new File(FileBackend.getBackupDirectory()); // Set the input file stream up: - final InputStream InputFile = new FileInputStream(directory.getPath() + "/database.bak"); + final InputStream InputFile = new FileInputStream(directory.getPath() + "database.bak"); //set temp file - File TempFile = new File(directory.getPath() + "/database.bak"); + File TempFile = new File(directory.getPath() + "database.bak"); // Transfer bytes from the input file to the output file byte[] buffer = new byte[1024]; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java index 01b432d65..e37ea6121 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java @@ -24,7 +24,7 @@ import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; import de.pixart.messenger.ui.ManageAccountActivity; import de.pixart.messenger.ui.XmppActivity; -import de.pixart.messenger.ui.util.Color; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.utils.UIHelper; public class AccountAdapter extends ArrayAdapter<Account> { @@ -65,14 +65,14 @@ public class AccountAdapter extends ArrayAdapter<Account> { statusView.setText(getContext().getString(account.getStatus().getReadableId())); switch (account.getStatus()) { case ONLINE: - statusView.setTextColor(Color.get(activity, R.attr.TextColorOnline)); + statusView.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline)); break; case DISABLED: case CONNECTING: - statusView.setTextColor(Color.get(activity, android.R.attr.textColorSecondary)); + statusView.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary)); break; default: - statusView.setTextColor(Color.get(activity, R.attr.TextColorError)); + statusView.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError)); break; } final SwitchCompat tglAccountState = view.findViewById(R.id.tgl_account_status); diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java index 5294ba7cd..55dd6d65f 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java @@ -31,7 +31,7 @@ import de.pixart.messenger.entities.MucOptions; import de.pixart.messenger.entities.Transferable; import de.pixart.messenger.ui.ConversationFragment; import de.pixart.messenger.ui.XmppActivity; -import de.pixart.messenger.ui.util.Color; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.ui.widget.UnreadCountCustomView; import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.IrregularUnicodeDetector; @@ -97,9 +97,9 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte } if (conversation == ConversationFragment.getConversation(activity)) { - viewHolder.frame.setBackgroundColor(Color.get(activity, R.attr.color_background_tertiary)); + viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary)); } else { - viewHolder.frame.setBackgroundColor(Color.get(activity,R.attr.color_background_primary)); + viewHolder.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_secondary)); } Message message = conversation.getLatestMessage(); @@ -250,11 +250,11 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte viewHolder.name.setTextColor(ContextCompat.getColor(activity, R.color.notavailable)); break; default: - viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main)); + viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main)); break; } } else { - viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main)); + viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main)); } if (activity.xmppConnectionService.indicateReceived()) { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java index 9f4a52d95..2e0bfccc6 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java @@ -7,8 +7,6 @@ import android.widget.Filter; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Locale; public class KnownHostsAdapter extends ArrayAdapter<String> { @@ -17,34 +15,31 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { @Override protected FilterResults performFiltering(CharSequence constraint) { - if (constraint != null) { - ArrayList<String> suggestions = new ArrayList<>(); - final String[] split = constraint.toString().split("@"); - if (split.length == 1) { - for (String domain : domains) { - suggestions.add(split[0].toLowerCase(Locale - .getDefault()) + "@" + domain); - } - } else if (split.length == 2) { - for (String domain : domains) { - if (domain.contentEquals(split[1])) { - suggestions.clear(); - break; - } else if (domain.contains(split[1])) { - suggestions.add(split[0].toLowerCase(Locale - .getDefault()) + "@" + domain); - } - } - } else { + final ArrayList<String> suggestions = new ArrayList<>(); + final String[] split = constraint == null ? new String[0] : constraint.toString().split("@"); + if (split.length == 1) { + final String local = split[0].toLowerCase(Locale.ENGLISH); + for (String domain : domains) { + suggestions.add(local + "@" + domain); + } + } else if (split.length == 2) { + final String localPart = split[0].toLowerCase(Locale.ENGLISH); + final String domainPart = split[1].toLowerCase(Locale.ENGLISH); + if (domains.contains(domainPart)) { return new FilterResults(); } - FilterResults filterResults = new FilterResults(); - filterResults.values = suggestions; - filterResults.count = suggestions.size(); - return filterResults; + for (String domain : domains) { + if (domain.contains(domainPart)) { + suggestions.add(localPart + "@" + domain); + } + } } else { return new FilterResults(); } + FilterResults filterResults = new FilterResults(); + filterResults.values = suggestions; + filterResults.count = suggestions.size(); + return filterResults; } @Override @@ -52,9 +47,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { ArrayList filteredList = (ArrayList) results.values; if (results.count > 0) { clear(); - for (Object c : filteredList) { - add((String) c); - } + addAll(filteredList); notifyDataSetChanged(); } } @@ -62,140 +55,7 @@ public class KnownHostsAdapter extends ArrayAdapter<String> { public KnownHostsAdapter(Context context, int viewResourceId, Collection<String> mKnownHosts) { super(context, viewResourceId, new ArrayList<>()); - - if (mKnownHosts == null) { - domains = new ArrayList<>(); - } else { - domains = new ArrayList<>(mKnownHosts); - } - - HashSet<String> hashSet = new HashSet<>(); - - // get servers from https://conversations.im/compliance/ - new Thread(() -> { - domains.add("pix-art.de"); - domains.add("conversations.im"); - domains.add("jabber.cat"); - domains.add("jabjab.de"); - domains.add("im.koderoot.net"); - domains.add("riotcat.org"); - domains.add("magicbroccoli.de"); - domains.add("kode.im"); - domains.add("jabber-germany.de"); - domains.add("simplewire.de"); - domains.add("suchat.org"); - domains.add("jabber.at"); - domains.add("trashserver.net"); - domains.add("wiuwiu.de"); - domains.add("5222.de"); - domains.add("dismail.de"); - domains.add("chat.sum7.eu"); - domains.add("xmpp.zone"); - domains.add("libranet.de"); - domains.add("laborversuch.de"); - domains.add("creep.im"); - domains.add("jabber.systemausfall.org"); - domains.add("jabber.hot-chilli.net"); - domains.add("jabber.fr"); - domains.add("jabber.de"); - domains.add("draugr.de"); - domains.add("elaon.de"); - domains.add("high-way.me"); - domains.add("jabber.rwth-aachen.de"); - domains.add("deshalbfrei.org"); - domains.add("mail.de"); - domains.add("bommboo.de"); - domains.add("jabber.systemli.org"); - domains.add("jabb.im"); - domains.add("mailbox.org"); - domains.add("hot-chilli.net"); - domains.add("jabberpl.org"); - domains.add("chinwag.im"); - domains.add("tchncs.de"); - domains.add("zsim.de"); - domains.add("patchcord.be"); - domains.add("gajim.org"); - domains.add("talker.to"); - domains.add("pimux.de"); - domains.add("jabber.home.vdlinde.org"); - domains.add("im.apinc.org"); - domains.add("chatme.im"); - domains.add("fusselkater.org"); - domains.add("datenknoten.me"); - domains.add("fysh.in"); - domains.add("jabber.chaos-darmstadt.de"); - domains.add("yax.im"); - domains.add("neko.im"); - domains.add("jabberzac.org"); - domains.add("xmpp.is"); - domains.add("home.zom.im"); - domains.add("jabber.ccc.de"); - domains.add("jwchat.org"); - domains.add("kdetalk.net"); - domains.add("kde.org"); - domains.add("riseup.net"); - domains.add("ruhr-uni-bochum.de"); - domains.add("njs.netlab.cz"); - domains.add("schokokeks.org"); - domains.add("jabber.cz"); - domains.add("ubuntu-jabber.de"); - domains.add("xabber.de"); - domains.add("ubuntu-jabber.net"); - domains.add("jabber.ru"); - domains.add("darknet.nz"); - domains.add("movim.eu"); - domains.add("404.city"); - domains.add("igniterealtime.org"); - domains.add("kapsi.fi"); - domains.add("jabbel.net"); - domains.add("joindiaspora.com"); - domains.add("alpha-labs.net"); - domains.add("xmppnet.de"); - domains.add("hoth.one"); - domains.add("blah.im"); - domains.add("xmpp.jp"); - domains.add("jabber.uni-mainz.de"); - domains.add("richim.org"); - domains.add("tigase.im"); - domains.add("jappix.com"); - domains.add("member.fsf.org"); - domains.add("jabber.rueckgr.at"); - domains.add("swissjabber.ch"); - domains.add("twattle.net"); - domains.add("jabber.calyxinstitute.org"); - domains.add("sapo.pt"); - domains.add("uprod.biz"); - domains.add("krautspace.de"); - domains.add("kraut.space"); - domains.add("null.pm"); - domains.add("anonymitaet-im-inter.net"); - domains.add("0nl1ne.at"); - domains.add("linuxlovers.at"); - domains.add("jabber.org"); - domains.add("jabber.no-sense.net"); - domains.add("swissjabber.eu"); - domains.add("swissjabber.org"); - domains.add("swissjabber.de"); - domains.add("swissjabber.li"); - domains.add("jabber.no"); - domains.add("cypherpunks.it"); - domains.add("adastra.re"); - domains.add("jabber-br.org"); - domains.add("einfachjabber.de"); - domains.add("jabber.smash-net.org"); - domains.add("freifunk.im"); - domains.add("openmailbox.org"); - domains.add("jabber.otr.im"); - domains.add("evil.im"); - domains.add("xmpp.slack.com"); - domains.add("chat.hipchat.com"); - domains.add("googlemail.com"); - - hashSet.addAll(domains); - domains.clear(); - domains.addAll(hashSet); - Collections.sort(domains, String::compareToIgnoreCase); - }).start(); + domains = new ArrayList<>(mKnownHosts); } public KnownHostsAdapter(Context context, int viewResourceId) { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java index 64d432041..17479cf99 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java @@ -27,7 +27,7 @@ import de.pixart.messenger.databinding.ContactBinding; import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.XmppActivity; -import de.pixart.messenger.ui.util.Color; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.UIHelper; import rocks.xmpp.addr.Jid; @@ -105,16 +105,16 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } } if (offline) { - viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main)); + viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main)); viewHolder.name.setAlpha(INACTIVE_ALPHA); viewHolder.jid.setAlpha(INACTIVE_ALPHA); viewHolder.avatar.setAlpha(INACTIVE_ALPHA); viewHolder.tags.setAlpha(INACTIVE_ALPHA); } else { if (ShowPresenceColoredNames()) { - viewHolder.name.setTextColor(color != 0 ? color : Color.get(activity, R.attr.text_Color_Main)); + viewHolder.name.setTextColor(color != 0 ? color : StyledAttributes.getColor(activity, R.attr.text_Color_Main)); } else { - viewHolder.name.setTextColor(Color.get(activity, R.attr.text_Color_Main)); + viewHolder.name.setTextColor(StyledAttributes.getColor(activity, R.attr.text_Color_Main)); } viewHolder.name.setAlpha(ACTIVE_ALPHA); viewHolder.jid.setAlpha(ACTIVE_ALPHA); diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java new file mode 100644 index 000000000..0472c6d11 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaAdapter.java @@ -0,0 +1,226 @@ +package de.pixart.messenger.ui.adapter; + +import android.content.Context; +import android.content.res.Resources; +import android.databinding.DataBindingUtil; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.support.annotation.AttrRes; +import android.support.annotation.DimenRes; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + +import de.pixart.messenger.R; +import de.pixart.messenger.databinding.MediaBinding; +import de.pixart.messenger.ui.XmppActivity; +import de.pixart.messenger.ui.util.Attachment; +import de.pixart.messenger.ui.util.StyledAttributes; +import de.pixart.messenger.ui.util.ViewUtil; + +public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHolder> { + + private static final List<String> DOCUMENT_MIMES = Arrays.asList( + "application/pdf", + "application/vnd.oasis.opendocument.text", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "text/x-tex", + "text/plain" + ); + + private final ArrayList<Attachment> attachments = new ArrayList<>(); + + private final XmppActivity activity; + + private int mediaSize = 0; + + public MediaAdapter(XmppActivity activity, @DimenRes int mediaSize) { + this.activity = activity; + this.mediaSize = Math.round(activity.getResources().getDimension(mediaSize)); + } + + public static void setMediaSize(RecyclerView recyclerView, int mediaSize) { + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + if (adapter instanceof MediaAdapter) { + ((MediaAdapter) adapter).setMediaSize(mediaSize); + } + } + + private static @AttrRes + int getImageAttr(Attachment attachment) { + final @AttrRes int attr; + if (attachment.getType() == Attachment.Type.LOCATION) { + attr = R.attr.media_preview_location; + } else if (attachment.getType() == Attachment.Type.RECORDING) { + attr = R.attr.media_preview_recording; + } else { + final String mime = attachment.getMime(); + if (mime == null) { + attr = R.attr.media_preview_unknown; + } else if (mime.startsWith("audio/")) { + attr = R.attr.media_preview_audio; + } else if (mime.equals("text/calendar") || (mime.equals("text/x-vcalendar"))) { + attr = R.attr.media_preview_calendar; + } else if (mime.equals("text/x-vcard")) { + attr = R.attr.media_preview_contact; + } else if (mime.equals("application/vnd.android.package-archive")) { + attr = R.attr.media_preview_app; + } else if (mime.equals("application/zip") || mime.equals("application/rar")) { + attr = R.attr.media_preview_archive; + } else if (DOCUMENT_MIMES.contains(mime)) { + attr = R.attr.media_preview_document; + } else { + attr = R.attr.media_preview_unknown; + } + } + return attr; + } + + public static void renderPreview(Context context, Attachment attachment, ImageView imageView) { + imageView.setBackgroundColor(StyledAttributes.getColor(context, R.attr.color_background_tertiary)); + imageView.setImageAlpha(Math.round(StyledAttributes.getFloat(context, R.attr.icon_alpha) * 255)); + imageView.setImageDrawable(StyledAttributes.getDrawable(context, getImageAttr(attachment))); + } + + private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Attachment oldAttachment = bitmapWorkerTask.attachment; + if (oldAttachment == null || !oldAttachment.equals(attachment)) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + @NonNull + @Override + public MediaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + MediaBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media, parent, false); + return new MediaViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull MediaViewHolder holder, int position) { + final Attachment attachment = attachments.get(position); + if (attachment.renderThumbnail()) { + holder.binding.media.setImageAlpha(255); + loadPreview(attachment, holder.binding.media); + } else { + cancelPotentialWork(attachment, holder.binding.media); + renderPreview(activity, attachment, holder.binding.media); + } + holder.binding.media.setOnClickListener(v -> ViewUtil.view(activity, attachment)); + } + + public void setAttachments(List<Attachment> attachments) { + this.attachments.clear(); + this.attachments.addAll(attachments); + notifyDataSetChanged(); + } + + private void setMediaSize(int mediaSize) { + this.mediaSize = mediaSize; + } + + private void loadPreview(Attachment attachment, ImageView imageView) { + if (cancelPotentialWork(attachment, imageView)) { + final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment, mediaSize, true); + if (bm != null) { + cancelPotentialWork(attachment, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(0xff333333); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(attachment); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + @Override + public int getItemCount() { + return attachments.size(); + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + class MediaViewHolder extends RecyclerView.ViewHolder { + + private final MediaBinding binding; + + MediaViewHolder(MediaBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private Attachment attachment = null; + + BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Attachment... params) { + this.attachment = params[0]; + return activity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, mediaSize, false); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null && !isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java new file mode 100644 index 000000000..ae4c42816 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/adapter/MediaPreviewAdapter.java @@ -0,0 +1,188 @@ +package de.pixart.messenger.ui.adapter; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.databinding.DataBindingUtil; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.ImageView; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + +import de.pixart.messenger.R; +import de.pixart.messenger.databinding.MediaPreviewBinding; +import de.pixart.messenger.ui.ConversationFragment; +import de.pixart.messenger.ui.XmppActivity; +import de.pixart.messenger.ui.util.Attachment; + +public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapter.MediaPreviewViewHolder> { + + private final ArrayList<Attachment> mediaPreviews = new ArrayList<>(); + + private final ConversationFragment conversationFragment; + + public MediaPreviewAdapter(ConversationFragment fragment) { + this.conversationFragment = fragment; + } + + private static boolean cancelPotentialWork(Attachment attachment, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Attachment oldAttachment = bitmapWorkerTask.attachment; + if (oldAttachment == null || !oldAttachment.equals(attachment)) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + @NonNull + @Override + public MediaPreviewViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + MediaPreviewBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.media_preview, parent, false); + return new MediaPreviewViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull MediaPreviewViewHolder holder, int position) { + final Context context = conversationFragment.getActivity(); + final Attachment attachment = mediaPreviews.get(position); + if (attachment.renderThumbnail()) { + holder.binding.mediaPreview.setImageAlpha(255); + loadPreview(attachment, holder.binding.mediaPreview); + } else { + cancelPotentialWork(attachment, holder.binding.mediaPreview); + MediaAdapter.renderPreview(context, attachment, holder.binding.mediaPreview); + } + holder.binding.deleteButton.setOnClickListener(v -> { + int pos = mediaPreviews.indexOf(attachment); + mediaPreviews.remove(pos); + notifyItemRemoved(pos); + conversationFragment.toggleInputMethod(); + }); + } + + public void addMediaPreviews(List<Attachment> attachments) { + this.mediaPreviews.addAll(attachments); + notifyDataSetChanged(); + } + + private void loadPreview(Attachment attachment, ImageView imageView) { + if (cancelPotentialWork(attachment, imageView)) { + XmppActivity activity = (XmppActivity) conversationFragment.getActivity(); + final Bitmap bm = activity.xmppConnectionService.getFileBackend().getPreviewForUri(attachment, Math.round(activity.getResources().getDimension(R.dimen.media_preview_size)), true); + if (bm != null) { + cancelPotentialWork(attachment, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(0xff333333); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(conversationFragment.getActivity().getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(attachment); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + @Override + public int getItemCount() { + return mediaPreviews.size(); + } + + public boolean hasAttachments() { + return mediaPreviews.size() > 0; + } + + public ArrayList<Attachment> getAttachments() { + return mediaPreviews; + } + + public void clearPreviews() { + this.mediaPreviews.clear(); + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + class MediaPreviewViewHolder extends RecyclerView.ViewHolder { + + private final MediaPreviewBinding binding; + + MediaPreviewViewHolder(MediaPreviewBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + class BitmapWorkerTask extends AsyncTask<Attachment, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private Attachment attachment = null; + + BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Attachment... params) { + Activity activity = conversationFragment.getActivity(); + if (activity instanceof XmppActivity) { + final XmppActivity xmppActivity = (XmppActivity) activity; + this.attachment = params[0]; + return xmppActivity.xmppConnectionService.getFileBackend().getPreviewForUri(this.attachment, Math.round(xmppActivity.getResources().getDimension(R.dimen.media_preview_size)), false); + } else { + return null; + } + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null && !isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index b720375e7..b13ccb910 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -2,11 +2,9 @@ package de.pixart.messenger.ui.adapter; import android.Manifest; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; @@ -29,7 +27,6 @@ import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.util.Base64; import android.util.DisplayMetrics; -import android.util.Log; import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; @@ -72,11 +69,11 @@ import de.pixart.messenger.services.MessageArchiveService; import de.pixart.messenger.services.NotificationService; import de.pixart.messenger.ui.ConversationFragment; import de.pixart.messenger.ui.ConversationsActivity; -import de.pixart.messenger.ui.ShowFullscreenMessageActivity; import de.pixart.messenger.ui.XmppActivity; import de.pixart.messenger.ui.text.DividerSpan; import de.pixart.messenger.ui.text.QuoteSpan; import de.pixart.messenger.ui.util.MyLinkify; +import de.pixart.messenger.ui.util.ViewUtil; import de.pixart.messenger.ui.widget.ClickableMovementMethod; import de.pixart.messenger.ui.widget.CopyTextView; import de.pixart.messenger.ui.widget.ListSelectionManager; @@ -498,7 +495,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie } else { viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1); } - viewHolder.messageBody.setHighlightColor(darkBackground ? type == SENT ? ContextCompat.getColor(activity, R.color.black26) : ContextCompat.getColor(activity, R.color.grey800) : ContextCompat.getColor(activity, R.color.grey500)); + viewHolder.messageBody.setHighlightColor(darkBackground ? type == SENT ? ContextCompat.getColor(activity, R.color.accent) : ContextCompat.getColor(activity, R.color.accent) : ContextCompat.getColor(activity, R.color.accent)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); if (message.getBody() != null) { final String nick = UIHelper.getMessageDisplayName(message); @@ -1047,52 +1044,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie return; } String mime = file.getMimeType(); - if (mime.startsWith("image/")) { - Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class); - intent.putExtra("image", Uri.fromFile(file)); - try { - activity.startActivity(intent); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - return; - } catch (ActivityNotFoundException e) { - //ignored - } - } else if (mime.startsWith("video/")) { - Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class); - intent.putExtra("video", Uri.fromFile(file)); - try { - activity.startActivity(intent); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - return; - } catch (ActivityNotFoundException e) { - //ignored - } - } - Intent openIntent = new Intent(Intent.ACTION_VIEW); if (mime == null) { mime = "*/*"; } - Uri uri; - try { - uri = FileBackend.getUriForFile(activity, file); - } catch (SecurityException e) { - Log.d(Config.LOGTAG, "No permission to access " + file.getAbsolutePath(), e); - Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); - return; - } - openIntent.setDataAndType(uri, mime); - openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - PackageManager manager = activity.getPackageManager(); - List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0); - if (info.size() == 0) { - openIntent.setDataAndType(uri,"*/*"); - } - try { - getContext().startActivity(openIntent); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - } catch (ActivityNotFoundException e) { - Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); - } + ViewUtil.view(activity, file, mime); } public void showLocation(Message message) { diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java index adcc11809..0dfd86327 100644 --- a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java +++ b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java @@ -1,7 +1,6 @@ package de.pixart.messenger.ui.forms; import android.content.Context; -import android.support.v4.content.ContextCompat; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; @@ -11,6 +10,7 @@ import android.view.View; import java.util.List; import de.pixart.messenger.R; +import de.pixart.messenger.ui.util.StyledAttributes; import de.pixart.messenger.xmpp.forms.Field; public abstract class FormFieldWrapper { @@ -18,9 +18,9 @@ public abstract class FormFieldWrapper { protected final Context context; protected final Field field; protected final View view; - protected OnFormFieldValuesEdited onFormFieldValuesEditedListener; + OnFormFieldValuesEdited onFormFieldValuesEditedListener; - protected FormFieldWrapper(Context context, Field field) { + FormFieldWrapper(Context context, Field field) { this.context = context; this.field = field; LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -58,7 +58,7 @@ public abstract class FormFieldWrapper { int start = label.length(); int end = label.length() + 2; spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); - spannableString.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.accent)), start, end, 0); + spannableString.setSpan(new ForegroundColorSpan(StyledAttributes.getColor(context, R.color.accent)), start, end, 0); } return spannableString; } diff --git a/src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java b/src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java new file mode 100644 index 000000000..0441be96d --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/interfaces/OnMediaLoaded.java @@ -0,0 +1,10 @@ +package de.pixart.messenger.ui.interfaces; + +import java.util.List; + +import de.pixart.messenger.ui.util.Attachment; + +public interface OnMediaLoaded { + + void onMediaLoaded(List<Attachment> attachments); +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/Attachment.java b/src/main/java/de/pixart/messenger/ui/util/Attachment.java new file mode 100644 index 000000000..96159c1bd --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/Attachment.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package de.pixart.messenger.ui.util; + +import android.content.ClipData; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import de.pixart.messenger.Config; +import de.pixart.messenger.utils.MimeUtils; + +public class Attachment implements Parcelable { + public static final Creator<Attachment> CREATOR = new Parcelable.Creator<Attachment>() { + @Override + public Attachment createFromParcel(Parcel in) { + return new Attachment(in); + } + + @Override + public Attachment[] newArray(int size) { + return new Attachment[size]; + } + }; + private final Uri uri; + private final Type type; + private final UUID uuid; + private final String mime; + + Attachment(Parcel in) { + uri = in.readParcelable(Uri.class.getClassLoader()); + mime = in.readString(); + uuid = UUID.fromString(in.readString()); + type = Type.valueOf(in.readString()); + } + + private Attachment(UUID uuid, Uri uri, Type type, String mime) { + this.uri = uri; + this.type = type; + this.mime = mime; + this.uuid = uuid; + } + + private Attachment(Uri uri, Type type, String mime) { + this.uri = uri; + this.type = type; + this.mime = mime; + this.uuid = UUID.randomUUID(); + } + + public static List<Attachment> of(final Context context, Uri uri, Type type) { + final String mime = type == Type.LOCATION ? null : MimeUtils.guessMimeTypeFromUri(context, uri); + return Collections.singletonList(new Attachment(uri, type, mime)); + } + + public static List<Attachment> of(final Context context, List<Uri> uris) { + List<Attachment> attachments = new ArrayList<>(); + for (Uri uri : uris) { + final String mime = MimeUtils.guessMimeTypeFromUri(context, uri); + attachments.add(new Attachment(uri, mime != null && mime.startsWith("image/") ? Type.IMAGE : Type.FILE, mime)); + } + return attachments; + } + + public static Attachment of(UUID uuid, final File file, String mime) { + return new Attachment(uuid, Uri.fromFile(file), mime != null && (mime.startsWith("image/") || mime.startsWith("video/")) ? Type.IMAGE : Type.FILE, mime); + } + + public static List<Attachment> extractAttachments(final Context context, final Intent intent, Type type) { + List<Attachment> uris = new ArrayList<>(); + if (intent == null) { + return uris; + } + final String contentType = intent.getType(); + final Uri data = intent.getData(); + if (data == null) { + final ClipData clipData = intent.getClipData(); + if (clipData != null) { + for (int i = 0; i < clipData.getItemCount(); ++i) { + final Uri uri = clipData.getItemAt(i).getUri(); + Log.d(Config.LOGTAG, "uri=" + uri + " contentType=" + contentType); + final String mime = contentType != null ? contentType : MimeUtils.guessMimeTypeFromUri(context, uri); + Log.d(Config.LOGTAG, "mime=" + mime); + uris.add(new Attachment(uri, type, mime)); + } + } + } else { + final String mime = contentType != null ? contentType : MimeUtils.guessMimeTypeFromUri(context, data); + uris.add(new Attachment(data, type, mime)); + } + return uris; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(uri, flags); + dest.writeString(mime); + dest.writeString(uuid.toString()); + dest.writeString(type.toString()); + } + + @Override + public int describeContents() { + return 0; + } + + public String getMime() { + return mime; + } + + public Type getType() { + return type; + } + + public boolean renderThumbnail() { + return type == Type.IMAGE || (type == Type.FILE && mime != null && (mime.startsWith("video/") || mime.startsWith("image/"))); + } + + public Uri getUri() { + return uri; + } + + public UUID getUuid() { + return uuid; + } + + public enum Type { + FILE, IMAGE, LOCATION, RECORDING + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java b/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java deleted file mode 100644 index c56f27ebf..000000000 --- a/src/main/java/de/pixart/messenger/ui/util/AttachmentTool.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package de.pixart.messenger.ui.util; - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.Intent; -import android.net.Uri; - -import java.util.ArrayList; -import java.util.List; - -public class AttachmentTool { - @SuppressLint("NewApi") - public static List<Uri> extractUriFromIntent(final Intent intent) { - List<Uri> uris = new ArrayList<>(); - if (intent == null) { - return uris; - } - final Uri uri = intent.getData(); - if (uri == null) { - final ClipData clipData = intent.getClipData(); - if (clipData != null) { - for (int i = 0; i < clipData.getItemCount(); ++i) { - uris.add(clipData.getItemAt(i).getUri()); - } - } - } else { - uris.add(uri); - } - return uris; - } -}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java index 5c86b4a1b..edca88f78 100644 --- a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java +++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java @@ -65,12 +65,13 @@ public class ConversationMenuConfigurator { menu.findItem(R.id.attach_location).setVisible(locationAvailable); } - public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu, Boolean Quick_share_attachment_choice) { + public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu, Boolean Quick_share_attachment_choice, boolean hasAttachments) { final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); - if (Quick_share_attachment_choice) { + if (Quick_share_attachment_choice && !hasAttachments) { menuAttach.setVisible(false); return; } + final boolean visible; if (conversation.getMode() == Conversation.MODE_MULTI) { visible = conversation.getAccount().httpUploadAvailable() && conversation.getMucOptions().participating(); diff --git a/src/main/java/de/pixart/messenger/ui/util/Drawable.java b/src/main/java/de/pixart/messenger/ui/util/Drawable.java deleted file mode 100644 index 5e0c770d4..000000000 --- a/src/main/java/de/pixart/messenger/ui/util/Drawable.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2018, Daniel Gultsch All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors - * may be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package de.pixart.messenger.ui.util; - -import android.content.Context; -import android.content.res.TypedArray; -import android.support.annotation.AttrRes; - -public class Drawable { - public static android.graphics.drawable.Drawable get(Context context, @AttrRes int id) { - TypedArray typedArray = context.obtainStyledAttributes(new int[]{id}); - android.graphics.drawable.Drawable drawable = typedArray.getDrawable(0); - typedArray.recycle(); - return drawable; - } -}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/GridManager.java b/src/main/java/de/pixart/messenger/ui/util/GridManager.java new file mode 100644 index 000000000..ae615b051 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/GridManager.java @@ -0,0 +1,76 @@ +package de.pixart.messenger.ui.util; + +import android.content.Context; +import android.support.annotation.DimenRes; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.ViewTreeObserver; + +import de.pixart.messenger.Config; +import de.pixart.messenger.ui.adapter.MediaAdapter; + +public class GridManager { + + public static void setupLayoutManager(final Context context, RecyclerView recyclerView, @DimenRes int desiredSize) { + int maxWidth = context.getResources().getDisplayMetrics().widthPixels; + ColumnInfo columnInfo = calculateColumnCount(context, maxWidth, desiredSize); + Log.d(Config.LOGTAG, "preliminary count=" + columnInfo.count); + MediaAdapter.setMediaSize(recyclerView, columnInfo.width); + recyclerView.setLayoutManager(new GridLayoutManager(context, columnInfo.count)); + recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); + final int availableWidth = recyclerView.getMeasuredWidth(); + if (availableWidth == 0) { + Log.e(Config.LOGTAG, "GridManager: available width was 0; probably because layout was hidden"); + return; + } + final ColumnInfo columnInfo = calculateColumnCount(context, recyclerView.getMeasuredWidth(), desiredSize); + Log.d(Config.LOGTAG, "final count " + columnInfo.count); + if (recyclerView.getAdapter().getItemCount() != 0) { + Log.e(Config.LOGTAG, "adapter already has items; just go with it now"); + return; + } + setupLayoutManagerInternal(recyclerView, columnInfo); + MediaAdapter.setMediaSize(recyclerView, columnInfo.width); + } + }); + } + + private static void setupLayoutManagerInternal(RecyclerView recyclerView, final ColumnInfo columnInfo) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager instanceof GridLayoutManager) { + ((GridLayoutManager) layoutManager).setSpanCount(columnInfo.count); + } + } + + private static ColumnInfo calculateColumnCount(Context context, int availableWidth, @DimenRes int desiredSize) { + final float desiredWidth = context.getResources().getDimension(desiredSize); + final int columns = Math.round(availableWidth / desiredWidth); + final int realWidth = availableWidth / columns; + Log.d(Config.LOGTAG, "desired=" + desiredWidth + " real=" + realWidth); + return new ColumnInfo(columns, realWidth); + } + + public static int getCurrentColumnCount(RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager instanceof GridLayoutManager) { + return ((GridLayoutManager) layoutManager).getSpanCount(); + } else { + return 0; + } + } + + public static class ColumnInfo { + private final int count; + private final int width; + + private ColumnInfo(int count, int width) { + this.count = count; + this.width = width; + } + } + +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/PendingItem.java b/src/main/java/de/pixart/messenger/ui/util/PendingItem.java index eaddf3a8c..d06f5bfc9 100644 --- a/src/main/java/de/pixart/messenger/ui/util/PendingItem.java +++ b/src/main/java/de/pixart/messenger/ui/util/PendingItem.java @@ -46,4 +46,10 @@ public class PendingItem<T> { public synchronized T peek() { return item; } + + public synchronized boolean clear() { + boolean notNull = this.item != null; + this.item = null; + return notNull; + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java index 9f9fabc5a..c7feb2105 100644 --- a/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java +++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonAction.java @@ -33,10 +33,11 @@ import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE; import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_CHOOSE_IMAGE; import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_LOCATION; import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VOICE; -import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_FROM_CAMERA; +import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_RECORD_VIDEO; +import static de.pixart.messenger.ui.ConversationFragment.ATTACHMENT_CHOICE_TAKE_PHOTO; public enum SendButtonAction { - TEXT, TAKE_FROM_CAMERA, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE, CHOOSE_ATTACHMENT; + TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE, RECORD_VIDEO, CHOOSE_ATTACHMENT; public static SendButtonAction valueOfOrDefault(String setting, SendButtonAction text) { try { @@ -52,8 +53,10 @@ public enum SendButtonAction { return SEND_LOCATION; case ATTACHMENT_CHOICE_RECORD_VOICE: return RECORD_VOICE; - case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: - return TAKE_FROM_CAMERA; + case ATTACHMENT_CHOICE_RECORD_VIDEO: + return RECORD_VIDEO; + case ATTACHMENT_CHOICE_TAKE_PHOTO: + return TAKE_PHOTO; case ATTACHMENT_CHOICE_CHOOSE_IMAGE: return CHOOSE_PICTURE; case ATTACHMENT_CHOICE: @@ -65,8 +68,10 @@ public enum SendButtonAction { public int toChoice() { switch (this) { - case TAKE_FROM_CAMERA: - return ATTACHMENT_CHOICE_TAKE_FROM_CAMERA; + case TAKE_PHOTO: + return ATTACHMENT_CHOICE_TAKE_PHOTO; + case RECORD_VIDEO: + return ATTACHMENT_CHOICE_RECORD_VIDEO; case SEND_LOCATION: return ATTACHMENT_CHOICE_LOCATION; case RECORD_VOICE: diff --git a/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java b/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java index b4b0ecc28..96bcad183 100644 --- a/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java +++ b/src/main/java/de/pixart/messenger/ui/util/SendButtonTool.java @@ -44,7 +44,6 @@ import de.pixart.messenger.utils.UIHelper; public class SendButtonTool { public static SendButtonAction getAction(Activity activity, Conversation c, String text) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); final boolean empty = text.length() == 0; final boolean conference = c.getMode() == Conversation.MODE_MULTI; if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) { @@ -57,14 +56,14 @@ public class SendButtonTool { } } else { if (empty) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); if (conference && c.getNextCounterpart() != null) { return SendButtonAction.CANCEL; } else { - boolean quickShareChoice = preferences.getBoolean(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, activity.getResources().getBoolean(R.bool.quick_share_attachment_choice)); String setting = preferences.getString("quick_action", activity.getResources().getString(R.string.quick_action)); - if (quickShareChoice && AttachmentsVisible(c)) { + if (quickShareChoice(activity) && AttachmentsVisible(c)) { return SendButtonAction.CHOOSE_ATTACHMENT; - } else if (quickShareChoice && !AttachmentsVisible(c)) { + } else if (quickShareChoice(activity) && !AttachmentsVisible(c)) { return SendButtonAction.TEXT; } else { if (!setting.equals("none") && UIHelper.receivedLocationQuestion(c.getLatestMessage())) { @@ -106,7 +105,20 @@ public class SendButtonTool { default: return getThemeResource(activity, R.attr.ic_send_text_offline, R.drawable.ic_send_text_offline); } - case TAKE_FROM_CAMERA: + case RECORD_VIDEO: + switch (status) { + case CHAT: + case ONLINE: + return R.drawable.ic_send_videocam_online; + case AWAY: + return R.drawable.ic_send_videocam_away; + case XA: + case DND: + return R.drawable.ic_send_videocam_dnd; + default: + return getThemeResource(activity, R.attr.ic_send_videocam_offline, R.drawable.ic_send_videocam_offline); + } + case TAKE_PHOTO: switch (status) { case CHAT: case ONLINE: @@ -198,4 +210,9 @@ public class SendButtonTool { return res; } + + public static boolean quickShareChoice(Activity activity) { + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + return preferences.getBoolean(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, activity.getResources().getBoolean(R.bool.quick_share_attachment_choice)); + } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/Color.java b/src/main/java/de/pixart/messenger/ui/util/StyledAttributes.java index 58ce6a465..cd4ad3e49 100644 --- a/src/main/java/de/pixart/messenger/ui/util/Color.java +++ b/src/main/java/de/pixart/messenger/ui/util/StyledAttributes.java @@ -35,14 +35,26 @@ import android.content.res.TypedArray; import android.support.annotation.AttrRes; import android.support.annotation.ColorInt; -public class Color { +public class StyledAttributes { + public static android.graphics.drawable.Drawable getDrawable(Context context, @AttrRes int id) { + TypedArray typedArray = context.obtainStyledAttributes(new int[]{id}); + android.graphics.drawable.Drawable drawable = typedArray.getDrawable(0); + typedArray.recycle(); + return drawable; + } + + public static float getFloat(Context context, @AttrRes int id) { + TypedArray typedArray = context.obtainStyledAttributes(new int[]{id}); + float value = typedArray.getFloat(0, 0f); + typedArray.recycle(); + return value; + } public static @ColorInt - int get(Context context, @AttrRes int attr) { + int getColor(Context context, @AttrRes int attr) { TypedArray typedArray = context.obtainStyledAttributes(new int[]{attr}); int color = typedArray.getColor(0, 0); typedArray.recycle(); return color; } - }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java new file mode 100644 index 000000000..b1905479b --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java @@ -0,0 +1,72 @@ +package de.pixart.messenger.ui.util; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.util.List; + +import de.pixart.messenger.Config; +import de.pixart.messenger.R; +import de.pixart.messenger.persistance.FileBackend; +import de.pixart.messenger.ui.ShowFullscreenMessageActivity; + +public class ViewUtil { + + public static void view(Context context, Attachment attachment) { + File file = new File(attachment.getUri().getPath()); + final String mime = attachment.getMime() == null ? "*/*" : attachment.getMime(); + view(context, file, mime); + } + + public static void view(Context context, File file, String mime) { + Uri uri; + try { + uri = FileBackend.getUriForFile(context, file); + } catch (SecurityException e) { + Log.d(Config.LOGTAG, "No permission to access " + file.getAbsolutePath(), e); + Toast.makeText(context, context.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show(); + return; + } + // use internal viewer for images and videos + if (mime.startsWith("image/")) { + Intent intent = new Intent(context, ShowFullscreenMessageActivity.class); + intent.putExtra("image", Uri.fromFile(file)); + try { + context.startActivity(intent); + return; + } catch (ActivityNotFoundException e) { + //ignored + } + } else if (mime.startsWith("video/")) { + Intent intent = new Intent(context, ShowFullscreenMessageActivity.class); + intent.putExtra("video", Uri.fromFile(file)); + try { + context.startActivity(intent); + return; + } catch (ActivityNotFoundException e) { + //ignored + } + } else { + Intent openIntent = new Intent(Intent.ACTION_VIEW); + openIntent.setDataAndType(uri, mime); + openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + PackageManager manager = context.getPackageManager(); + List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0); + if (info.size() == 0) { + openIntent.setDataAndType(uri, "*/*"); + } + try { + context.startActivity(openIntent); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java b/src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java new file mode 100644 index 000000000..4d2fcf92d --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/widget/SquareFrameLayout.java @@ -0,0 +1,25 @@ +package de.pixart.messenger.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +public class SquareFrameLayout extends FrameLayout { + public SquareFrameLayout(Context context) { + super(context); + } + + public SquareFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquareFrameLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //noinspection SuspiciousNameCombination + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/Compatibility.java b/src/main/java/de/pixart/messenger/utils/Compatibility.java index 95d7b4ef2..3ebc7c4f6 100644 --- a/src/main/java/de/pixart/messenger/utils/Compatibility.java +++ b/src/main/java/de/pixart/messenger/utils/Compatibility.java @@ -2,12 +2,14 @@ package de.pixart.messenger.utils; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.os.Build; import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.support.annotation.BoolRes; +import android.support.v4.content.ContextCompat; import java.util.Arrays; import java.util.Collections; @@ -26,10 +28,18 @@ public class Compatibility { "vibrate_on_notification"); private static final List<String> UNUESD_SETTINGS_PRE_TWENTYSIX = Collections.singletonList("more_notification_settings"); + public static boolean hasStoragePermission(Context context) { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + public static boolean twentySix() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; } + public static boolean twentyTwo() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1; + } + private static boolean getBooleanPreference(Context context, String name, @BoolRes int res) { return getPreferences(context).getBoolean(name, context.getResources().getBoolean(res)); } diff --git a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java index b54f8d074..3cd715a4c 100644 --- a/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java +++ b/src/main/java/de/pixart/messenger/utils/IrregularUnicodeDetector.java @@ -50,7 +50,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import de.pixart.messenger.R; -import de.pixart.messenger.ui.util.Color; +import de.pixart.messenger.ui.util.StyledAttributes; import rocks.xmpp.addr.Jid; public class IrregularUnicodeDetector { @@ -73,7 +73,7 @@ public class IrregularUnicodeDetector { } public static Spannable style(Context context, Jid jid) { - return style(jid, Color.get(context, R.attr.color_warning)); + return style(jid, StyledAttributes.getColor(context, R.attr.color_warning)); } private static Spannable style(Jid jid, @ColorInt int color) { |