ask for permissions before opening restore backup. use insert or ignore for messages

This commit is contained in:
Daniel Gultsch 2019-01-23 11:20:36 +01:00
parent c9fc40dfe5
commit 18982174ce
6 changed files with 237 additions and 157 deletions

View file

@ -5,6 +5,7 @@ import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
@ -20,13 +21,13 @@ import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import javax.crypto.AEADBadTagException;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherInputStream; 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.BackupFileHeader;
import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor; 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.CIPHERMODE;
import static eu.siacs.conversations.services.ExportBackupService.KEYTYPE; 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 { public class ImportBackupService extends Service {
private static final int NOTIFICATION_ID = 21; private static final int NOTIFICATION_ID = 21;
private static AtomicBoolean running = new AtomicBoolean(false);
private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder(); private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName()); private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>()); private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>());
private static AtomicBoolean running = new AtomicBoolean(false);
private DatabaseBackend mDatabaseBackend; private DatabaseBackend mDatabaseBackend;
private NotificationManager notificationManager; private NotificationManager notificationManager;
@ -85,7 +84,6 @@ public class ImportBackupService extends Service {
if (password == null || file == null) { if (password == null || file == null) {
return START_NOT_STICKY; return START_NOT_STICKY;
} }
Log.d(Config.LOGTAG, "on start command");
if (running.compareAndSet(false, true)) { if (running.compareAndSet(false, true)) {
executor.execute(() -> { executor.execute(() -> {
startForegroundService(); startForegroundService();
@ -106,7 +104,8 @@ public class ImportBackupService extends Service {
public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) { public void loadBackupFiles(OnBackupFilesLoaded onBackupFilesLoaded) {
executor.execute(() -> { executor.execute(() -> {
final ArrayList<BackupFile> backupFiles = new ArrayList<>(); final ArrayList<BackupFile> backupFiles = new ArrayList<>();
for (String app : Arrays.asList("Conversations", "Quicksy")) { final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
for (String app : apps) {
final File directory = new File(FileBackend.getBackupDirectory(app)); final File directory = new File(FileBackend.getBackupDirectory(app));
if (!directory.exists() || !directory.isDirectory()) { if (!directory.exists() || !directory.isDirectory()) {
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath()); 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")); BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8"));
String line; String line;
StringBuilder multiLineQuery = null; StringBuilder multiLineQuery = null;
int error = 0;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
int count = count(line, '\''); int count = count(line, '\'');
if (multiLineQuery != null) { if (multiLineQuery != null) {
multiLineQuery.append('\n');
multiLineQuery.append(line); multiLineQuery.append(line);
if (count % 2 == 1) { if (count % 2 == 1) {
db.execSQL(multiLineQuery.toString()); db.execSQL(multiLineQuery.toString());
@ -171,6 +172,12 @@ public class ImportBackupService extends Service {
} }
} }
Log.d(Config.LOGTAG, "done reading file"); 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(); stopBackgroundService();
synchronized (mOnBackupProcessedListeners) { synchronized (mOnBackupProcessedListeners) {
for (OnBackupProcessed l : mOnBackupProcessedListeners) { for (OnBackupProcessed l : mOnBackupProcessedListeners) {
@ -232,6 +239,18 @@ public class ImportBackupService extends Service {
return this.binder; return this.binder;
} }
public interface OnBackupFilesLoaded {
void onBackupFilesLoaded(List<BackupFile> files);
}
public interface OnBackupProcessed {
void onBackupRestored();
void onBackupDecryptionFailed();
void onBackupRestoreFailed();
}
public static class BackupFile { public static class BackupFile {
private final File file; private final File file;
private final BackupFileHeader header; private final BackupFileHeader header;
@ -263,14 +282,4 @@ public class ImportBackupService extends Service {
return ImportBackupService.this; return ImportBackupService.this;
} }
} }
public interface OnBackupFilesLoaded {
void onBackupFilesLoaded(List<BackupFile> files);
}
public interface OnBackupProcessed {
void onBackupRestored();
void onBackupDecryptionFailed();
void onBackupRestoreFailed();
}
} }

View file

@ -5,6 +5,7 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChainAliasCallback; import android.security.KeyChainAliasCallback;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.util.Pair; import android.util.Pair;
@ -35,10 +36,15 @@ import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid; 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 { public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
private final String STATE_SELECTED_ACCOUNT = "selected_account"; private final String STATE_SELECTED_ACCOUNT = "selected_account";
private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
protected Account selectedAccount = null; protected Account selectedAccount = null;
protected Jid selectedAccountJid = null; protected Jid selectedAccountJid = null;
@ -201,7 +207,9 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
startActivity(new Intent(this, EditAccountActivity.class)); startActivity(new Intent(this, EditAccountActivity.class));
break; break;
case R.id.action_import_backup: case R.id.action_import_backup:
if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
startActivity(new Intent(this, ImportBackupActivity.class)); startActivity(new Intent(this, ImportBackupActivity.class));
}
break; break;
case R.id.action_disable_all: case R.id.action_disable_all:
disableAllAccounts(); disableAllAccounts();
@ -218,6 +226,27 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
return super.onOptionsItemSelected(item); 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 @Override
public boolean onNavigateUp() { public boolean onNavigateUp() {
if (xmppConnectionService.getConversations().size() == 0) { if (xmppConnectionService.getConversations().size() == 0) {

View file

@ -3,22 +3,26 @@ package eu.siacs.conversations.ui;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.os.Bundle; 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.ActionBar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Button; import android.widget.Button;
import android.widget.Toast;
import java.util.List; import java.util.List;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account; 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 { public class WelcomeActivity extends XmppActivity {
private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
@Override @Override
protected void refreshUiReal() { protected void refreshUiReal() {
@ -90,12 +94,34 @@ public class WelcomeActivity extends XmppActivity {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_import_backup) { if (item.getItemId() == R.id.action_import_backup) {
if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
startActivity(new Intent(this, ImportBackupActivity.class)); startActivity(new Intent(this, ImportBackupActivity.class));
}
return true; return true;
} }
return super.onOptionsItemSelected(item); 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) { public void addInviteUri(Intent intent) {
StartConversationActivity.addInviteUri(intent, getIntent()); StartConversationActivity.addInviteUri(intent, getIntent());
} }

View file

@ -49,33 +49,14 @@ public class ExportBackupService extends Service {
public static final String PROVIDER = "BC"; public static final String PROVIDER = "BC";
private static final int NOTIFICATION_ID = 19; private static final int NOTIFICATION_ID = 19;
private static final int PAGE_SIZE = 20;
private static AtomicBoolean running = new AtomicBoolean(false); private static AtomicBoolean running = new AtomicBoolean(false);
private DatabaseBackend mDatabaseBackend; private DatabaseBackend mDatabaseBackend;
private List<Account> mAccounts; private List<Account> mAccounts;
private NotificationManager notificationManager; 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) { 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); final Cursor accountCursor = db.query(Account.TABLENAME, null, Account.UUID + "=?", new String[]{uuid}, null, null, null);
while (accountCursor != null && accountCursor.moveToNext()) { while (accountCursor != null && accountCursor.moveToNext()) {
builder.append("INSERT INTO ").append(Account.TABLENAME).append("("); builder.append("INSERT INTO ").append(Account.TABLENAME).append("(");
@ -95,10 +76,8 @@ public class ExportBackupService extends Service {
builder.append("NULL"); builder.append("NULL");
} else if (value.matches("\\d+")) { } else if (value.matches("\\d+")) {
int intValue = Integer.parseInt(value); int intValue = Integer.parseInt(value);
Log.d(Config.LOGTAG,"reading int value. "+intValue);
if (Account.OPTIONS.equals(accountCursor.getColumnName(i))) { if (Account.OPTIONS.equals(accountCursor.getColumnName(i))) {
intValue |= 1 << Account.OPTION_DISABLED; intValue |= 1 << Account.OPTION_DISABLED;
Log.d(Config.LOGTAG,"modified int value "+intValue);
} }
builder.append(intValue); builder.append(intValue);
} else { } else {
@ -109,13 +88,103 @@ public class ExportBackupService extends Service {
builder.append(';'); builder.append(';');
builder.append('\n'); builder.append('\n');
} }
Log.d(Config.LOGTAG,builder.toString());
if (accountCursor != null) { if (accountCursor != null) {
accountCursor.close(); accountCursor.close();
} }
writer.append(builder.toString()); writer.append(builder.toString());
} }
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, PAGE_SIZE));
}
if (cursor != null) {
cursor.close();
}
}
public static byte[] getKey(String password, byte[] salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new AssertionError(e);
}
}
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 ");
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(',');
}
builder.append(cursor.getColumnName(i));
}
builder.append(") VALUES");
for (int i = 0; i < max; ++i) {
if (i != 0) {
builder.append(',');
}
appendValues(cursor, builder);
if (i < max - 1 && !cursor.moveToNext()) {
break;
}
}
builder.append(';');
builder.append('\n');
return builder.toString();
}
private static void appendValues(Cursor cursor, StringBuilder builder) {
builder.append("(");
for (int i = 0; i < cursor.getColumnCount(); ++i) {
if (i != 0) {
builder.append(',');
}
final String value = cursor.getString(i);
if (value == null) {
builder.append("NULL");
} else if (value.matches("\\d+")) {
builder.append(value);
} else {
DatabaseUtils.appendEscapedSQLString(builder, value);
}
}
builder.append(")");
}
@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) { 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}); 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; int size = cursor != null ? cursor.getCount() : 0;
@ -123,17 +192,16 @@ public class ExportBackupService extends Service {
int i = 0; int i = 0;
int p = 0; int p = 0;
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
writer.write(cursorToString(Message.TABLENAME, cursor, 20)); writer.write(cursorToString(Message.TABLENAME, cursor, PAGE_SIZE, false));
if (i + 20 > size) { if (i + PAGE_SIZE > size) {
i = size; i = size;
} else { } else {
i += 20; i += PAGE_SIZE;
} }
final int percentage = i * 100 / size; final int percentage = i * 100 / size;
if (p < percentage) { if (p < percentage) {
p = percentage; p = percentage;
notificationManager.notify(NOTIFICATION_ID, progress.build(p)); notificationManager.notify(NOTIFICATION_ID, progress.build(p));
Log.d(Config.LOGTAG, "percentage=" + p);
} }
} }
if (cursor != null) { if (cursor != null) {
@ -141,16 +209,6 @@ public class ExportBackupService extends Service {
} }
} }
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));
}
if (cursor != null) {
cursor.close();
}
}
private void export() { private void export() {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.notification_create_backup_title)) mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
@ -205,58 +263,6 @@ public class ExportBackupService extends Service {
} }
} }
public static byte[] getKey(String password, byte[] salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return factory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 1024, 128)).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new AssertionError(e);
}
}
private static String cursorToString(String tablename, Cursor cursor, int max) {
StringBuilder builder = new StringBuilder();
builder.append("INSERT INTO ").append(tablename).append("(");
for (int i = 0; i < cursor.getColumnCount(); ++i) {
if (i != 0) {
builder.append(',');
}
builder.append(cursor.getColumnName(i));
}
builder.append(") VALUES");
for (int i = 0; i < max; ++i) {
if (i != 0) {
builder.append(',');
}
appendValues(cursor, builder);
if (!cursor.moveToNext()) {
break;
}
}
builder.append(';');
builder.append('\n');
return builder.toString();
}
private static void appendValues(Cursor cursor, StringBuilder builder) {
builder.append("(");
for (int i = 0; i < cursor.getColumnCount(); ++i) {
if (i != 0) {
builder.append(',');
}
final String value = cursor.getString(i);
if (value == null) {
builder.append("NULL");
} else if (value.matches("\\d+")) {
builder.append(value);
} else {
DatabaseUtils.appendEscapedSQLString(builder, value);
}
}
builder.append(")");
}
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; return null;

View file

@ -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.EXTRA_ACCOUNT;
import static eu.siacs.conversations.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION; 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.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 { 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); 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) { private static boolean scrolledToBottom(AbsListView listView) {
final int count = listView.getCount(); final int count = listView.getCount();
if (count == 0) { if (count == 0) {

View file

@ -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;
}
}