add backup import and export

This commit is contained in:
Martin/Geno 2018-11-17 21:57:23 +01:00
parent bba142bd3f
commit 084e9b8190
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
18 changed files with 653 additions and 5 deletions

View File

@ -1,20 +1,49 @@
package eu.siacs.conversations.ui;
import android.Manifest;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.NoSuchPaddingException;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.EncryptDecryptFile;
public class WelcomeActivity extends XmppActivity {
boolean importSuccessful = false;
@Override
protected void refreshUiReal() {
@ -25,6 +54,8 @@ public class WelcomeActivity extends XmppActivity {
}
private static final int REQUEST_READ_EXTERNAL_STORAGE = 0XD737;
@Override
public void onStart() {
super.onStart();
@ -54,7 +85,19 @@ public class WelcomeActivity extends XmppActivity {
ab.setDisplayShowHomeEnabled(false);
ab.setDisplayHomeAsUpEnabled(false);
}
//check if there is a backed up database --
if (hasStoragePermission(REQUEST_READ_EXTERNAL_STORAGE)) {
backupAvailable();
}
final Button importDatabase = findViewById(R.id.import_database);
final TextView importText = findViewById(R.id.import_text);
final Button createAccount = findViewById(R.id.create_account);
if (backupAvailable()) {
importDatabase.setOnClickListener(v -> enterPasswordDialog());
importDatabase.setVisibility(View.VISIBLE);
importText.setVisibility(View.VISIBLE);
}
createAccount.setOnClickListener(v -> {
final Intent intent = new Intent(WelcomeActivity.this, MagicCreateActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
@ -77,6 +120,216 @@ public class WelcomeActivity extends XmppActivity {
}
public void enterPasswordDialog() {
LayoutInflater li = LayoutInflater.from(WelcomeActivity.this);
View promptsView = li.inflate(R.layout.backup_password, null);
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(WelcomeActivity.this);
alertDialogBuilder.setView(promptsView);
final EditText userInput = promptsView
.findViewById(R.id.password);
alertDialogBuilder.setTitle(R.string.enter_password);
alertDialogBuilder.setMessage(R.string.enter_backup_password);
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
final String password = userInput.getText().toString();
final ProgressDialog pd = ProgressDialog.show(WelcomeActivity.this, getString(R.string.please_wait), getString(R.string.import_started), true);
if (!password.isEmpty()) {
new Thread(new Runnable() {
@Override
public void run() {
try {
checkDatabase(password);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
pd.dismiss();
}
}).start();
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(WelcomeActivity.this);
builder.setTitle(R.string.error);
builder.setMessage(R.string.password_should_not_be_empty);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.try_again, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
enterPasswordDialog();
}
});
builder.create().show();
}
}
})
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Toast.makeText(WelcomeActivity.this, R.string.import_canceled, Toast.LENGTH_LONG).show();
dialog.dismiss();
}
}
);
WelcomeActivity.this.runOnUiThread(new Runnable() {
public void run() {
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
});
}
private boolean backupAvailable() {
// Set the folder on the SDcard
File filePath = new File(FileBackend.getBackupDirectory() + "/database.db.crypt");
Log.d(Config.LOGTAG, "DB Path: " + filePath.toString());
if (filePath.exists()) {
Log.d(Config.LOGTAG, "DB Path existing");
return true;
} else {
Log.d(Config.LOGTAG, "DB Path not existing");
return false;
}
}
private void checkDatabase(String decryptionKey) throws IOException {
// Set the folder on the SDcard
File directory = new File(FileBackend.getBackupDirectory());
// Set the input file stream up:
FileInputStream inputFile = new FileInputStream(directory.getPath() + "/database.db.crypt");
// Temp output for DB checks
File tempFile = new File(directory.getPath() + "/database.db.tmp");
FileOutputStream outputTemp = new FileOutputStream(tempFile);
try {
EncryptDecryptFile.decrypt(inputFile, outputTemp, decryptionKey);
} catch (NoSuchAlgorithmException e) {
Log.d(Config.LOGTAG, "Database importer: decryption failed with " + e);
e.printStackTrace();
} catch (NoSuchPaddingException e) {
Log.d(Config.LOGTAG, "Database importer: decryption failed with " + e);
e.printStackTrace();
} catch (InvalidKeyException e) {
Log.d(Config.LOGTAG, "Database importer: decryption failed (invalid key) with " + e);
e.printStackTrace();
} catch (IOException e) {
Log.d(Config.LOGTAG, "Database importer: decryption failed (IO) with " + e);
e.printStackTrace();
} catch (Exception e) {
Log.d(Config.LOGTAG, "Database importer: Error " + e);
e.printStackTrace();
}
SQLiteDatabase checkDB = null;
int DBVersion = DatabaseBackend.DATABASE_VERSION;
int BackupDBVersion = 0;
try {
String dbPath = tempFile.toString();
checkDB = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
BackupDBVersion = checkDB.getVersion();
Log.d(Config.LOGTAG, "Backup found: " + checkDB + " Version: " + checkDB.getVersion());
} catch (SQLiteException e) {
//database does't exist yet.
Log.d(Config.LOGTAG, "No backup found: " + checkDB);
} catch (Exception e) {
Log.d(Config.LOGTAG, "Error importing backup: " + e);
}
if (checkDB != null) {
checkDB.close();
}
if (checkDB != null) {
Log.d(Config.LOGTAG, "checkDB = " + checkDB.toString() + ", Backup DB = " + BackupDBVersion + ", DB = " + DBVersion);
}
if (checkDB != null && BackupDBVersion != 0 && BackupDBVersion <= DBVersion) {
try {
importDatabase();
importSuccessful = true;
} catch (Exception e) {
importSuccessful = false;
e.printStackTrace();
} finally {
if (tempFile.exists()) {
Log.d(Config.LOGTAG, "Delete temp file from " + tempFile.toString());
tempFile.delete();
}
if (importSuccessful) {
this.getPreferences().edit().putString("backup_password", decryptionKey).commit();
restart();
}else{
WelcomeActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(WelcomeActivity.this, R.string.import_failed, Toast.LENGTH_LONG).show();
}
});
}
}
} else if (checkDB != null && BackupDBVersion == 0) {
WelcomeActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(WelcomeActivity.this, R.string.password_wrong, Toast.LENGTH_LONG).show();
enterPasswordDialog();
}
});
} else {
WelcomeActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(WelcomeActivity.this, R.string.import_failed, Toast.LENGTH_LONG).show();
}
});
}
if (tempFile.exists()) {
Log.d(Config.LOGTAG, "Delete temp file from " + tempFile.toString());
tempFile.delete();
}
}
private void importDatabase() throws Exception {
// Set location for the db:
final OutputStream outputFile = new FileOutputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
// Set the folder on the SDcard
File directory = new File(FileBackend.getBackupDirectory());
// Set the input file stream up:
final InputStream inputFile = new FileInputStream(directory.getPath() + "/database.db.tmp");
//set temp file
File tempFile = new File(directory.getPath() + "/database.db.tmp");
// Transfer bytes from the input file to the output file
byte[] buffer = new byte[1024];
int length;
while ((length = inputFile.read(buffer)) > 0) {
outputFile.write(buffer, 0, length);
}
}
private void restart() {
//restart app
Log.d(Config.LOGTAG, "Restarting " + getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName()));
Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(0);
}
public boolean hasStoragePermission(int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
return false;
} else {
return true;
}
} else {
return true;
}
}
public void addInviteUri(Intent intent) {
StartConversationActivity.addInviteUri(intent, getIntent());
}

View File

@ -42,6 +42,23 @@
android:layout_marginTop="8dp"
android:text="@string/welcome_text"
android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
<TextView
android:id="@+id/import_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/import_text"
android:textAppearance="@style/TextAppearance.Conversations.Caption.Highlight"
android:visibility="gone" />
<Button
android:id="@+id/import_database"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/import_database"
android:textColor="?colorAccent"
android:visibility="gone" />
<Button
android:id="@+id/create_account"
style="@style/Widget.Conversations.Button.Borderless"
@ -57,7 +74,7 @@
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="@string/use_own_provider"
android:textColor="?android:textColorSecondary"/>
android:textColor="?colorAccent"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"

View File

@ -226,6 +226,7 @@
android:label="@string/media_browser"/>
<service android:name=".services.ExportLogsService"/>
<service android:name=".services.BackupService"/>
<service
android:name=".services.ContactChooserTargetService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">

View File

@ -62,8 +62,8 @@ import rocks.xmpp.addr.Jid;
public class DatabaseBackend extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 43;
public static final String DATABASE_NAME = "history";
public static final int DATABASE_VERSION = 43;
private static DatabaseBackend instance = null;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "

View File

@ -149,6 +149,10 @@ public class FileBackend {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + "/Media/";
}
public static String getBackupDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations for Sum7/Database/";
}
public static String getConversationsLogsDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations for Sum7/";
}

View File

@ -0,0 +1,119 @@
package eu.siacs.conversations.services;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.support.v4.app.NotificationCompat;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.crypto.NoSuchPaddingException;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.EncryptDecryptFile;
public class BackupService extends Service {
private static final int NOTIFICATION_ID = 1;
private static AtomicBoolean running = new AtomicBoolean(false);
@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 void export() {
try {
NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.notification_backup_create))
.setSmallIcon(R.drawable.ic_import_export_white_24dp);
startForeground(NOTIFICATION_ID, mBuilder.build());
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String encryptionKey = prefs.getString("backup_password", null);
if (encryptionKey == null || encryptionKey.length() < 3) {
Log.d(Config.LOGTAG, "BackupService: failed to write encryted backup to sdcard because of missing password");
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(BackupService.this.getApplicationContext(),"Found no password to create backup!", Toast.LENGTH_LONG).show();
}
});
return;
}
Log.d(Config.LOGTAG, "BackupService: start creating backup");
// Get hold of the db:
FileInputStream inputFile = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
// Set the output folder on the SDcard
File directory = new File(FileBackend.getBackupDirectory());
// Create the folder if it doesn't exist:
if (!directory.exists()) {
boolean directory_created = directory.mkdirs();
Log.d(Config.LOGTAG, "BackupService: backup directory created " + directory_created);
}
//Delete old database export file
File tempDBFile = new File(directory + "/database.db.tmp");
if (tempDBFile.exists()) {
Log.d(Config.LOGTAG, "BackupService: Delete temp database backup file from " + tempDBFile.toString());
boolean temp_db_file_deleted = tempDBFile.delete();
Log.d(Config.LOGTAG, "BackupService: old backup file deleted " + temp_db_file_deleted);
}
// Set the output file stream up:
FileOutputStream outputFile = new FileOutputStream(directory.getPath() + "/database.db.crypt");
// encrypt database from the input file to the output file
try {
EncryptDecryptFile.encrypt(inputFile, outputFile, encryptionKey);
Log.d(Config.LOGTAG, "BackupService: starting encrypted output to " + outputFile.toString());
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Log.d(Config.LOGTAG, "BackupService: Database exporter: encryption failed with " + e);
e.printStackTrace();
} catch (InvalidKeyException e) {
Log.d(Config.LOGTAG, "BackupService: Database exporter: encryption failed (invalid key) with " + e);
e.printStackTrace();
} catch (IOException e) {
Log.d(Config.LOGTAG, "BackupService: Database exporter: encryption failed (IO) with " + e);
e.printStackTrace();
} finally {
Log.d(Config.LOGTAG, "BackupService: backup job finished");
}
mNotifyManager.notify(NOTIFICATION_ID, mBuilder.build());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@ -1,5 +1,7 @@
package eu.siacs.conversations.services;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@ -7,6 +9,8 @@ import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.util.Calendar;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.Compatibility;
@ -14,9 +18,27 @@ public class EventReceiver extends BroadcastReceiver {
public static final String SETTING_ENABLED_ACCOUNTS = "enabled_accounts";
public static final String EXTRA_NEEDS_FOREGROUND_SERVICE = "needs_foreground_service";
private static Boolean isAlarmStarted = false;
@Override
public void onReceive(final Context context, final Intent originalIntent) {
if (originalIntent.getAction().equals("android.intent.action.BOOT_COMPLETED") || !isAlarmStarted) {
PendingIntent alarmIntent = PendingIntent.getService(context, 0, new Intent(context, BackupService.class), 0);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 4);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
AlarmManager alarmMgr = (AlarmManager)(context.getSystemService(Context.ALARM_SERVICE));
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, alarmIntent);
Log.d(Config.LOGTAG, "BackupService: alarm started for next run: "+calendar.getTime().toString());
isAlarmStarted = true;
}
final Intent intentForService = new Intent(context, XmppConnectionService.class);
if (originalIntent.getAction() != null) {
intentForService.setAction(originalIntent.getAction());

View File

@ -139,6 +139,13 @@ public class NotificationService {
exportChannel.setGroup("status");
notificationManager.createNotificationChannel(exportChannel);
final NotificationChannel backupChannel = new NotificationChannel("backup",
c.getString(R.string.backup_channel_name),
NotificationManager.IMPORTANCE_LOW);
backupChannel.setShowBadge(false);
backupChannel.setGroup("status");
notificationManager.createNotificationChannel(backupChannel);
final NotificationChannel messagesChannel = new NotificationChannel("messages",
c.getString(R.string.messages_channel_name),
NotificationManager.IMPORTANCE_HIGH);

View File

@ -21,8 +21,9 @@ import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.provider.MediaStore;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
@ -36,6 +37,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.OmemoSetting;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.BackupService;
import eu.siacs.conversations.services.ExportLogsService;
import eu.siacs.conversations.services.MemorizingTrustManager;
import eu.siacs.conversations.services.QuickConversationsService;
@ -60,6 +62,7 @@ public class SettingsActivity extends XmppActivity implements
public static final String OMEMO_SETTING = "omemo";
public static final int REQUEST_WRITE_LOGS = 0xbf8701;
public static final int REQUEST_WRITE_BACKUP = 0xbf8702;
private SettingsFragment mSettingsFragment;
@Override
@ -219,6 +222,30 @@ public class SettingsActivity extends XmppActivity implements
});
}
final Preference backupPasswordPreference = mSettingsFragment.findPreference("backup_password");
if (backupPasswordPreference != null) {
backupPasswordPreference.setOnPreferenceClickListener(preference -> {
if (hasStoragePermission(REQUEST_WRITE_BACKUP)) {
enterPasswordDialog();
}
return true;
});
}
final Preference exportBackupPreference = mSettingsFragment.findPreference("export_backup");
if (exportBackupPreference != null) {
String backupPassword = this.getPreferences().getString("backup_password",null);
if(backupPassword == null || backupPassword.equals("")){
exportBackupPreference.setEnabled(false);
}
exportBackupPreference.setOnPreferenceClickListener(preference -> {
if (hasStoragePermission(REQUEST_WRITE_BACKUP)) {
startBackup();
}
return true;
});
}
final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
if (exportLogsPreference != null) {
exportLogsPreference.setOnPreferenceClickListener(preference -> {
@ -350,6 +377,50 @@ public class SettingsActivity extends XmppActivity implements
return true;
}
private void enterPasswordDialog() {
LayoutInflater li = LayoutInflater.from(this);
View promptsView = li.inflate(R.layout.backup_password, null);
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setView(promptsView);
final EditText password = promptsView.findViewById(R.id.password);
final EditText confirmPassword = promptsView.findViewById(R.id.confirm_password);
confirmPassword.setVisibility(View.VISIBLE);
alertDialogBuilder.setTitle(R.string.enter_password);
alertDialogBuilder.setMessage(R.string.enter_password);
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(R.string.ok,
(dialog, id) -> {
final String pw1 = password.getText().toString();
final String pw2 = confirmPassword.getText().toString();
if (!pw1.equals(pw2)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error);
builder.setMessage(R.string.passwords_do_not_match);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.try_again, (dialog12, id12) -> enterPasswordDialog());
builder.create().show();
} else if (pw1.trim().isEmpty()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error);
builder.setMessage(R.string.password_should_not_be_empty);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.try_again, (dialog1, id1) -> enterPasswordDialog());
builder.create().show();
} else {
boolean passwordstored = this.getPreferences().edit().putString("backup_password", pw1).commit();
Log.d(Config.LOGTAG, "saving password " + passwordstored);
if (passwordstored) {
recreate();
startBackup();
}
}
})
.setNegativeButton(R.string.cancel, null);
alertDialogBuilder.create().show();
}
@Override
public void onStop() {
super.onStop();
@ -399,6 +470,9 @@ public class SettingsActivity extends XmppActivity implements
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (grantResults.length > 0)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (requestCode == REQUEST_WRITE_BACKUP) {
startBackup();
}
if (requestCode == REQUEST_WRITE_LOGS) {
startExport();
}
@ -407,6 +481,10 @@ public class SettingsActivity extends XmppActivity implements
}
}
private void startBackup() {
ContextCompat.startForegroundService(this, new Intent(this, BackupService.class));
}
private void startExport() {
ContextCompat.startForegroundService(this, new Intent(this, ExportLogsService.class));
}

View File

@ -0,0 +1,73 @@
package eu.siacs.conversations.utils;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import eu.siacs.conversations.Config;
public class EncryptDecryptFile {
private static String cipher_mode = "AES/ECB/PKCS5Padding";
public static void encrypt(FileInputStream iFile, FileOutputStream oFile, String iKey) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
byte[] key = iKey.getBytes("UTF-8");
Log.d(Config.LOGTAG, "Cipher key: " + Arrays.toString(key));
MessageDigest sha = MessageDigest.getInstance("SHA-1");
Log.d(Config.LOGTAG, "Cipher sha: " + sha.toString());
key = sha.digest(key);
Log.d(Config.LOGTAG, "Cipher sha key: " + Arrays.toString(key));
key = Arrays.copyOf(key, 16); // use only first 128 bit
Log.d(Config.LOGTAG, "Cipher sha key 16 bytes: " + Arrays.toString(key));
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance(cipher_mode);
cipher.init(Cipher.ENCRYPT_MODE, sks);
Log.d(Config.LOGTAG, "Cipher IV: " + Arrays.toString(cipher.getIV()));
CipherOutputStream cos = new CipherOutputStream(oFile, cipher);
Log.d(Config.LOGTAG, "Encryption with: " + cos.toString());
int b;
byte[] d = new byte[8];
while ((b = iFile.read(d)) != -1) {
cos.write(d, 0, b);
}
cos.flush();
cos.close();
iFile.close();
}
public static void decrypt(FileInputStream iFile, FileOutputStream oFile, String iKey) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
byte[] key = iKey.getBytes("UTF-8");
Log.d(Config.LOGTAG, "Cipher key: " + Arrays.toString(key));
MessageDigest sha = MessageDigest.getInstance("SHA-1");
Log.d(Config.LOGTAG, "Cipher sha: " + sha.toString());
key = sha.digest(key);
Log.d(Config.LOGTAG, "Cipher sha key: " + Arrays.toString(key));
key = Arrays.copyOf(key, 16); // use only first 128 bit
Log.d(Config.LOGTAG, "Cipher sha key 16 bytes: " + Arrays.toString(key));
SecretKeySpec sks = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance(cipher_mode);
cipher.init(Cipher.DECRYPT_MODE, sks);
Log.d(Config.LOGTAG, "Cipher IV: " + Arrays.toString(cipher.getIV()));
CipherInputStream cis = new CipherInputStream(iFile, cipher);
Log.d(Config.LOGTAG, "Decryption with: " + cis.toString());
int b;
byte[] d = new byte[8];
while ((b = cis.read(d)) != -1) {
oFile.write(d, 0, b);
}
oFile.flush();
oFile.close();
cis.close();
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password"
android:inputType="textPassword" />
<EditText
android:id="@+id/confirm_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/confirm_password"
android:inputType="textPassword"
android:visibility="gone"/>
</LinearLayout>

View File

@ -175,6 +175,9 @@
<string name="block_jabber_id">Sperre Jabber-ID</string>
<string name="account_settings_example_jabber_id">benutzer@domain.de</string>
<string name="password">Passwort</string>
<string name="password_wrong">Falsches Passwort, erneut versuchen</string>
<string name="confirm_password">Passwort bestätigen</string>
<string name="passwords_do_not_match">Passwörter stimmen nicht überein</string>
<string name="invalid_jid">Ungültige Jabber-ID</string>
<string name="error_out_of_memory">Zu wenig Speicher vorhanden. Das Bild ist zu groß</string>
<string name="add_phone_book_text">%s zum Telefonbuch hinzufügen</string>
@ -535,6 +538,12 @@
<string name="dialog_title_create_conference">Gruppenchat erstellen</string>
<string name="choose_participants">Mitglieder wählen</string>
<string name="creating_conference">Erstelle Gruppenchat…</string>
<string name="import_text">Es wurde ein Backup gefunden, welches importiert werden kann.\nDein Messenger startet während des Importvorgangs neu. Soll das Backup importiert werden?</string>
<string name="import_database">Backup importieren</string>
<string name="import_started">Backup wird importiert, dies wird eine Weile dauern.</string>
<string name="import_canceled">Import abgebrochen</string>
<string name="import_failed">Backup import ist fehlgeschlagen und nicht möglich.</string>
<string name="enter_backup_password">Bitte gib das Passwort für das Backup ein.</string>
<string name="invite_again">Erneut einladen</string>
<string name="gp_disable">Deaktivieren</string>
<string name="gp_short">kurz</string>

View File

@ -175,6 +175,9 @@
<string name="block_jabber_id">Bloquear Identificador Jabber</string>
<string name="account_settings_example_jabber_id">usuario@ejemplo.com</string>
<string name="password">Contraseña</string>
<string name="password_wrong">Contraseña incorrecta, inténtalo de nuevo</string>
<string name="confirm_password">Confirmar contraseña</string>
<string name="passwords_do_not_match">Las contraseñas no coinciden</string>
<string name="invalid_jid">El identificador no es un identificador Jabber válido</string>
<string name="error_out_of_memory">Sin memoria. La imagen es demasiado grande</string>
<string name="add_phone_book_text">¿Quieres añadir a %s a tus contactos?</string>
@ -535,6 +538,11 @@
<string name="dialog_title_create_conference">Crear Conversación en Grupo</string>
<string name="choose_participants">Elige a los participantes</string>
<string name="creating_conference">Creando conversación en grupo...</string>
<string name="import_text">Hay una copia de seguridad en tu dispositivo que se puede importar.\nTu aplicación se reiniciará durante el proceso. ¿Quieres importar la copia de seguridad?</string>
<string name="import_database">Importar copia de seguridad</string>
<string name="import_started">Se importará la copia de seguridad, esto puede llevar un tiempo.</string>
<string name="import_canceled">Importación cancelada</string>
<string name="import_failed">La importación de la base de datos falló, una importación no es posible</string>
<string name="invite_again">Invitar de nuevo</string>
<string name="gp_disable">Deshabilitar</string>
<string name="gp_short">Corto</string>

View File

@ -171,6 +171,7 @@
<string name="block_jabber_id">Bloquer l\'ID Jabber</string>
<string name="account_settings_example_jabber_id">nom@exemple.com</string>
<string name="password">Mot de passe</string>
<string name="passwords_do_not_match">Les deux mots de passe ne correspondent pas.</string>
<string name="invalid_jid">Cet identifiant n\'est pas valide</string>
<string name="error_out_of_memory">Plus de mémoire disponible. L\'image est trop volumineuse.</string>
<string name="add_phone_book_text">Voulez-vous ajouter %s à votre carnet d\'adresses ?</string>

View File

@ -138,6 +138,9 @@
<string name="account_settings_jabber_id">Jabber ID</string>
<string name="account_settings_example_jabber_id">username@example.com</string>
<string name="password">Password</string>
<string name="password_wrong">Kata sandi salah, coba lagi</string>
<string name="confirm_password">Konfirmasi sandi</string>
<string name="passwords_do_not_match">Kata sandi tidak cocok</string>
<string name="invalid_jid">Jabber ID tidak valid</string>
<string name="error_out_of_memory">Memori habis. Gambar terlalu besar</string>
<string name="server_info_show_more">Info Server</string>
@ -354,6 +357,10 @@
<string name="secure_password_generated">Password aman berhasil dibuat</string>
<string name="registration_please_wait">Registrasi gagal: Coba lagi nanti</string>
<string name="choose_participants">Undang user</string>
<string name="import_database">Impor cadangan</string>
<string name="import_started">Cadangan akan diimpor, ini mungkin memakan waktu sebentar.</string>
<string name="import_canceled">Impor dibatalkan</string>
<string name="import_failed">Gagal memasukkan penyimpanan data, masukan tidak mungkin dilakukan</string>
<string name="invite_again">Undang lagi</string>
<string name="gp_short">Pendek</string>
<string name="gp_medium">Sedang</string>

View File

@ -169,6 +169,7 @@
<string name="block_jabber_id">Заблокировать Jabber ID</string>
<string name="account_settings_example_jabber_id">username@example.com</string>
<string name="password">Пароль</string>
<string name="passwords_do_not_match">Пароли не совпадают</string>
<string name="invalid_jid">Недопустимый Jabber ID</string>
<string name="error_out_of_memory">Недостаточно памяти. Изображение слишком большое</string>
<string name="add_phone_book_text">Вы хотите добавить %s в вашу адресную книгу?</string>

View File

@ -175,6 +175,9 @@
<string name="block_jabber_id">Block Jabber ID</string>
<string name="account_settings_example_jabber_id">username@example.com</string>
<string name="password">Password</string>
<string name="password_wrong">Wrong password, try again</string>
<string name="confirm_password">Confirm password</string>
<string name="passwords_do_not_match">Passwords do not match</string>
<string name="invalid_jid">This is not a valid Jabber ID</string>
<string name="error_out_of_memory">Out of memory. Image is too large</string>
<string name="add_phone_book_text">Do you want to add %s to your address book?</string>
@ -539,6 +542,18 @@
<string name="dialog_title_create_conference">Create Group Chat</string>
<string name="choose_participants">Choose participants</string>
<string name="creating_conference">Creating group chat…</string>
<string name="import_text">There is a backup on your device which can be imported.\nYour Messenger will be restarted during backup process. Shall the backup be imported?</string>
<string name="import_database">Import backup</string>
<string name="import_started">Backup will be imported, this may take awhile.</string>
<string name="import_canceled">Import canceled</string>
<string name="import_failed">Database import failed, an import is not possible</string>
<string name="backup_channel_name">Backups</string>
<string name="notification_backup_create">Creating backup</string>
<string name="pref_expert_options_backup">Backup</string>
<string name="pref_backup_password">Backup password</string>
<string name="pref_export_backup">Create backup</string>
<string name="pref_export_backup_summary">Export backup with set password now</string>
<string name="enter_backup_password">Please enter your password for your backup.</string>
<string name="invite_again">Invite again</string>
<string name="gp_disable">Disable</string>
<string name="gp_short">Short</string>

View File

@ -324,6 +324,18 @@
android:key="enable_foreground_service"
android:summary="@string/pref_keep_foreground_service_summary"
android:title="@string/pref_keep_foreground_service" />
</PreferenceCategory>
<PreferenceCategory
android:key="backup_expert_category"
android:title="@string/pref_expert_options_backup">
<Preference
android:key="backup_password"
android:summary="@string/enter_backup_password"
android:title="@string/pref_backup_password" />
<Preference
android:key="export_backup"
android:summary="@string/pref_export_backup_summary"
android:title="@string/pref_export_backup" />
<Preference
android:key="export_logs"
android:summary="@string/pref_export_logs_summary"