Refactor code.

TrackWriter = handle writing a track to an outputstream
SaveAsyncTask = handle saving to external storage
SaveActivity = handle all UI messages when saving to external storage
This commit is contained in:
Jimmy Shih
2013-01-08 15:27:46 -08:00
parent 986295c343
commit 67819ef7f5
16 changed files with 839 additions and 1097 deletions
+15 -7
View File
@@ -15,13 +15,21 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<plurals name="importFiles">
<item quantity="zero">@string/import_files_zero</item>
<item quantity="one">@string/import_files_one</item>
<item quantity="two">@string/import_files_two</item>
<item quantity="few">@string/import_files_few</item>
<item quantity="many">@string/import_files_many</item>
<item quantity="other">@string/import_files_other</item>
<plurals name="files">
<item quantity="zero">@string/files_zero</item>
<item quantity="one">@string/files_one</item>
<item quantity="two">@string/files_two</item>
<item quantity="few">@string/files_few</item>
<item quantity="many">@string/files_many</item>
<item quantity="other">@string/files_other</item>
</plurals>
<plurals name="tracks">
<item quantity="zero">@string/tracks_zero</item>
<item quantity="one">@string/tracks_one</item>
<item quantity="two">@string/tracks_two</item>
<item quantity="few">@string/tracks_few</item>
<item quantity="many">@string/tracks_many</item>
<item quantity="other">@string/tracks_other</item>
</plurals>
<plurals name="voiceHours">
<item quantity="zero">@string/voice_hours_zero</item>
+210 -148
View File
@@ -16,7 +16,7 @@ limitations under the License.
-->
<resources>
<!-- About Page -->
<!-- About -->
<string name="about_copyright_notice"
translation_description="In the 'About My Tracks' page, the copyright notice.">
Copyright 2009 Google Inc.
@@ -556,7 +556,7 @@ limitations under the License.
Segment
</string>
<!-- EULA -->
<!-- Eula -->
<string name="eula_accept"
translation_description="In the 'Terms of Service' dialog, the button to accept.">
Accept
@@ -599,44 +599,60 @@ limitations under the License.
</string>
<!-- External Storage -->
<string name="external_storage_canceled"
translation_description="When importing/saving to the external storage, the message indicating
that the operation was canceled.">
The operation was canceled.
</string>
<string name="external_storage_not_available"
translation_description="Error message when the external storage is not available.">
External storage not available.
</string>
<string name="external_storage_not_writeable"
translation_description="Error messazge when the external storage is not writeable.">
translation_description="Error message when the external storage is not writeable.">
External storage not writeable.
</string>
<string name="external_storage_save_error"
translation_description="When saving to the external storage, the error message indicating not
able to save.">
Unable to save to external storage.
<!-- Files -->
<string name="files_few"
translation_description="The phrase for 'few' files. This is used when the language requires
special treatment of 'small' numbers. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number. The number is a small number.">
%1$d files
</string>
<string name="external_storage_save_error_create_dir"
translation_description="When saving to the external storage, the error message indicating not
able to create a directory.">
Unable to create a directory on the external storage.
<string name="files_many"
translation_description="The phrase for 'many' files. This is used when the language requires
special treatment of 'large' numbers. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number. The number is a large number.">
%1$d files
</string>
<string name="external_storage_save_error_no_track"
translation_description="When saving to the external storage, the error message indicating that
no track is saved.">
No track is saved to the external storage.
<string name="files_one"
translation_description="The phrase for 1 file. This is used when the language requires special
treatment of numbers like one. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
1 file
</string>
<string name="external_storage_save_progress_message"
translation_description="When saving to the external storage, the progress message.">
Saving to external storage&#8230;
<string name="files_other"
translation_description="The phrase for 'other' files. This is used when the language does not
require special treatment of the given quantity (as with all numbers in Chinese). See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number.">
%1$d files
</string>
<string name="external_storage_save_success"
translation_description="When saving to the external storage, the success message">
Finished saving to external storage.
<string name="files_two"
translation_description="The phrase for 2 files. This is used when the language requires special
treatment of numbers like two. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
2 files
</string>
<string name="files_zero"
translation_description="The phrase for 0 files. This is used when the language requires special
treatment of the number 0. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
0 files
</string>
<!-- Generic Strings -->
<!-- Generic -->
<string name="generic_add"
translation_description="The text on a button to say add.">
Add
@@ -703,7 +719,7 @@ limitations under the License.
Success
</string>
<!-- GPS -->
<!-- Gps -->
<string name="gps_google_location_settings"
translation_description="In Android, the Google location settings to modify location settings
for all Google applications. See Settings -> Google -> Location settings.">
@@ -744,7 +760,7 @@ limitations under the License.
Waiting for GPS signal.
</string>
<!-- Help page -->
<!-- Help -->
<string name="help_about"
translation_description="In the 'Help' page, the button on the bottom to open the 'About My
Tracks' dialog.">
@@ -961,47 +977,14 @@ limitations under the License.
</string>
<!-- Import -->
<string name="import_files_few"
translation_description="When importing files, the phrase for 'few' files. This is used when the
language requires special treatment of 'small' numbers. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
<string name="import_error"
translation_description="When importing from the external storage, the error message indicating
that only some of the files are imported.
%1$d an integer number. The number is a small number.">
%1$d files
</string>
<string name="import_files_many"
translation_description="When importing files, the phrase for 'many' files. This is used when
the language requires special treatment of 'large' numbers. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number. The number is a large number.">
%1$d files
</string>
<string name="import_files_one"
translation_description="When importing files, the phrase for 1 file. This is used when the
language requires special treatment of numbers like one. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
1 file
</string>
<string name="import_files_other"
translation_description="When importing files, the phrase for 'other' files. This is used when
the language does not require special treatment of the given quantity (as with all numbers in
Chinese). See http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number.">
%1$d files
</string>
<string name="import_files_two"
translation_description="When importing files, the phrase for 2 files. This is used when the
language requires special treatment of numbers like two. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
2 files
</string>
<string name="import_files_zero"
translation_description="When importing files, the phrase for 0 files. This is used when the
language requires special treatment of the number 0. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
0 files
%1$d the number of files successfully imported. E.g., '2'.
%2$s the number of files in the directory. E.g., '2 files'.
%3$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
Imported %1$d of %2$s from %3$s.
</string>
<string name="import_no_directory"
translation_description="When importing from the external storage, the error message when the
@@ -1010,13 +993,6 @@ limitations under the License.
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
%1$s does not exist.
</string>
<string name="import_no_file"
translation_description="When importing from the external storage, the message indicating
that no file is found.
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
No file found in %1$s.
</string>
<string name="import_progress_message"
translation_description="When importing from the external storage, the progress message.
@@ -1026,10 +1002,9 @@ limitations under the License.
<string name="import_success"
translation_description="When importing from the external storage, the success message.
%1$d the number of files successfully imported. E.g., '2'.
%2$s the number of files in the directory. E.g., '2 files'.
%3$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
Imported %1$d of %2$s from %3$s.
%1$s the number of files. E.g., '2 files'.
%2$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
Imported %1$s from %2$s.
</string>
<!-- Maps List -->
@@ -1079,27 +1054,6 @@ limitations under the License.
user that the marker will be permanently deleted from the device.">
The marker will be permanently deleted from the device.
</string>
<string name="marker_detail_statistics_title"
translation_description="In the marker detail page, the title for the statistics section
indicating that the data below are since the last split marker.">
Since last split marker:
</string>
<string name="marker_detail_waypoint_marker_type"
translation_description="In the marker detail page, the label for the marker type.
%1$s marker type. E.g., 'building'.">
Marker type: %1$s
</string>
<string name="marker_edit_marker_type"
translation_description="In the marker edit page, the label for the marker type, including an
example.">
Marker type (e.g. waterfall)
</string>
<string name="marker_edit_marker_type_hint"
translation_description="In the marker edit page, the hint in the marker type text field when it
is empty.">
Marker type
</string>
<string name="marker_label_end"
translation_description="When inserting a marker to indicate the end of a track, the marker's
name. E.g., 'track_name (End)'.
@@ -1114,10 +1068,6 @@ limitations under the License.
%1$s track name. E.g., 'track_name'.">
%1$s (Start)
</string>
<string name="marker_list_empty_message"
translation_description="The message when the marker list is empty.">
You don\'t have any markers.
</string>
<string name="marker_name_format"
translation_description="When adding a marker, the string format for the default name.
@@ -1131,7 +1081,38 @@ limitations under the License.
Split %1$d
</string>
<!-- Menu Items -->
<!-- Marker Detail -->
<string name="marker_detail_statistics_title"
translation_description="In the marker detail page, the title for the statistics section
indicating that the data below are since the last split marker.">
Since last split marker:
</string>
<string name="marker_detail_waypoint_marker_type"
translation_description="In the marker detail page, the label for the marker type.
%1$s marker type. E.g., 'building'.">
Marker type: %1$s
</string>
<!-- Marker Edit -->
<string name="marker_edit_marker_type"
translation_description="In the marker edit page, the label for the marker type, including an
example.">
Marker type (e.g. waterfall)
</string>
<string name="marker_edit_marker_type_hint"
translation_description="In the marker edit page, the hint in the marker type text field when it
is empty.">
Marker type
</string>
<!-- Marker List -->
<string name="marker_list_empty_message"
translation_description="The message when the marker list is empty.">
You don\'t have any markers.
</string>
<!-- Menu -->
<string name="menu_aggregated_statistics"
translation_description="In the menu, to go to the aggregated statistics page.
@@ -1373,14 +1354,28 @@ limitations under the License.
write data to Google My Tracks and start/stop Google My Tracks recording
</string>
<!-- Restore -->
<string name="restore_error"
translation_description="When restoring from backup, the error message.">
Unable to restore from backup.
<!-- Save -->
<string name="save_error"
translation_description="When saving to the external storage, the error message indicating that
only some of the tracks are saved.
%1$d the number of tracks successfully saved. E.g., '1'.
%1$s the number of tracks to save. E.g., '2 tracks'.
%2$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
Saved %1$d of %2$s to %3$s.
</string>
<string name="restore_success"
translation_description="When restoring from backup, the success message.">
Finished restoring from backup.
<string name="save_progress_message"
translation_description="When saving to the external storage, the progress message.
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
Saving to %1$s&#8230;
</string>
<string name="save_success"
translation_description="When saving to the external storage, the success message.
%1$s the number of tracks. E.g., '2 tracks'.
%2$s the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
Saved %1$s to %2$s.
</string>
<!-- Search -->
@@ -1586,6 +1581,25 @@ limitations under the License.
Sending
</string>
<!-- Settings -->
<string name="settings_not_while_recording"
translation_description="In settings, the summary for an option that is not available during a
track recording.">
Not available while recording a track.
</string>
<string name="settings_show"
translation_description="In settings, the section label to show additional values.
Character limit: ~26 (1 line)">
Show
</string>
<string name="settings_switch_account"
translation_description="In the 'Settings' page, the option to switch account.
Character limit: ~26 (1 line)">
Switch account
</string>
<!-- Settings Backup -->
<string name="settings_backup"
translation_description="In the 'Settings' page, the option to change the data backup related
@@ -1594,6 +1608,8 @@ limitations under the License.
Character limit: ~26 (1 line)">
Backup
</string>
<!-- Settings Backup Now -->
<string name="settings_backup_now"
translation_description="In the 'Backup' settings, the option to backup to the external storage
now.
@@ -1601,6 +1617,10 @@ limitations under the License.
Character limit: ~26 (1 line)">
Backup now
</string>
<string name="settings_backup_now_error"
translation_description="When backing up to the external storage, the error messag.">
Unable to backup.
</string>
<string name="settings_backup_now_progress_message"
translation_description="In the 'Backup' settings, after pressing the 'Backup now' option, the
progress message telling the user that it is writing all tracks and settings to the external
@@ -1612,6 +1632,12 @@ limitations under the License.
explaining that all tracks and settings will be written to the external storage.">
Write a full copy of all your tracks and settings to the external storage.
</string>
<string name="settings_backup_now_success"
translation_description="When backing up to the external storage, the success message">
Finished backup.
</string>
<!-- Settings Backup Restore -->
<string name="settings_backup_restore"
translation_description="In the 'Backup' settings, the option to restore from the external
storage.
@@ -1626,6 +1652,10 @@ limitations under the License.
Restoring a backup from the external storage will overwrite all your current tracks and
settings.
</string>
<string name="settings_backup_restore_error"
translation_description="When restoring from backup, the error message.">
Unable to restore from backup.
</string>
<string name="settings_backup_restore_no_backup"
translation_description="In the 'Backup' settings, after pressing the 'Restore now' option, the
message in the alert dialog informing the user that there is no backup to restore from.">
@@ -1647,6 +1677,10 @@ limitations under the License.
explaining that it restores all tracks and settings from the external storage.">
Restore all tracks and settings from the external storage.
</string>
<string name="settings_backup_restore_success"
translation_description="When restoring from backup, the success message.">
Finished restoring from backup.
</string>
<!-- Settings Chart -->
<string name="settings_chart_by_distance"
@@ -1731,13 +1765,6 @@ limitations under the License.
The way to color a track based on slow, medium, and fast speed.
</string>
<!-- Settings Not While Recording -->
<string name="settings_not_while_recording"
translation_description="In settings, the summary for an option that is not available during a
track recording.">
Not available while recording a track.
</string>
<!-- Settings Recording -->
<string name="settings_recording"
translation_description="In the 'Settings' page, the option to change the track recording
@@ -2093,14 +2120,6 @@ limitations under the License.
New maps created in Google Maps will be public, instead of unlisted. See %1$s for more info.
</string>
<!-- Settings Show -->
<string name="settings_show"
translation_description="In settings, the section label to show additional values.
Character limit: ~26 (1 line)">
Show
</string>
<!-- Settings Stats -->
<string name="settings_stats_coordinate"
translation_description="In the 'Stats settings, the option to show latitude and longitude.
@@ -2140,14 +2159,6 @@ limitations under the License.
Preferred units
</string>
<!-- Settings Switch Account -->
<string name="settings_switch_account"
translation_description="In the 'Settings' page, the option to switch account.
Character limit: ~26 (1 line)">
Switch account
</string>
<!-- Share Track -->
<string name="share_track_confirm_message"
translation_description="The confirmation message when sharing a track with friends.
@@ -2193,7 +2204,7 @@ limitations under the License.
I would like to share a track with you
</string>
<!-- Stats Page -->
<!-- Stats -->
<string name="stats_average_moving_pace"
translation_description="In the 'Stats' page, the average moving pace.">
Avg moving pace
@@ -2278,6 +2289,19 @@ limitations under the License.
</string>
<!-- Track -->
<string name="track_name_format"
translation_description="When creating a new track, the string format to name the track.
%1$d: track number. E.g., '1'.">
Track %1$d
</string>
<string name="track_record_notification"
translation_description="When recording a track, the message on the My Tracks notification
informing the user that My Tracks is actively recording his track.">
Recording your track&#8230;
</string>
<!-- Track Detail -->
<string name="track_detail_chart_tab"
translation_description="In the track detail page, the title of the tab showing the chart.">
Chart
@@ -2310,6 +2334,8 @@ limitations under the License.
translation_description="In the track detail page, the title of the tab showing the stats.">
Stats
</string>
<!-- Track Edit -->
<string name="track_edit_activity_type"
translation_description="In the page to edit a track, the label for the text field to edit the
activity type, including an example.">
@@ -2324,6 +2350,8 @@ limitations under the License.
translation_description="The title of the track edit page when saving a new track.">
New Track
</string>
<!-- Track List -->
<string name="track_list_delete_all_confirm_message"
translation_description="When deleting all the tracks, the confirmation dialog message informing
the user that all tracks and markers will be permanently deleted from the device.">
@@ -2344,17 +2372,6 @@ limitations under the License.
success.">
Now recording a new track.
</string>
<string name="track_name_format"
translation_description="When creating a new track, the string format to name the track.
%1$d: track number. E.g., '1'.">
Track %1$d
</string>
<string name="track_record_notification"
translation_description="When recording a track, the message on the My Tracks notification
informing the user that My Tracks is actively recording his track.">
Recording your track&#8230;
</string>
<!-- Track Widget -->
<string name="track_widget_config_title"
@@ -2362,6 +2379,50 @@ limitations under the License.
Configure Widget Items
</string>
<!-- Tracks -->
<string name="tracks_few"
translation_description="The phrase for 'few' tracks. This is used when the language requires
special treatment of 'small' numbers. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number. The number is a small number.">
%1$d tracks
</string>
<string name="tracks_many"
translation_description="The phrase for 'many' tracks. This is used when the language requires
special treatment of 'large' numbers. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number. The number is a large number.">
%1$d tracks
</string>
<string name="tracks_one"
translation_description="The phrase for 1 track. This is used when the language requires special
treatment of numbers like one. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
1 track
</string>
<string name="tracks_other"
translation_description="The phrase for 'other' tracks. This is used when the language does not
require special treatment of the given quantity (as with all numbers in Chinese). See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals
%1$d an integer number.">
%1$d tracks
</string>
<string name="tracks_two"
translation_description="The phrase for 2 tracks. This is used when the language requires
special treatment of numbers like two. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
2 tracks
</string>
<string name="tracks_zero"
translation_description="The phrase for 0 tracks. This is used when the language requires
special treatment of the number 0. See
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
0 tracks
</string>
<!-- Unit -->
<string name="unit_feet"
translation_description="Abbreviation for feet. E.g., '2 ft'.">
@@ -2395,6 +2456,7 @@ limitations under the License.
translation_description="Abbreviation for minutes per mile. E.g, '5 min/mi'.">
min/mi
</string>
<!-- Value -->
<string name="value_adapt_accuracy"
translation_description="In a setting picker, the value to adapt for accuracy.">
@@ -56,7 +56,7 @@ public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> {
this.externalFileBackup = new ExternalFileBackup(backupActivity);
success = false;
completed = false;
messageId = R.string.external_storage_save_error;
messageId = R.string.settings_backup_now_error;
}
/**
@@ -86,13 +86,13 @@ public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
if (!externalFileBackup.isBackupsDirectoryAvailable(true)) {
messageId = R.string.external_storage_save_error_create_dir;
messageId = R.string.external_storage_not_writeable;
return false;
}
try {
externalFileBackup.writeToDefaultFile();
messageId = R.string.external_storage_save_success;
messageId = R.string.settings_backup_now_success;
return true;
} catch (IOException e) {
Log.d(TAG, "IO exception", e);
@@ -59,7 +59,7 @@ public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
this.externalFileBackup = new ExternalFileBackup(restoreActivity);
success = false;
completed = false;
messageId = R.string.restore_error;
messageId = R.string.settings_backup_restore_error;
}
/**
@@ -85,7 +85,7 @@ public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected Boolean doInBackground(Void... params) {
try {
externalFileBackup.restoreFromDate(date);
messageId = R.string.restore_success;
messageId = R.string.settings_backup_restore_success;
return true;
} catch (IOException e) {
Log.d(TAG, "IO exception", e);
@@ -61,7 +61,7 @@ public class ImportActivity extends Activity {
// the path on the external storage to import
private String path;
// the number of successfully imported files
// the number of files successfully imported
private int successCount;
// the number of files to import
@@ -137,29 +137,35 @@ public class ImportActivity extends Activity {
@Override
public void onCancel(DialogInterface dialog) {
importAsyncTask.cancel(true);
dialog.dismiss();
finish();
}
}, path);
return progressDialog;
case DIALOG_RESULT_ID:
final boolean success;
String message;
if (totalCount == 0) {
message = getString(R.string.import_no_file, path);
String totalFiles = getResources()
.getQuantityString(R.plurals.files, totalCount, totalCount);
if (successCount == totalCount && totalCount > 0) {
success = true;
message = getString(R.string.import_success, totalFiles, path);
} else {
String totalFiles = getResources()
.getQuantityString(R.plurals.importFiles, totalCount, totalCount);
message = getString(R.string.import_success, successCount, totalFiles, path);
success = false;
message = getString(R.string.import_error, successCount, totalFiles, path);
}
return new AlertDialog.Builder(this).setCancelable(true)
return new AlertDialog.Builder(this).setCancelable(true).setIcon(
success ? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
finish();
}
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (!importAll && trackId != -1L) {
if (success && !importAll && trackId != -1L) {
Intent intent = IntentUtils.newIntent(
ImportActivity.this, TrackDetailActivity.class)
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
@@ -167,9 +173,11 @@ public class ImportActivity extends Activity {
taskStackBuilder.addNextIntent(intent);
taskStackBuilder.startActivities();
}
dialog.dismiss();
finish();
}
}).create();
}).setTitle(success ? R.string.generic_success_title : R.string.generic_error_title)
.create();
default:
return null;
}
@@ -179,7 +187,7 @@ public class ImportActivity extends Activity {
* Invokes when the associated AsyncTask completes.
*
* @param aSuccessCount the number of files successfully imported
* @param aTotalCount the total number of files to import
* @param aTotalCount the number of files to import
* @param aTrackId the last successfully imported track id
*/
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) {
@@ -20,6 +20,7 @@ import com.google.android.apps.mytracks.util.PreferencesUtils;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.maps.mytracks.R;
import android.content.Context;
import android.os.AsyncTask;
import android.os.PowerManager.WakeLock;
import android.util.Log;
@@ -42,6 +43,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private final boolean importAll;
private final TrackFileFormat trackFileFormat;
private final String path;
private final Context context;
private WakeLock wakeLock;
// true if the AsyncTask has completed
@@ -70,7 +72,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
this.importAll = importAll;
this.trackFileFormat = trackFileFormat;
this.path = path;
context = importActivity.getApplicationContext();
// Get the wake lock if not recording or paused
if (PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key)
== PreferencesUtils.RECORDING_TRACK_ID_DEFAULT || PreferencesUtils.getBoolean(
@@ -154,9 +157,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
*/
private boolean importFile(final File file) {
try {
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(
importActivity)
: new GpxImporter(importActivity);
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(context)
: new GpxImporter(context);
long trackIds[] = importer.importFile(new FileInputStream(file));
int length = trackIds.length;
if (length > 0) {
@@ -17,6 +17,7 @@
package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.util.DialogUtils;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.IntentUtils;
import com.google.android.maps.mytracks.R;
@@ -28,12 +29,13 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import java.io.File;
/**
* An activity for saving tracks to the SD card. If saving a specific track,
* option to save it to a temp directory and play the track afterward.
* An activity for saving tracks to the external storage. If saving a specific
* track, option to save it to a temp directory and play the track afterward.
*
* @author Rodrigo Damazio
*/
@@ -57,17 +59,18 @@ public class SaveActivity extends Activity {
private TrackFileFormat trackFileFormat;
private long trackId;
private boolean playTrack;
private String directoryName;
private SaveAsyncTask saveAsyncTask;
private ProgressDialog progressDialog;
// result from the AsyncTask
private boolean success;
// message id from the AsyncTask
private int messageId;
// saved file path from the AsyncTask
// the number of tracks successfully saved
private int successCount;
// the number of tracks to save
private int totalCount;
// the last successfully saved path
private String savedPath;
@Override
@@ -79,12 +82,29 @@ public class SaveActivity extends Activity {
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L);
playTrack = intent.getBooleanExtra(EXTRA_PLAY_TRACK, false);
if (!FileUtils.isExternalStorageWriteable()) {
Toast.makeText(this, R.string.external_storage_not_writeable, Toast.LENGTH_LONG).show();
finish();
return;
}
directoryName = playTrack ? FileUtils.buildExternalDirectoryPath(
trackFileFormat.getExtension(), "tmp")
: FileUtils.buildExternalDirectoryPath(trackFileFormat.getExtension());
File directory = new File(directoryName);
if (!FileUtils.ensureDirectoryExists(directory)) {
Toast.makeText(this, R.string.external_storage_not_writeable, Toast.LENGTH_LONG).show();
finish();
return;
}
Object retained = getLastNonConfigurationInstance();
if (retained instanceof SaveAsyncTask) {
saveAsyncTask = (SaveAsyncTask) retained;
saveAsyncTask.setActivity(this);
} else {
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackId, playTrack);
saveAsyncTask = new SaveAsyncTask(this, trackFileFormat, trackId, directory);
saveAsyncTask.execute();
}
}
@@ -99,37 +119,43 @@ public class SaveActivity extends Activity {
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_PROGRESS_ID:
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
R.string.external_storage_save_progress_message,
new DialogInterface.OnCancelListener() {
progressDialog = DialogUtils.createHorizontalProgressDialog(
this, R.string.save_progress_message, new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
saveAsyncTask.cancel(true);
dialog.dismiss();
finish();
}
});
}, directoryName);
return progressDialog;
case DIALOG_RESULT_ID:
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setCancelable(true)
.setIcon(success
? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
.setMessage(messageId)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
boolean success;
String message;
String totalTracks = getResources()
.getQuantityString(R.plurals.tracks, totalCount, totalCount);
if (successCount == totalCount && totalCount > 0) {
success = true;
message = getString(R.string.save_success, totalTracks, directoryName);
} else {
success = false;
message = getString(R.string.save_error, successCount, totalTracks, directoryName);
}
AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(true).setIcon(
success ? android.R.drawable.ic_dialog_info : android.R.drawable.ic_dialog_alert)
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
finish();
}
})
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
dialog.dismiss();
finish();
}
})
.setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
}).setTitle(success ? R.string.generic_success_title : R.string.generic_error_title);
if (success && trackId != -1L && !playTrack && savedPath != null) {
builder.setNegativeButton(
@@ -152,19 +178,19 @@ public class SaveActivity extends Activity {
/**
* Invokes when the associated AsyncTask completes.
*
* @param isSuccess true if the AsyncTask is successful
* @param aMessageId the id of the AsyncTask message
* @param aSavedPath the path of the saved file
*
* @param aSuccessCount the number of tracks successfully saved
* @param aTotalCount the number of tracks to save
* @param aSavedPath the last successfully saved path
*/
public void onAsyncTaskCompleted(boolean isSuccess, int aMessageId, String aSavedPath) {
this.success = isSuccess;
this.messageId = aMessageId;
this.savedPath = aSavedPath;
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, String aSavedPath) {
successCount = aSuccessCount;
totalCount = aTotalCount;
savedPath = aSavedPath;
removeDialog(DIALOG_PROGRESS_ID);
if (success && playTrack && savedPath != null) {
Intent intent = new Intent()
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
if (successCount == 1 && totalCount == 1 && playTrack && savedPath != null) {
Intent intent = new Intent().addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(GOOGLE_EARTH_TOUR_FEATURE_ID, KmlTrackWriter.TOUR_FEATURE_ID)
.setClassName(GOOGLE_EARTH_PACKAGE, GOOGLE_EARTH_CLASS)
.setDataAndType(Uri.fromFile(new File(savedPath)), GOOGLE_EARTH_KML_MIME_TYPE);
@@ -184,7 +210,7 @@ public class SaveActivity extends Activity {
/**
* Sets the progress dialog value.
*
*
* @param number the number of points saved
* @param max the maximum number of points
*/
@@ -17,6 +17,7 @@
package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.PreferencesUtils;
@@ -30,9 +31,12 @@ import android.os.PowerManager.WakeLock;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
/**
* Async Task to save tracks to the SD card.
* Async Task to save tracks to the external storage.
*
* @author Jimmy Shih
*/
@@ -43,42 +47,41 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
private SaveActivity saveActivity;
private final TrackFileFormat trackFileFormat;
private final long trackId;
private final boolean useTempDir;
private final File directory;
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;
// the number of tracks successfully saved
private int successCount;
// saved file path to return to the activity
// the number of tracks to save
private int totalCount;
// the last successfully saved path
private String savedPath;
/**
* Creates an AsyncTask.
*
* @param saveActivity the activity currently associated with this task.
* @param trackFileFormat the track format
* @param trackId the track id
* @param useTempDir true to use the temp directory
* @param saveActivity the activity currently associated with this task
* @track id the track id to save, -1L to save all tracks
* @param trackFileFormat the track file format
* @param directory the directory to save to
*/
public SaveAsyncTask(SaveActivity saveActivity, TrackFileFormat trackFileFormat, long trackId,
boolean useTempDir) {
public SaveAsyncTask(
SaveActivity saveActivity, TrackFileFormat trackFileFormat, long trackId, File directory) {
this.saveActivity = saveActivity;
this.trackFileFormat = trackFileFormat;
this.trackId = trackId;
this.useTempDir = useTempDir;
this.directory = directory;
context = saveActivity.getApplicationContext();
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(saveActivity);
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
// Get the wake lock if not recording or paused
if (PreferencesUtils.getLong(saveActivity, R.string.recording_track_id_key)
@@ -86,11 +89,10 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
R.string.recording_track_paused_key, PreferencesUtils.RECORDING_TRACK_PAUSED_DEFAULT)) {
wakeLock = SystemUtils.acquireWakeLock(saveActivity, wakeLock);
}
trackWriter = null;
success = false;
completed = false;
messageId = R.string.external_storage_save_error;
successCount = 0;
totalCount = 0;
savedPath = null;
}
@@ -102,7 +104,7 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
public void setActivity(SaveActivity saveActivity) {
this.saveActivity = saveActivity;
if (completed && saveActivity != null) {
saveActivity.onAsyncTaskCompleted(success, messageId, savedPath);
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
}
}
@@ -117,7 +119,13 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
protected Boolean doInBackground(Void... params) {
try {
if (trackId != -1L) {
return saveOneTrack(trackId, true);
totalCount = 1;
if (saveOneTrack(trackId)) {
successCount = 1;
return true;
} else {
return false;
}
} else {
return saveAllTracks();
}
@@ -129,35 +137,79 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
}
@Override
protected void onProgressUpdate(Integer... values) {
if (saveActivity != null) {
saveActivity.setProgressDialogValue(values[0], values[1]);
}
}
@Override
protected void onPostExecute(Boolean result) {
completed = true;
if (saveActivity != null) {
saveActivity.onAsyncTaskCompleted(successCount, totalCount, savedPath);
}
}
@Override
protected void onCancelled() {
if (trackWriter != null) {
trackWriter.stopWriteTrack();
}
}
/**
* Saves one track.
*
* @param id the track id
* @param updateSavingProgress true to update the saving progress
*/
private Boolean saveOneTrack(long id, final boolean updateSavingProgress) {
trackWriter = TrackWriterFactory.newWriter(
context, myTracksProviderUtils, id, trackFileFormat);
if (trackWriter == null) {
Log.e(TAG, "Track writer is null");
private Boolean saveOneTrack(long id) {
Track track = myTracksProviderUtils.getTrack(id);
if (track == null) {
Log.d(TAG, "No track for " + id);
return false;
}
if (useTempDir) {
String dirName = FileUtils.buildExternalDirectoryPath(trackFileFormat.getExtension(), "tmp");
trackWriter.setDirectory(new File(dirName));
// Make sure the file doesn't exist yet (possibly by changing the filename)
String fileName = FileUtils.buildUniqueFileName(
directory, track.getName(), trackFileFormat.getExtension());
if (fileName == null) {
Log.d(TAG, "Unable to get a unique filename for " + track.getName());
return false;
}
trackWriter.setOnWriteListener(new TrackWriter.OnWriteListener() {
@Override
public void onWrite(int number, int max) {
// Update the progress dialog once every 500 points
if (updateSavingProgress && number % 500 == 0) {
publishProgress(number, max);
}
trackWriter = new TrackWriter(
context, myTracksProviderUtils, track, trackFileFormat, new TrackWriter.OnWriteListener() {
@Override
public void onWrite(int number, int max) {
/*
* If only saving one track, update the progress dialog once every
* 500 points
*/
if (trackId != -1L && number % 500 == 0) {
publishProgress(number, max);
}
}
});
File file = null;
try {
file = new File(directory, fileName);
OutputStream outputStream = new FileOutputStream(file);
trackWriter.writeTrack(outputStream);
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found " + fileName, e);
return false;
}
if (trackWriter.wasSuccess()) {
savedPath = file.getAbsolutePath();
} else {
if (!file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
}
});
trackWriter.writeTrack();
messageId = trackWriter.getErrorMessage();
savedPath = trackWriter.getAbsolutePath();
}
return trackWriter.wasSuccess();
}
@@ -169,27 +221,21 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
try {
cursor = myTracksProviderUtils.getTrackCursor(null, null, TracksColumns._ID);
if (cursor == null) {
messageId = R.string.external_storage_save_error_no_track;
return false;
}
int count = cursor.getCount();
if (count == 0) {
messageId = R.string.external_storage_save_error_no_track;
return false;
}
totalCount = cursor.getCount();
int idIndex = cursor.getColumnIndexOrThrow(TracksColumns._ID);
for (int i = 0; i < count; i++) {
for (int i = 0; i < totalCount; i++) {
if (isCancelled()) {
return false;
}
cursor.moveToPosition(i);
long id = cursor.getLong(idIndex);
if (!saveOneTrack(id, false)) {
return false;
}
publishProgress(i + 1, count);
if (saveOneTrack(id)) {
successCount++;
}
publishProgress(i + 1, totalCount);
}
messageId = R.string.external_storage_save_success;
return true;
} finally {
if (cursor != null) {
@@ -197,26 +243,4 @@ public class SaveAsyncTask extends AsyncTask<Void, Integer, Boolean> {
}
}
}
@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, messageId, savedPath);
}
}
@Override
protected void onCancelled() {
if (trackWriter != null) {
trackWriter.stopWriteTrack();
}
}
}
@@ -13,65 +13,267 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import java.io.File;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.OutputStream;
/**
* Implementations of this class export tracks to the SD card. This class is
* intended to be format-neutral - it handles creating the output file and
* reading the track to be exported, but requires an instance of
* {@link TrackFormatWriter} to actually format the data.
*
* Track Writer for writing a track to an {@link OutputStream}.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
public interface TrackWriter {
public class TrackWriter {
/** This listener is used to signal track writes. */
/**
* Listener for when a track location is written.
*/
public interface OnWriteListener {
/**
* This method is invoked whenever a location within a track is written.
* When a track location is written.
*
* @param number the location number
* @param max the maximum number of locations, for calculation of
* completion percentage
* @param max the maximum number of locations, for calculation of completion
* percentage
*/
public void onWrite(int number, int max);
}
/**
* Sets a listener to be invoked for each location writer.
*/
void setOnWriteListener(OnWriteListener onWriteListener);
private final static String TAG = TrackWriter.class.getSimpleName();
private final MyTracksProviderUtils myTracksProviderUtils;
private final Track track;
private final TrackFormatWriter trackFormatWriter;
private final OnWriteListener onWriteListener;
private Thread writeThread;
private boolean success = false;
/**
* Sets a custom directory where the file will be written.
* Constructor.
*
* @param context the context
* @param myTracksProviderUtils the my tracks provider utils
* @param track the track
* @param trackFileFormat the track file format
* @param onWriteListener the on write listener
*/
void setDirectory(File directory);
/**
* Returns the absolute path to the file which was created.
*/
String getAbsolutePath();
/**
* Writes the given track id to the SD card.
* This is blocking.
*/
void writeTrack();
/**
* Stop any in-progress writes
*/
void stopWriteTrack();
public TrackWriter(Context context, MyTracksProviderUtils myTracksProviderUtils, Track track,
TrackFileFormat trackFileFormat, OnWriteListener onWriteListener) {
this(myTracksProviderUtils, track, trackFileFormat.newFormatWriter(context), onWriteListener);
}
@VisibleForTesting
public TrackWriter(MyTracksProviderUtils myTracksProviderUtils, Track track,
TrackFormatWriter trackFormatWriter, OnWriteListener onWriteListener) {
this.myTracksProviderUtils = myTracksProviderUtils;
this.track = track;
this.trackFormatWriter = trackFormatWriter;
this.onWriteListener = onWriteListener;
}
/**
* Returns true if the write completed successfully.
*/
boolean wasSuccess();
public boolean wasSuccess() {
return success;
}
/**
* Returns the error message (if any) generated by a writer failure.
* Writes the given track to the output stream.
*
* @param outputStream the output stream.
*/
int getErrorMessage();
public void writeTrack(final OutputStream outputStream) {
writeThread = new Thread() {
@Override
public void run() {
try {
trackFormatWriter.prepare(track, outputStream);
trackFormatWriter.writeHeader();
writeWaypoints();
writeLocations();
trackFormatWriter.writeFooter();
trackFormatWriter.close();
success = true;
} catch (InterruptedException e) {
success = false;
}
}
};
writeThread.start();
try {
writeThread.join();
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for write to complete", e);
success = false;
}
}
/**
* Stops any in-progress writes.
*/
public void stopWriteTrack() {
if (writeThread != null && writeThread.isAlive()) {
Log.i(TAG, "Attempting to stop track write");
writeThread.interrupt();
try {
writeThread.join();
Log.i(TAG, "Track write stopped");
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted while waiting for writer to stop", e);
success = false;
}
}
}
/**
* Writes the waypoints.
*/
private void writeWaypoints() {
/*
* TODO: Stream through the waypoints in chunks. I am leaving the number of
* waypoints very high which should not be a problem because we don't try to
* load them into objects all at the same time.
*/
Cursor cursor = myTracksProviderUtils.getWaypointCursor(
track.getId(), -1L, Constants.MAX_LOADED_WAYPOINTS_POINTS);
boolean hasWaypoints = false;
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
/*
* Yes, this will skip the first waypoint and that is intentional as
* the first waypoint holds the stats for the track.
*/
while (cursor.moveToNext()) {
if (!hasWaypoints) {
trackFormatWriter.writeBeginWaypoints();
hasWaypoints = true;
}
Waypoint waypoint = myTracksProviderUtils.createWaypoint(cursor);
trackFormatWriter.writeWaypoint(waypoint);
}
}
} finally {
cursor.close();
}
}
if (hasWaypoints) {
trackFormatWriter.writeEndWaypoints();
}
}
/**
* Writes the locations.
*/
private void writeLocations() throws InterruptedException {
boolean wroteTrack = false;
boolean wroteSegment = false;
boolean isLastLocationValid = false;
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator iterator = myTracksProviderUtils.getTrackPointLocationIterator(
track.getId(), -1L, false, locationFactory);
try {
int locationNumber = 0;
while (iterator.hasNext()) {
Location location = iterator.next();
if (Thread.interrupted()) {
throw new InterruptedException();
}
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(location);
boolean isSegmentValid = isLocationValid && isLastLocationValid;
if (!wroteTrack && isSegmentValid) {
// Found the first two consecutive locations that are valid
trackFormatWriter.writeBeginTrack(locationFactory.lastLocation);
wroteTrack = true;
}
if (isSegmentValid) {
if (!wroteSegment) {
// Start a segment
trackFormatWriter.writeOpenSegment();
wroteSegment = true;
// Write the previous location, which we had previously skipped
trackFormatWriter.writeLocation(locationFactory.lastLocation);
}
// Write the current location
trackFormatWriter.writeLocation(location);
if (onWriteListener != null) {
onWriteListener.onWrite(locationNumber, track.getNumberOfPoints());
}
} else {
if (wroteSegment) {
trackFormatWriter.writeCloseSegment();
wroteSegment = false;
}
}
locationFactory.swapLocations();
isLastLocationValid = isLocationValid;
}
if (wroteSegment) {
trackFormatWriter.writeCloseSegment();
wroteSegment = false;
}
if (wroteTrack) {
trackFormatWriter.writeEndTrack(locationFactory.lastLocation);
} else {
// Write an empty track
trackFormatWriter.writeBeginTrack(null);
trackFormatWriter.writeEndTrack(null);
}
} finally {
iterator.close();
}
}
/**
* Track writer location factory. Keeping the last two locations.
*
* @author Jimmy Shih
*/
private class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
Location currentLocation;
Location lastLocation;
@Override
public Location createLocation() {
if (currentLocation == null) {
currentLocation = new MyTracksLocation("");
}
return currentLocation;
}
public void swapLocations() {
Location tempLocation = lastLocation;
lastLocation = currentLocation;
currentLocation = tempLocation;
if (currentLocation != null) {
currentLocation.reset();
}
}
}
}
@@ -1,57 +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.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import android.content.Context;
import android.util.Log;
/**
* A factory to produce track writers for any format.
*
* @author Rodrigo Damazio
*/
public class TrackWriterFactory {
private TrackWriterFactory() {}
/**
* Creates a new track writer to write the track with the given ID.
*
* @param context the context in which the track will be read
* @param providerUtils the data provider utils to read the track with
* @param trackId the ID of the track to be written
* @param format the output format to write in
* @return the new track writer
*/
public static TrackWriter newWriter(
Context context, MyTracksProviderUtils providerUtils, long trackId, TrackFileFormat format) {
Track track = providerUtils.getTrack(trackId);
if (track == null) {
Log.d(TAG, "No track for " + trackId);
return null;
}
TrackFormatWriter writer = format.newFormatWriter(context);
return new TrackWriterImpl(context, providerUtils, track, writer);
}
}
@@ -1,373 +0,0 @@
/*
* Copyright 2008 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.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
/**
* This class exports tracks to the SD card. It is intended to be format-
* neutral -- it handles creating the output file and reading the track to be
* exported, but requires an instance of {@link TrackFormatWriter} to actually
* format the data.
*
* @author Sandor Dornbush
* @author Rodrigo Damazio
*/
class TrackWriterImpl implements TrackWriter {
private final Context context;
private final MyTracksProviderUtils providerUtils;
private final Track track;
private final TrackFormatWriter writer;
private boolean success = false;
private int errorMessage = R.string.external_storage_save_error;
private File directory = null;
private File file = null;
private OnWriteListener onWriteListener;
private Thread writeThread;
TrackWriterImpl(Context context, MyTracksProviderUtils providerUtils,
Track track, TrackFormatWriter writer) {
this.context = context;
this.providerUtils = providerUtils;
this.track = track;
this.writer = writer;
}
@Override
public void setOnWriteListener(OnWriteListener onWriteListener) {
this.onWriteListener = onWriteListener;
}
@Override
public void setDirectory(File directory) {
this.directory = directory;
}
@Override
public String getAbsolutePath() {
return file == null ? null : file.getAbsolutePath();
}
private void writeTrackAsync() {
writeThread = new Thread() {
@Override
public void run() {
doWriteTrack();
}
};
writeThread.start();
}
@Override
public void writeTrack() {
writeTrackAsync();
try {
writeThread.join();
} catch (InterruptedException e) {
Log.e(Constants.TAG, "Interrupted waiting for write to complete", e);
}
}
private void doWriteTrack() {
// Open the input and output
success = false;
errorMessage = R.string.external_storage_save_error;
if (track != null) {
if (openFile()) {
try {
writeDocument();
} catch (InterruptedException e) {
Log.i(Constants.TAG, "The track write was interrupted");
if (file != null && !file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
}
success = false;
errorMessage = R.string.external_storage_canceled;
}
}
}
}
public void stopWriteTrack() {
if (writeThread != null && writeThread.isAlive()) {
Log.i(Constants.TAG, "Attempting to stop track write");
writeThread.interrupt();
try {
writeThread.join();
Log.i(Constants.TAG, "Track write stopped");
} catch (InterruptedException e) {
Log.e(Constants.TAG, "Failed to wait for writer to stop", e);
}
}
}
@Override
public int getErrorMessage() {
return errorMessage;
}
@Override
public boolean wasSuccess() {
return success;
}
/*
* Helper methods:
* ===============
*/
/**
* Runs the given runnable in the UI thread.
*/
protected void runOnUiThread(Runnable runnable) {
if (context instanceof Activity) {
((Activity) context).runOnUiThread(runnable);
}
}
/**
* Opens the file and prepares the format writer for it.
*
* @return true on success, false otherwise (and errorMessage is set)
*/
protected boolean openFile() {
if (!canWriteFile()) {
return false;
}
// Make sure the file doesn't exist yet (possibly by changing the filename)
String fileName = FileUtils.buildUniqueFileName(
directory, track.getName(), writer.getExtension());
if (fileName == null) {
Log.e(Constants.TAG,
"Unable to get a unique filename for " + track.getName());
return false;
}
Log.i(Constants.TAG, "Writing track to: " + fileName);
try {
writer.prepare(track, newOutputStream(fileName));
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "Failed to open output file.", e);
errorMessage = R.string.external_storage_save_error;
return false;
}
return true;
}
/**
* Checks and returns whether we're ready to create the output file.
*/
protected boolean canWriteFile() {
if (!FileUtils.isExternalStorageWriteable()) {
errorMessage = R.string.external_storage_not_writeable;
return false;
}
if (directory == null) {
String dirName =
FileUtils.buildExternalDirectoryPath(writer.getExtension());
directory = newFile(dirName);
}
if (!FileUtils.ensureDirectoryExists(directory)) {
Log.i(Constants.TAG, "Could not create export directory.");
errorMessage = R.string.external_storage_save_error_create_dir;
return false;
}
return true;
}
/**
* Creates a new output stream to write to the given filename.
*
* @throws FileNotFoundException if the file could't be created
*/
protected OutputStream newOutputStream(String fileName)
throws FileNotFoundException {
file = new File(directory, fileName);
return new FileOutputStream(file);
}
/**
* Creates a new file object for the given path.
*/
protected File newFile(String path) {
return new File(path);
}
/**
* Writes the waypoints for the given track.
*
* @param trackId the ID of the track to write waypoints for
*/
private void writeWaypoints(long trackId) {
// TODO: Stream through he waypoints in chunks.
// I am leaving the number of waypoints very high which should not be a
// problem because we don't try to load them into objects all at the
// same time.
Cursor cursor = null;
cursor = providerUtils.getWaypointCursor(trackId, -1L,
Constants.MAX_LOADED_WAYPOINTS_POINTS);
boolean hasWaypoints = false;
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
// Yes, this will skip the 1st way point and that is intentional
// as the 1st points holds the stats for the current/last segment.
while (cursor.moveToNext()) {
if (!hasWaypoints) {
writer.writeBeginWaypoints();
hasWaypoints = true;
}
Waypoint wpt = providerUtils.createWaypoint(cursor);
writer.writeWaypoint(wpt);
}
}
} finally {
cursor.close();
}
}
if (hasWaypoints) {
writer.writeEndWaypoints();
}
}
/**
* Does the actual work of writing the track to the now open file.
*/
void writeDocument() throws InterruptedException {
Log.d(Constants.TAG, "Started writing track.");
writer.writeHeader();
writeWaypoints(track.getId());
writeLocations();
writer.writeFooter();
writer.close();
success = true;
Log.d(Constants.TAG, "Done writing track.");
errorMessage = R.string.external_storage_save_success;
}
private void writeLocations() throws InterruptedException {
boolean wroteFirst = false;
boolean segmentOpen = false;
boolean isLastValid = false;
class TrackWriterLocationFactory implements MyTracksProviderUtils.LocationFactory {
Location currentLocation;
Location lastLocation;
@Override
public Location createLocation() {
if (currentLocation == null) {
currentLocation = new MyTracksLocation("");
}
return currentLocation;
}
public void swapLocations() {
Location tmpLoc = lastLocation;
lastLocation = currentLocation;
currentLocation = tmpLoc;
if (currentLocation != null) {
currentLocation.reset();
}
}
};
TrackWriterLocationFactory locationFactory = new TrackWriterLocationFactory();
LocationIterator it = providerUtils.getTrackPointLocationIterator(track.getId(), -1L, false,
locationFactory);
try {
int pointNumber = 0;
while (it.hasNext()) {
Location location = it.next();
if (Thread.interrupted()) {
throw new InterruptedException();
}
pointNumber++;
boolean isValid = LocationUtils.isValidLocation(location);
boolean validSegment = isValid && isLastValid;
if (!wroteFirst && validSegment) {
// Found the first two consecutive points which are valid
writer.writeBeginTrack(locationFactory.lastLocation);
wroteFirst = true;
}
if (validSegment) {
if (!segmentOpen) {
// Start a segment for this point
writer.writeOpenSegment();
segmentOpen = true;
// Write the previous point, which we had previously skipped
writer.writeLocation(locationFactory.lastLocation);
}
// Write the current point
writer.writeLocation(location);
if (onWriteListener != null) {
onWriteListener.onWrite(pointNumber, track.getNumberOfPoints());
}
} else {
if (segmentOpen) {
writer.writeCloseSegment();
segmentOpen = false;
}
}
locationFactory.swapLocations();
isLastValid = isValid;
}
if (segmentOpen) {
writer.writeCloseSegment();
segmentOpen = false;
}
if (wroteFirst) {
writer.writeEndTrack(locationFactory.lastLocation);
} else {
// Writes an empty track
writer.writeBeginTrack(null);
writer.writeEndTrack(null);
}
} finally {
it.close();
}
}
}
@@ -1,64 +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 java.io.File;
/**
* A simple, fake {@link TrackWriter} subclass with all methods mocked out.
* Tests are expected to override {@link #writeTrack}.
*
* @author Matthew Simmons
*
*/
public class MockTrackWriter implements TrackWriter {
public OnWriteListener onWriteListener;
@Override
public void setOnWriteListener(OnWriteListener onWriteListener) {
this.onWriteListener = onWriteListener;
}
@Override
public void setDirectory(File directory) {
throw new UnsupportedOperationException("not implemented");
}
@Override
public String getAbsolutePath() {
throw new UnsupportedOperationException("not implemented");
}
@Override
public void writeTrack() {
throw new UnsupportedOperationException("not implemented");
}
@Override
public void stopWriteTrack() {
throw new UnsupportedOperationException("not implemented");
}
@Override
public boolean wasSuccess() {
return false;
}
@Override
public int getErrorMessage() {
return 0;
}
}
@@ -2,13 +2,12 @@
package com.google.android.apps.mytracks.io.file;
import static org.easymock.EasyMock.expect;
import com.google.android.apps.mytracks.content.MyTracksProvider;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.TrackWriter.OnWriteListener;
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
@@ -19,8 +18,6 @@ import android.test.RenamingDelegatingContext;
import android.test.mock.MockContentResolver;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import org.easymock.EasyMock;
@@ -28,112 +25,24 @@ import org.easymock.IArgumentMatcher;
import org.easymock.IMocksControl;
/**
* Tests for the track writer.
*
* Tests for {@link TrackWriter}.
*
* @author Rodrigo Damazio
*/
public class TrackWriterTest extends AndroidTestCase {
/**
* {@link TrackWriterImpl} subclass which mocks out methods called from
* {@link TrackWriterImpl#openFile}.
*/
private static final class OpenFileTrackWriter extends TrackWriterImpl {
private final ByteArrayOutputStream stream;
private final boolean canWrite;
/**
* Constructor.
*
* @param stream the stream to return from
* {@link TrackWriterImpl#newOutputStream}, or null to throw a
* {@link FileNotFoundException}
* @param canWrite the value that {@link TrackWriterImpl#canWriteFile} will
* return
*/
private OpenFileTrackWriter(Context context,
MyTracksProviderUtils providerUtils, Track track,
TrackFormatWriter writer, ByteArrayOutputStream stream,
boolean canWrite) {
super(context, providerUtils, track, writer);
this.stream = stream;
this.canWrite = canWrite;
// The directory is set in the canWriteFile. However, this class
// overwrites canWriteFile, thus needs to set it.
setDirectory(new File("/"));
}
@Override
protected boolean canWriteFile() {
return canWrite;
}
@Override
protected OutputStream newOutputStream(String fileName)
throws FileNotFoundException {
assertEquals(FULL_TRACK_NAME, fileName);
if (stream == null) {
throw new FileNotFoundException();
}
return stream;
}
}
/**
* {@link TrackWriterImpl} subclass which mocks out methods called from
* {@link TrackWriterImpl#writeTrack}.
*/
private final class WriteTracksTrackWriter extends TrackWriterImpl {
private final boolean openResult;
/**
* Constructor.
*
* @param openResult the return value for {@link TrackWriterImpl#openFile}
*/
private WriteTracksTrackWriter(Context context,
MyTracksProviderUtils providerUtils, Track track,
TrackFormatWriter writer, boolean openResult) {
super(context, providerUtils, track, writer);
this.openResult = openResult;
}
@Override
protected boolean openFile() {
openFileCalls++;
return openResult;
}
@Override
void writeDocument() {
writeDocumentCalls++;
}
@Override
protected void runOnUiThread(Runnable runnable) {
runnable.run();
}
}
private static final long TRACK_ID = 1234567L;
private static final String EXTENSION = "ext";
private static final String TRACK_NAME = "Swimming across the pacific";
private static final String FULL_TRACK_NAME =
"Swimming across the pacific.ext";
private Track track;
private TrackFormatWriter formatWriter;
private TrackWriterImpl writer;
private IMocksControl mocksControl;
private MyTracksProviderUtils providerUtils;
private MyTracksProviderUtils myTracksProviderUtils;
private Factory oldProviderUtilsFactory;
// State used in specific tests
private int writeDocumentCalls;
private int openFileCalls;
private IMocksControl mocksControl;
private TrackFormatWriter trackFormatWriter;
private Track track;
private OutputStream outputStream;
private TrackWriter trackWriter;
@Override
protected void setUp() throws Exception {
@@ -143,20 +52,31 @@ public class TrackWriterTest extends AndroidTestCase {
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
getContext(), getContext(), "test.");
Context context = new MockContext(mockContentResolver, targetContext);
MyTracksProvider provider = new MyTracksProvider();
provider.attachInfo(context, null);
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
MyTracksProvider myTracksProvider = new MyTracksProvider();
myTracksProvider.attachInfo(context, null);
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, myTracksProvider);
setContext(context);
providerUtils = MyTracksProviderUtils.Factory.get(context);
oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(providerUtils);
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(context);
oldProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(
myTracksProviderUtils);
mocksControl = EasyMock.createStrictControl();
formatWriter = mocksControl.createMock(TrackFormatWriter.class);
expect(formatWriter.getExtension()).andStubReturn(EXTENSION);
trackFormatWriter = mocksControl.createMock(TrackFormatWriter.class);
track = new Track();
track.setName(TRACK_NAME);
track.setId(TRACK_ID);
outputStream = new ByteArrayOutputStream();
OnWriteListener onWriteListener = new OnWriteListener() {
@Override
public void onWrite(int number, int max) {
// Safe to ignore
}
};
trackWriter = new TrackWriter(myTracksProviderUtils, track, trackFormatWriter, onWriteListener);
}
@Override
@@ -165,226 +85,196 @@ public class TrackWriterTest extends AndroidTestCase {
super.tearDown();
}
public void testWriteTrack() {
writer = new WriteTracksTrackWriter(getContext(), providerUtils, track,
formatWriter, true);
mocksControl.replay();
writer.writeTrack();
assertEquals(1, writeDocumentCalls);
assertEquals(1, openFileCalls);
mocksControl.verify();
}
public void testWriteTrack_openFails() {
writer = new WriteTracksTrackWriter(getContext(), providerUtils, track,
formatWriter, false);
mocksControl.replay();
writer.writeTrack();
assertEquals(0, writeDocumentCalls);
assertEquals(1, openFileCalls);
mocksControl.verify();
}
public void testOpenFile() {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
writer = new OpenFileTrackWriter(
getContext(), providerUtils, track, formatWriter, stream, true);
formatWriter.prepare(track, stream);
mocksControl.replay();
assertTrue(writer.openFile());
mocksControl.verify();
}
public void testOpenFile_cantWrite() {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
writer = new OpenFileTrackWriter(
getContext(), providerUtils, track, formatWriter, stream, false);
mocksControl.replay();
assertFalse(writer.openFile());
mocksControl.verify();
}
public void testOpenFile_streamError() {
writer = new OpenFileTrackWriter(
getContext(), providerUtils, track, formatWriter, null, true);
mocksControl.replay();
assertFalse(writer.openFile());
mocksControl.verify();
}
public void testWriteDocument_emptyTrack() throws Exception {
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
/**
* Tests write track with an empty track.
*/
public void testWriteTrack_emptyTrack() throws Exception {
// Set expected mock behavior
formatWriter.writeHeader();
formatWriter.writeBeginTrack(null);
formatWriter.writeEndTrack(null);
formatWriter.writeFooter();
formatWriter.close();
trackFormatWriter.prepare(track, outputStream);
trackFormatWriter.writeHeader();
trackFormatWriter.writeBeginTrack(null);
trackFormatWriter.writeEndTrack(null);
trackFormatWriter.writeFooter();
trackFormatWriter.close();
mocksControl.replay();
writer.writeDocument();
trackWriter.writeTrack(outputStream);
assertTrue(writer.wasSuccess());
assertTrue(trackWriter.wasSuccess());
mocksControl.verify();
}
/**
* Tests when a track only contains invalid locations. Make sure an empty
* track is written.
* Tests write track with invalid locations. Make sure an empty track is
* written.
*/
public void testWriteDocument_oneInvalidLocation() throws Exception {
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
public void testWriteTrack_oneInvalidLocation() throws Exception {
Location[] locs = { new Location("fake0") };
fillLocations(locs);
// Add two locations
Location[] locations = { new Location("fake0"), new Location("fake1") };
fillLocations(locations);
// Make location invalid
locs[0].setLatitude(100);
// Make locations invalid
locations[0].setLatitude(100.0);
locations[1].setLatitude(100.0);
assertEquals(locs.length, providerUtils.bulkInsertTrackPoint(locs, locs.length, TRACK_ID));
assertEquals(locations.length,
myTracksProviderUtils.bulkInsertTrackPoint(locations, locations.length, TRACK_ID));
formatWriter.writeHeader();
formatWriter.writeBeginTrack(null);
formatWriter.writeEndTrack(null);
formatWriter.writeFooter();
formatWriter.close();
// Set expected mock behavior
trackFormatWriter.prepare(track, outputStream);
trackFormatWriter.writeHeader();
trackFormatWriter.writeBeginTrack(null);
trackFormatWriter.writeEndTrack(null);
trackFormatWriter.writeFooter();
trackFormatWriter.close();
mocksControl.replay();
writer.writeDocument();
trackWriter.writeTrack(outputStream);
assertTrue(writer.wasSuccess());
assertTrue(trackWriter.wasSuccess());
mocksControl.verify();
}
public void testWriteDocument() throws Exception {
writer = new TrackWriterImpl(getContext(), providerUtils, track, formatWriter);
/**
* Tests write track.
*/
public void testWriteTrack() throws Exception {
final Location[] locs = {
new Location("fake0"),
new Location("fake1"),
new Location("fake2"),
new Location("fake3"),
new Location("fake4"),
new Location("fake5")
};
Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() };
// Fill locations with valid values
fillLocations(locs);
// Add six locations
Location[] locations = { new Location("fake0"), new Location("fake1"), new Location("fake2"),
new Location("fake3"), new Location("fake4"), new Location("fake5") };
fillLocations(locations);
// Make location 3 invalid
locs[2].setLatitude(100);
locations[2].setLatitude(100.0);
assertEquals(locs.length, providerUtils.bulkInsertTrackPoint(locs, locs.length, TRACK_ID));
for (int i = 0; i < wps.length; ++i) {
Waypoint wpt = wps[i];
wpt.setTrackId(TRACK_ID);
assertNotNull(providerUtils.insertWaypoint(wpt));
wpt.setId(i + 1);
assertEquals(locations.length,
myTracksProviderUtils.bulkInsertTrackPoint(locations, locations.length, TRACK_ID));
Waypoint[] waypoints = { new Waypoint(), new Waypoint(), new Waypoint() };
for (int i = 0; i < waypoints.length; i++) {
Waypoint waypoint = waypoints[i];
waypoint.setTrackId(TRACK_ID);
assertNotNull(myTracksProviderUtils.insertWaypoint(waypoint));
waypoint.setId(i + 1);
}
formatWriter.writeHeader();
trackFormatWriter.prepare(track, outputStream);
trackFormatWriter.writeHeader();
// Expect reading/writing of the waypoints (except the first)
formatWriter.writeBeginWaypoints();
formatWriter.writeWaypoint(wptEq(wps[1]));
formatWriter.writeWaypoint(wptEq(wps[2]));
formatWriter.writeEndWaypoints();
trackFormatWriter.writeBeginWaypoints();
trackFormatWriter.writeWaypoint(waypointEq(waypoints[1]));
trackFormatWriter.writeWaypoint(waypointEq(waypoints[2]));
trackFormatWriter.writeEndWaypoints();
// Begin the track
formatWriter.writeBeginTrack(locEq(locs[0]));
trackFormatWriter.writeBeginTrack(locationEq(locations[0]));
// Write locations 1-2
formatWriter.writeOpenSegment();
formatWriter.writeLocation(locEq(locs[0]));
formatWriter.writeLocation(locEq(locs[1]));
formatWriter.writeCloseSegment();
trackFormatWriter.writeOpenSegment();
trackFormatWriter.writeLocation(locationEq(locations[0]));
trackFormatWriter.writeLocation(locationEq(locations[1]));
trackFormatWriter.writeCloseSegment();
// Location 3 is not written - it's invalid
// Write locations 4-6
formatWriter.writeOpenSegment();
formatWriter.writeLocation(locEq(locs[3]));
formatWriter.writeLocation(locEq(locs[4]));
formatWriter.writeLocation(locEq(locs[5]));
formatWriter.writeCloseSegment();
trackFormatWriter.writeOpenSegment();
trackFormatWriter.writeLocation(locationEq(locations[3]));
trackFormatWriter.writeLocation(locationEq(locations[4]));
trackFormatWriter.writeLocation(locationEq(locations[5]));
trackFormatWriter.writeCloseSegment();
// End the track
formatWriter.writeEndTrack(locEq(locs[5]));
trackFormatWriter.writeEndTrack(locationEq(locations[5]));
formatWriter.writeFooter();
formatWriter.close();
trackFormatWriter.writeFooter();
trackFormatWriter.close();
mocksControl.replay();
writer.writeDocument();
trackWriter.writeTrack(outputStream);
assertTrue(writer.wasSuccess());
assertTrue(trackWriter.wasSuccess());
mocksControl.verify();
}
private static Waypoint wptEq(final Waypoint wpt) {
/**
* Waypoint equals.
*
* @param waypoint the waypoint
*/
private Waypoint waypointEq(final Waypoint waypoint) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object wptObj2) {
if (wptObj2 == null || wpt == null) return wpt == wptObj2;
Waypoint wpt2 = (Waypoint) wptObj2;
@Override
public boolean matches(Object object) {
if (object == null || waypoint == null) {
return waypoint == object;
}
Waypoint waypoint2 = (Waypoint) object;
return wpt.getId() == wpt2.getId();
return waypoint.getId() == waypoint2.getId();
}
@Override
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("wptEq(");
buffer.append(wpt);
buffer.append("waypointEq(");
buffer.append(waypoint);
buffer.append(")");
}
});
return null;
}
private static Location locEq(final Location loc) {
/**
* Location equals.
*
* @param location the location
*/
private Location locationEq(final Location location) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object locObj2) {
if (locObj2 == null || loc == null) return loc == locObj2;
Location loc2 = (Location) locObj2;
@Override
public boolean matches(Object object) {
if (object == null || location == null) {
return location == object;
}
Location location2 = (Location) object;
return loc.hasAccuracy() == loc2.hasAccuracy()
&& (!loc.hasAccuracy() || loc.getAccuracy() == loc2.getAccuracy())
&& loc.hasAltitude() == loc2.hasAltitude()
&& (!loc.hasAltitude() || loc.getAltitude() == loc2.getAltitude())
&& loc.hasBearing() == loc2.hasBearing()
&& (!loc.hasBearing() || loc.getBearing() == loc2.getBearing())
&& loc.hasSpeed() == loc2.hasSpeed()
&& (!loc.hasSpeed() || loc.getSpeed() == loc2.getSpeed())
&& loc.getLatitude() == loc2.getLatitude()
&& loc.getLongitude() == loc2.getLongitude()
&& loc.getTime() == loc2.getTime();
return location.hasAccuracy() == location2.hasAccuracy()
&& (!location.hasAccuracy() || location.getAccuracy() == location2.getAccuracy())
&& location.hasAltitude() == location2.hasAltitude()
&& (!location.hasAltitude() || location.getAltitude() == location2.getAltitude())
&& location.hasBearing() == location2.hasBearing()
&& (!location.hasBearing() || location.getBearing() == location2.getBearing())
&& location.hasSpeed() == location2.hasSpeed()
&& (!location.hasSpeed() || location.getSpeed() == location2.getSpeed())
&& location.getLatitude() == location2.getLatitude()
&& location.getLongitude() == location2.getLongitude()
&& location.getTime() == location2.getTime();
}
@Override
@Override
public void appendTo(StringBuffer buffer) {
buffer.append("locEq(");
buffer.append(loc);
buffer.append("locationEq(");
buffer.append(location);
buffer.append(")");
}
});
return null;
}
private void fillLocations(Location... locs) {
assertTrue(locs.length < 90);
for (int i = 0; i < locs.length; i++) {
Location location = locs[i];
/**
* Fills the locations.
*
* @param locations the locations
*/
private void fillLocations(Location... locations) {
assertTrue(locations.length < 90);
for (int i = 0; i < locations.length; i++) {
Location location = locations[i];
location.setLatitude(i + 1);
location.setLongitude(i + 1);
location.setTime(i + 1000);
@@ -16,6 +16,7 @@
package com.google.android.apps.mytracks.endtoendtest;
import com.google.android.apps.mytracks.TrackListActivity;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.maps.mytracks.R;
import android.app.Instrumentation;
@@ -73,8 +74,10 @@ public class DeleteTest extends ActivityInstrumentationTestCase2<TrackListActivi
EndToEndTestUtils.SOLO
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
EndToEndTestUtils.GPX.toUpperCase()));
assertTrue(EndToEndTestUtils.SOLO.waitForText(activityMyTracks
.getString(R.string.external_storage_save_error_no_track)));
String totalTracks = activityMyTracks.getResources().getQuantityString(R.plurals.tracks, 0, 0);
String directoryName = FileUtils.buildExternalDirectoryPath(EndToEndTestUtils.GPX);
String message = activityMyTracks.getString(R.string.save_error, 0, totalTracks, directoryName);
assertTrue(EndToEndTestUtils.SOLO.waitForText(message));
EndToEndTestUtils
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
}
@@ -87,16 +87,14 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
EndToEndTestUtils.SOLO
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
EndToEndTestUtils.GPX.toUpperCase()));
EndToEndTestUtils.SOLO.waitForText(activityMyTracks
.getString(R.string.external_storage_save_success));
EndToEndTestUtils.SOLO.waitForText(getSaveSuccessMessage(1, EndToEndTestUtils.GPX));
EndToEndTestUtils
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
EndToEndTestUtils.deleteAllTracks();
// Import this tracks.
EndToEndTestUtils.findMenuItem(activityMyTracks.getString(R.string.menu_import), true);
EndToEndTestUtils.SOLO.waitForText(activityMyTracks.getString(
R.string.external_storage_import_success_count).split("%")[0]);
EndToEndTestUtils.SOLO.waitForText(getImportSuccessMessage(1, EndToEndTestUtils.GPX));
// Check import tracks should be equal with the sum of trackNumber and
// gpxFilesNumber;
EndToEndTestUtils
@@ -171,9 +169,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
// No file to imported.
EndToEndTestUtils.findMenuItem(activityMyTracks.getString(R.string.menu_import), true);
EndToEndTestUtils.SOLO.waitForText(activityMyTracks.getString(
R.string.import_no_file,
FileUtils.buildExternalDirectoryPath(EndToEndTestUtils.GPX)));
EndToEndTestUtils.SOLO.waitForText(getImportErrorMessage(0, 0, EndToEndTestUtils.GPX));
EndToEndTestUtils
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
@@ -182,8 +178,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
EndToEndTestUtils.SOLO
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
EndToEndTestUtils.GPX.toUpperCase()));
EndToEndTestUtils.SOLO.waitForText(activityMyTracks
.getString(R.string.external_storage_save_success));
EndToEndTestUtils.SOLO.waitForText(getSaveSuccessMessage(1, EndToEndTestUtils.GPX));
EndToEndTestUtils
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
// Check export file.
@@ -217,8 +212,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
EndToEndTestUtils.SOLO
.clickOnText(String.format(activityMyTracks.getString(R.string.menu_save_format),
EndToEndTestUtils.KML.toUpperCase()));
EndToEndTestUtils.SOLO.waitForText(activityMyTracks
.getString(R.string.external_storage_save_success));
EndToEndTestUtils.SOLO.waitForText(getSaveSuccessMessage(2, EndToEndTestUtils.KM));
EndToEndTestUtils
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
// Check export files.
@@ -231,5 +225,22 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
EndToEndTestUtils.SOLO.finishOpenedActivities();
super.tearDown();
}
private String getSaveSuccessMessage(int count, String type) {
String tracks = activityMyTracks.getResources().getQuantityString(R.plurals.tracks, count, count);
String directoryName = FileUtils.buildExternalDirectoryPath(type);
return activityMyTracks.getString(R.string.save_success, tracks, directoryName);
}
private String getImportSuccessMessage(int count, String type) {
String files = activityMyTracks.getResources().getQuantityString(R.plurals.files, count, count);
String directoryName = FileUtils.buildExternalDirectoryPath(type);
return activityMyTracks.getString(R.string.import_success, files, directoryName);
}
private String getImportErrorMessage(int count, int total, String type) {
String files = activityMyTracks.getResources().getQuantityString(R.plurals.files, total, total);
String directoryName = FileUtils.buildExternalDirectoryPath(type);
return activityMyTracks.getString(R.string.import_error, count, files, directoryName);
}
}
@@ -246,7 +246,7 @@ public class SettingTest extends ActivityInstrumentationTestCase2<TrackListActiv
EndToEndTestUtils.SOLO.clickOnText(activityMyTracks.getString(R.string.settings_backup));
EndToEndTestUtils.SOLO.clickOnText(activityMyTracks.getString(R.string.settings_backup_now));
assertTrue(EndToEndTestUtils.SOLO.waitForText(
activityMyTracks.getString(R.string.external_storage_save_success), 0,
activityMyTracks.getString(R.string.settings_backup_now_success), 0,
EndToEndTestUtils.SUPER_LONG_WAIT_TIME));
instrumentation.waitForIdleSync();
@@ -264,7 +264,7 @@ public class SettingTest extends ActivityInstrumentationTestCase2<TrackListActiv
EndToEndTestUtils
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
assertTrue(EndToEndTestUtils.SOLO.waitForText(
activityMyTracks.getString(R.string.restore_success), 0,
activityMyTracks.getString(R.string.settings_backup_restore_success), 0,
EndToEndTestUtils.SUPER_LONG_WAIT_TIME));
// Check restore track.
assertTrue(EndToEndTestUtils.SOLO.searchText(EndToEndTestUtils.trackName));