From 9329670ca8f299456dc39cc9beb9e6ed467e8dc9 Mon Sep 17 00:00:00 2001 From: jannis Date: Tue, 20 Aug 2019 22:54:02 +0200 Subject: [PATCH] #9 Ability to Import and Export data - Import and Export Backups working --- app/src/main/AndroidManifest.xml | 1 + .../fitness/activity/FitoTrackActivity.java | 36 ++++ .../fitness/activity/SettingsActivity.java | 169 ++++++++++++++++-- .../fitness/activity/ShowWorkoutActivity.java | 37 +--- .../de/tadris/fitness/data/WorkoutDao.java | 3 + .../tadris/fitness/util/export/Exporter.java | 106 +++++++++++ .../util/export/FitoTrackDataContainer.java | 58 ++++++ .../util/export/FitoTrackSettings.java | 30 ++++ .../view/ProgressDialogController.java | 2 +- app/src/main/res/values/strings.xml | 20 ++- app/src/main/res/xml/preferences_main.xml | 9 + 11 files changed, 419 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/de/tadris/fitness/util/export/Exporter.java create mode 100644 app/src/main/java/de/tadris/fitness/util/export/FitoTrackDataContainer.java create mode 100644 app/src/main/java/de/tadris/fitness/util/export/FitoTrackSettings.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 078121a..2101dcd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + target) { - loadHeadersFromResource(R.xml.pref_headers, target); - }*/ - - /** - * Binds a preference's summary to its value. More specifically, when the - * preference's value is changed, its summary (line of text below the - * preference title) is updated to reflect the value. The summary is also - * immediately updated upon calling this method. The exact display format is - * dependent on the type of preference. - * - * @see #sBindPreferenceSummaryToValueListener - */ private static void bindPreferenceSummaryToValue(Preference preference) { // Set the listener to watch for value changes. preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); @@ -119,6 +148,8 @@ public class SettingsActivity extends PreferenceActivity { .getString(preference.getKey(), "")); } + private Handler mHandler= new Handler(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -131,9 +162,117 @@ public class SettingsActivity extends PreferenceActivity { bindPreferenceSummaryToValue(findPreference("unitSystem")); findPreference("weight").setOnPreferenceClickListener(preference -> showWeightPicker()); + findPreference("import").setOnPreferenceClickListener(preference -> showImportDialog()); + findPreference("export").setOnPreferenceClickListener(preference -> showExportDialog()); } + private boolean showExportDialog(){ + new AlertDialog.Builder(this) + .setTitle(R.string.exportData) + .setMessage(R.string.exportDataSummary) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.backup, (dialog, which) -> { + exportBackup(); + }).create().show(); + return true; + } + + private void exportBackup(){ + ProgressDialogController dialogController= new ProgressDialogController(this, getString(R.string.backup)); + dialogController.show(); + new Thread(() -> { + try{ + String file= getFilesDir().getAbsolutePath() + "/shared/backup.ftb"; + new File(file).getParentFile().mkdirs(); + Uri uri= FileProvider.getUriForFile(getBaseContext(), "de.tadris.fitness.fileprovider", new File(file)); + + Exporter.exportData(getBaseContext(), new File(file), + (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action))); + + mHandler.post(() -> { + dialogController.cancel(); + shareFile(uri); + }); + }catch (Exception e){ + e.printStackTrace(); + mHandler.post(() -> { + dialogController.cancel(); + showErrorDialog(e, R.string.error, R.string.errorExportFailed); + }); + } + }).start(); + } + + private boolean showImportDialog(){ + if(!hasPermission()){ + requestPermissions(); + return true; + } + new AlertDialog.Builder(this) + .setTitle(R.string.importBackup) + .setMessage(R.string.importBackupMessage) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.restore, (dialog, which) -> { + importBackup(); + }).create().show(); + return true; + } + + void requestPermissions(){ + if (!hasPermission()) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 10); + } + } + + public boolean hasPermission(){ + return ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + + private static final int FILE_SELECT_CODE= 21; + private void importBackup(){ + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + try { + startActivityForResult(Intent.createChooser(intent, getString(R.string.chooseBackupFile)), FILE_SELECT_CODE); + } catch (android.content.ActivityNotFoundException ignored) { } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case FILE_SELECT_CODE: + if (resultCode == RESULT_OK){ + importBackup(data.getData()); + } + break; + } + super.onActivityResult(requestCode, resultCode, data); + } + + private void importBackup(Uri uri){ + ProgressDialogController dialogController= new ProgressDialogController(this, getString(R.string.backup)); + dialogController.show(); + new Thread(() -> { + try{ + Exporter.importData(getBaseContext(), uri, + (progress, action) -> mHandler.post(() -> dialogController.setProgress(progress, action))); + + mHandler.post(() -> { + // DO on backup finished + dialogController.cancel(); + }); + }catch (Exception e){ + e.printStackTrace(); + mHandler.post(() -> { + dialogController.cancel(); + showErrorDialog(e, R.string.error, R.string.errorImportFailed); + }); + } + }).start(); + } + private boolean showWeightPicker() { final AlertDialog.Builder d = new AlertDialog.Builder(this); final SharedPreferences preferences= PreferenceManager.getDefaultSharedPreferences(this); diff --git a/app/src/main/java/de/tadris/fitness/activity/ShowWorkoutActivity.java b/app/src/main/java/de/tadris/fitness/activity/ShowWorkoutActivity.java index 3bc8b4a..b9f464e 100644 --- a/app/src/main/java/de/tadris/fitness/activity/ShowWorkoutActivity.java +++ b/app/src/main/java/de/tadris/fitness/activity/ShowWorkoutActivity.java @@ -39,6 +39,7 @@ import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.StringRes; import androidx.core.app.ActivityCompat; import androidx.core.content.FileProvider; @@ -347,46 +348,12 @@ public class ShowWorkoutActivity extends FitoTrackActivity { dialogController.cancel(); mHandler.post(() -> shareFile(uri)); }catch (Exception e){ - mHandler.post(() -> showErrorDialog(e)); + mHandler.post(() -> showErrorDialog(e, R.string.error, R.string.errorGpxExportFailed)); } }).start(); } - private void shareFile(Uri uri){ - Intent intentShareFile = new Intent(Intent.ACTION_SEND); - intentShareFile.setDataAndType(uri, getContentResolver().getType(uri)); - intentShareFile.putExtra(Intent.EXTRA_STREAM, uri); - intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivity(Intent.createChooser(intentShareFile, getString(R.string.shareFile))); - - Log.d("Export", uri.toString()); - Log.d("Export", getContentResolver().getType(uri)); - try { - Log.d("Export", new BufferedInputStream(getContentResolver().openInputStream(uri)).toString()); - } catch (FileNotFoundException e) { - - } - } - - /*void requestPermissions(){ - if (!hasPermission()) { - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 10); - } - } - - public boolean hasPermission(){ - return ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; - }*/ - - - private void showErrorDialog(Exception e){ - new AlertDialog.Builder(this) - .setTitle(R.string.error) - .setMessage(getString(R.string.errorGpxExportFailed) + "\n\n" + e.getMessage()) - .setPositiveButton(R.string.okay, null) - .create().show(); - } @Override public boolean onOptionsItemSelected(MenuItem item) { diff --git a/app/src/main/java/de/tadris/fitness/data/WorkoutDao.java b/app/src/main/java/de/tadris/fitness/data/WorkoutDao.java index eeea868..ba84deb 100644 --- a/app/src/main/java/de/tadris/fitness/data/WorkoutDao.java +++ b/app/src/main/java/de/tadris/fitness/data/WorkoutDao.java @@ -46,5 +46,8 @@ public interface WorkoutDao { @Update void updateWorkout(Workout workout); + @Update + void insertSample(WorkoutSample sample); + } diff --git a/app/src/main/java/de/tadris/fitness/util/export/Exporter.java b/app/src/main/java/de/tadris/fitness/util/export/Exporter.java new file mode 100644 index 0000000..d7c6df8 --- /dev/null +++ b/app/src/main/java/de/tadris/fitness/util/export/Exporter.java @@ -0,0 +1,106 @@ +package de.tadris.fitness.util.export; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.Uri; +import android.preference.PreferenceManager; + +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; + +import de.tadris.fitness.Instance; +import de.tadris.fitness.R; +import de.tadris.fitness.data.AppDatabase; +import de.tadris.fitness.data.UserPreferences; +import de.tadris.fitness.data.Workout; +import de.tadris.fitness.data.WorkoutSample; +import de.tadris.fitness.util.unit.UnitUtils; + +public class Exporter { + + public static final int VERSION= 1; + + public static void exportData(Context context, File output, ExportStatusListener listener) throws IOException { + listener.onStatusChanged(0, context.getString(R.string.initialising)); + UserPreferences preferences= Instance.getInstance(context).userPreferences; + AppDatabase database= Instance.getInstance(context).db; + UnitUtils.setUnit(context); + + FitoTrackDataContainer container= new FitoTrackDataContainer(); + container.version= VERSION; + container.workouts= new ArrayList<>(); + container.samples= new ArrayList<>(); + + listener.onStatusChanged(10, context.getString(R.string.preferences)); + FitoTrackSettings settings= new FitoTrackSettings(); + settings.weight= preferences.getUserWeight(); + settings.preferredUnitSystem= String.valueOf(UnitUtils.CHOSEN_SYSTEM.getId()); + container.settings= settings; + + listener.onStatusChanged(20, context.getString(R.string.workouts)); + container.workouts.addAll(Arrays.asList(database.workoutDao().getWorkouts())); + listener.onStatusChanged(40, context.getString(R.string.locationData)); + container.workouts.addAll(Arrays.asList(database.workoutDao().getWorkouts())); + + listener.onStatusChanged(60, context.getString(R.string.converting)); + + XmlMapper mapper= new XmlMapper(); + mapper.writeValue(output, container); + + listener.onStatusChanged(100, context.getString(R.string.finished)); + } + + @SuppressLint("ApplySharedPref") + public static void importData(Context context, Uri input, ExportStatusListener listener) throws IOException{ + listener.onStatusChanged(0, context.getString(R.string.loadingFile)); + XmlMapper xmlMapper = new XmlMapper(); + FitoTrackDataContainer container = xmlMapper.readValue(context.getContentResolver().openInputStream(input), FitoTrackDataContainer.class); + + if(container.version != 1){ + throw new UnsupportedEncodingException("Version Code" + container.version + " is unsupported!"); + } + + listener.onStatusChanged(40, context.getString(R.string.preferences)); + PreferenceManager.getDefaultSharedPreferences(context) + .edit().clear() + .putInt("weight", container.settings.weight) + .putString("unitSystem", container.settings.preferredUnitSystem) + .commit(); + + AppDatabase database= Instance.getInstance(context).db; + database.clearAllTables(); + + listener.onStatusChanged(60, context.getString(R.string.workouts)); + if(container.workouts != null){ + for(Workout workout : container.workouts){ + database.workoutDao().insertWorkout(workout); + } + } + + listener.onStatusChanged(80, context.getString(R.string.locationData)); + if(container.samples != null){ + for(WorkoutSample sample : container.samples){ + database.workoutDao().insertSample(sample); + } + } + + listener.onStatusChanged(100, context.getString(R.string.finished)); + } + + + public interface ExportStatusListener{ + void onStatusChanged(int progress, String action); + } + + public static class UnsupportedVersionException extends Exception{ + public UnsupportedVersionException(String message) { + super(message); + } + } + +} diff --git a/app/src/main/java/de/tadris/fitness/util/export/FitoTrackDataContainer.java b/app/src/main/java/de/tadris/fitness/util/export/FitoTrackDataContainer.java new file mode 100644 index 0000000..2ad55bb --- /dev/null +++ b/app/src/main/java/de/tadris/fitness/util/export/FitoTrackDataContainer.java @@ -0,0 +1,58 @@ +package de.tadris.fitness.util.export; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +import java.util.List; + +import de.tadris.fitness.data.Workout; +import de.tadris.fitness.data.WorkoutSample; + +@JacksonXmlRootElement(localName = "fito-track") +public class FitoTrackDataContainer { + + int version; + List workouts; + List samples; + FitoTrackSettings settings; + + public FitoTrackDataContainer(){} + + public FitoTrackDataContainer(int version, List workouts, List samples, FitoTrackSettings settings) { + this.version = version; + this.workouts = workouts; + this.samples = samples; + this.settings = settings; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public List getWorkouts() { + return workouts; + } + + public void setWorkouts(List workouts) { + this.workouts = workouts; + } + + public List getSamples() { + return samples; + } + + public void setSamples(List samples) { + this.samples = samples; + } + + public FitoTrackSettings getSettings() { + return settings; + } + + public void setSettings(FitoTrackSettings settings) { + this.settings = settings; + } +} diff --git a/app/src/main/java/de/tadris/fitness/util/export/FitoTrackSettings.java b/app/src/main/java/de/tadris/fitness/util/export/FitoTrackSettings.java new file mode 100644 index 0000000..645b9af --- /dev/null +++ b/app/src/main/java/de/tadris/fitness/util/export/FitoTrackSettings.java @@ -0,0 +1,30 @@ +package de.tadris.fitness.util.export; + +public class FitoTrackSettings { + + String preferredUnitSystem; + int weight; + + public FitoTrackSettings(){} + + public FitoTrackSettings(String preferredUnitSystem, int weight) { + this.preferredUnitSystem = preferredUnitSystem; + this.weight = weight; + } + + public String getPreferredUnitSystem() { + return preferredUnitSystem; + } + + public void setPreferredUnitSystem(String preferredUnitSystem) { + this.preferredUnitSystem = preferredUnitSystem; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } +} diff --git a/app/src/main/java/de/tadris/fitness/view/ProgressDialogController.java b/app/src/main/java/de/tadris/fitness/view/ProgressDialogController.java index e52906c..f418c3f 100644 --- a/app/src/main/java/de/tadris/fitness/view/ProgressDialogController.java +++ b/app/src/main/java/de/tadris/fitness/view/ProgressDialogController.java @@ -53,6 +53,6 @@ public class ProgressDialogController { } public void cancel(){ - dialog.dismiss(); + dialog.cancel(); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee2a099..07b4de3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,8 +25,22 @@ Exporting Error - The GPX export has been failed. + The GPX export has failed. + The data export has failed. + The data import has failed. Share file + Initialising + Preferences + Workouts + Location data + Converting + Finished + Loading file + Choose Backup-File + + WARNING: All your existing data in the app will be cleared. Are you sure, you want to restore this backup? + Restore + Backup Silent @@ -80,4 +94,8 @@ Your weight is needed to calculate the burned calories Preferred system of units Settings + Export Data + This takes a backup of all your preferences and workout data + Import Data Backup + Restore a taken backup diff --git a/app/src/main/res/xml/preferences_main.xml b/app/src/main/res/xml/preferences_main.xml index 87faae1..e8a0ec8 100644 --- a/app/src/main/res/xml/preferences_main.xml +++ b/app/src/main/res/xml/preferences_main.xml @@ -14,5 +14,14 @@ android:summary="@string/pref_weight_summary" android:title="@string/pref_weight" /> + + + \ No newline at end of file