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.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) {
|
||||||
|
@ -207,7 +214,7 @@ public class ImportBackupService extends Service {
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
|
.setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
|
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
|
||||||
notificationManager.notify(NOTIFICATION_ID,mBuilder.build());
|
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopBackgroundService() {
|
private void stopBackgroundService() {
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,102 +88,22 @@ 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 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) {
|
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);
|
final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null);
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
writer.write(cursorToString(table, cursor, 20));
|
writer.write(cursorToString(table, cursor, PAGE_SIZE));
|
||||||
}
|
}
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
cursor.close();
|
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) {
|
public static byte[] getKey(String password, byte[] salt) {
|
||||||
try {
|
try {
|
||||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
||||||
|
@ -215,8 +114,16 @@ public class ExportBackupService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String cursorToString(String tablename, Cursor cursor, int max) {
|
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();
|
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) {
|
for (int i = 0; i < cursor.getColumnCount(); ++i) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
builder.append(',');
|
builder.append(',');
|
||||||
|
@ -229,7 +136,7 @@ public class ExportBackupService extends Service {
|
||||||
builder.append(',');
|
builder.append(',');
|
||||||
}
|
}
|
||||||
appendValues(cursor, builder);
|
appendValues(cursor, builder);
|
||||||
if (!cursor.moveToNext()) {
|
if (i < max - 1 && !cursor.moveToNext()) {
|
||||||
break;
|
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
|
@Override
|
||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -274,7 +280,7 @@ public class ExportBackupService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification build(int percentage) {
|
private Notification build(int percentage) {
|
||||||
builder.setProgress(max * 100,count * 100 + percentage,false);
|
builder.setProgress(max * 100, count * 100 + percentage, false);
|
||||||
return builder.build();
|
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.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) {
|
||||||
|
|
|
@ -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