diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/DebugActivity.java
index d52beb67..b58773ae 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/DebugActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/DebugActivity.java
@@ -13,20 +13,34 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class DebugActivity extends Activity {
- Button sendSMSButton;
- Button sendEmailButton;
- Button incomingCallButton;
- Button outgoingCallButton;
- Button startCallButton;
- Button endCallButton;
- Button testNotificationButton;
- Button setMusicInfoButton;
- Button setTimeButton;
- Button rebootButton;
- EditText editContent;
+ private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
+
+ private Button sendSMSButton;
+ private Button sendEmailButton;
+ private Button incomingCallButton;
+ private Button outgoingCallButton;
+ private Button startCallButton;
+ private Button endCallButton;
+ private Button testNotificationButton;
+ private Button setMusicInfoButton;
+ private Button setTimeButton;
+ private Button rebootButton;
+ private Button exportDBButton;
+ private Button importDBButton;
+ private EditText editContent;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -114,6 +128,21 @@ public class DebugActivity extends Activity {
}
});
+ exportDBButton = (Button) findViewById(R.id.exportDBButton);
+ exportDBButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ exportDB();
+ }
+ });
+ importDBButton = (Button) findViewById(R.id.importDBButton);
+ importDBButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ importDB();
+ }
+ });
+
rebootButton = (Button) findViewById(R.id.rebootButton);
rebootButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -156,6 +185,34 @@ public class DebugActivity extends Activity {
});
}
+ private void exportDB() {
+ try {
+ ActivityDatabaseHandler dbHandler = GBApplication.getActivityDatabaseHandler();
+ DBHelper helper = new DBHelper(this);
+ File dir = FileUtils.getExternalFilesDir();
+ File destFile = helper.exportDB(dbHandler, dir);
+ Toast.makeText(this, "Exported to: " + destFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
+ } catch (Exception ex) {
+ LOG.error("Unable to export db", ex);
+ Toast.makeText(this, "Error exporting DB: " + ex.getMessage(), Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private void importDB() {
+ try {
+ ActivityDatabaseHandler dbHandler = GBApplication.getActivityDatabaseHandler();
+ DBHelper helper = new DBHelper(this);
+ File dir = FileUtils.getExternalFilesDir();
+ File sourceFile = new File(dir, dbHandler.getDatabaseName());
+ helper.importDB(dbHandler, sourceFile);
+ helper.validateDB(dbHandler);
+ Toast.makeText(this, "Import successful.", Toast.LENGTH_LONG).show();
+ } catch (Exception ex) {
+ LOG.error("Unable to import db", ex);
+ Toast.makeText(this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG).show();
+ }
+ }
+
private void testNotification() {
NotificationManager nManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder ncomp = new NotificationCompat.Builder(this);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
index a512087e..9bf18f1b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
@@ -12,8 +12,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class GBApplication extends Application {
private static GBApplication context;
@@ -47,20 +49,15 @@ public class GBApplication extends Application {
private void setupLogging() {
if (isFileLoggingEnabled()) {
- File dir = getExternalFilesDir(null);
- if (dir != null) {
- if (!dir.exists()) {
- dir.mkdirs();
- }
+ try {
+ File dir = FileUtils.getExternalFilesDir();
// used by assets/logback.xml since the location cannot be statically determined
System.setProperty("GB_LOGFILES_DIR", dir.getAbsolutePath());
- } else {
- Log.e("GBApplication", "External files dir is null, cannot log to file");
+ } catch (IOException ex) {
+ Log.e("GBApplication", "External files dir not available, cannot log to file, ex");
System.setProperty("GB_LOGFILES_DIR", "/dev/null");
-
}
} else {
- System.setProperty("GB_LOGFILES_DIR", "/dev/null"); // just to please logback configuration, not used at all
try {
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.detachAppender("FILE");
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
new file mode 100644
index 00000000..ff445285
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
@@ -0,0 +1,67 @@
+package nodomain.freeyourgadget.gadgetbridge.database;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class DBHelper {
+ private final Context context;
+
+ public DBHelper(Context context) {
+ this.context = context;
+ }
+
+ private String getClosedDBPath(SQLiteOpenHelper dbHandler) throws IllegalStateException {
+ SQLiteDatabase db = dbHandler.getReadableDatabase();
+ String path = db.getPath();
+ db.close(); // reference counted, so may still be open
+ if (db.isOpen()) {
+ throw new IllegalStateException("Database must be closed");
+ }
+ return path;
+ }
+
+ public File exportDB(SQLiteOpenHelper dbHandler, File toDir) throws IllegalStateException, IOException {
+ String dbPath = getClosedDBPath(dbHandler);
+ File sourceFile = new File(dbPath);
+ File destFile = new File(toDir, sourceFile.getName());
+ if (destFile.exists()) {
+ File backup = new File(toDir, destFile.getName() + "_" + getDate());
+ destFile.renameTo(backup);
+ } else if (!toDir.exists()) {
+ if (!toDir.mkdirs()) {
+ throw new IOException("Unable to create directory: " + toDir.getAbsolutePath());
+ }
+ }
+
+ FileUtils.copyFile(sourceFile, destFile);
+ return destFile;
+ }
+
+ private String getDate() {
+ return new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(new Date());
+ }
+
+ public void importDB(SQLiteOpenHelper dbHandler, File fromFile) throws IllegalStateException, IOException {
+ String dbPath = getClosedDBPath(dbHandler);
+ File toFile = new File(dbPath);
+ FileUtils.copyFile(fromFile, toFile);
+ }
+
+ public void validateDB(SQLiteOpenHelper dbHandler) throws IOException {
+ try (SQLiteDatabase db = dbHandler.getReadableDatabase()) {
+ if (!db.isDatabaseIntegrityOk()) {
+ throw new IOException("Database integrity is not OK");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
new file mode 100644
index 00000000..d3fbd9b9
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
@@ -0,0 +1,45 @@
+package nodomain.freeyourgadget.gadgetbridge.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+
+public class FileUtils {
+ /**
+ * Copies the the given sourceFile to destFile, overwriting it, in case it exists.
+ * @param sourceFile
+ * @param destFile
+ * @throws IOException
+ */
+ public static void copyFile(File sourceFile, File destFile) throws IOException {
+ if (!sourceFile.exists()) {
+ throw new IOException("Does not exist: " + sourceFile.getAbsolutePath());
+ }
+ copyFile(new FileInputStream(sourceFile), new FileOutputStream(destFile));
+ }
+
+ private static void copyFile(FileInputStream sourceStream, FileOutputStream destStream) throws IOException {
+ try (FileChannel fromChannel = sourceStream.getChannel(); FileChannel toChannel = destStream.getChannel()) {
+ fromChannel.transferTo(0, fromChannel.size(), toChannel);
+ }
+ }
+
+ /**
+ * Returns the existing external storage dir.
+ * @throws IOException when the directory is not available
+ */
+ public static File getExternalFilesDir() throws IOException {
+ File dir = GBApplication.getContext().getExternalFilesDir(null);
+ if (dir == null) {
+ throw new IOException("Unable to access external files dir: null");
+ }
+ if (!dir.exists() && !dir.mkdirs()) {
+ throw new IOException("Unable to access external files dir: does not exist");
+ }
+ return dir;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml
index 49353c51..108fa711 100644
--- a/app/src/main/res/layout/activity_debug.xml
+++ b/app/src/main/res/layout/activity_debug.xml
@@ -109,4 +109,23 @@
android:layout_alignParentStart="true"
android:layout_alignEnd="@+id/endCallButton" />
+
+
+
+