#9 Ability to Import and Export data

- Import and Export Backups working
This commit is contained in:
jannis 2019-08-20 22:54:02 +02:00
parent 2f71b2a3d5
commit 9329670ca8
11 changed files with 419 additions and 52 deletions

View File

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"

View File

@ -20,8 +20,19 @@
package de.tadris.fitness.activity;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
import androidx.annotation.StringRes;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import de.tadris.fitness.R;
abstract public class FitoTrackActivity extends Activity {
@ -32,5 +43,30 @@ abstract public class FitoTrackActivity extends Activity {
return value.data;
}
protected 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) {
}
}
protected void showErrorDialog(Exception e, @StringRes int title, @StringRes int message){
new AlertDialog.Builder(this)
.setTitle(title)
.setMessage(getString(message) + "\n\n" + e.getMessage())
.setPositiveButton(R.string.okay, null)
.create().show();
}
}

View File

@ -19,30 +19,74 @@
package de.tadris.fitness.activity;
import android.Manifest;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.RingtonePreference;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.NumberPicker;
import androidx.annotation.StringRes;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NavUtils;
import androidx.core.content.FileProvider;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import de.tadris.fitness.R;
import de.tadris.fitness.util.export.Exporter;
import de.tadris.fitness.util.gpx.GpxExporter;
import de.tadris.fitness.util.unit.UnitUtils;
import de.tadris.fitness.view.ProgressDialogController;
public class SettingsActivity extends PreferenceActivity {
protected 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) {
}
}
protected void showErrorDialog(Exception e, @StringRes int title, @StringRes int message){
new AlertDialog.Builder(this)
.setTitle(title)
.setMessage(getString(message) + "\n\n" + e.getMessage())
.setPositiveButton(R.string.okay, null)
.create().show();
}
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
@ -92,21 +136,6 @@ public class SettingsActivity extends PreferenceActivity {
return true;
};
/*@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> 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);

View File

@ -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) {

View File

@ -46,5 +46,8 @@ public interface WorkoutDao {
@Update
void updateWorkout(Workout workout);
@Update
void insertSample(WorkoutSample sample);
}

View File

@ -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);
}
}
}

View File

@ -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<Workout> workouts;
List<WorkoutSample> samples;
FitoTrackSettings settings;
public FitoTrackDataContainer(){}
public FitoTrackDataContainer(int version, List<Workout> workouts, List<WorkoutSample> 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<Workout> getWorkouts() {
return workouts;
}
public void setWorkouts(List<Workout> workouts) {
this.workouts = workouts;
}
public List<WorkoutSample> getSamples() {
return samples;
}
public void setSamples(List<WorkoutSample> samples) {
this.samples = samples;
}
public FitoTrackSettings getSettings() {
return settings;
}
public void setSettings(FitoTrackSettings settings) {
this.settings = settings;
}
}

View File

@ -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;
}
}

View File

@ -53,6 +53,6 @@ public class ProgressDialogController {
}
public void cancel(){
dialog.dismiss();
dialog.cancel();
}
}

View File

@ -25,8 +25,22 @@
<string name="exporting">Exporting</string>
<string name="error">Error</string>
<string name="errorGpxExportFailed">The GPX export has been failed.</string>
<string name="errorGpxExportFailed">The GPX export has failed.</string>
<string name="errorExportFailed">The data export has failed.</string>
<string name="errorImportFailed">The data import has failed.</string>
<string name="shareFile">Share file</string>
<string name="initialising">Initialising</string>
<string name="preferences">Preferences</string>
<string name="workouts">Workouts</string>
<string name="locationData">Location data</string>
<string name="converting">Converting</string>
<string name="finished">Finished</string>
<string name="loadingFile">Loading file</string>
<string name="chooseBackupFile">Choose Backup-File</string>
<string name="importBackupMessage">WARNING: All your existing data in the app will be cleared. Are you sure, you want to restore this backup?</string>
<string name="restore">Restore</string>
<string name="backup">Backup</string>
<string name="pref_ringtone_silent">Silent</string>
@ -80,4 +94,8 @@
<string name="pref_weight_summary">Your weight is needed to calculate the burned calories</string>
<string name="pref_unit_system">Preferred system of units</string>
<string name="settings">Settings</string>
<string name="exportData">Export Data</string>
<string name="exportDataSummary">This takes a backup of all your preferences and workout data</string>
<string name="importBackup">Import Data Backup</string>
<string name="importBackupSummary">Restore a taken backup</string>
</resources>

View File

@ -14,5 +14,14 @@
android:summary="@string/pref_weight_summary"
android:title="@string/pref_weight" />
<Preference
android:key="import"
android:summary="@string/importBackupSummary"
android:title="@string/importBackup" />
<Preference
android:key="export"
android:summary="@string/exportDataSummary"
android:title="@string/exportData" />
</PreferenceScreen>