diff --git a/MyTracks/AndroidManifest.xml b/MyTracks/AndroidManifest.xml index 5ff5ac77e..5dc90998b 100755 --- a/MyTracks/AndroidManifest.xml +++ b/MyTracks/AndroidManifest.xml @@ -158,17 +158,26 @@ limitations under the License. - + + - + + + + - + + + + + + + + - - - - - - - - - - - - - - - - + diff --git a/MyTracks/res/values/strings.xml b/MyTracks/res/values/strings.xml index e11616a30..ec8c509b1 100644 --- a/MyTracks/res/values/strings.xml +++ b/MyTracks/res/values/strings.xml @@ -1537,8 +1537,7 @@ limitations under the License. and it will overwrite all the current tracks and settings. Screenshot: settings_backup_restore_confirm_message.png"> - 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. { + + private ExportAllActivity exportAllActivity; + private final TrackFileFormat trackFileFormat; + private final Context context; + private final MyTracksProviderUtils myTracksProviderUtils; + private WakeLock wakeLock; + private TrackWriter trackWriter; + + // 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 exportAllActivity the activity currently associated with this + * AsyncTask + * @param trackFileFormat the track file format + */ + public ExportAllAsyncTask(ExportAllActivity exportAllActivity, TrackFileFormat trackFileFormat) { + this.exportAllActivity = exportAllActivity; + this.trackFileFormat = trackFileFormat; + context = exportAllActivity.getApplicationContext(); + myTracksProviderUtils = MyTracksProviderUtils.Factory.get(exportAllActivity); + + SharedPreferences sharedPreferences = exportAllActivity.getSharedPreferences( + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + long recordingTrackId = sharedPreferences.getLong( + exportAllActivity.getString(R.string.recording_track_key), -1L); + + // Get the wake lock if not recording + if (recordingTrackId == -1L) { + wakeLock = SystemUtils.acquireWakeLock(exportAllActivity, wakeLock); + } + success = false; + completed = false; + messageId = R.string.export_error; + } + + /** + * Sets the current {@link ExportAllActivity} associated with this AyncTask. + * + * @param exportAllActivity the current {@link ExportAllActivity}, can be null + */ + public void setActivity(ExportAllActivity exportAllActivity) { + this.exportAllActivity = exportAllActivity; + if (completed && exportAllActivity != null) { + exportAllActivity.onAsyncTaskCompleted(success, messageId); + } + } + + @Override + protected void onPreExecute() { + if (exportAllActivity != null) { + exportAllActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + Cursor cursor = null; + try { + cursor = myTracksProviderUtils.getTracksCursor(null, null, TracksColumns._ID); + if (cursor == null) { + messageId = R.string.export_success; + return true; + } + int count = cursor.getCount(); + int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID); + for (int i = 0; i < count; i++) { + if (isCancelled()) { + return false; + } + cursor.moveToPosition(i); + long id = cursor.getLong(idIndex); + trackWriter = TrackWriterFactory.newWriter( + context, myTracksProviderUtils, id, trackFileFormat); + if (trackWriter == null) { + return false; + } + trackWriter.writeTrack(); + + if (!trackWriter.wasSuccess()) { + messageId = trackWriter.getErrorMessage(); + return false; + } + publishProgress(i + 1, count); + } + messageId = R.string.export_success; + return true; + } finally { + if (cursor != null) { + cursor.close(); + } + // Release the wake lock if obtained + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } + } + } + + @Override + protected void onProgressUpdate(Integer... values) { + if (exportAllActivity != null) { + exportAllActivity.setProgressDialogValue(values[0], values[1]); + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (exportAllActivity != null) { + exportAllActivity.onAsyncTaskCompleted(success, messageId); + } + } + + @Override + protected void onCancelled() { + if (trackWriter != null) { + trackWriter.stopWriteTrack(); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java deleted file mode 100644 index ef6169658..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/ExportAllTracks.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2009 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; - -import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.TracksColumns; -import com.google.android.apps.mytracks.io.file.TrackWriter; -import com.google.android.apps.mytracks.io.file.TrackWriterFactory; -import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; -import com.google.android.apps.mytracks.util.SystemUtils; -import com.google.android.maps.mytracks.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.os.PowerManager.WakeLock; -import android.util.Log; -import android.widget.Toast; - -/** - * A class that will export all tracks to the sd card. - * - * @author Sandor Dornbush - */ -public class ExportAllTracks { - // These must line up with the index in the array. - public static final int GPX_OPTION_INDEX = 0; - public static final int KML_OPTION_INDEX = 1; - public static final int CSV_OPTION_INDEX = 2; - public static final int TCX_OPTION_INDEX = 3; - - private final Activity activity; - private WakeLock wakeLock; - private ProgressDialog progress; - - private TrackFileFormat format = TrackFileFormat.GPX; - - private final DialogInterface.OnClickListener itemClick = - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case GPX_OPTION_INDEX: - format = TrackFileFormat.GPX; - break; - case KML_OPTION_INDEX: - format = TrackFileFormat.KML; - break; - case CSV_OPTION_INDEX: - format = TrackFileFormat.CSV; - break; - case TCX_OPTION_INDEX: - format = TrackFileFormat.TCX; - break; - default: - Log.w(Constants.TAG, "Unknown export format: " + which); - } - } - }; - - public ExportAllTracks(Activity activity) { - this.activity = activity; - Log.i(Constants.TAG, "ExportAllTracks: Starting"); - - String exportFileFormat = activity.getString(R.string.track_list_export_file); - String fileTypes[] = activity.getResources().getStringArray(R.array.file_types); - - String[] choices = new String[fileTypes.length]; - for (int i = 0; i < fileTypes.length; i++) { - choices[i] = String.format(exportFileFormat, fileTypes[i]); - } - - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder.setTitle(R.string.track_list_export_all); - builder.setSingleChoiceItems(choices, 0, itemClick); - builder.setPositiveButton(R.string.generic_ok, positiveClick); - builder.setNegativeButton(R.string.generic_cancel, null); - builder.show(); - } - - private final DialogInterface.OnClickListener positiveClick = - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new Thread(runner, "ExportAllTracks").start(); - } - }; - - private final Runnable runner = new Runnable() { - public void run() { - aquireLocksAndExport(); - } - }; - - /** - * Makes sure that we keep the phone from sleeping. - * See if there is a current track. Aquire a wake lock if there is no - * current track. - */ - private void aquireLocksAndExport() { - SharedPreferences prefs = activity.getSharedPreferences( - Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - long recordingTrackId = -1; - if (prefs != null) { - recordingTrackId = - prefs.getLong(activity.getString(R.string.recording_track_key), -1); - } - if (recordingTrackId != -1) { - wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock); - } - - // Now we can safely export everything. - exportAll(); - - // Release the wake lock if we recorded one. - // TODO check what happens if we started recording after getting this lock. - if (wakeLock != null && wakeLock.isHeld()) { - wakeLock.release(); - Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock."); - } - Log.i(Constants.TAG, "ExportAllTracks: Done"); - showToast(R.string.export_success, Toast.LENGTH_SHORT); - } - - private void makeProgressDialog(final int trackCount) { - String exportMsg = activity.getString(R.string.track_list_export_all); - progress = new ProgressDialog(activity); - progress.setIcon(android.R.drawable.ic_dialog_info); - progress.setTitle(exportMsg); - progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progress.setMax(trackCount); - progress.setProgress(0); - progress.show(); - } - - /** - * Actually export the tracks. - * This should be called after the wake locks have been aquired. - */ - private void exportAll() { - // Get a cursor over all tracks. - Cursor cursor = null; - try { - MyTracksProviderUtils providerUtils = - MyTracksProviderUtils.Factory.get(activity); - cursor = providerUtils.getTracksCursor(null, null, TracksColumns._ID); - if (cursor == null) { - return; - } - - final int trackCount = cursor.getCount(); - Log.i(Constants.TAG, - "ExportAllTracks: Exporting: " + cursor.getCount() + " tracks."); - int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID); - activity.runOnUiThread(new Runnable() { - public void run() { - makeProgressDialog(trackCount); - } - }); - - for (int i = 0; cursor.moveToNext(); i++) { - final int status = i; - activity.runOnUiThread(new Runnable() { - public void run() { - synchronized (this) { - if (progress == null) { - return; - } - progress.setProgress(status); - } - } - }); - - long id = cursor.getLong(idxTrackId); - Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id); - TrackWriter writer = - TrackWriterFactory.newWriter(activity, providerUtils, id, format); - if (writer == null) { - showToast(R.string.export_error, Toast.LENGTH_LONG); - return; - } - - writer.writeTrack(); - - if (!writer.wasSuccess()) { - // Abort the whole export on the first error. - showToast(writer.getErrorMessage(), Toast.LENGTH_LONG); - return; - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - if (progress != null) { - synchronized (this) { - progress.dismiss(); - progress = null; - } - } - } - } - - private void showToast(final int messageId, final int length) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, messageId, length).show(); - } - }); - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/ImportActivity.java b/MyTracks/src/com/google/android/apps/mytracks/ImportActivity.java new file mode 100644 index 000000000..8d47e1be3 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/ImportActivity.java @@ -0,0 +1,207 @@ +/* + * Copyright 2011 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; + +import com.google.android.apps.mytracks.content.TracksColumns; +import com.google.android.apps.mytracks.util.FileUtils; +import com.google.android.apps.mytracks.util.UriUtils; +import com.google.android.maps.mytracks.R; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.ContentUris; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + +/** + * An activity to import GPX files from the SD card. Optionally to import one + * GPX file and display it in My Tracks. + * + * @author Rodrigo Damazio + */ +public class ImportActivity extends Activity { + + public static final String EXTRA_IMPORT_ALL = "import_all"; + + private static final String TAG = ImportActivity.class.getSimpleName(); + + private static final int DIALOG_PROGRESS_ID = 0; + private static final int DIALOG_RESULT_ID = 1; + + private ImportAsyncTask importAsyncTask; + private ProgressDialog progressDialog; + + private boolean importAll; + + // path on the SD card to import + private String path; + + // number of succesfully imported files + private int successCount; + + // number of files to import + private int totalCount; + + // last successfully imported track id + private long trackId; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false); + if (importAll) { + path = new FileUtils().buildExternalDirectoryPath("gpx"); + } else { + String action = intent.getAction(); + if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) { + Log.d(TAG, "Invalid action: " + intent); + finish(); + return; + } + + Uri data = intent.getData(); + if (!UriUtils.isFileUri(data)) { + Log.d(TAG, "Invalid data: " + intent); + finish(); + return; + } + path = data.getPath(); + } + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof ImportAsyncTask) { + importAsyncTask = (ImportAsyncTask) retained; + importAsyncTask.setActivity(this); + } else { + importAsyncTask = new ImportAsyncTask(this, importAll, path); + importAsyncTask.execute(); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + importAsyncTask.setActivity(null); + return importAsyncTask; + } + + @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.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + importAsyncTask.cancel(true); + finish(); + } + }); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(R.string.track_list_import_all); + return progressDialog; + case DIALOG_RESULT_ID: + String message; + if (successCount == 0) { + message = getString(R.string.import_no_file, path); + } else { + String totalFiles = getResources() + .getQuantityString(R.plurals.importGpxFiles, totalCount, totalCount); + message = getString(R.string.import_success, successCount, totalFiles, path); + } + return new AlertDialog.Builder(this).setCancelable(true) + .setCancelable(true) + .setMessage(message) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }) + .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (!importAll && trackId != -1L) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setDataAndType( + ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId), + TracksColumns.CONTENT_ITEMTYPE); + startActivity(intent); + } + finish(); + } + }) + .create(); + default: + return null; + } + } + + /** + * Invokes when the associated AsyncTask completes. + * + * @param success true if the AsyncTask is successful + * @param imported the number of files successfully imported + * @param total the total number of files to import + * @param id the last successfully imported track id + */ + public void onAsyncTaskCompleted(boolean success, int imported, int total, long id) { + successCount = imported; + totalCount = total; + trackId = id; + removeDialog(DIALOG_PROGRESS_ID); + if (success) { + showDialog(DIALOG_RESULT_ID); + } else { + Toast.makeText(this, getString(R.string.import_error, path), Toast.LENGTH_LONG).show(); + finish(); + } + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } + + /** + * Sets the progress dialog value. + * + * @param number the number of files imported + * @param max the maximum number of files + */ + public void setProgressDialogValue(int number, int max) { + if (progressDialog != null) { + progressDialog.setIndeterminate(false); + progressDialog.setMax(max); + progressDialog.setProgress(Math.min(number, max)); + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java b/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java deleted file mode 100644 index db329f7e7..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/ImportAllTracks.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2011 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; - -import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.TracksColumns; -import com.google.android.apps.mytracks.io.file.GpxImporter; -import com.google.android.apps.mytracks.util.FileUtils; -import com.google.android.apps.mytracks.util.SystemUtils; -import com.google.android.maps.mytracks.R; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.ContentUris; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.PowerManager.WakeLock; -import android.util.Log; -import android.widget.Toast; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import javax.xml.parsers.ParserConfigurationException; - -import org.xml.sax.SAXException; - -/** - * A class that will import all GPX tracks in /sdcard/MyTracks/gpx/ - * - * @author David Piggott - */ -public class ImportAllTracks { - - private final Activity activity; - private FileUtils fileUtils; - private boolean singleTrackSelected; - private String gpxPath; - private WakeLock wakeLock; - private ProgressDialog progress; - private int gpxFileCount; - private int importSuccessCount; - private long importedTrackIds[]; - - public ImportAllTracks(Activity activity) { - this(activity, null); - } - - /** - * Constructor to import tracks. - * - * @param activity the activity - * @param path path of the gpx file to import and display. If null, then just - * import all the gpx files under MyTracks/gpx and do not display any - * track. - */ - public ImportAllTracks(Activity activity, String path) { - Log.i(Constants.TAG, "ImportAllTracks: Starting"); - this.activity = activity; - fileUtils = new FileUtils(); - singleTrackSelected = path != null; - gpxPath = path == null ? fileUtils.buildExternalDirectoryPath("gpx") : path; - new Thread(runner).start(); - } - - private final Runnable runner = new Runnable() { - public void run() { - aquireLocksAndImport(); - } - }; - - /** - * Makes sure that we keep the phone from sleeping. See if there is a current - * track. Acquire a wake lock if there is no current track. - */ - private void aquireLocksAndImport() { - SharedPreferences prefs = activity.getSharedPreferences( - Constants.SETTINGS_NAME, Context.MODE_PRIVATE); - long recordingTrackId = -1; - if (prefs != null) { - recordingTrackId = prefs.getLong(activity.getString(R.string.recording_track_key), -1); - } - if (recordingTrackId == -1) { - wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock); - } - - // Now we can safely import everything. - importAll(); - - // Release the wake lock if we acquired one. - // TODO check what happens if we started recording after getting this lock. - if (wakeLock != null && wakeLock.isHeld()) { - wakeLock.release(); - Log.i(Constants.TAG, "ImportAllTracks: Releasing wake lock."); - } - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - showDoneDialog(); - } - }); - } - - private void showDoneDialog() { - Log.i(Constants.TAG, "ImportAllTracks: Done"); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - if (gpxFileCount == 0) { - builder.setMessage(activity.getString(R.string.import_no_file, gpxPath)); - } else { - String totalFiles = activity.getResources().getQuantityString( - R.plurals.importGpxFiles, gpxFileCount, gpxFileCount); - builder.setMessage( - activity.getString(R.string.import_success, importSuccessCount, totalFiles, gpxPath)); - } - builder.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (singleTrackSelected) { - long lastTrackId = importedTrackIds[importedTrackIds.length - 1]; - Uri trackUri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, lastTrackId); - - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(trackUri, TracksColumns.CONTENT_ITEMTYPE); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - activity.startActivity(intent); - activity.finish(); - } - } - }); - builder.show(); - } - - private void makeProgressDialog(final int trackCount) { - String importMsg = activity.getString(R.string.track_list_import_all); - progress = new ProgressDialog(activity); - progress.setIcon(android.R.drawable.ic_dialog_info); - progress.setTitle(importMsg); - progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progress.setMax(trackCount); - progress.setProgress(0); - progress.show(); - } - - /** - * Actually import the tracks. This should be called after the wake locks have - * been acquired. - */ - private void importAll() { - MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(activity); - - if (!fileUtils.isSdCardAvailable()) { - return; - } - - List gpxFiles = getGpxFiles(); - gpxFileCount = gpxFiles.size(); - if (gpxFileCount == 0) { - return; - } - - Log.i(Constants.TAG, "ImportAllTracks: Importing: " + gpxFileCount + " tracks."); - activity.runOnUiThread(new Runnable() { - public void run() { - makeProgressDialog(gpxFileCount); - } - }); - - Iterator gpxFilesIterator = gpxFiles.iterator(); - for (int currentFileNumber = 0; gpxFilesIterator.hasNext(); currentFileNumber++) { - File currentFile = gpxFilesIterator.next(); - final int status = currentFileNumber; - activity.runOnUiThread(new Runnable() { - public void run() { - synchronized (this) { - if (progress == null) { - return; - } - progress.setProgress(status); - } - } - }); - if (importFile(currentFile, providerUtils)) { - importSuccessCount++; - } - } - - if (progress != null) { - synchronized (this) { - progress.dismiss(); - progress = null; - } - } - } - - /** - * Attempts to import a GPX file. Returns true on success, issues error - * notifications and returns false on failure. - */ - private boolean importFile(final File gpxFile, MyTracksProviderUtils providerUtils) { - Log.i(Constants.TAG, "ImportAllTracks: importing: " + gpxFile.getName()); - try { - importedTrackIds = GpxImporter.importGPXFile(new FileInputStream(gpxFile), providerUtils); - return true; - } catch (FileNotFoundException e) { - Log.w(Constants.TAG, "GPX file wasn't found/went missing: " - + gpxFile.getAbsolutePath(), e); - } catch (ParserConfigurationException e) { - Log.w(Constants.TAG, "Error parsing file: " + gpxFile.getAbsolutePath(), e); - } catch (SAXException e) { - Log.w(Constants.TAG, "Error parsing file: " + gpxFile.getAbsolutePath(), e); - } catch (IOException e) { - Log.w(Constants.TAG, "Error reading file: " + gpxFile.getAbsolutePath(), e); - } - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, activity.getString(R.string.import_error, gpxFile.getName()), - Toast.LENGTH_LONG).show(); - } - }); - return false; - } - - /** - * Gets a list of the GPX files. If singleTrackSelected is true, returns a - * list containing just the gpxPath file. If singleTrackSelected is false, - * returns a list of GPX files under the gpxPath directory. - */ - private List getGpxFiles() { - List gpxFiles = new LinkedList(); - File file = new File(gpxPath); - if (singleTrackSelected) { - gpxFiles.add(file); - } else { - File[] gpxFileCandidates = file.listFiles(); - if (gpxFileCandidates != null) { - for (File candidate : gpxFileCandidates) { - if (!candidate.isDirectory() && candidate.getName().endsWith(".gpx")) { - gpxFiles.add(candidate); - } - } - } - } - return gpxFiles; - } -} \ No newline at end of file diff --git a/MyTracks/src/com/google/android/apps/mytracks/ImportAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/ImportAsyncTask.java new file mode 100644 index 000000000..4e8898936 --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/ImportAsyncTask.java @@ -0,0 +1,221 @@ +/* + * 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; + +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; +import com.google.android.apps.mytracks.io.file.GpxImporter; +import com.google.android.apps.mytracks.util.FileUtils; +import com.google.android.apps.mytracks.util.SystemUtils; +import com.google.android.maps.mytracks.R; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.PowerManager.WakeLock; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + +import org.xml.sax.SAXException; + +/** + * AsyncTask to import GPX files from the SD card. + * + * @author Jimmy Shih + */ +public class ImportAsyncTask extends AsyncTask { + + private static final String TAG = ImportAsyncTask.class.getSimpleName(); + + private ImportActivity importActivity; + private final boolean importAll; + private final String path; + private final MyTracksProviderUtils myTracksProviderUtils; + private WakeLock wakeLock; + + // true if the AsyncTask result is success + private boolean success; + + // true if the AsyncTask has completed + private boolean completed; + + // number of files successfully imported + private int successCount; + + // number of files to import + private int totalCount; + + // the last successfully imported track id + private long trackId; + + /** + * Creates an AsyncTask. + * + * @param importActivity the activity currently associated with this AsyncTask + * @param importAll true to import all GPX files + * @param path path to import GPX files + */ + public ImportAsyncTask(ImportActivity importActivity, boolean importAll, String path) { + this.importActivity = importActivity; + this.importAll = importAll; + this.path = path; + + myTracksProviderUtils = MyTracksProviderUtils.Factory.get(importActivity); + SharedPreferences sharedPreferences = importActivity.getSharedPreferences( + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + long recordingTrackId = sharedPreferences.getLong( + importActivity.getString(R.string.recording_track_key), -1L); + + // Get the wake lock if not recording + if (recordingTrackId == -1L) { + wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock); + } + + success = false; + completed = false; + successCount = 0; + totalCount = 0; + trackId = -1L; + } + + /** + * Sets the current {@link ImportActivity} associated with this AyncTask. + * + * @param importActivity the current {@link ImportActivity}, can be null + */ + public void setActivity(ImportActivity importActivity) { + this.importActivity = importActivity; + if (completed && importActivity != null) { + importActivity.onAsyncTaskCompleted(success, successCount, totalCount, trackId); + } + } + + @Override + protected void onPreExecute() { + if (importActivity != null) { + importActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + FileUtils fileUtils = new FileUtils(); + if (!fileUtils.isSdCardAvailable()) { + return false; + } + + List files = getFiles(); + totalCount = files.size(); + if (totalCount == 0) { + return true; + } + + for (int i = 0; i < totalCount; i++) { + if (isCancelled()) { + // If cancelled, return true to show the number of files imported + return true; + } + File file = files.get(i); + if (importFile(file)) { + successCount++; + } + publishProgress(i + 1, totalCount); + } + return true; + } finally { + // Release the wake lock if obtained + if (wakeLock != null && wakeLock.isHeld()) { + wakeLock.release(); + } + } + } + + @Override + protected void onProgressUpdate(Integer... values) { + if (importActivity != null) { + importActivity.setProgressDialogValue(values[0], values[1]); + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (importActivity != null) { + importActivity.onAsyncTaskCompleted(success, successCount, totalCount, trackId); + } + } + + /** + * Imports a GPX file. + * + * @param file the file + */ + private boolean importFile(final File file) { + try { + long trackIds[] = GpxImporter.importGPXFile(new FileInputStream(file), myTracksProviderUtils); + int length = trackIds.length; + if (length > 0) { + trackId = trackIds[length - 1]; + } + return true; + } catch (FileNotFoundException e) { + Log.d(TAG, "file: " + file.getAbsolutePath(), e); + return false; + } catch (ParserConfigurationException e) { + Log.d(TAG, "file: " + file.getAbsolutePath(), e); + return false; + } catch (SAXException e) { + Log.d(TAG, "file: " + file.getAbsolutePath(), e); + return false; + } catch (IOException e) { + Log.d(TAG, "file: " + file.getAbsolutePath(), e); + return false; + } + } + + /** + * Gets a list of GPX files. If importAll is true, returns a list of GPX files + * under the path directory. If importAll is false, returns a list containing + * just the path file. + */ + private List getFiles() { + List files = new ArrayList(); + File file = new File(path); + if (importAll) { + File[] candidates = file.listFiles(); + if (candidates != null) { + for (File candidate : candidates) { + if (!candidate.isDirectory() && candidate.getName().endsWith(".gpx")) { + files.add(candidate); + } + } + } + } else { + files.add(file); + } + return files; + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java index c4d5ab2d9..7214687d0 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MapActivity.java @@ -22,13 +22,14 @@ import com.google.android.apps.mytracks.content.Track; import com.google.android.apps.mytracks.content.TrackDataHub; import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType; import com.google.android.apps.mytracks.content.TrackDataListener; -import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.content.Waypoint; import com.google.android.apps.mytracks.io.file.SaveActivity; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest; import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity; import com.google.android.apps.mytracks.stats.TripStatistics; import com.google.android.apps.mytracks.util.ApiAdapterFactory; +import com.google.android.apps.mytracks.util.DialogUtils; import com.google.android.apps.mytracks.util.GeoRect; import com.google.android.apps.mytracks.util.LocationUtils; import com.google.android.apps.mytracks.util.PlayTrackUtils; @@ -38,11 +39,14 @@ import com.google.android.maps.MapView; import com.google.android.maps.mytracks.R; import android.app.Dialog; -import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; import android.location.Location; -import android.net.Uri; import android.os.Bundle; +import android.os.Parcelable; import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.util.Log; @@ -72,7 +76,8 @@ public class MapActivity extends com.google.android.maps.MapActivity implements View.OnTouchListener, View.OnClickListener, TrackDataListener { - private static final int DIALOG_INSTALL_EARTH = 0; + private static final int DIALOG_INSTALL_EARTH_ID = 0; + private static final int DIALOG_DELETE_CURRENT_ID = 1; // Saved instance state keys: // --------------------------- @@ -241,8 +246,26 @@ public class MapActivity extends com.google.android.maps.MapActivity @Override protected Dialog onCreateDialog(int id) { switch (id) { - case DIALOG_INSTALL_EARTH: + case DIALOG_INSTALL_EARTH_ID: return PlayTrackUtils.createInstallEarthDialog(this); + case DIALOG_DELETE_CURRENT_ID: + return DialogUtils.createConfirmationDialog(this, + R.string.track_list_delete_track_confirm_message, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + long trackId = dataHub.getSelectedTrackId(); + MyTracksProviderUtils.Factory.get(MapActivity.this).deleteTrack(trackId); + // If the deleted track was selected, unselect it. + String selectedTrackKey = getString(R.string.selected_track_key); + SharedPreferences sharedPreferences = getSharedPreferences( + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.getLong(selectedTrackKey, -1L) == trackId) { + Editor editor = sharedPreferences.edit().putLong(selectedTrackKey, -1L); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); + } + } + }); default: return null; } @@ -508,7 +531,7 @@ public class MapActivity extends com.google.android.maps.MapActivity PlayTrackUtils.playTrack(this, trackId); return true; } else { - showDialog(DIALOG_INSTALL_EARTH); + showDialog(DIALOG_INSTALL_EARTH_ID); return true; } case Constants.MENU_SEND_TO_GOOGLE: @@ -527,29 +550,56 @@ public class MapActivity extends com.google.android.maps.MapActivity startActivity(intent); return true; case Constants.MENU_SHARE_GPX_FILE: - case Constants.MENU_SHARE_KML_FILE: - case Constants.MENU_SHARE_CSV_FILE: - case Constants.MENU_SHARE_TCX_FILE: - case Constants.MENU_SAVE_GPX_FILE: - case Constants.MENU_SAVE_KML_FILE: - case Constants.MENU_SAVE_CSV_FILE: - case Constants.MENU_SAVE_TCX_FILE: - SaveActivity.handleExportTrackAction( - this, trackId, Constants.getActionFromMenuId(item.getItemId())); + startSaveActivity(trackId, TrackFileFormat.GPX, true); return true; + case Constants.MENU_SHARE_KML_FILE: + startSaveActivity(trackId, TrackFileFormat.KML, true); + return true; + case Constants.MENU_SHARE_CSV_FILE: + startSaveActivity(trackId, TrackFileFormat.CSV, true); + return true; + case Constants.MENU_SHARE_TCX_FILE: + startSaveActivity(trackId, TrackFileFormat.TCX, true); + return true; + case Constants.MENU_SAVE_GPX_FILE: + startSaveActivity(trackId, TrackFileFormat.GPX, false); + return true; + case Constants.MENU_SAVE_KML_FILE: + startSaveActivity(trackId, TrackFileFormat.KML, false); + return true; + case Constants.MENU_SAVE_CSV_FILE: + startSaveActivity(trackId, TrackFileFormat.CSV, false); + return true; + case Constants.MENU_SAVE_TCX_FILE: + startSaveActivity(trackId, TrackFileFormat.TCX, false); + return true; case Constants.MENU_CLEAR_MAP: dataHub.unloadCurrentTrack(); return true; case Constants.MENU_DELETE: - Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId); - intent = new Intent(Intent.ACTION_DELETE, uri); - startActivity(intent); + showDialog(DIALOG_DELETE_CURRENT_ID); return true; default: return super.onMenuItemSelected(featureId, item); } } + /** + * Starts the {@link SaveActivity} to save a track. + * + * @param trackId the track id + * @param trackFileFormat the track file format + * @param shareTrack true to share the track after saving + */ + private void startSaveActivity( + long trackId, TrackFileFormat trackFileFormat, boolean shareTrack) { + Intent intent = new Intent(this, SaveActivity.class) + .putExtra(SaveActivity.EXTRA_TRACK_ID, trackId) + .putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat) + .putExtra(SaveActivity.EXTRA_SHARE_TRACK, shareTrack); + startActivity(intent); + } + /** * Returns whether the map is currently in satellite view mode. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java index 2c156b54c..479739111 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java +++ b/MyTracks/src/com/google/android/apps/mytracks/MyTracks.java @@ -70,7 +70,10 @@ import android.widget.Toast; @SuppressWarnings("deprecation") public class MyTracks extends TabActivity implements OnTouchListener { private static final int DIALOG_EULA_ID = 0; - + private static final int DIALOG_CHECK_UNITS_ID = 1; + private static final String CHECK_UNITS_PREFERENCE_FILE = "checkunits"; + private static final String CHECK_UNITS_PREFERENCE_KEY = "checkunits.checked"; + private TrackDataHub dataHub; /** @@ -262,31 +265,59 @@ public class MyTracks extends TabActivity implements OnTouchListener { protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_EULA_ID: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.eula_title); - builder.setMessage(EulaUtils.getEulaMessage(this)); - builder.setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - EulaUtils.setEulaValue(MyTracks.this); - Intent startIntent = new Intent(MyTracks.this, WelcomeActivity.class); - startActivityForResult(startIntent, Constants.WELCOME); - } - }); - builder.setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - builder.setCancelable(true); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - finish(); - } - }); - return builder.create(); + return new AlertDialog.Builder(this) + .setCancelable(true) + .setMessage(EulaUtils.getEulaMessage(this)) + .setNegativeButton(R.string.eula_decline, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }) + .setPositiveButton(R.string.eula_accept, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + EulaUtils.setEulaValue(MyTracks.this); + Intent startIntent = new Intent(MyTracks.this, WelcomeActivity.class); + startActivityForResult(startIntent, Constants.WELCOME); + } + }) + .setTitle(R.string.eula_title) + .create(); + case DIALOG_CHECK_UNITS_ID: + return new AlertDialog.Builder(this) + .setCancelable(true) + .setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + SharedPreferences sharedPreferences = getSharedPreferences( + CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges( + sharedPreferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true)); + } + }) + .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + SharedPreferences sharedPreferences = getSharedPreferences( + CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges( + sharedPreferences.edit().putBoolean(CHECK_UNITS_PREFERENCE_KEY, true)); + + int position = ((AlertDialog) dialog).getListView().getSelectedItemPosition(); + SharedPreferences.Editor editor = preferences.edit(); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges( + editor.putBoolean(getString(R.string.metric_units_key), position == 0)); + } + }) + .setSingleChoiceItems(new CharSequence[] { getString(R.string.preferred_units_metric), + getString(R.string.preferred_units_imperial) }, 0, null) + .setTitle(R.string.preferred_units_title) + .create(); default: return null; } @@ -380,7 +411,11 @@ public class MyTracks extends TabActivity implements OnTouchListener { break; } case Constants.WELCOME: { - CheckUnits.check(this); + SharedPreferences sharedPreferences = getSharedPreferences( + CHECK_UNITS_PREFERENCE_FILE, Context.MODE_PRIVATE); + if (!sharedPreferences.getBoolean(CHECK_UNITS_PREFERENCE_KEY, false)) { + showDialog(DIALOG_CHECK_UNITS_ID); + } break; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java index 76af12627..0d66d3c85 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/SettingsActivity.java @@ -17,8 +17,9 @@ package com.google.android.apps.mytracks; import static com.google.android.apps.mytracks.Constants.TAG; -import com.google.android.apps.mytracks.io.backup.BackupActivityHelper; +import com.google.android.apps.mytracks.io.backup.BackupActivity; import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener; +import com.google.android.apps.mytracks.io.backup.RestoreChooserActivity; import com.google.android.apps.mytracks.services.sensors.ant.AntUtils; import com.google.android.apps.mytracks.util.ApiAdapterFactory; import com.google.android.apps.mytracks.util.BluetoothDeviceUtils; @@ -62,8 +63,9 @@ import java.util.Set; */ public class SettingsActivity extends PreferenceActivity { - private static final int DIALOG_CONFIRM_RESET = 0; - private static final int DIALOG_CONFIRM_ACCESS = 1; + private static final int DIALOG_CONFIRM_RESET_ID = 0; + private static final int DIALOG_CONFIRM_ACCESS_ID = 1; + private static final int DIALOG_CONFIRM_RESTORE_ID = 2; // Value when the task frequency is off. private static final String TASK_FREQUENCY_OFF = "0"; @@ -150,7 +152,7 @@ public class SettingsActivity extends PreferenceActivity { resetPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference arg0) { - showDialog(DIALOG_CONFIRM_RESET); + showDialog(DIALOG_CONFIRM_RESET_ID); return true; } }); @@ -162,7 +164,7 @@ public class SettingsActivity extends PreferenceActivity { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if ((Boolean) newValue) { - showDialog(DIALOG_CONFIRM_ACCESS); + showDialog(DIALOG_CONFIRM_ACCESS_ID); return false; } else { return true; @@ -174,7 +176,7 @@ public class SettingsActivity extends PreferenceActivity { @Override protected Dialog onCreateDialog(int id) { switch (id) { - case DIALOG_CONFIRM_RESET: + case DIALOG_CONFIRM_RESET_ID: return DialogUtils.createConfirmationDialog( this, R.string.settings_reset_confirm_message, new OnClickListener() { @Override @@ -182,7 +184,7 @@ public class SettingsActivity extends PreferenceActivity { onResetPreferencesConfirmed(); } }); - case DIALOG_CONFIRM_ACCESS: + case DIALOG_CONFIRM_ACCESS_ID: return DialogUtils.createConfirmationDialog( this, R.string.settings_sharing_allow_access_confirm_message, new OnClickListener() { @Override @@ -192,6 +194,14 @@ public class SettingsActivity extends PreferenceActivity { pref.setChecked(true); } }); + case DIALOG_CONFIRM_RESTORE_ID: + return DialogUtils.createConfirmationDialog( + this, R.string.settings_backup_restore_confirm_message, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + startActivity(new Intent(SettingsActivity.this, RestoreChooserActivity.class)); + } + }); default: return null; } @@ -354,9 +364,7 @@ public class SettingsActivity extends PreferenceActivity { new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - BackupActivityHelper backupHelper = - new BackupActivityHelper(SettingsActivity.this); - backupHelper.writeBackup(); + startActivity(new Intent(SettingsActivity.this, BackupActivity.class)); return true; } }); @@ -364,9 +372,7 @@ public class SettingsActivity extends PreferenceActivity { new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - BackupActivityHelper backupHelper = - new BackupActivityHelper(SettingsActivity.this); - backupHelper.restoreBackup(); + showDialog(DIALOG_CONFIRM_RESTORE_ID); return true; } }); diff --git a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java index cbbf32efa..c71b03aab 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/TrackList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/TrackList.java @@ -17,26 +17,31 @@ package com.google.android.apps.mytracks; import static com.google.android.apps.mytracks.Constants.TAG; +import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.io.file.SaveActivity; +import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.io.sendtogoogle.SendRequest; import com.google.android.apps.mytracks.io.sendtogoogle.UploadServiceChooserActivity; import com.google.android.apps.mytracks.services.ServiceUtils; import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; +import com.google.android.apps.mytracks.util.ApiAdapterFactory; +import com.google.android.apps.mytracks.util.DialogUtils; import com.google.android.apps.mytracks.util.PlayTrackUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.maps.mytracks.R; +import android.app.AlertDialog; import android.app.Dialog; import android.app.ListActivity; -import android.content.ContentUris; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; import android.database.Cursor; -import android.net.Uri; import android.os.Bundle; -import android.os.Handler; +import android.os.Parcelable; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -62,7 +67,10 @@ public class TrackList extends ListActivity implements SharedPreferences.OnSharedPreferenceChangeListener, View.OnClickListener { - private static final int DIALOG_INSTALL_EARTH = 0; + private static final int DIALOG_INSTALL_EARTH_ID = 0; + private static final int DIALOG_EXPORT_ALL_ID = 1; + private static final int DIALOG_DELETE_ALL_ID = 2; + private static final int DIALOG_DELETE_CURRENT_ID = 3; private int contextPosition = -1; private long trackId = -1; @@ -178,7 +186,7 @@ public class TrackList extends ListActivity PlayTrackUtils.playTrack(this, trackId); return true; } else { - showDialog(DIALOG_INSTALL_EARTH); + showDialog(DIALOG_INSTALL_EARTH_ID); return true; } case Constants.MENU_SEND_TO_GOOGLE: @@ -197,21 +205,31 @@ public class TrackList extends ListActivity startActivity(intent); return true; case Constants.MENU_SHARE_GPX_FILE: - case Constants.MENU_SHARE_KML_FILE: - case Constants.MENU_SHARE_CSV_FILE: - case Constants.MENU_SHARE_TCX_FILE: - case Constants.MENU_SAVE_GPX_FILE: - case Constants.MENU_SAVE_KML_FILE: - case Constants.MENU_SAVE_CSV_FILE: - case Constants.MENU_SAVE_TCX_FILE: - SaveActivity.handleExportTrackAction( - this, trackId, Constants.getActionFromMenuId(item.getItemId())); + startSaveActivity(TrackFileFormat.GPX, true); return true; + case Constants.MENU_SHARE_KML_FILE: + startSaveActivity(TrackFileFormat.KML, true); + return true; + case Constants.MENU_SHARE_CSV_FILE: + startSaveActivity(TrackFileFormat.CSV, true); + return true; + case Constants.MENU_SHARE_TCX_FILE: + startSaveActivity(TrackFileFormat.TCX, true); + return true; + case Constants.MENU_SAVE_GPX_FILE: + startSaveActivity(TrackFileFormat.GPX, false); + return true; + case Constants.MENU_SAVE_KML_FILE: + startSaveActivity(TrackFileFormat.KML, false); + return true; + case Constants.MENU_SAVE_CSV_FILE: + startSaveActivity(TrackFileFormat.CSV, false); + return true; + case Constants.MENU_SAVE_TCX_FILE: + startSaveActivity(TrackFileFormat.TCX, false); + return true; case Constants.MENU_DELETE: - Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId); - intent = new Intent(Intent.ACTION_DELETE) - .setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE); - startActivity(intent); + showDialog(DIALOG_DELETE_CURRENT_ID); return true; default: Log.w(TAG, "Unknown menu item: " + item.getItemId() + "(" + item.getTitle() + ")"); @@ -219,22 +237,34 @@ public class TrackList extends ListActivity } } + /** + * Starts the {@link SaveActivity} to save trackId. + * + * @param trackFileFormat the track file format + * @param shareTrack true to share the track after saving + */ + private void startSaveActivity(TrackFileFormat trackFileFormat, boolean shareTrack) { + Intent intent = new Intent(this, SaveActivity.class) + .putExtra(SaveActivity.EXTRA_TRACK_ID, trackId) + .putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat) + .putExtra(SaveActivity.EXTRA_SHARE_TRACK, shareTrack); + startActivity(intent); + } + @Override public void onClick(View v) { switch (v.getId()) { - case R.id.tracklist_btn_delete_all: { - Handler h = new DeleteAllTracks(this, null); - h.handleMessage(null); + case R.id.tracklist_btn_delete_all: + showDialog(DIALOG_DELETE_ALL_ID); break; - } - case R.id.tracklist_btn_export_all: { - new ExportAllTracks(this); + case R.id.tracklist_btn_export_all: + showDialog(DIALOG_EXPORT_ALL_ID); break; - } - case R.id.tracklist_btn_import_all: { - new ImportAllTracks(this); - break; - } + case R.id.tracklist_btn_import_all: + Intent intent = new Intent(this, ImportActivity.class) + .putExtra(ImportActivity.EXTRA_IMPORT_ALL, true); + startActivity(intent); + break; } } @@ -298,8 +328,60 @@ public class TrackList extends ListActivity @Override protected Dialog onCreateDialog(int id) { switch (id) { - case DIALOG_INSTALL_EARTH: + case DIALOG_INSTALL_EARTH_ID: return PlayTrackUtils.createInstallEarthDialog(this); + case DIALOG_EXPORT_ALL_ID: + String exportFileFormat = getString(R.string.track_list_export_file); + String fileTypes[] = getResources().getStringArray(R.array.file_types); + String[] choices = new String[fileTypes.length]; + for (int i = 0; i < fileTypes.length; i++) { + choices[i] = String.format(exportFileFormat, fileTypes[i]); + } + return new AlertDialog.Builder(this) + .setNegativeButton(R.string.generic_cancel, null) + .setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int index = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + Intent intent = new Intent(TrackList.this, ExportAllActivity.class); + intent.putExtra(ExportAllActivity.EXTRA_TRACK_FILE_FORMAT, + (Parcelable) TrackFileFormat.values()[index]); + TrackList.this.startActivity(intent); + } + }) + .setSingleChoiceItems(choices, 0, null) + .setTitle(R.string.track_list_export_all) + .create(); + case DIALOG_DELETE_ALL_ID: + return DialogUtils.createConfirmationDialog(this, + R.string.track_list_delete_all_confirm_message, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MyTracksProviderUtils.Factory.get(TrackList.this).deleteAllTracks(); + SharedPreferences sharedPreferences = getSharedPreferences( + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + Editor editor = sharedPreferences.edit(); + // TODO: Go through data manager + editor.putLong(getString(R.string.selected_track_key), -1L); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); + } + }); + case DIALOG_DELETE_CURRENT_ID: + return DialogUtils.createConfirmationDialog(this, + R.string.track_list_delete_track_confirm_message, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MyTracksProviderUtils.Factory.get(TrackList.this).deleteTrack(trackId); + // If the deleted track was selected, unselect it. + String selectedTrackKey = getString(R.string.selected_track_key); + SharedPreferences sharedPreferences = getSharedPreferences( + Constants.SETTINGS_NAME, Context.MODE_PRIVATE); + if (sharedPreferences.getLong(selectedTrackKey, -1L) == trackId) { + Editor editor = sharedPreferences.edit().putLong(selectedTrackKey, -1L); + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); + } + } + }); default: return null; } diff --git a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java index 792026215..135caffc5 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java +++ b/MyTracks/src/com/google/android/apps/mytracks/WaypointsList.java @@ -24,10 +24,11 @@ import com.google.android.apps.mytracks.content.WaypointCreationRequest; import com.google.android.apps.mytracks.content.WaypointsColumns; import com.google.android.apps.mytracks.services.ITrackRecordingService; import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection; +import com.google.android.apps.mytracks.util.DialogUtils; import com.google.android.apps.mytracks.util.StringUtils; import com.google.android.maps.mytracks.R; -import android.app.AlertDialog; +import android.app.Dialog; import android.app.ListActivity; import android.content.Context; import android.content.DialogInterface; @@ -57,9 +58,10 @@ import android.widget.Toast; * * @author Leif Hendrik Wilden */ -public class WaypointsList extends ListActivity - implements View.OnClickListener { +public class WaypointsList extends ListActivity implements View.OnClickListener { + private static final int DIALOG_DELETE_CURRENT_ID = 0; + private int contextPosition = -1; private long trackId = -1; private long selectedWaypointId = -1; @@ -119,7 +121,7 @@ public class WaypointsList extends ListActivity return true; } case Constants.MENU_DELETE: { - deleteWaypoint(selectedWaypointId); + showDialog(DIALOG_DELETE_CURRENT_ID); } } } @@ -199,6 +201,24 @@ public class WaypointsList extends ListActivity super.onDestroy(); } + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_DELETE_CURRENT_ID: + return DialogUtils.createConfirmationDialog(this, + R.string.marker_list_delete_marker_confirm_message, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + providerUtils.deleteWaypoint( + selectedWaypointId, new DescriptionGeneratorImpl(WaypointsList.this)); + } + }); + default: + return null; + } + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.search_only, menu); @@ -303,34 +323,4 @@ public class WaypointsList extends ListActivity }); setListAdapter(adapter); } - - /** - * Deletes the way point with the given id. - * Prompts the user if he want to really delete it. - */ - public void deleteWaypoint(final long waypointId) { - AlertDialog dialog = null; - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getString(R.string.marker_list_delete_marker_confirm_message)); - builder.setTitle(getString(R.string.generic_confirm_title)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setPositiveButton(getString(R.string.generic_yes), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - providerUtils.deleteWaypoint(waypointId, - new DescriptionGeneratorImpl(WaypointsList.this)); - } - }); - builder.setNegativeButton(getString(R.string.generic_no), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - dialogInterface.dismiss(); - } - }); - dialog = builder.create(); - dialog.show(); - } } 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..2db4e61da --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivity.java @@ -0,0 +1,101 @@ +/* + * 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.maps.mytracks.R; + +import android.app.Activity; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.widget.Toast; + +/** + * Activity to backup data to the SD card. + * + * @author Jimmy Shih + */ +public class BackupActivity extends Activity { + + private static final int DIALOG_PROGRESS_ID = 0; + + private BackupAsyncTask backupAsyncTask; + private ProgressDialog progressDialog; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof BackupAsyncTask) { + backupAsyncTask = (BackupAsyncTask) retained; + backupAsyncTask.setActivity(this); + } else { + backupAsyncTask = new BackupAsyncTask(this); + backupAsyncTask.execute(); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + backupAsyncTask.setActivity(null); + return backupAsyncTask; + } + + @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_now_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(); + finish(); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivityHelper.java b/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivityHelper.java deleted file mode 100644 index d78d8c96e..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/io/backup/BackupActivityHelper.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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.Constants; -import com.google.android.apps.mytracks.MyTracks; -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.AlertDialog.Builder; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.util.Log; -import android.widget.Toast; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; - -/** - * Helper which shows a UI for writing or restoring a backup, - * and calls the appropriate handler for actually executing those - * operations. - * - * @author Rodrigo Damazio - */ -public class BackupActivityHelper { - - private static final Comparator 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; + } + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportActivity.java deleted file mode 100644 index 027e35002..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/ImportActivity.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2011 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.file; - -import static com.google.android.apps.mytracks.Constants.TAG; - -import com.google.android.apps.mytracks.ImportAllTracks; -import com.google.android.apps.mytracks.util.UriUtils; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.util.Log; - -/** - * An activity that imports a track from a file and displays the track in My Tracks. - * - * @author Rodrigo Damazio - */ -public class ImportActivity extends Activity { - - @Override - public void onStart() { - super.onStart(); - - Intent intent = getIntent(); - String action = intent.getAction(); - Uri data = intent.getData(); - if (!(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_ATTACH_DATA.equals(action))) { - Log.e(TAG, "Received an intent with unsupported action: " + intent); - finish(); - return; - } - - if (!UriUtils.isFileUri(data)) { - Log.e(TAG, "Received an intent with unsupported data: " + intent); - finish(); - return; - } - - String path = data.getPath(); - Log.i(TAG, "Importing GPX file at " + path); - new ImportAllTracks(this, path); - } -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java index daa35dd3e..8ecbfb596 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveActivity.java @@ -13,24 +13,19 @@ * License for the specific language governing permissions and limitations under * the License. */ + package com.google.android.apps.mytracks.io.file; -import static com.google.android.apps.mytracks.Constants.TAG; - -import com.google.android.apps.mytracks.Constants; import com.google.android.apps.mytracks.content.MyTracksProviderUtils; -import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.apps.mytracks.util.FileUtils; import com.google.android.apps.mytracks.util.PlayTrackUtils; -import com.google.android.apps.mytracks.util.UriUtils; import com.google.android.maps.mytracks.R; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.content.ContentUris; -import android.content.Context; +import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; @@ -42,208 +37,190 @@ import android.util.Log; import java.io.File; /** - * Activity for saving a track to a file (and optionally sending that file). + * Activity for saving a track to the SD card, and optionally share or play the + * track. * * @author Rodrigo Damazio */ public class SaveActivity extends Activity { - public static final String EXTRA_FILE_FORMAT = "file_format"; - public static final String EXTRA_SHARE_FILE = "share_file"; - public static final String EXTRA_PLAY_FILE = "play_file"; - private static final int RESULT_DIALOG = 1; - /* VisibleForTesting */ - static final int PROGRESS_DIALOG = 2; + public static final String EXTRA_TRACK_ID = "track_id"; + public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format"; + public static final String EXTRA_SHARE_TRACK = "share_track"; + public static final String EXTRA_PLAY_TRACK = "play_track"; + + private static final String TAG = SaveActivity.class.getSimpleName(); + + private static final int DIALOG_PROGRESS_ID = 0; + private static final int DIALOG_RESULT_ID = 1; - private MyTracksProviderUtils providerUtils; private long trackId; - private TrackWriter writer; - private boolean shareFile; - private boolean playFile; - private TrackFileFormat format; - private WriteProgressController controller; + private TrackFileFormat trackFileFormat; + private boolean shareTrack; + private boolean playTrack; + + private SaveAsyncTask saveAsyncTask; + private ProgressDialog progressDialog; + + // result from the AsyncTask + private boolean success; + + // message id from the AsyncTask + private int messageId; + + // path of the saved file + private String filePath; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - providerUtils = MyTracksProviderUtils.Factory.get(this); + Intent intent = getIntent(); + trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L); + if (trackId < 0) { + Log.d(TAG, "Invalid track id"); + finish(); + return; + } + + trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT); + shareTrack = intent.getBooleanExtra(EXTRA_SHARE_TRACK, false); + playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false); + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof SaveAsyncTask) { + saveAsyncTask = (SaveAsyncTask) retained; + saveAsyncTask.setActivity(this); + } else { + TrackWriter trackWriter = TrackWriterFactory.newWriter( + this, MyTracksProviderUtils.Factory.get(this), trackId, trackFileFormat); + if (trackWriter == null) { + Log.e(TAG, "Track writer is null"); + finish(); + return; + } + if (shareTrack || playTrack) { + // Save to the temp directory + String dirName = new FileUtils().buildExternalDirectoryPath( + trackFileFormat.getExtension(), "tmp"); + trackWriter.setDirectory(new File(dirName)); + } + saveAsyncTask = new SaveAsyncTask(this, trackWriter); + saveAsyncTask.execute(); + } } @Override - protected void onStart() { - super.onStart(); + public Object onRetainNonConfigurationInstance() { + saveAsyncTask.setActivity(null); + return saveAsyncTask; + } - Intent intent = getIntent(); - String action = intent.getAction(); - String type = intent.getType(); - Uri data = intent.getData(); - if (!getString(R.string.track_action_save).equals(action) - || !TracksColumns.CONTENT_ITEMTYPE.equals(type) - || !UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) { - Log.e(TAG, "Got bad save intent: " + intent); - finish(); - return; + @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.sd_card_progress_write_file)); + progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + saveAsyncTask.cancel(true); + finish(); + } + }); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(R.string.generic_progress_title); + return progressDialog; + case DIALOG_RESULT_ID: + return new AlertDialog.Builder(this) + .setCancelable(true) + .setIcon(success + ? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert) + .setMessage(messageId) + .setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.dismiss(); + onPostResultDialog(); + } + }) + .setPositiveButton(R.string.generic_ok, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int arg1) { + dialog.dismiss(); + onPostResultDialog(); + } + }) + .setTitle(success ? R.string.generic_success_title : R.string.generic_error_title) + .create(); + default: + return null; } + } - trackId = ContentUris.parseId(data); + /** + * Invokes when the associated AsyncTask completes. + * + * @param isSuccess true if the AsyncTask is successful + * @param aMessageId the id of the AsyncTask message + * @param aPath the path of the saved file + */ + public void onAsyncTaskCompleted(boolean isSuccess, int aMessageId, String aPath) { + this.success = isSuccess; + this.messageId = aMessageId; + this.filePath = aPath; + removeDialog(DIALOG_PROGRESS_ID); + showDialog(DIALOG_RESULT_ID); + } - int formatIdx = intent.getIntExtra(EXTRA_FILE_FORMAT, -1); - format = TrackFileFormat.values()[formatIdx]; - shareFile = intent.getBooleanExtra(EXTRA_SHARE_FILE, false); - playFile = intent.getBooleanExtra(EXTRA_PLAY_FILE, false); - - writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format); - if (writer == null) { - Log.e(TAG, "Unable to build writer"); - finish(); - return; + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } + + /** + * Sets the progress dialog value. + * + * @param number the number of points saved + * @param max the maximum number of points + */ + public void setProgressDialogValue(int number, int max) { + if (progressDialog != null) { + progressDialog.setIndeterminate(false); + progressDialog.setMax(max); + progressDialog.setProgress(Math.min(number, max)); } - - if (shareFile || playFile) { - // If the file is for sending, save it to a temporary location instead. - FileUtils fileUtils = new FileUtils(); - String extension = format.getExtension(); - String dirName = fileUtils.buildExternalDirectoryPath(extension, "tmp"); - - File dir = new File(dirName); - writer.setDirectory(dir); - } - - controller = new WriteProgressController(this, writer, PROGRESS_DIALOG); - controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() { - @Override - public void onComplete() { - showDialog(RESULT_DIALOG); - } - }); - controller.startWrite(); } /** * To be invoked after showing the result dialog. */ private void onPostResultDialog() { - if (writer.wasSuccess()) { - if (shareFile) { - shareWrittenFile(); - } else if (playFile) { - playWrittenFile(); + if (success) { + if (shareTrack) { + Intent intent = new Intent(Intent.ACTION_SEND) + .putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filePath))) + .putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_track_subject)) + .putExtra(Intent.EXTRA_TEXT, getString(R.string.share_track_file_body_format)) + .putExtra(getString(R.string.track_id_broadcast_extra), trackId) + .setType(trackFileFormat.getMimeType()); + startActivity(Intent.createChooser(intent, getString(R.string.share_track_picker_title))); + } else if (playTrack) { + Intent intent = new Intent() + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(PlayTrackUtils.TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID) + .setClassName(PlayTrackUtils.GOOGLE_EARTH_PACKAGE, PlayTrackUtils.GOOGLE_EARTH_CLASS) + .setDataAndType(Uri.fromFile(new File(filePath)), PlayTrackUtils.KML_MIME_TYPE); + startActivity(intent); } } finish(); } - - private void shareWrittenFile() { - // Share the file. - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, - getResources().getText(R.string.share_track_subject).toString()); - shareIntent.putExtra(Intent.EXTRA_TEXT, - getResources().getText(R.string.share_track_file_body_format) - .toString()); - shareIntent.setType(format.getMimeType()); - Uri u = Uri.fromFile(new File(writer.getAbsolutePath())); - shareIntent.putExtra(Intent.EXTRA_STREAM, u); - shareIntent.putExtra(getString(R.string.track_id_broadcast_extra), trackId); - startActivity(Intent.createChooser(shareIntent, - getResources().getText(R.string.share_track_picker_title).toString())); - } - - private void playWrittenFile() { - Uri uri = Uri.fromFile(new File(writer.getAbsolutePath())); - Intent intent = new Intent() - .setClassName(PlayTrackUtils.GOOGLE_EARTH_PACKAGE, PlayTrackUtils.GOOGLE_EARTH_CLASS) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .setDataAndType(uri, PlayTrackUtils.KML_MIME_TYPE) - .putExtra(PlayTrackUtils.TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID); - startActivity(intent); - } - - @Override - protected Dialog onCreateDialog(int id) { - switch (id) { - case RESULT_DIALOG: - return createResultDialog(); - case PROGRESS_DIALOG: - if (controller != null) { - return controller.createProgressDialog(); - } - //$FALL-THROUGH$ - default: - return super.onCreateDialog(id); - } - } - - private Dialog createResultDialog() { - boolean success = writer.wasSuccess(); - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(writer.getErrorMessage()); - builder.setPositiveButton(R.string.generic_ok, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int arg1) { - dialog.dismiss(); - onPostResultDialog(); - } - }); - builder.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.dismiss(); - onPostResultDialog(); - } - }); - builder.setIcon(success ? android.R.drawable.ic_dialog_info : - android.R.drawable.ic_dialog_alert); - builder.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title); - return builder.create(); - } - - public static void handleExportTrackAction(Context ctx, long trackId, int actionCode) { - if (trackId < 0) { - return; - } - - TrackFileFormat exportFormat = null; - switch (actionCode) { - case Constants.SAVE_GPX_FILE: - case Constants.SHARE_GPX_FILE: - exportFormat = TrackFileFormat.GPX; - break; - case Constants.SAVE_KML_FILE: - case Constants.SHARE_KML_FILE: - exportFormat = TrackFileFormat.KML; - break; - case Constants.SAVE_CSV_FILE: - case Constants.SHARE_CSV_FILE: - exportFormat = TrackFileFormat.CSV; - break; - case Constants.SAVE_TCX_FILE: - case Constants.SHARE_TCX_FILE: - exportFormat = TrackFileFormat.TCX; - break; - default: - throw new IllegalArgumentException("Warning unhandled action code: " + actionCode); - } - - boolean shareFile = false; - switch (actionCode) { - case Constants.SHARE_GPX_FILE: - case Constants.SHARE_KML_FILE: - case Constants.SHARE_CSV_FILE: - case Constants.SHARE_TCX_FILE: - shareFile = true; - } - - Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId); - - Intent intent = new Intent(ctx, SaveActivity.class); - intent.setAction(ctx.getString(R.string.track_action_save)); - intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE); - intent.putExtra(EXTRA_FILE_FORMAT, exportFormat.ordinal()); - intent.putExtra(EXTRA_SHARE_FILE, shareFile); - ctx.startActivity(intent); - } } diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveAsyncTask.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveAsyncTask.java new file mode 100644 index 000000000..2c5aa929a --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/SaveAsyncTask.java @@ -0,0 +1,107 @@ +/* + * 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.file; + +import android.os.AsyncTask; + +/** + * Async Task to save a track to the SD card. + * + * @author Jimmy Shih + */ +public class SaveAsyncTask extends AsyncTask { + + private SaveActivity saveActivity; + private final TrackWriter trackWriter; + + // true if the AsyncTask result is success + private boolean success; + + // true if the AsyncTask has completed + private boolean completed; + + /** + * Creates an AsyncTask. + * + * @param saveActivity the {@link SaveActivity} currently associated with this + * AsyncTask + * @param trackWriter the track writer + */ + public SaveAsyncTask(SaveActivity saveActivity, TrackWriter trackWriter) { + this.saveActivity = saveActivity; + this.trackWriter = trackWriter; + success = false; + completed = false; + } + + /** + * Sets the current {@link SaveActivity} associated with this AyncTask. + * + * @param saveActivity the current {@link SaveActivity}, can be null + */ + public void setActivity(SaveActivity saveActivity) { + this.saveActivity = saveActivity; + if (completed && saveActivity != null) { + saveActivity.onAsyncTaskCompleted( + success, trackWriter.getErrorMessage(), trackWriter.getAbsolutePath()); + } + } + + @Override + protected void onPreExecute() { + if (saveActivity != null) { + saveActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + trackWriter.setOnWriteListener(new TrackWriter.OnWriteListener() { + @Override + public void onWrite(int number, int max) { + // Update the progress dialog once every 500 points + if (number % 500 == 0) { + publishProgress(number, max); + } + } + }); + trackWriter.writeTrack(); + return trackWriter.wasSuccess(); + } + + @Override + protected void onProgressUpdate(Integer... values) { + if (saveActivity != null) { + saveActivity.setProgressDialogValue(values[0], values[1]); + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (saveActivity != null) { + saveActivity.onAsyncTaskCompleted( + success, trackWriter.getErrorMessage(), trackWriter.getAbsolutePath()); + } + } + + @Override + protected void onCancelled() { + trackWriter.stopWriteTrack(); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java index bace24d33..26e1c59c6 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriter.java @@ -27,10 +27,6 @@ import java.io.File; * @author Rodrigo Damazio */ public interface TrackWriter { - /** This listener is used to signal completion of track write */ - public interface OnCompletionListener { - public void onComplete(); - } /** This listener is used to signal track writes. */ public interface OnWriteListener { @@ -43,11 +39,6 @@ public interface TrackWriter { public void onWrite(int number, int max); } - /** - * Sets listener to be invoked when the writer has finished. - */ - void setOnCompletionListener(OnCompletionListener onCompletionListener); - /** * Sets a listener to be invoked for each location writer. */ @@ -63,12 +54,6 @@ public interface TrackWriter { */ String getAbsolutePath(); - /** - * Writes the given track id to the SD card. - * This is non-blocking. - */ - void writeTrackAsync(); - /** * Writes the given track id to the SD card. * This is blocking. diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java index 7df87364f..37ea46b96 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterFactory.java @@ -22,6 +22,8 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtils; import com.google.android.apps.mytracks.content.Track; import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; import android.util.Log; /** @@ -34,7 +36,7 @@ public class TrackWriterFactory { /** * Definition of all possible track formats. */ - public enum TrackFileFormat { + public enum TrackFileFormat implements Parcelable { GPX { @Override TrackFormatWriter newFormatWriter(Context context) { @@ -60,6 +62,28 @@ public class TrackWriterFactory { } }; + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(final Parcel dest, final int flags) { + dest.writeInt(ordinal()); + } + + public static final Creator CREATOR = new Creator() { + @Override + public TrackFileFormat createFromParcel(final Parcel source) { + return TrackFileFormat.values()[source.readInt()]; + } + + @Override + public TrackFileFormat[] newArray(final int size) { + return new TrackFileFormat[size]; + } + }; + /** * Creates and returns a new format writer for each format. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java index 21617daa3..76517544b 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java +++ b/MyTracks/src/com/google/android/apps/mytracks/io/file/TrackWriterImpl.java @@ -57,7 +57,6 @@ class TrackWriterImpl implements TrackWriter { private int errorMessage = -1; private File directory = null; private File file = null; - private OnCompletionListener onCompletionListener; private OnWriteListener onWriteListener; private Thread writeThread; @@ -70,11 +69,6 @@ class TrackWriterImpl implements TrackWriter { this.fileUtils = new FileUtils(); } - @Override - public void setOnCompletionListener(OnCompletionListener onCompletionListener) { - this.onCompletionListener = onCompletionListener; - } - @Override public void setOnWriteListener(OnWriteListener onWriteListener) { this.onWriteListener = onWriteListener; @@ -90,8 +84,7 @@ class TrackWriterImpl implements TrackWriter { return file.getAbsolutePath(); } - @Override - public void writeTrackAsync() { + private void writeTrackAsync() { writeThread = new Thread() { @Override public void run() { @@ -131,7 +124,6 @@ class TrackWriterImpl implements TrackWriter { } } } - finished(); } public void stopWriteTrack() { @@ -163,18 +155,6 @@ class TrackWriterImpl implements TrackWriter { * =============== */ - private void finished() { - if (onCompletionListener != null) { - runOnUiThread(new Runnable() { - @Override - public void run() { - onCompletionListener.onComplete(); - } - }); - return; - } - } - /** * Runs the given runnable in the UI thread. */ diff --git a/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java b/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java deleted file mode 100644 index 8da3a1871..000000000 --- a/MyTracks/src/com/google/android/apps/mytracks/io/file/WriteProgressController.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2011 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.file; - -import com.google.android.maps.mytracks.R; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.content.DialogInterface; - -/** - * Given a {@link TrackWriter}, this class manages the process of writing the - * data in the track represented by the writer. This includes the display of - * a progress dialog, updating the progress bar in said dialog, and notifying - * interested parties when the write completes. - * - * @author Matthew Simmons - */ -class WriteProgressController { - /** - * This listener is used to notify interested parties when the write has - * completed. - */ - public interface OnCompletionListener { - /** - * When this method is invoked, the write has completed, and the progress - * dialog has been dismissed. Whether the write succeeded can be - * determined by examining the {@link TrackWriter}. - */ - public void onComplete(); - } - - private final Activity activity; - private final TrackWriter writer; - private ProgressDialog dialog; - - private OnCompletionListener onCompletionListener; - private final int progressDialogId; - - /** - * @param activity the activity associated with this write - * @param writer the writer which writes the track to disk. Note that this - * class will use the writer's completion listener. If callers are - * interested in notification upon completion of the write, they should - * use {@link #setOnCompletionListener}. - */ - public WriteProgressController(Activity activity, TrackWriter writer, int progressDialogId) { - this.activity = activity; - this.writer = writer; - this.progressDialogId = progressDialogId; - - writer.setOnCompletionListener(writerCompleteListener); - writer.setOnWriteListener(writerWriteListener); - } - - /** Set a listener to be invoked when the write completes. */ - public void setOnCompletionListener(OnCompletionListener onCompletionListener) { - this.onCompletionListener = onCompletionListener; - } - - // For testing purpose - OnCompletionListener getOnCompletionListener() { - return onCompletionListener; - } - - public ProgressDialog createProgressDialog() { - dialog = new ProgressDialog(activity); - dialog.setIcon(android.R.drawable.ic_dialog_info); - dialog.setTitle(activity.getString(R.string.generic_progress_title)); - dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - dialog.setMessage(activity.getString(R.string.sd_card_progress_write_file)); - dialog.setIndeterminate(true); - dialog.setOnCancelListener(dialogCancelListener); - return dialog; - } - - /** Initiate an asynchronous write. */ - public void startWrite() { - activity.showDialog(progressDialogId); - writer.writeTrackAsync(); - } - - /** VisibleForTesting */ - ProgressDialog getDialog() { - return dialog; - } - - private final DialogInterface.OnCancelListener dialogCancelListener = - new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialogInterface) { - writer.stopWriteTrack(); - } - }; - - private final TrackWriter.OnCompletionListener writerCompleteListener = - new TrackWriter.OnCompletionListener() { - @Override - public void onComplete() { - activity.dismissDialog(progressDialogId); - - if (onCompletionListener != null) { - onCompletionListener.onComplete(); - } - } - }; - - private final TrackWriter.OnWriteListener writerWriteListener = - new TrackWriter.OnWriteListener() { - @Override - public void onWrite(int number, int max) { - if (number % 500 == 0) { - dialog.setIndeterminate(false); - dialog.setMax(max); - dialog.setProgress(Math.min(number, max)); - } - } - }; -} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java index 09e37656d..675bef4f5 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/PlayTrackUtils.java @@ -16,14 +16,12 @@ package com.google.android.apps.mytracks.util; -import com.google.android.apps.mytracks.content.TracksColumns; import com.google.android.apps.mytracks.io.file.SaveActivity; import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat; import com.google.android.maps.mytracks.R; import android.app.AlertDialog; import android.app.Dialog; -import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; @@ -31,6 +29,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Parcelable; import java.util.List; @@ -80,12 +79,10 @@ public class PlayTrackUtils { public static void playTrack(Context context, long trackId) { AnalyticsUtils.sendPageViews(context, "/action/play"); - Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId); Intent intent = new Intent(context, SaveActivity.class) - .putExtra(SaveActivity.EXTRA_FILE_FORMAT, TrackFileFormat.KML.ordinal()) - .putExtra(SaveActivity.EXTRA_PLAY_FILE, true) - .setAction(context.getString(R.string.track_action_save)) - .setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE); + .putExtra(SaveActivity.EXTRA_TRACK_ID, trackId) + .putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) TrackFileFormat.KML) + .putExtra(SaveActivity.EXTRA_PLAY_TRACK, true); context.startActivity(intent); } diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/MockTrackWriter.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/MockTrackWriter.java index 813d42a52..60e35bf20 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/MockTrackWriter.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/MockTrackWriter.java @@ -19,21 +19,14 @@ import java.io.File; /** * A simple, fake {@link TrackWriter} subclass with all methods mocked out. - * Tests are expected to override {@link #writeTrack} and/or - * {@link #writeTrackAsync}, at the very least. + * Tests are expected to override {@link #writeTrack}. * * @author Matthew Simmons * */ public class MockTrackWriter implements TrackWriter { - public OnCompletionListener onCompletionListener; public OnWriteListener onWriteListener; - @Override - public void setOnCompletionListener(OnCompletionListener onCompletionListener) { - this.onCompletionListener = onCompletionListener; - } - @Override public void setOnWriteListener(OnWriteListener onWriteListener) { this.onWriteListener = onWriteListener; @@ -49,11 +42,6 @@ public class MockTrackWriter implements TrackWriter { throw new UnsupportedOperationException("not implemented"); } - @Override - public void writeTrackAsync() { - throw new UnsupportedOperationException("not implemented"); - } - @Override public void writeTrack() { throw new UnsupportedOperationException("not implemented"); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java index c2026d32b..90684bc04 100644 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java +++ b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/TrackWriterTest.java @@ -169,13 +169,7 @@ public class TrackWriterTest extends AndroidTestCase { writer = new WriteTracksTrackWriter(getContext(), providerUtils, track, formatWriter, true); - // Expect the completion listener to be run - TrackWriter.OnCompletionListener completionListener - = mocksControl.createMock(TrackWriter.OnCompletionListener.class); - completionListener.onComplete(); - mocksControl.replay(); - writer.setOnCompletionListener(completionListener); writer.writeTrack(); assertEquals(1, writeDocumentCalls); @@ -187,13 +181,7 @@ public class TrackWriterTest extends AndroidTestCase { writer = new WriteTracksTrackWriter(getContext(), providerUtils, track, formatWriter, false); - // Expect the completion listener to be run - TrackWriter.OnCompletionListener completionListener - = mocksControl.createMock(TrackWriter.OnCompletionListener.class); - completionListener.onComplete(); - mocksControl.replay(); - writer.setOnCompletionListener(completionListener); writer.writeTrack(); assertEquals(0, writeDocumentCalls); diff --git a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/WriteProgressControllerTest.java b/MyTracksTest/src/com/google/android/apps/mytracks/io/file/WriteProgressControllerTest.java deleted file mode 100644 index c6028c325..000000000 --- a/MyTracksTest/src/com/google/android/apps/mytracks/io/file/WriteProgressControllerTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2011 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.file; - -import android.app.ProgressDialog; -import android.test.ActivityInstrumentationTestCase2; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Tests {@link WriteProgressController}. - * - * @author Matthew Simmons - */ -public class WriteProgressControllerTest extends ActivityInstrumentationTestCase2 { - public WriteProgressControllerTest() { - super(SaveActivity.class); - } - - private static void assertProgress(ProgressDialog dialog, int expectedProgress, - int expectedMax) { - assertEquals(expectedProgress, dialog.getProgress()); - assertEquals(expectedMax, dialog.getMax()); - } - - public void testSimple() throws Exception { - final AtomicReference dialogRef = new AtomicReference(); - final AtomicReference controllerDoneRef = new AtomicReference(); - final Semaphore writerDone = new Semaphore(0); - - TrackWriter mockWriter = new MockTrackWriter() { - @Override - public void writeTrackAsync() { - onWriteListener.onWrite(1000, 10000); - assertProgress(dialogRef.get(), 1000, 10000); - - onCompletionListener.onComplete(); - writerDone.release(); - } - }; - - final WriteProgressController controller = new WriteProgressController( - getActivity(), mockWriter, SaveActivity.PROGRESS_DIALOG); - controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() { - @Override - public void onComplete() { - controllerDoneRef.set(true); - } - }); - - /* - * The WriteProgressController constructor calls the mockWriter's - * setOnCompletionListener method with a listener that dismisses the - * progress dialog. However, this unit test only tests the - * WriteProgressController and doesn't actually show any progress dialog. - * Thus after the WriteProgressController is setup, we need to call the - * mockWriter's setOnCompletionListener method again with an listener that - * doesn't dismiss dialog. - */ - mockWriter.setOnCompletionListener(new TrackWriter.OnCompletionListener() { - @Override - public void onComplete() { - controller.getOnCompletionListener().onComplete(); - } - }); - - dialogRef.set(controller.createProgressDialog()); - - controller.startWrite(); - - // wait for the writer to finish - writerDone.acquire(); - - assertTrue(controllerDoneRef.get()); - } -}