forked from upstream-mirrors/OpenTracks
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:
@@ -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
@@ -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…
|
||||
<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…
|
||||
</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…
|
||||
</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…
|
||||
</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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user