add backup import and export
This commit is contained in:
parent
ea1435c5f9
commit
13846bf4fd
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -246,6 +246,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">
|
||||
|
|
|
@ -63,8 +63,8 @@ import rocks.xmpp.addr.Jid;
|
|||
|
||||
public class DatabaseBackend extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DATABASE_NAME = "history";
|
||||
private static final int DATABASE_VERSION = 44;
|
||||
public static final String DATABASE_NAME = "history";
|
||||
public static final int DATABASE_VERSION = 44;
|
||||
private static DatabaseBackend instance = null;
|
||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||
|
|
|
@ -150,6 +150,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/";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -143,6 +143,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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
||||
|
@ -539,6 +542,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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -539,6 +542,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -353,6 +356,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
@ -541,6 +544,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>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue