ask for permissions before opening restore backup. use insert or ignore for messages
This commit is contained in:
parent
c9fc40dfe5
commit
18982174ce
|
@ -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<OnBackupProcessed> 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<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));
|
||||
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<BackupFile> 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<BackupFile> files);
|
||||
}
|
||||
|
||||
public interface OnBackupProcessed {
|
||||
void onBackupRestored();
|
||||
void onBackupDecryptionFailed();
|
||||
void onBackupRestoreFailed();
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<Account> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue