From e5d41576c9da8af039d0db883a3ae162e6bd59df Mon Sep 17 00:00:00 2001 From: Jimmy Shih Date: Mon, 19 Mar 2012 12:47:59 -0700 Subject: [PATCH] Support screen rotation for back and restore from the SD card. --- MyTracks/AndroidManifest.xml | 5 +- MyTracks/res/values/strings.xml | 3 +- .../apps/mytracks/SettingsActivity.java | 32 ++- .../mytracks/io/backup/BackupActivity.java | 101 ++++++++ .../io/backup/BackupActivityHelper.java | 240 ------------------ .../mytracks/io/backup/BackupAsyncTask.java | 113 +++++++++ .../mytracks/io/backup/RestoreActivity.java | 115 +++++++++ .../mytracks/io/backup/RestoreAsyncTask.java | 105 ++++++++ .../io/backup/RestoreChooserActivity.java | 122 +++++++++ 9 files changed, 580 insertions(+), 256 deletions(-) create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivity.java delete mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivityHelper.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupAsyncTask.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreActivity.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreAsyncTask.java create mode 100644 MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreChooserActivity.java diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 6a17ae991..07f38a5e2 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -274,7 +274,10 @@ limitations under the License. - + + + + - Restoring a backup from the SD card will overwrite all your current tracks and settings. Are you - sure? + Restoring a backup from the SD card will overwrite all your current tracks and settings. REVERSE_DATE_ORDER = - new Comparator() { - @Override - public int compare(Date s1, Date s2) { - return s2.compareTo(s1); - } - }; - - private final FileUtils fileUtils; - private final ExternalFileBackup backup; - private final Activity activity; - - public BackupActivityHelper(Activity activity) { - this.activity = activity; - this.fileUtils = new FileUtils(); - this.backup = new ExternalFileBackup(activity, fileUtils); - } - - /** - * Writes a full backup to the default file. - * This shows the results to the user. - */ - public void writeBackup() { - if (!fileUtils.isSdCardAvailable()) { - showToast(R.string.sd_card_error_no_storage); - return; - } - - if (!backup.isBackupsDirectoryAvailable(true)) { - showToast(R.string.sd_card_error_create_dir); - return; - } - - final ProgressDialog progressDialog = ProgressDialog.show( - activity, - activity.getString(R.string.generic_progress_title), - activity.getString(R.string.settings_backup_now_progress_message), - true); - - // Do the writing in another thread - new Thread() { - @Override - public void run() { - try { - backup.writeToDefaultFile(); - showToast(R.string.sd_card_success_write_file); - } catch (IOException e) { - Log.e(Constants.TAG, "Failed to write backup", e); - showToast(R.string.sd_card_error_write_file); - } 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 restoreBackup() { - // Get the list of existing backups - if (!fileUtils.isSdCardAvailable()) { - showToast(R.string.sd_card_error_no_storage); - return; - } - - if (!backup.isBackupsDirectoryAvailable(false)) { - showToast(R.string.settings_backup_restore_no_backup); - return; - } - - final Date[] backupDates = backup.getAvailableBackups(); - if (backupDates == null || backupDates.length == 0) { - showToast(R.string.settings_backup_restore_no_backup); - return; - } - Arrays.sort(backupDates, REVERSE_DATE_ORDER); - - // Show a confirmation dialog - Builder confirmationDialogBuilder = new AlertDialog.Builder(activity); - confirmationDialogBuilder.setMessage(R.string.settings_backup_restore_confirm_message); - confirmationDialogBuilder.setCancelable(true); - confirmationDialogBuilder.setPositiveButton(android.R.string.yes, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - pickBackupForRestore(backupDates); - } - }); - confirmationDialogBuilder.setNegativeButton(android.R.string.no, null); - confirmationDialogBuilder.create().show(); - } - - /** - * Shows a backup list for the user to pick, then restores it. - * - * @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]); - 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] = StringUtils.formatDateTime(activity, backupDates[i].getTime()); - } - - // Show a dialog for the user to pick which backup to restore - Builder dialogBuilder = new AlertDialog.Builder(activity); - dialogBuilder.setCancelable(true); - dialogBuilder.setTitle(R.string.settings_backup_restore_select_title); - dialogBuilder.setItems(backupDateStrs, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // User picked to restore this one - restoreFromDateAsync(backupDates[which]); - } - }); - dialogBuilder.create().show(); - } - - /** - * Shows a progress dialog, then starts restoring the backup asynchronously. - * - * @param date the date - */ - private void restoreFromDateAsync(final Date date) { - // Show a progress dialog - ProgressDialog.show( - activity, - activity.getString(R.string.generic_progress_title), - activity.getString(R.string.settings_backup_restore_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.sd_card_success_read_file); - } catch (IOException e) { - Log.e(Constants.TAG, "Failed to restore backup", e); - showToast(R.string.sd_card_error_read_file); - } finally { - // 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(activity, MyTracks.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - activity.startActivity(intent); - } - - /** - * Shows a toast with the given contents. - */ - private void showToast(final int resId) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, resId, Toast.LENGTH_LONG).show(); - } - }); - } - - /** - * Safely dismisses the given dialog. - */ - private void dismissDialog(final Dialog dialog) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - dialog.dismiss(); - } - }); - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupAsyncTask.java new file mode 100644 index 000000000..3ef19ad31 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupAsyncTask.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012 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.util.FileUtils; +import com.google.android.maps.mytracks.R; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.IOException; + +/** + * AsyncTask to backup data to the SD card. + * + * @author Jimmy Shih + */ +public class BackupAsyncTask extends AsyncTask { + + private static final String TAG = BackupAsyncTask.class.getSimpleName(); + + private BackupActivity backupActivity; + private final ExternalFileBackup externalFileBackup; + + // true if the AsyncTask result is success + private boolean success; + + // true if the AsyncTask has completed + private boolean completed; + + // message id to return to the activity + private int messageId; + + /** + * Creates an AsyncTask. + * + * @param backupActivity the activity currently associated with this + * AsyncTask + */ + public BackupAsyncTask(BackupActivity backupActivity) { + this.backupActivity = backupActivity; + this.externalFileBackup = new ExternalFileBackup(backupActivity, new FileUtils()); + success = false; + completed = false; + messageId = R.string.sd_card_error_write_file; + } + + /** + * Sets the current {@link BackupActivity} associated with this AyncTask. + * + * @param activity the current {@link BackupActivity}, can be null + */ + public void setActivity(BackupActivity activity) { + this.backupActivity = activity; + if (completed && backupActivity != null) { + backupActivity.onAsyncTaskCompleted(success, messageId); + } + } + + @Override + protected void onPreExecute() { + if (backupActivity != null) { + backupActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + FileUtils fileUtils = new FileUtils(); + + if (!fileUtils.isSdCardAvailable()) { + messageId = R.string.sd_card_error_no_storage; + return false; + } + + if (!externalFileBackup.isBackupsDirectoryAvailable(true)) { + messageId = R.string.sd_card_error_create_dir; + return false; + } + + try { + externalFileBackup.writeToDefaultFile(); + messageId = R.string.sd_card_success_write_file; + return true; + } catch (IOException e) { + Log.d(TAG, "IO exception", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (backupActivity != null) { + backupActivity.onAsyncTaskCompleted(success, messageId); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreActivity.java new file mode 100644 index 000000000..5e3492394 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreActivity.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012 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.maps.mytracks.R; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +import java.util.Date; + +/** + * An activity to restore data from the SD card. + * + * @author Jimmy Shih + */ +public class RestoreActivity extends Activity { + + public static final String EXTRA_DATE = "date"; + + private static final String TAG = RestoreActivity.class.getSimpleName(); + private static final int DIALOG_PROGRESS_ID = 0; + + private RestoreAsyncTask restoreAsyncTask; + private ProgressDialog progressDialog; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof RestoreAsyncTask) { + restoreAsyncTask = (RestoreAsyncTask) retained; + restoreAsyncTask.setActivity(this); + } else { + long date = getIntent().getLongExtra(EXTRA_DATE, -1L); + if (date == -1L) { + Log.d(TAG, "Invalid date"); + finish(); + } + restoreAsyncTask = new RestoreAsyncTask(this, new Date(date)); + restoreAsyncTask.execute(); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + restoreAsyncTask.setActivity(null); + return restoreAsyncTask; + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_PROGRESS_ID: + progressDialog = new ProgressDialog(this); + progressDialog.setCancelable(true); + progressDialog.setIcon(android.R.drawable.ic_dialog_info); + progressDialog.setIndeterminate(true); + progressDialog.setMessage(getString(R.string.settings_backup_restore_progress_message)); + progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(R.string.generic_progress_title); + return progressDialog; + default: + return null; + } + } + + /** + * Invokes when the associated AsyncTask completes. + * + * @param success true if the AsyncTask is successful + * @param messageId message id to display to user + */ + public void onAsyncTaskCompleted(boolean success, int messageId) { + removeDialog(DIALOG_PROGRESS_ID); + Toast.makeText(this, messageId, success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show(); + startActivity(new Intent(this, MyTracks.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); + finish(); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreAsyncTask.java new file mode 100644 index 000000000..23917d521 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreAsyncTask.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012 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.util.FileUtils; +import com.google.android.maps.mytracks.R; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.IOException; +import java.util.Date; + +/** + * AsyncTask to restore data from the SD card. + * + * @author Jimmy Shih + */ +public class RestoreAsyncTask extends AsyncTask { + + private static final String TAG = RestoreAsyncTask.class.getSimpleName(); + + private RestoreActivity restoreActivity; + private final Date date; + private final ExternalFileBackup externalFileBackup; + + // true if the AsyncTask result is success + private boolean success; + + // true if the AsyncTask has completed + private boolean completed; + + // message id to return to the activity + private int messageId; + + /** + * Creates an AsyncTask. + * + * @param restoreActivity the activity currently associated with this + * AsyncTask + * @param date the date to retore from + */ + public RestoreAsyncTask(RestoreActivity restoreActivity, Date date) { + this.restoreActivity = restoreActivity; + this.date = date; + this.externalFileBackup = new ExternalFileBackup(restoreActivity, new FileUtils()); + success = false; + completed = false; + messageId = R.string.sd_card_error_read_file; + } + + /** + * Sets the current {@link RestoreActivity} associated with this AyncTask. + * + * @param activity the current {@link RestoreActivity}, can be null + */ + public void setActivity(RestoreActivity activity) { + this.restoreActivity = activity; + if (completed && restoreActivity != null) { + restoreActivity.onAsyncTaskCompleted(success, messageId); + } + } + + @Override + protected void onPreExecute() { + if (restoreActivity != null) { + restoreActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + externalFileBackup.restoreFromDate(date); + messageId = R.string.sd_card_success_read_file; + return true; + } catch (IOException e) { + Log.d(TAG, "IO exception", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (restoreActivity != null) { + restoreActivity.onAsyncTaskCompleted(success, messageId); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreChooserActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreChooserActivity.java new file mode 100644 index 000000000..8b5fc81c7 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/RestoreChooserActivity.java @@ -0,0 +1,122 @@ +/* + * Copyright 2012 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.util.FileUtils; +import com.google.android.apps.mytracks.util.StringUtils; +import com.google.android.maps.mytracks.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +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.util.Arrays; +import java.util.Comparator; +import java.util.Date; + +/** + * An activity to choose a date to restore from. + * + * @author Jimmy Shih + */ +public class RestoreChooserActivity extends Activity { + + private static final Comparator REVERSE_DATE_ORDER = new Comparator() { + @Override + public int compare(Date s1, Date s2) { + return s2.compareTo(s1); + } + }; + private static final int DIALOG_CHOOSER_ID = 0; + + private Date[] backupDates; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FileUtils fileUtils = new FileUtils(); + ExternalFileBackup externalFileBackup = new ExternalFileBackup(this, fileUtils); + + // Get the list of existing backups + if (!fileUtils.isSdCardAvailable()) { + Toast.makeText(this, R.string.sd_card_error_no_storage, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + if (!externalFileBackup.isBackupsDirectoryAvailable(false)) { + Toast.makeText(this, R.string.settings_backup_restore_no_backup, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + backupDates = externalFileBackup.getAvailableBackups(); + if (backupDates == null || backupDates.length == 0) { + Toast.makeText(this, R.string.settings_backup_restore_no_backup, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + if (backupDates.length == 1) { + startActivity(new Intent(this, RestoreActivity.class) + .putExtra(RestoreActivity.EXTRA_DATE, backupDates[0].getTime())); + finish(); + return; + } + + Arrays.sort(backupDates, REVERSE_DATE_ORDER); + showDialog(DIALOG_CHOOSER_ID); + } + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_CHOOSER_ID: + String items[] = new String[backupDates.length]; + for (int i = 0; i < backupDates.length; i++) { + items[i] = StringUtils.formatDateTime(this, backupDates[i].getTime()); + } + return new AlertDialog.Builder(this) + .setCancelable(true) + .setItems(items, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(RestoreChooserActivity.this, RestoreActivity.class) + .putExtra(RestoreActivity.EXTRA_DATE, backupDates[which].getTime())); + finish(); + } + }) + .setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }) + .setTitle(R.string.settings_backup_restore_select_title) + .create(); + default: + return null; + } + } +}