forked from upstream-mirrors/OpenTracks
Adding SD card backup option to the preferences
This commit is contained in:
@@ -165,9 +165,10 @@ limitations under the License.
|
|||||||
<string name="settings_recording">Recording</string>
|
<string name="settings_recording">Recording</string>
|
||||||
<string name="settings_sharing">Sharing</string>
|
<string name="settings_sharing">Sharing</string>
|
||||||
|
|
||||||
|
<string name="settings_not_available_summary">Not available in your version of Android</string>
|
||||||
|
|
||||||
<string name="settings_announcement_frequency">Announcement frequency</string>
|
<string name="settings_announcement_frequency">Announcement frequency</string>
|
||||||
<string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</string>
|
<string name="settings_announcement_frequency_summary">Minimum time between two voice announcements</string>
|
||||||
<string name="settings_announcement_not_available_summary">Text-to-speech announcements are not available in your version of Android</string>
|
|
||||||
|
|
||||||
<string name="settings_min_recording_distance">Min distance between points</string>
|
<string name="settings_min_recording_distance">Min distance between points</string>
|
||||||
<string name="settings_min_recording_distance_summary">Smallest distance between two recordings of your position</string>
|
<string name="settings_min_recording_distance_summary">Smallest distance between two recordings of your position</string>
|
||||||
@@ -204,6 +205,24 @@ limitations under the License.
|
|||||||
<string name="settings_split_frequency_summary">Maximum time or distance after which a new split marker will be inserted</string>
|
<string name="settings_split_frequency_summary">Maximum time or distance after which a new split marker will be inserted</string>
|
||||||
<string name="pace_label">Pace</string>
|
<string name="pace_label">Pace</string>
|
||||||
|
|
||||||
|
<string name="settings_backup">Backup</string>
|
||||||
|
<string name="settings_sd_backup">SD Card Backup</string>
|
||||||
|
<string name="settings_backup_to_sd">Backup now</string>
|
||||||
|
<string name="settings_backup_to_sd_summary">Write a full backup of all your tracks to the SD card</string>
|
||||||
|
<string name="settings_restore_from_sd">Restore now</string>
|
||||||
|
<string name="settings_restore_from_sd_summary">Restore a backup from the SD card</string>
|
||||||
|
<string name="settings_cloud_backup">Cloud Backup</string>
|
||||||
|
<string name="settings_backup_to_cloud">Enable cloud backup</string>
|
||||||
|
<string name="settings_backup_to_cloud_summary_on">Your data will be automatically copied to servers</string>
|
||||||
|
<string name="settings_backup_to_cloud_summary_off">Your data will NOT be copied to servers</string>
|
||||||
|
|
||||||
|
<!-- Backups -->
|
||||||
|
<string name="restore_overwrites_warning">Restoring a backup from the SD card will OVERWRITE all your current tracks. Are you sure?</string>
|
||||||
|
<string name="select_backup_to_restore">Select the backup to restore</string>
|
||||||
|
<string name="no_backups">No backups found.</string>
|
||||||
|
<string name="backup_write_progress_message">Writing all tracks to the SD card. This may take a while...</string>
|
||||||
|
<string name="backup_import_progress_message">Reading all tracks from the SD card. This may take a while...</string>
|
||||||
|
|
||||||
<!-- MyTracksSky -->
|
<!-- MyTracksSky -->
|
||||||
<string name="mytracks_sky">GPS Satellites</string>
|
<string name="mytracks_sky">GPS Satellites</string>
|
||||||
|
|
||||||
@@ -364,8 +383,10 @@ limitations under the License.
|
|||||||
<!-- SD card -->
|
<!-- SD card -->
|
||||||
<string name="io_no_external_storage_found">No external storage was found. Make sure that a SD card is inserted and that your phone is not mounted to a computer.</string>
|
<string name="io_no_external_storage_found">No external storage was found. Make sure that a SD card is inserted and that your phone is not mounted to a computer.</string>
|
||||||
<string name="io_write_failed">Unable to write to file.</string>
|
<string name="io_write_failed">Unable to write to file.</string>
|
||||||
|
<string name="io_read_failed">Unable to read from file.</string>
|
||||||
<string name="io_create_dir_failed">Unable to create directory on SD card.</string>
|
<string name="io_create_dir_failed">Unable to create directory on SD card.</string>
|
||||||
<string name="io_write_finished">Finished writing the track to SD card.</string>
|
<string name="io_write_finished">Finished writing to SD card.</string>
|
||||||
|
<string name="io_read_finished">Finished reading from SD card.</string>
|
||||||
|
|
||||||
<!-- VersionChecker -->
|
<!-- VersionChecker -->
|
||||||
<string name="version_check_title">Upgrade Check</string>
|
<string name="version_check_title">Upgrade Check</string>
|
||||||
|
|||||||
@@ -109,4 +109,28 @@ limitations under the License.
|
|||||||
android:summaryOff="@string/settings_share_url_only_summary_off"
|
android:summaryOff="@string/settings_share_url_only_summary_off"
|
||||||
android:defaultValue="false" />
|
android:defaultValue="false" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<!-- Backup settings -->
|
||||||
|
<PreferenceScreen android:title="@string/settings_backup">
|
||||||
|
<PreferenceCategory android:title="@string/settings_sd_backup">
|
||||||
|
<Preference
|
||||||
|
android:key="backupToSd"
|
||||||
|
android:title="@string/settings_backup_to_sd"
|
||||||
|
android:summary="@string/settings_backup_to_sd_summary"
|
||||||
|
android:persistent="false" />
|
||||||
|
<Preference
|
||||||
|
android:key="restoreFromSd"
|
||||||
|
android:title="@string/settings_restore_from_sd"
|
||||||
|
android:summary="@string/settings_restore_from_sd_summary"
|
||||||
|
android:persistent="false" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory android:title="@string/settings_cloud_backup">
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="backupToCloud"
|
||||||
|
android:title="@string/settings_backup_to_cloud"
|
||||||
|
android:summaryOn="@string/settings_backup_to_cloud_summary_on"
|
||||||
|
android:summaryOff="@string/settings_backup_to_cloud_summary_off"
|
||||||
|
android:defaultValue="true" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
</PreferenceScreen>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants used by the MyTracks application.
|
* Constants used by the MyTracks application.
|
||||||
*
|
*
|
||||||
@@ -38,6 +40,11 @@ public abstract class MyTracksConstants {
|
|||||||
*/
|
*/
|
||||||
public static final Object SDCARD_TOP_DIR = "MyTracks";
|
public static final Object SDCARD_TOP_DIR = "MyTracks";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API level of the Android version we're being run under.
|
||||||
|
*/
|
||||||
|
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* onActivityResult request codes:
|
* onActivityResult request codes:
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.io.backup.ExternalFileBackup;
|
||||||
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
|
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ import android.preference.CheckBoxPreference;
|
|||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.Preference.OnPreferenceChangeListener;
|
import android.preference.Preference.OnPreferenceChangeListener;
|
||||||
|
import android.preference.Preference.OnPreferenceClickListener;
|
||||||
import android.preference.PreferenceActivity;
|
import android.preference.PreferenceActivity;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -118,6 +120,35 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
announcementFrequency.setSummary(
|
announcementFrequency.setSummary(
|
||||||
R.string.settings_announcement_not_available_summary);
|
R.string.settings_announcement_not_available_summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable cloud backup options if not supported
|
||||||
|
if (MyTracksConstants.ANDROID_API_LEVEL < 8) {
|
||||||
|
// TODO: Merge by moving pref names to XML
|
||||||
|
CheckBoxPreference cloudBackupPreference = (CheckBoxPreference) findPreference("backupToCloud");
|
||||||
|
cloudBackupPreference.setEnabled(false);
|
||||||
|
cloudBackupPreference.setChecked(false);
|
||||||
|
cloudBackupPreference.setSummaryOff(R.string.settings_not_available_summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
Preference backupNowPreference = findPreference("backupToSd");
|
||||||
|
backupNowPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
ExternalFileBackup helper = new ExternalFileBackup(MyTracksSettings.this);
|
||||||
|
helper.writeToDefaultFile();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Preference restoreNowPreference = findPreference("restoreFromSd");
|
||||||
|
restoreNowPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
ExternalFileBackup helper = new ExternalFileBackup(MyTracksSettings.this);
|
||||||
|
helper.restoreFromFileList();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,376 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.io.backup;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
|
import com.google.android.apps.mytracks.MyTracksSettings;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.AlertDialog.Builder;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for writing or reading single-file backups.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class ExternalFileBackup {
|
||||||
|
// Since the user sees this format, we use the local timezone
|
||||||
|
private static final SimpleDateFormat BACKUP_FILENAME_FORMAT =
|
||||||
|
new SimpleDateFormat("'backup-'yyyy-MM-dd_HH-mm-ss");
|
||||||
|
private static final SimpleDateFormat DISPLAY_BACKUP_FORMAT =
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
private static final String BACKUPS_SUBDIR = "backups";
|
||||||
|
private static final int BACKUP_FILE_VERSION = 1;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final FileUtils fileUtils;
|
||||||
|
private final Handler uiThreadHandler = new Handler();
|
||||||
|
|
||||||
|
public ExternalFileBackup(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.fileUtils = new FileUtils();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a full backup to the default file.
|
||||||
|
* This shows the results to the user.
|
||||||
|
*/
|
||||||
|
public void writeToDefaultFile() {
|
||||||
|
if (!fileUtils.isSdCardAvailable()) {
|
||||||
|
showToast(R.string.io_no_external_storage_found);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dirName = fileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR);
|
||||||
|
final File dir = new File(dirName);
|
||||||
|
if (!fileUtils.ensureDirectoryExists(dir)) {
|
||||||
|
showToast(R.string.io_create_dir_failed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgressDialog progressDialog = ProgressDialog.show(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.progress_title),
|
||||||
|
context.getString(R.string.backup_write_progress_message),
|
||||||
|
true);
|
||||||
|
|
||||||
|
// Do the writing in another thread
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final String filename = BACKUP_FILENAME_FORMAT.format(new Date());
|
||||||
|
final File outputFile = new File(dir, filename);
|
||||||
|
|
||||||
|
Log.d(MyTracksConstants.TAG, "Writing backup to file " + filename);
|
||||||
|
writeToFile(outputFile);
|
||||||
|
showToast(R.string.io_write_finished);
|
||||||
|
} catch (IOException e) {
|
||||||
|
showToast(R.string.io_write_failed);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
dismissDialog(progressDialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores a full backup from the SD card.
|
||||||
|
* The user will be given a choice of which backup to restore as well as a
|
||||||
|
* confirmation dialog.
|
||||||
|
*/
|
||||||
|
public void restoreFromFileList() {
|
||||||
|
// Get the list of existing backups
|
||||||
|
if (!fileUtils.isSdCardAvailable()) {
|
||||||
|
showToast(R.string.io_no_external_storage_found);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String dirName = fileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR);
|
||||||
|
final File backupDir = new File(dirName);
|
||||||
|
if (!backupDir.isDirectory()) {
|
||||||
|
showToast(R.string.no_backups);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] backupFiles = getAvailableBackups(backupDir);
|
||||||
|
|
||||||
|
if (backupFiles == null || backupFiles.length == 0) {
|
||||||
|
showToast(R.string.no_backups);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a confirmation dialog
|
||||||
|
Builder confirmationDialogBuilder = new AlertDialog.Builder(context);
|
||||||
|
confirmationDialogBuilder.setMessage(R.string.restore_overwrites_warning);
|
||||||
|
confirmationDialogBuilder.setCancelable(false);
|
||||||
|
confirmationDialogBuilder.setNegativeButton(android.R.string.no, null);
|
||||||
|
confirmationDialogBuilder.setPositiveButton(android.R.string.yes, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
restoreFromFileListConfirmed(backupDir, backupFiles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
confirmationDialogBuilder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a backup list for the user to pick, then restores it.
|
||||||
|
*
|
||||||
|
* @param backupDir the backup directory
|
||||||
|
* @param backupFiles the list of available backup files
|
||||||
|
*/
|
||||||
|
private void restoreFromFileListConfirmed(final File backupDir, final String[] backupFiles) {
|
||||||
|
if (backupFiles.length == 1) {
|
||||||
|
// Only one choice, don't bother showing the list
|
||||||
|
File inputFile = new File(backupDir, backupFiles[0]);
|
||||||
|
restoreFromFileAsync(inputFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a user-visible version of the backup filenames
|
||||||
|
final String backupFileDates[] = new String[backupFiles.length];
|
||||||
|
for (int i = 0; i < backupFiles.length; i++) {
|
||||||
|
try {
|
||||||
|
Date backupDate = BACKUP_FILENAME_FORMAT.parse(backupFiles[i]);
|
||||||
|
backupFileDates[i] = DISPLAY_BACKUP_FORMAT.format(backupDate);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new IllegalStateException("All filenames should be good here");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Invert order
|
||||||
|
Arrays.sort(backupFileDates);
|
||||||
|
|
||||||
|
// Show a dialog for the user to pick which backup to restore
|
||||||
|
Builder dialogBuilder = new AlertDialog.Builder(context);
|
||||||
|
dialogBuilder.setCancelable(true);
|
||||||
|
dialogBuilder.setTitle(R.string.select_backup_to_restore);
|
||||||
|
dialogBuilder.setItems(backupFileDates, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
// User picked to restore this one
|
||||||
|
final String fileName = backupFiles[which];
|
||||||
|
final File inputFile = new File(backupDir, fileName);
|
||||||
|
|
||||||
|
restoreFromFileAsync(inputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialogBuilder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a progress dialog, then starts restoring the backup osynchronously.
|
||||||
|
*
|
||||||
|
* @param inputFile the file to restore from
|
||||||
|
*/
|
||||||
|
private void restoreFromFileAsync(final File inputFile) {
|
||||||
|
// Show a progress dialog
|
||||||
|
final ProgressDialog progressDialog = ProgressDialog.show(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.progress_title),
|
||||||
|
context.getString(R.string.backup_import_progress_message),
|
||||||
|
true);
|
||||||
|
|
||||||
|
// Do the actual importing in another thread (don't block the UI)
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Log.d(MyTracksConstants.TAG, "Restoring from file " + inputFile.getAbsolutePath());
|
||||||
|
restoreFromFile(inputFile);
|
||||||
|
showToast(R.string.io_read_finished);
|
||||||
|
} catch (IOException e) {
|
||||||
|
showToast(R.string.io_read_failed);
|
||||||
|
} finally {
|
||||||
|
dismissDialog(progressDialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of available backups to be restored.
|
||||||
|
*/
|
||||||
|
public String[] getAvailableBackups(File dir) {
|
||||||
|
return dir.list(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String filename) {
|
||||||
|
try {
|
||||||
|
BACKUP_FILENAME_FORMAT.parse(filename);
|
||||||
|
return true;
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a toast with the given contents.
|
||||||
|
*/
|
||||||
|
private void showToast(final int resId) {
|
||||||
|
uiThreadHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(context, resId, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely dismisses the given dialog.
|
||||||
|
*/
|
||||||
|
private void dismissDialog(final Dialog dialog) {
|
||||||
|
uiThreadHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously writes a backup to the given file.
|
||||||
|
*/
|
||||||
|
private void writeToFile(File outputFile) throws IOException {
|
||||||
|
// Create all the auxiliary classes that will do the writing
|
||||||
|
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
|
||||||
|
DatabaseDumper trackDumper = new DatabaseDumper(
|
||||||
|
TracksColumns.BACKUP_COLUMNS,
|
||||||
|
TracksColumns.BACKUP_COLUMN_TYPES,
|
||||||
|
false);
|
||||||
|
DatabaseDumper waypointDumper = new DatabaseDumper(
|
||||||
|
WaypointsColumns.BACKUP_COLUMNS,
|
||||||
|
WaypointsColumns.BACKUP_COLUMN_TYPES,
|
||||||
|
false);
|
||||||
|
DatabaseDumper pointDumper = new DatabaseDumper(
|
||||||
|
TrackPointsColumns.BACKUP_COLUMNS,
|
||||||
|
TrackPointsColumns.BACKUP_COLUMN_TYPES,
|
||||||
|
false);
|
||||||
|
|
||||||
|
// Open the target for writing
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(outputFile);
|
||||||
|
DataOutputStream outWriter = new DataOutputStream(outputStream);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Output a version header
|
||||||
|
outWriter.writeInt(BACKUP_FILE_VERSION);
|
||||||
|
|
||||||
|
// Dump preferences
|
||||||
|
SharedPreferences preferences =
|
||||||
|
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
preferencesHelper.exportPreferences(preferences, outWriter);
|
||||||
|
|
||||||
|
// Dump the entire contents of each table
|
||||||
|
ContentResolver contentResolver = context.getContentResolver();
|
||||||
|
Cursor tracksCursor = contentResolver.query(
|
||||||
|
TracksColumns.CONTENT_URI, null, null, null, null);
|
||||||
|
try {
|
||||||
|
trackDumper.writeAllRows(tracksCursor, outWriter);
|
||||||
|
} finally {
|
||||||
|
tracksCursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor waypointsCursor = contentResolver.query(
|
||||||
|
WaypointsColumns.CONTENT_URI, null, null, null, null);
|
||||||
|
try {
|
||||||
|
waypointDumper.writeAllRows(waypointsCursor, outWriter);
|
||||||
|
} finally {
|
||||||
|
waypointsCursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor pointsCursor = contentResolver.query(
|
||||||
|
TrackPointsColumns.CONTENT_URI, null, null, null, null);
|
||||||
|
try {
|
||||||
|
pointDumper.writeAllRows(pointsCursor, outWriter);
|
||||||
|
} finally {
|
||||||
|
pointsCursor.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
outputStream.flush();
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronously restores the backup from the given file.
|
||||||
|
*/
|
||||||
|
private void restoreFromFile(File inputFile) throws IOException {
|
||||||
|
// TODO: At this point we should stop recording if we were
|
||||||
|
|
||||||
|
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
DatabaseImporter trackImporter = new DatabaseImporter(TracksColumns.CONTENT_URI, resolver, false);
|
||||||
|
DatabaseImporter waypointImporter = new DatabaseImporter(WaypointsColumns.CONTENT_URI, resolver, false);
|
||||||
|
DatabaseImporter pointImporter = new DatabaseImporter(TrackPointsColumns.CONTENT_URI, resolver, false);
|
||||||
|
|
||||||
|
FileInputStream inputStream = new FileInputStream(inputFile);
|
||||||
|
DataInputStream reader = new DataInputStream(inputStream);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int backupVersion = reader.readInt();
|
||||||
|
if (backupVersion != BACKUP_FILE_VERSION) {
|
||||||
|
throw new IOException("Unknown backup file version " + backupVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore preferences
|
||||||
|
SharedPreferences preferences =
|
||||||
|
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
preferencesHelper.importPreferences(reader, preferences);
|
||||||
|
|
||||||
|
// Delete all previous contents of the tables.
|
||||||
|
resolver.delete(TracksColumns.CONTENT_URI, null, null);
|
||||||
|
resolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
|
||||||
|
resolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
||||||
|
|
||||||
|
// Import the new contents of each table
|
||||||
|
trackImporter.importAllRows(reader);
|
||||||
|
waypointImporter.importAllRows(reader);
|
||||||
|
pointImporter.importAllRows(reader);
|
||||||
|
} finally {
|
||||||
|
inputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,12 +84,19 @@ public class MyTracksBackupAgent extends BackupAgent {
|
|||||||
@Override
|
@Override
|
||||||
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
|
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
|
||||||
ParcelFileDescriptor newState) throws IOException {
|
ParcelFileDescriptor newState) throws IOException {
|
||||||
|
SharedPreferences preferences = this.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
// TODO: Pref name to XML
|
||||||
|
if (!preferences.getBoolean("backupToCloud", true)) {
|
||||||
|
Log.i(MyTracksConstants.TAG, "Cloud backup disabled - not doing it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Trigger backup on: track stop, preference change, track edit
|
// TODO: Trigger backup on: track stop, preference change, track edit
|
||||||
// TODO: How to handle backup while recording? (track may still change)
|
// TODO: How to handle backup while recording? (track may still change)
|
||||||
BackupStateManager backupStateManager = createStateManager(oldState, newState);
|
BackupStateManager backupStateManager = createStateManager(oldState, newState);
|
||||||
BackupStateManager stateManager = backupStateManager;
|
BackupStateManager stateManager = backupStateManager;
|
||||||
|
|
||||||
backupPreferences(data);
|
backupPreferences(data, preferences);
|
||||||
backupTracks(data, stateManager);
|
backupTracks(data, stateManager);
|
||||||
|
|
||||||
stateManager.flushNewState();
|
stateManager.flushNewState();
|
||||||
@@ -100,8 +107,7 @@ public class MyTracksBackupAgent extends BackupAgent {
|
|||||||
return new BackupStateManager(oldState, newState);
|
return new BackupStateManager(oldState, newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backupPreferences(BackupDataOutput data) throws IOException {
|
private void backupPreferences(BackupDataOutput data, SharedPreferences preferences) throws IOException {
|
||||||
SharedPreferences preferences = this.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
PreferenceBackupHelper preferenceDumper = createPreferenceBackupHelper();
|
PreferenceBackupHelper preferenceDumper = createPreferenceBackupHelper();
|
||||||
byte[] dumpedContents = preferenceDumper.exportPreferences(preferences);
|
byte[] dumpedContents = preferenceDumper.exportPreferences(preferences);
|
||||||
data.writeEntityHeader(PREFERENCES_ENTITY, dumpedContents.length);
|
data.writeEntityHeader(PREFERENCES_ENTITY, dumpedContents.length);
|
||||||
|
|||||||
+37
-8
@@ -25,6 +25,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,9 +44,26 @@ class PreferenceBackupHelper {
|
|||||||
* @return the corresponding byte array
|
* @return the corresponding byte array
|
||||||
* @throws IOException if there are any errors while writing to the byte array
|
* @throws IOException if there are any errors while writing to the byte array
|
||||||
*/
|
*/
|
||||||
public byte[] exportPreferences(SharedPreferences preferences) throws IOException {
|
public byte[] exportPreferences(SharedPreferences preferences)
|
||||||
|
throws IOException {
|
||||||
ByteArrayOutputStream bufStream = new ByteArrayOutputStream(BUFFER_SIZE);
|
ByteArrayOutputStream bufStream = new ByteArrayOutputStream(BUFFER_SIZE);
|
||||||
DataOutputStream outWriter = new DataOutputStream(bufStream);
|
DataOutputStream outWriter = new DataOutputStream(bufStream);
|
||||||
|
exportPreferences(preferences, outWriter);
|
||||||
|
|
||||||
|
return bufStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports all shared preferences from the given object into the given output
|
||||||
|
* stream.
|
||||||
|
*
|
||||||
|
* @param preferences the preferences to export
|
||||||
|
* @param bufStream the stream to write them to
|
||||||
|
* @throws IOException if there are any errors while writing the output
|
||||||
|
*/
|
||||||
|
public void exportPreferences(
|
||||||
|
SharedPreferences preferences,
|
||||||
|
DataOutputStream outWriter) throws IOException {
|
||||||
Map<String, ?> values = preferences.getAll();
|
Map<String, ?> values = preferences.getAll();
|
||||||
|
|
||||||
outWriter.writeInt(values.size());
|
outWriter.writeInt(values.size());
|
||||||
@@ -53,8 +71,6 @@ class PreferenceBackupHelper {
|
|||||||
writePreference(entry.getKey(), entry.getValue(), outWriter);
|
writePreference(entry.getKey(), entry.getValue(), outWriter);
|
||||||
}
|
}
|
||||||
outWriter.flush();
|
outWriter.flush();
|
||||||
|
|
||||||
return bufStream.toByteArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,14 +82,27 @@ class PreferenceBackupHelper {
|
|||||||
* @throws IOException if there are any errors while reading
|
* @throws IOException if there are any errors while reading
|
||||||
*/
|
*/
|
||||||
public boolean importPreferences(byte[] data, SharedPreferences preferences) throws IOException {
|
public boolean importPreferences(byte[] data, SharedPreferences preferences) throws IOException {
|
||||||
Editor editor = preferences.edit();
|
|
||||||
editor.clear();
|
|
||||||
|
|
||||||
ByteArrayInputStream bufStream = new ByteArrayInputStream(data);
|
ByteArrayInputStream bufStream = new ByteArrayInputStream(data);
|
||||||
DataInputStream reader = new DataInputStream(bufStream);
|
DataInputStream reader = new DataInputStream(bufStream);
|
||||||
|
|
||||||
int numPreferneces = reader.readInt();
|
return importPreferences(reader, preferences);
|
||||||
for (int i = 0; i < numPreferneces; i++) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports all preferences from the given stream.
|
||||||
|
*
|
||||||
|
* @param reader the stream to read from
|
||||||
|
* @param preferences the shared preferences to edit
|
||||||
|
* @return whether the preference change was successful
|
||||||
|
* @throws IOException if there are any errors while reading
|
||||||
|
*/
|
||||||
|
public boolean importPreferences(DataInputStream reader,
|
||||||
|
SharedPreferences preferences) throws IOException {
|
||||||
|
Editor editor = preferences.edit();
|
||||||
|
editor.clear();
|
||||||
|
|
||||||
|
int numPreferences = reader.readInt();
|
||||||
|
for (int i = 0; i < numPreferences; i++) {
|
||||||
String name = reader.readUTF();
|
String name = reader.readUTF();
|
||||||
byte typeId = reader.readByte();
|
byte typeId = reader.readByte();
|
||||||
readAndSetPreference(name, typeId, reader, editor);
|
readAndSetPreference(name, typeId, reader, editor);
|
||||||
|
|||||||
Reference in New Issue
Block a user