From 18982174ceeb14d9da4c23736b800a2be1e5a77b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 23 Jan 2019 11:20:36 +0100 Subject: [PATCH] ask for permissions before opening restore backup. use insert or ignore for messages --- .../services/ImportBackupService.java | 45 ++-- .../ui/ManageAccountActivity.java | 31 ++- .../conversations/ui/WelcomeActivity.java | 34 ++- .../services/ExportBackupService.java | 220 +++++++++--------- .../ui/ConversationFragment.java | 30 +-- .../conversations/utils/PermissionUtils.java | 34 +++ 6 files changed, 237 insertions(+), 157 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/utils/PermissionUtils.java diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index d7deb599c..970cbd570 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -5,6 +5,7 @@ import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Binder; import android.os.IBinder; @@ -20,13 +21,13 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPInputStream; -import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; @@ -41,6 +42,7 @@ import eu.siacs.conversations.ui.ManageAccountActivity; import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.SerialSingleThreadExecutor; +import rocks.xmpp.addr.Jid; import static eu.siacs.conversations.services.ExportBackupService.CIPHERMODE; import static eu.siacs.conversations.services.ExportBackupService.KEYTYPE; @@ -49,13 +51,10 @@ import static eu.siacs.conversations.services.ExportBackupService.PROVIDER; public class ImportBackupService extends Service { private static final int NOTIFICATION_ID = 21; - + private static AtomicBoolean running = new AtomicBoolean(false); private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder(); private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName()); - private final Set mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>()); - - private static AtomicBoolean running = new AtomicBoolean(false); private DatabaseBackend mDatabaseBackend; private NotificationManager notificationManager; @@ -85,7 +84,6 @@ public class ImportBackupService extends Service { if (password == null || file == null) { return START_NOT_STICKY; } - Log.d(Config.LOGTAG, "on start command"); if (running.compareAndSet(false, true)) { executor.execute(() -> { startForegroundService(); @@ -106,7 +104,8 @@ public class ImportBackupService extends Service { public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) { executor.execute(() -> { final ArrayList backupFiles = new ArrayList<>(); - for (String app : Arrays.asList("Conversations", "Quicksy")) { + final Set apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name))); + for (String app : apps) { final File directory = new File(FileBackend.getBackupDirectory(app)); if (!directory.exists() || !directory.isDirectory()) { Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath()); @@ -154,9 +153,11 @@ public class ImportBackupService extends Service { BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8")); String line; StringBuilder multiLineQuery = null; + int error = 0; while ((line = reader.readLine()) != null) { int count = count(line, '\''); if (multiLineQuery != null) { + multiLineQuery.append('\n'); multiLineQuery.append(line); if (count % 2 == 1) { db.execSQL(multiLineQuery.toString()); @@ -171,6 +172,12 @@ public class ImportBackupService extends Service { } } Log.d(Config.LOGTAG, "done reading file"); + final Jid jid = backupFileHeader.getJid(); + Cursor countCursor = db.rawQuery("select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", new String[]{jid.getEscapedLocal(), jid.getDomain()}); + countCursor.moveToFirst(); + int count = countCursor.getInt(0); + Log.d(Config.LOGTAG, "restored " + count + " messages"); + countCursor.close(); stopBackgroundService(); synchronized (mOnBackupProcessedListeners) { for (OnBackupProcessed l : mOnBackupProcessedListeners) { @@ -207,7 +214,7 @@ public class ImportBackupService extends Service { .setAutoCancel(true) .setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)) .setSmallIcon(R.drawable.ic_unarchive_white_24dp); - notificationManager.notify(NOTIFICATION_ID,mBuilder.build()); + notificationManager.notify(NOTIFICATION_ID, mBuilder.build()); } private void stopBackgroundService() { @@ -232,6 +239,18 @@ public class ImportBackupService extends Service { return this.binder; } + public interface OnBackupFilesLoaded { + void onBackupFilesLoaded(List files); + } + + public interface OnBackupProcessed { + void onBackupRestored(); + + void onBackupDecryptionFailed(); + + void onBackupRestoreFailed(); + } + public static class BackupFile { private final File file; private final BackupFileHeader header; @@ -263,14 +282,4 @@ public class ImportBackupService extends Service { return ImportBackupService.this; } } - - public interface OnBackupFilesLoaded { - void onBackupFilesLoaded(List files); - } - - public interface OnBackupProcessed { - void onBackupRestored(); - void onBackupDecryptionFailed(); - void onBackupRestoreFailed(); - } } \ No newline at end of file diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java index c0d2d7eff..f694e5dba 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java @@ -5,6 +5,7 @@ import android.content.Intent; import android.os.Bundle; import android.security.KeyChain; import android.security.KeyChainAliasCallback; +import android.support.annotation.NonNull; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.util.Pair; @@ -35,10 +36,15 @@ import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.xmpp.XmppConnection; import rocks.xmpp.addr.Jid; +import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; + public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState { private final String STATE_SELECTED_ACCOUNT = "selected_account"; + private static final int REQUEST_IMPORT_BACKUP = 0x63fb; + protected Account selectedAccount = null; protected Jid selectedAccountJid = null; @@ -201,7 +207,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda startActivity(new Intent(this, EditAccountActivity.class)); break; case R.id.action_import_backup: - startActivity(new Intent(this, ImportBackupActivity.class)); + if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) { + startActivity(new Intent(this, ImportBackupActivity.class)); + } break; case R.id.action_disable_all: disableAllAccounts(); @@ -218,6 +226,27 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda return super.onOptionsItemSelected(item); } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + if (grantResults.length > 0) { + if (allGranted(grantResults)) { + switch (requestCode) { + case REQUEST_IMPORT_BACKUP: + startActivity(new Intent(this, ImportBackupActivity.class)); + break; + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + if (writeGranted(grantResults, permissions)) { + if (xmppConnectionService != null) { + xmppConnectionService.restartFileObserver(); + } + } + } + @Override public boolean onNavigateUp() { if (xmppConnectionService.getConversations().size() == 0) { diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index 332927550..eb466e37a 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -3,22 +3,26 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Bundle; -import android.support.v4.content.ContextCompat; +import android.support.annotation.NonNull; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.widget.Button; +import android.widget.Toast; import java.util.List; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.services.ImportBackupService; -import eu.siacs.conversations.utils.XmppUri; + +import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; public class WelcomeActivity extends XmppActivity { + private static final int REQUEST_IMPORT_BACKUP = 0x63fb; + @Override protected void refreshUiReal() { @@ -90,12 +94,34 @@ public class WelcomeActivity extends XmppActivity { @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_import_backup) { - startActivity(new Intent(this, ImportBackupActivity.class)); + if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) { + startActivity(new Intent(this, ImportBackupActivity.class)); + } return true; } return super.onOptionsItemSelected(item); } + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + if (grantResults.length > 0) { + if (allGranted(grantResults)) { + switch (requestCode) { + case REQUEST_IMPORT_BACKUP: + startActivity(new Intent(this, ImportBackupActivity.class)); + break; + } + } else { + Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show(); + } + } + if (writeGranted(grantResults, permissions)) { + if (xmppConnectionService != null) { + xmppConnectionService.restartFileObserver(); + } + } + } + public void addInviteUri(Intent intent) { StartConversationActivity.addInviteUri(intent, getIntent()); } diff --git a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java index 63fc66e51..470c0814d 100644 --- a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java +++ b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java @@ -49,33 +49,14 @@ public class ExportBackupService extends Service { public static final String PROVIDER = "BC"; private static final int NOTIFICATION_ID = 19; + private static final int PAGE_SIZE = 20; private static AtomicBoolean running = new AtomicBoolean(false); private DatabaseBackend mDatabaseBackend; private List mAccounts; private NotificationManager notificationManager; - @Override - public void onCreate() { - mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); - mAccounts = mDatabaseBackend.getAccounts(); - notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (running.compareAndSet(false, true)) { - new Thread(() -> { - export(); - stopForeground(true); - running.set(false); - stopSelf(); - }).start(); - } - return START_NOT_STICKY; - } - private static void accountExport(SQLiteDatabase db, String uuid, PrintWriter writer) { - StringBuilder builder = new StringBuilder(); + final StringBuilder builder = new StringBuilder(); final Cursor accountCursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", new String[]{uuid}, null, null, null); while (accountCursor != null && accountCursor.moveToNext()) { builder.append("INSERT INTO ").append(Account.TABLENAME).append("("); @@ -95,10 +76,8 @@ public class ExportBackupService extends Service { builder.append("NULL"); } else if (value.matches("\\d+")) { int intValue = Integer.parseInt(value); - Log.d(Config.LOGTAG,"reading int value. "+intValue); if (Account.OPTIONS.equals(accountCursor.getColumnName(i))) { intValue |= 1 << Account.OPTION_DISABLED; - Log.d(Config.LOGTAG,"modified int value "+intValue); } builder.append(intValue); } else { @@ -109,102 +88,22 @@ public class ExportBackupService extends Service { builder.append(';'); builder.append('\n'); } - Log.d(Config.LOGTAG,builder.toString()); if (accountCursor != null) { accountCursor.close(); } writer.append(builder.toString()); } - private void messageExport(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) { - Cursor cursor = db.rawQuery("select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid}); - int size = cursor != null ? cursor.getCount() : 0; - Log.d(Config.LOGTAG, "exporting " + size + " messages"); - int i = 0; - int p = 0; - while (cursor != null && cursor.moveToNext()) { - writer.write(cursorToString(Message.TABLENAME, cursor, 20)); - if (i + 20 > size) { - i = size; - } else { - i += 20; - } - final int percentage = i * 100 / size; - if (p < percentage) { - p = percentage; - notificationManager.notify(NOTIFICATION_ID,progress.build(p)); - Log.d(Config.LOGTAG, "percentage=" + p); - } - } - if (cursor != null) { - cursor.close(); - } - } - private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) { final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null); while (cursor != null && cursor.moveToNext()) { - writer.write(cursorToString(table, cursor, 20)); + writer.write(cursorToString(table, cursor, PAGE_SIZE)); } if (cursor != null) { cursor.close(); } } - private void export() { - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); - mBuilder.setContentTitle(getString(R.string.notification_create_backup_title)) - .setSmallIcon(R.drawable.ic_archive_white_24dp) - .setProgress(1, 0, false); - startForeground(NOTIFICATION_ID, mBuilder.build()); - try { - int count = 0; - final int max = this.mAccounts.size(); - final SecureRandom secureRandom = new SecureRandom(); - for (Account account : this.mAccounts) { - final byte[] IV = new byte[12]; - final byte[] salt = new byte[16]; - secureRandom.nextBytes(IV); - secureRandom.nextBytes(salt); - final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name),account.getJid(),System.currentTimeMillis(),IV,salt); - final Progress progress = new Progress(mBuilder, max, count); - final File file = new File(FileBackend.getBackupDirectory(this)+account.getJid().asBareJid().toEscapedString()+".ceb"); - if (file.getParentFile().mkdirs()) { - Log.d(Config.LOGTAG,"created backup directory "+file.getParentFile().getAbsolutePath()); - } - final FileOutputStream fileOutputStream = new FileOutputStream(file); - final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); - backupFileHeader.write(dataOutputStream); - dataOutputStream.flush(); - - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - byte[] key = getKey(account.getPassword(), salt); - Log.d(Config.LOGTAG,backupFileHeader.toString()); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(IV); - cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); - CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); - - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); - PrintWriter writer = new PrintWriter(gzipOutputStream); - SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase(); - final String uuid = account.getUuid(); - accountExport(db, uuid, writer); - simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer); - messageExport(db, uuid, writer, progress); - for(String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { - simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT,uuid,writer); - } - writer.flush(); - writer.close(); - Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); - count++; - } - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to create backup ", e); - } - } - public static byte[] getKey(String password, byte[] salt) { try { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); @@ -215,8 +114,16 @@ public class ExportBackupService extends Service { } private static String cursorToString(String tablename, Cursor cursor, int max) { + return cursorToString(tablename, cursor, max, false); + } + + private static String cursorToString(String tablename, Cursor cursor, int max, boolean ignore) { StringBuilder builder = new StringBuilder(); - builder.append("INSERT INTO ").append(tablename).append("("); + builder.append("INSERT "); + if (ignore) { + builder.append("OR IGNORE "); + } + builder.append("INTO ").append(tablename).append("("); for (int i = 0; i < cursor.getColumnCount(); ++i) { if (i != 0) { builder.append(','); @@ -229,7 +136,7 @@ public class ExportBackupService extends Service { builder.append(','); } appendValues(cursor, builder); - if (!cursor.moveToNext()) { + if (i < max - 1 && !cursor.moveToNext()) { break; } } @@ -257,6 +164,105 @@ public class ExportBackupService extends Service { } + @Override + public void onCreate() { + mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext()); + mAccounts = mDatabaseBackend.getAccounts(); + notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (running.compareAndSet(false, true)) { + new Thread(() -> { + export(); + stopForeground(true); + running.set(false); + stopSelf(); + }).start(); + return START_STICKY; + } + return START_NOT_STICKY; + } + + private void messageExport(SQLiteDatabase db, String uuid, PrintWriter writer, Progress progress) { + Cursor cursor = db.rawQuery("select messages.* from messages join conversations on conversations.uuid=messages.conversationUuid where conversations.accountUuid=?", new String[]{uuid}); + int size = cursor != null ? cursor.getCount() : 0; + Log.d(Config.LOGTAG, "exporting " + size + " messages"); + int i = 0; + int p = 0; + while (cursor != null && cursor.moveToNext()) { + writer.write(cursorToString(Message.TABLENAME, cursor, PAGE_SIZE, false)); + if (i + PAGE_SIZE > size) { + i = size; + } else { + i += PAGE_SIZE; + } + final int percentage = i * 100 / size; + if (p < percentage) { + p = percentage; + notificationManager.notify(NOTIFICATION_ID, progress.build(p)); + } + } + if (cursor != null) { + cursor.close(); + } + } + + private void export() { + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); + mBuilder.setContentTitle(getString(R.string.notification_create_backup_title)) + .setSmallIcon(R.drawable.ic_archive_white_24dp) + .setProgress(1, 0, false); + startForeground(NOTIFICATION_ID, mBuilder.build()); + try { + int count = 0; + final int max = this.mAccounts.size(); + final SecureRandom secureRandom = new SecureRandom(); + for (Account account : this.mAccounts) { + final byte[] IV = new byte[12]; + final byte[] salt = new byte[16]; + secureRandom.nextBytes(IV); + secureRandom.nextBytes(salt); + final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt); + final Progress progress = new Progress(mBuilder, max, count); + final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb"); + if (file.getParentFile().mkdirs()) { + Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath()); + } + final FileOutputStream fileOutputStream = new FileOutputStream(file); + final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); + backupFileHeader.write(dataOutputStream); + dataOutputStream.flush(); + + final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + byte[] key = getKey(account.getPassword(), salt); + Log.d(Config.LOGTAG, backupFileHeader.toString()); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(IV); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); + + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); + PrintWriter writer = new PrintWriter(gzipOutputStream); + SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase(); + final String uuid = account.getUuid(); + accountExport(db, uuid, writer); + simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer); + messageExport(db, uuid, writer, progress); + for (String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { + simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer); + } + writer.flush(); + writer.close(); + Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); + count++; + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "unable to create backup ", e); + } + } + @Override public IBinder onBind(Intent intent) { return null; @@ -274,7 +280,7 @@ public class ExportBackupService extends Service { } private Notification build(int percentage) { - builder.setProgress(max * 100,count * 100 + percentage,false); + builder.setProgress(max * 100, count * 100 + percentage, false); return builder.build(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 16763c329..8630e6713 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -119,6 +119,9 @@ import rocks.xmpp.addr.Jid; import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT; import static eu.siacs.conversations.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION; import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard; +import static eu.siacs.conversations.utils.PermissionUtils.allGranted; +import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; +import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, MessageAdapter.OnContactPictureLongClicked, MessageAdapter.OnContactPictureClicked { @@ -523,33 +526,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return getConversation(activity, R.id.main_fragment); } - private static boolean allGranted(int[] grantResults) { - for (int grantResult : grantResults) { - if (grantResult != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - return true; - } - - private static boolean writeGranted(int[] grantResults, String[] permission) { - for (int i = 0; i < grantResults.length; ++i) { - if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission[i])) { - return grantResults[i] == PackageManager.PERMISSION_GRANTED; - } - } - return false; - } - - private static String getFirstDenied(int[] grantResults, String[] permissions) { - for (int i = 0; i < grantResults.length; ++i) { - if (grantResults[i] == PackageManager.PERMISSION_DENIED) { - return permissions[i]; - } - } - return null; - } - private static boolean scrolledToBottom(AbsListView listView) { final int count = listView.getCount(); if (count == 0) { diff --git a/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java b/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java new file mode 100644 index 000000000..706b6c2f8 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java @@ -0,0 +1,34 @@ +package eu.siacs.conversations.utils; + +import android.Manifest; +import android.content.pm.PackageManager; + +public class PermissionUtils { + + public static boolean allGranted(int[] grantResults) { + for (int grantResult : grantResults) { + if (grantResult != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + public static boolean writeGranted(int[] grantResults, String[] permission) { + for (int i = 0; i < grantResults.length; ++i) { + if (Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission[i])) { + return grantResults[i] == PackageManager.PERMISSION_GRANTED; + } + } + return false; + } + + public static String getFirstDenied(int[] grantResults, String[] permissions) { + for (int i = 0; i < grantResults.length; ++i) { + if (grantResults[i] == PackageManager.PERMISSION_DENIED) { + return permissions[i]; + } + } + return null; + } +}