diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml
index 184ec39d8..141e5eb89 100755
--- a/MyTracks/AndroidManifest.xml
+++ b/MyTracks/AndroidManifest.xml
@@ -65,6 +65,9 @@
+
+
-
+
announcementFrequency
bluetoothDevice
bluetoothPairing
@@ -28,9 +28,7 @@ limitations under the License.
minRequiredAccuracy
modularStatsDisplay
pickExistingMap
- recordingTrack
reportSpeed
- selectedTrack
sendStatsAndPoints
sendToDocs
sendToMyMaps
@@ -39,6 +37,12 @@ limitations under the License.
shareUrlOnly
signalSamplingFrequency
splitFrequency
+
+
+ recordingTrack
+ selectedTrack
+
+
backupToSd
restoreFromSd
\ No newline at end of file
diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml
index 28974d6f9..dfa0b7cb3 100644
--- a/MyTracks/res/values/strings.xml
+++ b/MyTracks/res/values/strings.xml
@@ -222,16 +222,17 @@ limitations under the License.
Backup
SD Card Backup
Backup now
- Write a full backup of all your tracks to the SD card
+ Write a full copy of all your tracks and settings to the SD card
Restore now
- Restore a backup from the SD card
+ Restore all tracks and settings from the SD card
+ Cannot manipulate backups while recording a track.
- Restoring a backup from the SD card will OVERWRITE all your current tracks. Are you sure?
+ Restoring a backup from the SD card will OVERWRITE all your current tracks and settings. Are you sure?
Select the backup to restore
No backups found.
- Writing all tracks to the SD card. This may take a while...
- Reading all tracks from the SD card. This may take a while...
+ Writing all tracks and settings to the SD card. This may take a while...
+ Reading all tracks and settings from the SD card. This may take a while...
GPS Satellites
diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java
index cc4506e99..a1d492634 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracksSettings.java
@@ -15,10 +15,13 @@
*/
package com.google.android.apps.mytracks;
-import com.google.android.apps.mytracks.io.backup.ExternalFileBackup;
+import com.google.android.apps.mytracks.io.backup.BackupActivity;
+import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
import com.google.android.maps.mytracks.R;
+import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
@@ -50,6 +53,9 @@ public class MyTracksSettings extends PreferenceActivity {
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
private static boolean mTTSAvailable;
+ private BackupPreferencesListener backupListener;
+
+ private SharedPreferences preferences;
/* establish whether the tts class is available to us */
static {
@@ -72,6 +78,11 @@ public class MyTracksSettings extends PreferenceActivity {
preferenceManager.setSharedPreferencesName(SETTINGS_NAME);
preferenceManager.setSharedPreferencesMode(0);
+ // Set up automatic preferences backup
+ backupListener = BackupPreferencesListener.create(this);
+ preferences = preferenceManager.getSharedPreferences();
+ preferences.registerOnSharedPreferenceChangeListener(backupListener);
+
// Load the preferences to be displayed
addPreferencesFromResource(R.xml.preferences);
@@ -101,28 +112,55 @@ public class MyTracksSettings extends PreferenceActivity {
announcementFrequency.setSummary(
R.string.settings_announcement_not_available_summary);
}
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
- // Add actions to the backup preferences
Preference backupNowPreference = findPreference(getString(R.string.backup_to_sd_key));
Preference restoreNowPreference = findPreference(getString(R.string.restore_from_sd_key));
+
+ // If recording, disable backup/restore (we don't want to get to inconsistent states)
+ boolean recording = preferences.getLong(getString(R.string.recording_track_key), -1) != -1;
+ backupNowPreference.setEnabled(!recording);
+ restoreNowPreference.setEnabled(!recording);
+ backupNowPreference.setSummary(
+ recording ? R.string.settings_no_backup_while_recording
+ : R.string.settings_backup_to_sd_summary);
+ restoreNowPreference.setSummary(
+ recording ? R.string.settings_no_backup_while_recording
+ : R.string.settings_restore_from_sd_summary);
+
+ // Add actions to the backup preferences
backupNowPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
- ExternalFileBackup helper = new ExternalFileBackup(MyTracksSettings.this);
- helper.writeToDefaultFile();
+ Intent intent = new Intent(MyTracksSettings.this, BackupActivity.class);
+ intent.setAction(BackupActivity.BACKUP_ACTION);
+ startActivity(intent);
return true;
}
});
restoreNowPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
- ExternalFileBackup helper = new ExternalFileBackup(MyTracksSettings.this);
- helper.restoreFromFileList();
+ Intent intent = new Intent(MyTracksSettings.this, BackupActivity.class);
+ intent.setAction(BackupActivity.RESTORE_ACTION);
+ startActivity(intent);
return true;
}
});
}
+ @Override
+ protected void onDestroy() {
+ getPreferenceManager().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(backupListener);
+
+ super.onPause();
+ }
+
/**
* Updates all the preferences which give options with distance units to use
* the proper unit the user has selected.
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivity.java
new file mode 100644
index 000000000..45ca909ab
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivity.java
@@ -0,0 +1,278 @@
+/*
+ * 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.MyTracks;
+import com.google.android.apps.mytracks.util.FileUtils;
+import com.google.android.maps.mytracks.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+
+/**
+ * Activity which shows a UI for writing or restoring a backup,
+ * and calls the approriate handler for actually executing those
+ * operations.
+ *
+ * @author Rodrigo Damazio
+ */
+public class BackupActivity extends Activity {
+
+ // Since the user sees this format, we use the local timezone
+ private static final SimpleDateFormat DISPLAY_BACKUP_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ private static final Comparator REVERSE_DATE_ORDER =
+ new Comparator() {
+ @Override
+ public int compare(Date s1, Date s2) {
+ return s2.compareTo(s1);
+ }
+ };
+
+ public static final String BACKUP_ACTION = "backup";
+ public static final String RESTORE_ACTION = "restore";
+
+ private final FileUtils fileUtils = new FileUtils();
+ private final ExternalFileBackup backup = new ExternalFileBackup(this, fileUtils);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ if (intent != null) {
+ String action = intent.getAction();
+ if (action != null) {
+ if (action.equals(BACKUP_ACTION)) {
+ writeBackup();
+ } else if (action.equals(RESTORE_ACTION)) {
+ restoreBackup();
+ } else {
+ throw new IllegalArgumentException("Unknown action: " + action);
+ }
+ }
+ }
+ }
+
+ /**
+ * Writes a full backup to the default file.
+ * This shows the results to the user.
+ */
+ private void writeBackup() {
+ if (!fileUtils.isSdCardAvailable()) {
+ showToast(R.string.io_no_external_storage_found);
+ finish();
+ return;
+ }
+
+ if (!backup.isBackupsDirectoryAvailable(true)) {
+ showToast(R.string.io_create_dir_failed);
+ finish();
+ return;
+ }
+
+ final ProgressDialog progressDialog = ProgressDialog.show(
+ this,
+ getString(R.string.progress_title),
+ getString(R.string.backup_write_progress_message),
+ true);
+
+ // Do the writing in another thread
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ backup.writeToDefaultFile();
+ showToast(R.string.io_write_finished);
+ } catch (IOException e) {
+ showToast(R.string.io_write_failed);
+ return;
+ } finally {
+ dismissDialog(progressDialog);
+ finish();
+ }
+ }
+ }.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.
+ */
+ private void restoreBackup() {
+ // Get the list of existing backups
+ if (!fileUtils.isSdCardAvailable()) {
+ showToast(R.string.io_no_external_storage_found);
+ finish();
+ return;
+ }
+
+ if (!backup.isBackupsDirectoryAvailable(false)) {
+ showToast(R.string.no_backups);
+ finish();
+ return;
+ }
+
+ final Date[] backupDates = backup.getAvailableBackups();
+ if (backupDates == null || backupDates.length == 0) {
+ showToast(R.string.no_backups);
+ finish();
+ return;
+ }
+ Arrays.sort(backupDates, REVERSE_DATE_ORDER);
+
+ // Show a confirmation dialog
+ Builder confirmationDialogBuilder = new AlertDialog.Builder(this);
+ confirmationDialogBuilder.setMessage(R.string.restore_overwrites_warning);
+ confirmationDialogBuilder.setCancelable(false);
+ confirmationDialogBuilder.setNegativeButton(android.R.string.no, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ BackupActivity.this.finish();
+ }
+ });
+ confirmationDialogBuilder.setPositiveButton(android.R.string.yes, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ pickBackupForRestore(backupDates);
+ }
+ });
+ confirmationDialogBuilder.create().show();
+ }
+
+ /**
+ * Shows a backup list for the user to pick, then restores it.
+ *
+ * @param backupDir the backup directory
+ * @param backupDates the list of available backup files
+ */
+ private void pickBackupForRestore(final Date[] backupDates) {
+ if (backupDates.length == 1) {
+ // Only one choice, don't bother showing the list
+ restoreFromDateAsync(backupDates[0]);
+ finish();
+ return;
+ }
+
+ // Make a user-visible version of the backup filenames
+ final String backupDateStrs[] = new String[backupDates.length];
+ for (int i = 0; i < backupDates.length; i++) {
+ backupDateStrs[i] = DISPLAY_BACKUP_FORMAT.format(backupDates[i]);
+ }
+
+ // Show a dialog for the user to pick which backup to restore
+ Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setCancelable(true);
+ dialogBuilder.setTitle(R.string.select_backup_to_restore);
+ dialogBuilder.setItems(backupDateStrs, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // User picked to restore this one
+ restoreFromDateAsync(backupDates[which]);
+ }
+ });
+ dialogBuilder.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ BackupActivity.this.finish();
+ }
+ });
+ dialogBuilder.create().show();
+ }
+
+ /**
+ * Shows a progress dialog, then starts restoring the backup osynchronously.
+ *
+ * @param inputFile the file to restore from
+ */
+ private void restoreFromDateAsync(final Date date) {
+ // Show a progress dialog
+ final ProgressDialog progressDialog = ProgressDialog.show(
+ this,
+ getString(R.string.progress_title),
+ 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 {
+ backup.restoreFromDate(date);
+ showToast(R.string.io_read_finished);
+ } catch (IOException e) {
+ showToast(R.string.io_read_failed);
+ } finally {
+ dismissDialog(progressDialog);
+
+ // Data may have been restored, "reboot" the app to catch it
+ restartApplication();
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Restarts My Tracks completely.
+ * This forces any modified data to be re-read.
+ */
+ private void restartApplication() {
+ Intent intent = new Intent(this, MyTracks.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+
+ /**
+ * Shows a toast with the given contents.
+ */
+ private void showToast(final int resId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(BackupActivity.this, resId, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ /**
+ * Safely dismisses the given dialog.
+ */
+ private void dismissDialog(final Dialog dialog) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ dialog.dismiss();
+ }
+ });
+ }
+}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java
new file mode 100644
index 000000000..007aacea2
--- /dev/null
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java
@@ -0,0 +1,71 @@
+/*
+ * 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 android.app.backup.BackupManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+/**
+ * Shared preferences listener which notifies the backup system about new data
+ * being available for backup.
+ * This class is API-version-safe and will provide a dummy implementation if
+ * the device doesn't support backup services.
+ *
+ * @author Rodrigo Damazio
+ */
+public abstract class BackupPreferencesListener implements OnSharedPreferenceChangeListener {
+
+ /**
+ * Real implementation of the listener, which calls the {@link BackupManager}.
+ */
+ private static class BackupPreferencesListenerImpl extends BackupPreferencesListener {
+ private final BackupManager backupManager;
+
+ public BackupPreferencesListenerImpl(Context context) {
+ this.backupManager = new BackupManager(context);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ backupManager.dataChanged();
+ }
+ }
+
+ /**
+ * Dummy implementation of the listener which does nothing.
+ */
+ private static class DummyBackupPreferencesListener extends BackupPreferencesListener {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ // Do nothing
+ }
+ }
+
+ /**
+ * Creates and returns a proper instance of the listener for this device.
+ */
+ public static BackupPreferencesListener create(Context context) {
+ if (MyTracksConstants.ANDROID_API_LEVEL >= 8) {
+ return new BackupPreferencesListenerImpl(context);
+ } else {
+ return new DummyBackupPreferencesListener();
+ }
+ }
+}
\ No newline at end of file
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
index f9928033d..771a9d842 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/ExternalFileBackup.java
@@ -21,34 +21,24 @@ 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.Comparator;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
import java.util.TimeZone;
/**
@@ -56,7 +46,7 @@ import java.util.TimeZone;
*
* @author Rodrigo Damazio
*/
-public class ExternalFileBackup {
+class ExternalFileBackup {
// Filename format - in UTC
private static final SimpleDateFormat BACKUP_FILENAME_FORMAT =
new SimpleDateFormat("'backup-'yyyy-MM-dd_HH-mm-ss");
@@ -64,232 +54,95 @@ public class ExternalFileBackup {
BACKUP_FILENAME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
- // Since the user sees this format, we use the local timezone
- 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 static final Comparator REVERSE_STRING_COMPARATOR =
- new Comparator() {
- @Override
- public int compare(String s1, String s2) {
- return s2.compareTo(s1);
- }
- };
private final Context context;
private final FileUtils fileUtils;
- private final Handler uiThreadHandler = new Handler();
- public ExternalFileBackup(Context context) {
+ public ExternalFileBackup(Context context, FileUtils fileUtils) {
this.context = context;
- this.fileUtils = new FileUtils();
+ this.fileUtils = fileUtils;
}
/**
- * Writes a full backup to the default file.
- * This shows the results to the user.
+ * Returns whether the backups directory is (or can be made) available.
+ *
+ * @param create whether to try creating the directory if it doesn't exist
*/
- public void writeToDefaultFile() {
- if (!fileUtils.isSdCardAvailable()) {
- showToast(R.string.io_no_external_storage_found);
- return;
- }
+ public boolean isBackupsDirectoryAvailable(boolean create) {
+ return getBackupsDirectory(create) != null;
+ }
+ /**
+ * Returns the backup directory, or null if not available.
+ *
+ * @param create whether to try creating the directory if it doesn't exist
+ */
+ private File getBackupsDirectory(boolean create) {
String dirName = fileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR);
final File dir = new File(dirName);
- if (!fileUtils.ensureDirectoryExists(dir)) {
- showToast(R.string.io_create_dir_failed);
- return;
+ Log.d(MyTracksConstants.TAG, "Dir: " + dir.getAbsolutePath());
+ if (create) {
+ // Try to create - if that fails, return null
+ return fileUtils.ensureDirectoryExists(dir) ? dir : null;
+ } else {
+ // Return it if it already exists, otherwise return null
+ return dir.isDirectory() ? dir : null;
}
-
- 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");
- }
- }
- Arrays.sort(backupFileDates, REVERSE_STRING_COMPARATOR);
-
- // 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;
- }
+ public Date[] getAvailableBackups() {
+ File dir = getBackupsDirectory(false);
+ if (dir == null) { return null; }
+ String[] fileNames = dir.list();
+
+ List backupDates = new ArrayList(fileNames.length);
+ for (int i = 0; i < fileNames.length; i++) {
+ String fileName = fileNames[i];
+ try {
+ backupDates.add(BACKUP_FILENAME_FORMAT.parse(fileName));
+ } catch (ParseException e) {
+ // Not a backup file, ignore
}
- });
+ }
+
+ return backupDates.toArray(new Date[backupDates.size()]);
}
/**
- * Shows a toast with the given contents.
+ * Writes the backup to the default file.
*/
- private void showToast(final int resId) {
- uiThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(context, resId, Toast.LENGTH_LONG).show();
- }
- });
+ public void writeToDefaultFile() throws IOException {
+ writeToFile(getFileForDate(new Date()));
}
/**
- * Safely dismisses the given dialog.
+ * Restores the backup from the given date.
*/
- private void dismissDialog(final Dialog dialog) {
- uiThreadHandler.post(new Runnable() {
- @Override
- public void run() {
- dialog.dismiss();
- }
- });
+ public void restoreFromDate(Date when) throws IOException {
+ restoreFromFile(getFileForDate(when));
+ }
+
+ /**
+ * Produces the proper file descriptor for the given backup date.
+ */
+ private File getFileForDate(Date when) {
+ File dir = getBackupsDirectory(false);
+ String fileName = BACKUP_FILENAME_FORMAT.format(when);
+ File file = new File(dir, fileName);
+ return file;
}
/**
* Synchronously writes a backup to the given file.
*/
private void writeToFile(File outputFile) throws IOException {
+ Log.d(MyTracksConstants.TAG, "Writing backup to file " + outputFile.getAbsolutePath());
+
// Create all the auxiliary classes that will do the writing
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
DatabaseDumper trackDumper = new DatabaseDumper(
@@ -313,11 +166,6 @@ public class ExternalFileBackup {
// 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(
@@ -343,6 +191,17 @@ public class ExternalFileBackup {
} finally {
pointsCursor.close();
}
+
+ // Dump preferences
+ SharedPreferences preferences =
+ context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
+ preferencesHelper.exportPreferences(preferences, outWriter);
+ } catch (IOException e) {
+ // We tried to delete the partially created file, but do nothing
+ // if that also fails.
+ outputFile.delete();
+
+ throw e;
} finally {
outputStream.flush();
outputStream.close();
@@ -353,7 +212,7 @@ public class ExternalFileBackup {
* 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
+ Log.d(MyTracksConstants.TAG, "Restoring from file " + inputFile.getAbsolutePath());
PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper();
ContentResolver resolver = context.getContentResolver();
@@ -370,12 +229,7 @@ public class ExternalFileBackup {
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.
+ // Delete all previous contents of the tables and preferences.
resolver.delete(TracksColumns.CONTENT_URI, null, null);
resolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
resolver.delete(WaypointsColumns.CONTENT_URI, null, null);
@@ -384,6 +238,11 @@ public class ExternalFileBackup {
trackImporter.importAllRows(reader);
waypointImporter.importAllRows(reader);
pointImporter.importAllRows(reader);
+
+ // Restore preferences
+ SharedPreferences preferences =
+ context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
+ preferencesHelper.importPreferences(reader, preferences);
} finally {
inputStream.close();
}
diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java
index d6fc86bf8..317aea997 100644
--- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java
+++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/PreferenceBackupHelper.java
@@ -25,7 +25,6 @@ import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.util.Map;
/**