From 23f1e81cf086ff1726f0d38c7efce5f24c8e08c5 Mon Sep 17 00:00:00 2001 From: Rodrigo Damazio Date: Tue, 24 Aug 2010 21:27:43 -0300 Subject: [PATCH] A lot of cleanup: * Splitting ExternalFileBackup into UI and non-UI parts * Disabling backups while recording * Triggering cloud backups when preferences are changed --- MyTracks/AndroidManifest.xml | 3 + MyTracks/res/values/keys.xml | 10 +- MyTracks/res/values/strings.xml | 11 +- .../apps/mytracks/MyTracksSettings.java | 50 ++- .../mytracks/io/backup/BackupActivity.java | 278 +++++++++++++++++ .../io/backup/BackupPreferencesListener.java | 71 +++++ .../io/backup/ExternalFileBackup.java | 291 +++++------------- .../io/backup/PreferenceBackupHelper.java | 1 - 8 files changed, 484 insertions(+), 231 deletions(-) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivity.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupPreferencesListener.java 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; /**