forked from upstream-mirrors/OpenTracks
Add KmlImporter.
This commit is contained in:
@@ -45,7 +45,8 @@ limitations under the License.
|
|||||||
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
||||||
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
<!-- Permission to read phone state so voice announcement will not interrupt ongoing calls -->
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<!-- Permission to write to SD card -->
|
<!-- Permission to read/write external storage -->
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<!-- Permissions to access My Tracks data -->
|
<!-- Permissions to access My Tracks data -->
|
||||||
<uses-permission android:name="com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS" />
|
<uses-permission android:name="com.google.android.apps.mytracks.MYTRACKS_NOTIFICATIONS" />
|
||||||
|
|||||||
@@ -28,17 +28,34 @@ limitations under the License.
|
|||||||
android:title="@string/menu_start_gps"/>
|
android:title="@string/menu_start_gps"/>
|
||||||
<item
|
<item
|
||||||
android:icon="@drawable/menu_import"
|
android:icon="@drawable/menu_import"
|
||||||
android:id="@+id/track_list_import"
|
android:id="@+id/track_list_import_all"
|
||||||
android:title="@string/menu_import"/>
|
android:title="@string/menu_import_all">
|
||||||
|
<menu>
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_list_import_all_gpx"
|
||||||
|
android:title=""/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_list_import_all_kml"
|
||||||
|
android:title=""/>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
<item
|
<item
|
||||||
android:icon="@drawable/menu_save"
|
android:icon="@drawable/menu_save"
|
||||||
android:id="@+id/track_list_save_all"
|
android:id="@+id/track_list_save_all"
|
||||||
android:title="@string/menu_save_all">
|
android:title="@string/menu_save_all">
|
||||||
<menu>
|
<menu>
|
||||||
<item android:id="@+id/track_list_save_all_gpx"/>
|
<item
|
||||||
<item android:id="@+id/track_list_save_all_kml"/>
|
android:id="@+id/track_list_save_all_gpx"
|
||||||
<item android:id="@+id/track_list_save_all_csv"/>
|
android:title=""/>
|
||||||
<item android:id="@+id/track_list_save_all_tcx"/>
|
<item
|
||||||
|
android:id="@+id/track_list_save_all_kml"
|
||||||
|
android:title=""/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_list_save_all_csv"
|
||||||
|
android:title=""/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/track_list_save_all_tcx"
|
||||||
|
android:title=""/>
|
||||||
</menu>
|
</menu>
|
||||||
</item>
|
</item>
|
||||||
<item
|
<item
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<plurals name="importGpxFiles">
|
<plurals name="importFiles">
|
||||||
<item quantity="zero">@string/import_gpx_files_zero</item>
|
<item quantity="zero">@string/import_files_zero</item>
|
||||||
<item quantity="one">@string/import_gpx_files_one</item>
|
<item quantity="one">@string/import_files_one</item>
|
||||||
<item quantity="two">@string/import_gpx_files_two</item>
|
<item quantity="two">@string/import_files_two</item>
|
||||||
<item quantity="few">@string/import_gpx_files_few</item>
|
<item quantity="few">@string/import_files_few</item>
|
||||||
<item quantity="many">@string/import_gpx_files_many</item>
|
<item quantity="many">@string/import_files_many</item>
|
||||||
<item quantity="other">@string/import_gpx_files_other</item>
|
<item quantity="other">@string/import_files_other</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="voiceHours">
|
<plurals name="voiceHours">
|
||||||
<item quantity="zero">@string/voice_hours_zero</item>
|
<item quantity="zero">@string/voice_hours_zero</item>
|
||||||
|
|||||||
@@ -604,39 +604,13 @@ limitations under the License.
|
|||||||
that the operation was canceled.">
|
that the operation was canceled.">
|
||||||
The operation was canceled.
|
The operation was canceled.
|
||||||
</string>
|
</string>
|
||||||
<string name="external_storage_error_no_storage"
|
<string name="external_storage_not_available"
|
||||||
translation_description="When importing/saving to the external storage, the error message
|
translation_description="Error message when the external storage is not available.">
|
||||||
indicating that no external storage is found.">
|
External storage not available.
|
||||||
No external storage is found.
|
|
||||||
</string>
|
</string>
|
||||||
<string name="external_storage_import_error"
|
<string name="external_storage_not_writeable"
|
||||||
translation_description="When importing from the external storage, the error message indicating
|
translation_description="Error messazge when the external storage is not writeable.">
|
||||||
not able to import.">
|
External storage not writeable.
|
||||||
Unable to import from external storage.
|
|
||||||
</string>
|
|
||||||
<string name="external_storage_import_error_no_file"
|
|
||||||
translation_description="When importing from the external storage, the error message indicating
|
|
||||||
that no file is found on an external storage directory.
|
|
||||||
|
|
||||||
%1$s: the external storage directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
|
||||||
No file found in %1$s.
|
|
||||||
</string>
|
|
||||||
<string name="external_storage_import_progress_message"
|
|
||||||
translation_description="When importing from the external storage, the progress message.">
|
|
||||||
Importing from external storage…
|
|
||||||
</string>
|
|
||||||
<string name="external_storage_import_success"
|
|
||||||
translation_description="When importing from the external storage, the success message.">
|
|
||||||
Finished importing from external storage.
|
|
||||||
</string>
|
|
||||||
<string name="external_storage_import_success_count"
|
|
||||||
translation_description="When importing from the external storage, the success message with a
|
|
||||||
count of the imported files.
|
|
||||||
|
|
||||||
%1$d the number of successfully imported tracks. E.g., '1'.
|
|
||||||
%2$s the number of files in the external storage directory. E.g., '2 GPX files'.
|
|
||||||
%3$s the external storage directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
|
||||||
Imported %1$d of %2$s from %3$s.
|
|
||||||
</string>
|
</string>
|
||||||
<string name="external_storage_save_error"
|
<string name="external_storage_save_error"
|
||||||
translation_description="When saving to the external storage, the error message indicating not
|
translation_description="When saving to the external storage, the error message indicating not
|
||||||
@@ -986,37 +960,76 @@ limitations under the License.
|
|||||||
Track
|
Track
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Import GPX Files -->
|
<!-- Import -->
|
||||||
<string name="import_gpx_files_few"
|
<string name="import_files_few"
|
||||||
translation_description="When importing all the tracks, phrase for 'few' GPX files.
|
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
|
||||||
|
|
||||||
%1$d an integer number. The number is a small number.">
|
%1$d an integer number. The number is a small number.">
|
||||||
%1$d GPX files
|
%1$d files
|
||||||
</string>
|
</string>
|
||||||
<string name="import_gpx_files_many"
|
<string name="import_files_many"
|
||||||
translation_description="When importing all the tracks, phrase for 'many' GPX files.
|
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 an integer number. The number is a large number.">
|
||||||
%1$d GPX files
|
%1$d files
|
||||||
</string>
|
</string>
|
||||||
<string name="import_gpx_files_one"
|
<string name="import_files_one"
|
||||||
translation_description="When importing all the tracks, phrase for 1 GPX file.">
|
translation_description="When importing files, the phrase for 1 file. This is used when the
|
||||||
1 GPX file
|
language requires special treatment of numbers like one. See
|
||||||
|
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||||
|
1 file
|
||||||
</string>
|
</string>
|
||||||
<string name="import_gpx_files_other"
|
<string name="import_files_other"
|
||||||
translation_description="When importing all the tracks, phrase for 'other' GPX files, 'other'
|
translation_description="When importing files, the phrase for 'other' files. This is used when
|
||||||
means for cases that the language does not require special treatment of the given quantity.
|
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 an integer number.">
|
||||||
%1$d GPX files
|
%1$d files
|
||||||
</string>
|
</string>
|
||||||
<string name="import_gpx_files_two"
|
<string name="import_files_two"
|
||||||
translation_description="When importing all the tracks, phrase for 2 GPX files.">
|
translation_description="When importing files, the phrase for 2 files. This is used when the
|
||||||
2 GPX files
|
language requires special treatment of numbers like two. See
|
||||||
|
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||||
|
2 files
|
||||||
</string>
|
</string>
|
||||||
<string name="import_gpx_files_zero"
|
<string name="import_files_zero"
|
||||||
translation_description="When importing all the tracks, phrase for 0 GPX files.">
|
translation_description="When importing files, the phrase for 0 files. This is used when the
|
||||||
0 GPX files
|
language requires special treatment of the number 0. See
|
||||||
|
http://developer.android.com/guide/topics/resources/string-resource.html#Plurals">
|
||||||
|
0 files
|
||||||
|
</string>
|
||||||
|
<string name="import_no_directory"
|
||||||
|
translation_description="When importing from the external storage, the error message when the
|
||||||
|
directory doesn't exist.
|
||||||
|
|
||||||
|
%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.
|
||||||
|
|
||||||
|
%1$s: the directory. E.g., '/storage/sdcard0/MyTracks/gpx/'.">
|
||||||
|
Importing from %1$s…
|
||||||
|
</string>
|
||||||
|
<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.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<!-- Maps List -->
|
<!-- Maps List -->
|
||||||
@@ -1149,11 +1162,19 @@ limitations under the License.
|
|||||||
Character limit: ~20 (1 line)">
|
Character limit: ~20 (1 line)">
|
||||||
Help
|
Help
|
||||||
</string>
|
</string>
|
||||||
<string name="menu_import"
|
<string name="menu_import_all"
|
||||||
translation_description="In the menu, to import tracks from the external storage.
|
translation_description="In the menu, to import all tracks from the external storage.
|
||||||
|
|
||||||
Character limit: ~20 (1 line)">
|
Character limit: ~20 (1 line)">
|
||||||
Import from external storage
|
Import all from external storage
|
||||||
|
</string>
|
||||||
|
<string name="menu_import_all_format"
|
||||||
|
translation_description="Under the import all menu option, the submenu option to import all
|
||||||
|
files of a certain file format.
|
||||||
|
|
||||||
|
%1$s: file format. e.g., 'GPX', 'KML'
|
||||||
|
Character limit: ~20 (1 line)">
|
||||||
|
Import %1$s
|
||||||
</string>
|
</string>
|
||||||
<string name="menu_insert_marker"
|
<string name="menu_insert_marker"
|
||||||
translation_description="In the menu, to insert a marker.
|
translation_description="In the menu, to insert a marker.
|
||||||
@@ -1352,6 +1373,16 @@ limitations under the License.
|
|||||||
write data to Google My Tracks and start/stop Google My Tracks recording
|
write data to Google My Tracks and start/stop Google My Tracks recording
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<!-- Restore -->
|
||||||
|
<string name="restore_error"
|
||||||
|
translation_description="When restoring from backup, the error message.">
|
||||||
|
Unable to restore from backup.
|
||||||
|
</string>
|
||||||
|
<string name="restore_success"
|
||||||
|
translation_description="When restoring from backup, the success message.">
|
||||||
|
Finished restoring from backup.
|
||||||
|
</string>
|
||||||
|
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<string name="search_global_description"
|
<string name="search_global_description"
|
||||||
translation_description="Description of what is searched in My Tracks, to be displayed in the
|
translation_description="Description of what is searched in My Tracks, to be displayed in the
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import android.widget.TabHost;
|
|||||||
import android.widget.TabHost.TabSpec;
|
import android.widget.TabHost.TabSpec;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to show the track detail.
|
* An activity to show the track detail.
|
||||||
@@ -506,7 +507,8 @@ public class TrackDetailActivity extends AbstractMyTracksActivity implements Del
|
|||||||
* @param trackFileFormat the track file format
|
* @param trackFileFormat the track file format
|
||||||
*/
|
*/
|
||||||
private void startSaveActivity(TrackFileFormat trackFileFormat) {
|
private void startSaveActivity(TrackFileFormat trackFileFormat) {
|
||||||
AnalyticsUtils.sendPageViews(this, "/action/save");
|
AnalyticsUtils.sendPageViews(
|
||||||
|
this, "/action/save_" + trackFileFormat.name().toLowerCase(Locale.US));
|
||||||
Intent intent = IntentUtils.newIntent(this, SaveActivity.class)
|
Intent intent = IntentUtils.newIntent(this, SaveActivity.class)
|
||||||
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
|
.putExtra(SaveActivity.EXTRA_TRACK_ID, trackId)
|
||||||
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
|
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import android.widget.ListView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity displaying a list of tracks.
|
* An activity displaying a list of tracks.
|
||||||
@@ -323,7 +324,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac
|
|||||||
// Menu items
|
// Menu items
|
||||||
private MenuItem searchMenuItem;
|
private MenuItem searchMenuItem;
|
||||||
private MenuItem startGpsMenuItem;
|
private MenuItem startGpsMenuItem;
|
||||||
private MenuItem importMenuItem;
|
private MenuItem importAllMenuItem;
|
||||||
private MenuItem saveAllMenuItem;
|
private MenuItem saveAllMenuItem;
|
||||||
private MenuItem deleteAllMenuItem;
|
private MenuItem deleteAllMenuItem;
|
||||||
|
|
||||||
@@ -486,6 +487,12 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac
|
|||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.track_list, menu);
|
getMenuInflater().inflate(R.menu.track_list, menu);
|
||||||
String fileTypes[] = getResources().getStringArray(R.array.file_types);
|
String fileTypes[] = getResources().getStringArray(R.array.file_types);
|
||||||
|
// Import all submenu titles
|
||||||
|
menu.findItem(R.id.track_list_import_all_gpx)
|
||||||
|
.setTitle(getString(R.string.menu_import_all_format, fileTypes[0]));
|
||||||
|
menu.findItem(R.id.track_list_import_all_kml)
|
||||||
|
.setTitle(getString(R.string.menu_import_all_format, fileTypes[1]));
|
||||||
|
// Save all submenu titles
|
||||||
menu.findItem(R.id.track_list_save_all_gpx)
|
menu.findItem(R.id.track_list_save_all_gpx)
|
||||||
.setTitle(getString(R.string.menu_save_format, fileTypes[0]));
|
.setTitle(getString(R.string.menu_save_format, fileTypes[0]));
|
||||||
menu.findItem(R.id.track_list_save_all_kml)
|
menu.findItem(R.id.track_list_save_all_kml)
|
||||||
@@ -497,7 +504,7 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac
|
|||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.track_list_search);
|
searchMenuItem = menu.findItem(R.id.track_list_search);
|
||||||
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
startGpsMenuItem = menu.findItem(R.id.track_list_start_gps);
|
||||||
importMenuItem = menu.findItem(R.id.track_list_import);
|
importAllMenuItem = menu.findItem(R.id.track_list_import_all);
|
||||||
saveAllMenuItem = menu.findItem(R.id.track_list_save_all);
|
saveAllMenuItem = menu.findItem(R.id.track_list_save_all);
|
||||||
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
deleteAllMenuItem = menu.findItem(R.id.track_list_delete_all);
|
||||||
|
|
||||||
@@ -529,11 +536,11 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac
|
|||||||
handleStartGps();
|
handleStartGps();
|
||||||
updateMenuItems(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
updateMenuItems(recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_list_import:
|
case R.id.track_list_import_all_gpx:
|
||||||
AnalyticsUtils.sendPageViews(this, "/action/import");
|
startImportActivity(TrackFileFormat.GPX);
|
||||||
intent = IntentUtils.newIntent(this, ImportActivity.class)
|
return true;
|
||||||
.putExtra(ImportActivity.EXTRA_IMPORT_ALL, true);
|
case R.id.track_list_import_all_kml:
|
||||||
startActivity(intent);
|
startImportActivity(TrackFileFormat.KML);
|
||||||
return true;
|
return true;
|
||||||
case R.id.track_list_save_all_gpx:
|
case R.id.track_list_save_all_gpx:
|
||||||
startSaveActivity(TrackFileFormat.GPX);
|
startSaveActivity(TrackFileFormat.GPX);
|
||||||
@@ -653,8 +660,8 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac
|
|||||||
startGpsMenuItem.setTitle(startGps ? R.string.menu_stop_gps : R.string.menu_start_gps);
|
startGpsMenuItem.setTitle(startGps ? R.string.menu_stop_gps : R.string.menu_start_gps);
|
||||||
startGpsMenuItem.setVisible(!isRecording);
|
startGpsMenuItem.setVisible(!isRecording);
|
||||||
}
|
}
|
||||||
if (importMenuItem != null) {
|
if (importAllMenuItem != null) {
|
||||||
importMenuItem.setVisible(!isRecording);
|
importAllMenuItem.setVisible(!isRecording);
|
||||||
}
|
}
|
||||||
if (saveAllMenuItem != null) {
|
if (saveAllMenuItem != null) {
|
||||||
saveAllMenuItem.setVisible(!isRecording);
|
saveAllMenuItem.setVisible(!isRecording);
|
||||||
@@ -680,13 +687,28 @@ public class TrackListActivity extends FragmentActivity implements DeleteOneTrac
|
|||||||
bindChangedCallback.run();
|
bindChangedCallback.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the {@link ImportActivity} to import all tracks.
|
||||||
|
*
|
||||||
|
* @param trackFileFormat the track file format
|
||||||
|
*/
|
||||||
|
private void startImportActivity(TrackFileFormat trackFileFormat) {
|
||||||
|
AnalyticsUtils.sendPageViews(
|
||||||
|
this, "/action/import_all_" + trackFileFormat.name().toLowerCase(Locale.US));
|
||||||
|
Intent intent = IntentUtils.newIntent(this, ImportActivity.class)
|
||||||
|
.putExtra(ImportActivity.EXTRA_IMPORT_ALL, true)
|
||||||
|
.putExtra(ImportActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the {@link SaveActivity} to save all tracks.
|
* Starts the {@link SaveActivity} to save all tracks.
|
||||||
*
|
*
|
||||||
* @param trackFileFormat the track file format
|
* @param trackFileFormat the track file format
|
||||||
*/
|
*/
|
||||||
private void startSaveActivity(TrackFileFormat trackFileFormat) {
|
private void startSaveActivity(TrackFileFormat trackFileFormat) {
|
||||||
AnalyticsUtils.sendPageViews(this, "/action/save_all");
|
AnalyticsUtils.sendPageViews(
|
||||||
|
this, "/action/save_all_" + trackFileFormat.name().toLowerCase(Locale.US));
|
||||||
Intent intent = IntentUtils.newIntent(this, SaveActivity.class)
|
Intent intent = IntentUtils.newIntent(this, SaveActivity.class)
|
||||||
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
|
.putExtra(SaveActivity.EXTRA_TRACK_FILE_FORMAT, (Parcelable) trackFileFormat);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
if (!FileUtils.isSdCardAvailable()) {
|
if (!FileUtils.isExternalStorageWriteable()) {
|
||||||
messageId = R.string.external_storage_error_no_storage;
|
messageId = R.string.external_storage_not_writeable;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class ExternalFileBackup {
|
|||||||
return FileUtils.ensureDirectoryExists(dir) ? dir : null;
|
return FileUtils.ensureDirectoryExists(dir) ? dir : null;
|
||||||
} else {
|
} else {
|
||||||
// Return it if it already exists, otherwise return null
|
// Return it if it already exists, otherwise return null
|
||||||
return dir.isDirectory() ? dir : null;
|
return FileUtils.isDirectory(dir) ? dir : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
this.externalFileBackup = new ExternalFileBackup(restoreActivity);
|
this.externalFileBackup = new ExternalFileBackup(restoreActivity);
|
||||||
success = false;
|
success = false;
|
||||||
completed = false;
|
completed = false;
|
||||||
messageId = R.string.external_storage_import_error;
|
messageId = R.string.restore_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,7 +85,7 @@ public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
externalFileBackup.restoreFromDate(date);
|
externalFileBackup.restoreFromDate(date);
|
||||||
messageId = R.string.external_storage_import_success;
|
messageId = R.string.restore_success;
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(TAG, "IO exception", e);
|
Log.d(TAG, "IO exception", e);
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ public class RestoreChooserActivity extends Activity {
|
|||||||
ExternalFileBackup externalFileBackup = new ExternalFileBackup(this);
|
ExternalFileBackup externalFileBackup = new ExternalFileBackup(this);
|
||||||
|
|
||||||
// Get the list of existing backups
|
// Get the list of existing backups
|
||||||
if (!FileUtils.isSdCardAvailable()) {
|
if (!FileUtils.isExternalStorageAvailable()) {
|
||||||
Toast.makeText(this, R.string.external_storage_error_no_storage, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show();
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,571 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
|
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
|
||||||
|
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
||||||
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
|
||||||
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.TrackIconUtils;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.xml.parsers.SAXParser;
|
||||||
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
|
|
||||||
|
import org.xml.sax.Locator;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for various importers like {@link GpxImporter} and
|
||||||
|
* {@link KmlImporter}.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
abstract class AbstractImporter extends DefaultHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data for the current track.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
private class TrackData {
|
||||||
|
|
||||||
|
// The current track
|
||||||
|
Track track = new Track();
|
||||||
|
|
||||||
|
// The number of segments processed for the current track
|
||||||
|
int numberOfSegments = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The last location in the current segment. Null if the current segment
|
||||||
|
* doesn't have a last location.
|
||||||
|
*/
|
||||||
|
Location lastLocationInCurrentSegment;
|
||||||
|
|
||||||
|
// The number of locations processed for the current track
|
||||||
|
int numberOfLocations = 0;
|
||||||
|
|
||||||
|
// The trip statistics updater for the current track
|
||||||
|
TripStatisticsUpdater tripStatisticsUpdater;
|
||||||
|
|
||||||
|
// The import time of the track.
|
||||||
|
long importTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// The buffered locations
|
||||||
|
Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS];
|
||||||
|
|
||||||
|
// The number of buffered locations
|
||||||
|
int numBufferedLocations = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final String DEFAULT_ALTITUDE = "0";
|
||||||
|
|
||||||
|
// The maximum number of buffered locations for bulk-insertion
|
||||||
|
private static final int MAX_BUFFERED_LOCATIONS = 512;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final MyTracksProviderUtils myTracksProviderUtils;
|
||||||
|
private final int minRecordingDistance;
|
||||||
|
private final List<Long> trackIds;
|
||||||
|
private final List<Waypoint> waypoints;
|
||||||
|
|
||||||
|
// The current track data
|
||||||
|
private TrackData trackData;
|
||||||
|
|
||||||
|
// The SAX locator to get the current line information
|
||||||
|
private Locator locator;
|
||||||
|
|
||||||
|
// The current element content
|
||||||
|
protected String content;
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
protected String description;
|
||||||
|
protected String category;
|
||||||
|
protected String latitude;
|
||||||
|
protected String longitude;
|
||||||
|
protected String altitude;
|
||||||
|
protected String time;
|
||||||
|
protected String waypointType;
|
||||||
|
|
||||||
|
protected AbstractImporter(Context context) {
|
||||||
|
this(context, MyTracksProviderUtils.Factory.get(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected AbstractImporter(
|
||||||
|
Context context, MyTracksProviderUtils myTracksProviderUtils) {
|
||||||
|
this.context = context;
|
||||||
|
this.myTracksProviderUtils = myTracksProviderUtils;
|
||||||
|
this.minRecordingDistance = PreferencesUtils.getInt(context, R.string.min_recording_distance_key,
|
||||||
|
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
||||||
|
trackIds = new ArrayList<Long>();
|
||||||
|
waypoints = new ArrayList<Waypoint>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDocumentLocator(Locator locator) {
|
||||||
|
this.locator = locator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||||
|
String newContent = new String(ch, start, length);
|
||||||
|
if (content == null) {
|
||||||
|
content = newContent;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* In 99% of the cases, a single call to this method will be made for each
|
||||||
|
* sequence of characters we're interested in, so we'll rarely be
|
||||||
|
* concatenating strings, thus not justifying the use of a StringBuilder.
|
||||||
|
*/
|
||||||
|
content += newContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports a file.
|
||||||
|
*
|
||||||
|
* @param inputStream the input stream.
|
||||||
|
* @return an array of imported track ids.
|
||||||
|
*/
|
||||||
|
public long[] importFile(InputStream inputStream) throws Exception {
|
||||||
|
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
||||||
|
SAXParser saxParser = saxParserFactory.newSAXParser();
|
||||||
|
|
||||||
|
try {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
saxParser.parse(inputStream, this);
|
||||||
|
|
||||||
|
long end = System.currentTimeMillis();
|
||||||
|
Log.d(Constants.TAG, "Total import time: " + (end - start) + "ms");
|
||||||
|
} catch (SAXException e) {
|
||||||
|
handleException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
handleException(e);
|
||||||
|
}
|
||||||
|
long[] result = new long[trackIds.size()];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
result[i] = trackIds.get(i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On file end.
|
||||||
|
*/
|
||||||
|
protected void onFileEnd() {
|
||||||
|
// Add waypoints to the last imported track
|
||||||
|
int size = trackIds.size();
|
||||||
|
if (size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long trackId = trackIds.get(size - 1);
|
||||||
|
Track track = myTracksProviderUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Cursor trackPointCursor = null;
|
||||||
|
try {
|
||||||
|
trackPointCursor = myTracksProviderUtils.getTrackPointCursor(track.getId(), -1L, -1, false);
|
||||||
|
if (trackPointCursor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int waypointPosition = -1;
|
||||||
|
Waypoint waypoint = null;
|
||||||
|
int trackPointPosition = -1;
|
||||||
|
Location trackPoint = null;
|
||||||
|
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
|
||||||
|
track.getTripStatistics().getStartTime());
|
||||||
|
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
|
||||||
|
track.getTripStatistics().getStartTime());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (waypoint == null) {
|
||||||
|
waypointPosition++;
|
||||||
|
waypoint = waypointPosition < waypoints.size() ? waypoints.get(waypointPosition) : null;
|
||||||
|
if (waypoint == null) {
|
||||||
|
// No more waypoints
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trackPoint == null) {
|
||||||
|
trackPointPosition++;
|
||||||
|
trackPoint = trackPointCursor.moveToPosition(trackPointPosition) ? myTracksProviderUtils
|
||||||
|
.createTrackPoint(trackPointCursor)
|
||||||
|
: null;
|
||||||
|
if (trackPoint == null) {
|
||||||
|
// No more track points. Ignore the rest of the waypoints.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trackTripStatisticstrackUpdater.addLocation(trackPoint, minRecordingDistance);
|
||||||
|
markerTripStatisticsUpdater.addLocation(trackPoint, minRecordingDistance);
|
||||||
|
}
|
||||||
|
if (waypoint.getLocation().getTime() > trackPoint.getTime()) {
|
||||||
|
trackPoint = null;
|
||||||
|
} else if (waypoint.getLocation().getTime() < trackPoint.getTime()) {
|
||||||
|
waypoint = null;
|
||||||
|
} else {
|
||||||
|
// The waypoint location time matches the track point time
|
||||||
|
if (trackPoint.getLatitude() == waypoint.getLocation().getLatitude()
|
||||||
|
&& trackPoint.getLongitude() == waypoint.getLocation().getLongitude()) {
|
||||||
|
|
||||||
|
// Get tripStatistics, description, and icon
|
||||||
|
TripStatistics tripStatistics;
|
||||||
|
String waypointDescription;
|
||||||
|
String icon;
|
||||||
|
if (waypoint.getType() == WaypointType.STATISTICS) {
|
||||||
|
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
|
||||||
|
markerTripStatisticsUpdater = new TripStatisticsUpdater(trackPoint.getTime());
|
||||||
|
waypointDescription = new DescriptionGeneratorImpl(context)
|
||||||
|
.generateWaypointDescription(tripStatistics);
|
||||||
|
icon = context.getString(R.string.marker_statistics_icon_url);
|
||||||
|
} else {
|
||||||
|
tripStatistics = null;
|
||||||
|
waypointDescription = waypoint.getDescription();
|
||||||
|
icon = context.getString(R.string.marker_waypoint_icon_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get length and duration
|
||||||
|
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
|
||||||
|
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
|
||||||
|
|
||||||
|
// Insert waypoint
|
||||||
|
Waypoint newWaypoint = new Waypoint(waypoint.getName(), waypointDescription,
|
||||||
|
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
|
||||||
|
-1L, -1L, trackPoint, tripStatistics);
|
||||||
|
myTracksProviderUtils.insertWaypoint(newWaypoint);
|
||||||
|
}
|
||||||
|
waypoint = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (trackPointCursor != null) {
|
||||||
|
trackPointCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On track start.
|
||||||
|
*/
|
||||||
|
protected void onTrackStart() {
|
||||||
|
trackData = new TrackData();
|
||||||
|
Uri uri = myTracksProviderUtils.insertTrack(trackData.track);
|
||||||
|
long id = Long.parseLong(uri.getLastPathSegment());
|
||||||
|
trackIds.add(id);
|
||||||
|
trackData.track.setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On track end.
|
||||||
|
*/
|
||||||
|
protected void onTrackEnd() {
|
||||||
|
flushLocations(trackData);
|
||||||
|
if (name != null) {
|
||||||
|
trackData.track.setName(name);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
trackData.track.setDescription(description);
|
||||||
|
}
|
||||||
|
if (category != null) {
|
||||||
|
trackData.track.setCategory(category);
|
||||||
|
trackData.track.setIcon(TrackIconUtils.getIconValue(context, category));
|
||||||
|
}
|
||||||
|
if (trackData.tripStatisticsUpdater == null) {
|
||||||
|
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(trackData.importTime);
|
||||||
|
trackData.tripStatisticsUpdater.updateTime(trackData.importTime);
|
||||||
|
}
|
||||||
|
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
|
||||||
|
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
|
||||||
|
myTracksProviderUtils.updateTrack(trackData.track);
|
||||||
|
insertFirstWaypoint(trackData.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On track segment start.
|
||||||
|
*/
|
||||||
|
protected void onTrackSegmentStart() {
|
||||||
|
trackData.numberOfSegments++;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If not the first segment, add a pause separator if there is at least one
|
||||||
|
* location in the last segment.
|
||||||
|
*/
|
||||||
|
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
|
||||||
|
insertLocation(createLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0, 0.0,
|
||||||
|
trackData.lastLocationInCurrentSegment.getTime()));
|
||||||
|
}
|
||||||
|
trackData.lastLocationInCurrentSegment = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a waypoint.
|
||||||
|
*
|
||||||
|
* @param type the waypoint type
|
||||||
|
*/
|
||||||
|
protected void addWaypoint(WaypointType type) throws SAXException {
|
||||||
|
// Waypoint must have a time, else cannot match to the track points
|
||||||
|
if (time == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint waypoint = new Waypoint();
|
||||||
|
Location location = createLocation();
|
||||||
|
|
||||||
|
if (!LocationUtils.isValidLocation(location)) {
|
||||||
|
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||||
|
}
|
||||||
|
waypoint.setLocation(location);
|
||||||
|
|
||||||
|
if (name != null) {
|
||||||
|
waypoint.setName(name);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
waypoint.setDescription(description);
|
||||||
|
}
|
||||||
|
if (category != null) {
|
||||||
|
waypoint.setCategory(category);
|
||||||
|
}
|
||||||
|
waypoint.setType(type);
|
||||||
|
waypoints.add(waypoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a track point.
|
||||||
|
*/
|
||||||
|
protected Location getTrackPoint() throws SAXException {
|
||||||
|
Location location = createLocation();
|
||||||
|
|
||||||
|
// Calculate derived attributes from the previous point
|
||||||
|
if (trackData.lastLocationInCurrentSegment != null
|
||||||
|
&& trackData.lastLocationInCurrentSegment.getTime() != 0) {
|
||||||
|
long timeDifference = location.getTime() - trackData.lastLocationInCurrentSegment.getTime();
|
||||||
|
|
||||||
|
// Check for negative time change
|
||||||
|
if (timeDifference <= 0) {
|
||||||
|
Log.w(Constants.TAG, "Time difference not postive.");
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't have a speed and bearing in GPX, make something up from the
|
||||||
|
* last two points. GPS points tend to have some inherent imprecision,
|
||||||
|
* speed and bearing will likely be off, so the statistics for things
|
||||||
|
* like max speed will also be off.
|
||||||
|
*/
|
||||||
|
float speed = trackData.lastLocationInCurrentSegment.distanceTo(location) * 1000.0f
|
||||||
|
/ timeDifference;
|
||||||
|
location.setSpeed(speed);
|
||||||
|
}
|
||||||
|
location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LocationUtils.isValidLocation(location)) {
|
||||||
|
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
||||||
|
/*
|
||||||
|
* If not the first segment, add a resume separator before adding the
|
||||||
|
* first location.
|
||||||
|
*/
|
||||||
|
insertLocation(
|
||||||
|
createLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, 0.0, location.getTime()));
|
||||||
|
}
|
||||||
|
trackData.lastLocationInCurrentSegment = location;
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a track point.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
*/
|
||||||
|
protected void insertTrackPoint(Location location) {
|
||||||
|
insertLocation(location);
|
||||||
|
|
||||||
|
if (trackData.track.getStartId() == -1L) {
|
||||||
|
// Flush the location to set the track start id and the track end id
|
||||||
|
flushLocations(trackData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an error message.
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
*/
|
||||||
|
protected String createErrorMessage(String message) {
|
||||||
|
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s",
|
||||||
|
locator.getLineNumber(), locator.getColumnNumber(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a location.
|
||||||
|
*/
|
||||||
|
private Location createLocation() throws SAXException {
|
||||||
|
if (latitude == null || longitude == null || altitude == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double longitudeValue;
|
||||||
|
double latitudeValue;
|
||||||
|
double altitudeValue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
longitudeValue = Double.parseDouble(longitude);
|
||||||
|
latitudeValue = Double.parseDouble(latitude);
|
||||||
|
altitudeValue = Double.parseDouble(altitude);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new SAXException(createErrorMessage(String.format(
|
||||||
|
"Unable to parse longitude latitude altitude: %s %s %s", longitude, latitude, altitude)),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
long timeValue;
|
||||||
|
|
||||||
|
if (time == null) {
|
||||||
|
timeValue = trackData.importTime;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
timeValue = StringUtils.getTime(time);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new SAXException(
|
||||||
|
createErrorMessage(String.format("Unable to parse time: %s", time)), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createLocation(latitudeValue, longitudeValue, altitudeValue, timeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a location.
|
||||||
|
*
|
||||||
|
* @param latitudeValue the latitude value
|
||||||
|
* @param longitudeValue the longitude value
|
||||||
|
* @param altitudeValue the altitude value
|
||||||
|
* @param timeValue the time value
|
||||||
|
*/
|
||||||
|
private Location createLocation(
|
||||||
|
double latitudeValue, double longitudeValue, double altitudeValue, long timeValue) {
|
||||||
|
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||||
|
location.setLatitude(latitudeValue);
|
||||||
|
location.setLongitude(longitudeValue);
|
||||||
|
location.setAltitude(altitudeValue);
|
||||||
|
location.setTime(timeValue);
|
||||||
|
location.removeAccuracy();
|
||||||
|
location.removeBearing();
|
||||||
|
location.removeSpeed();
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a location.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
*/
|
||||||
|
private void insertLocation(Location location) {
|
||||||
|
if (trackData.tripStatisticsUpdater == null) {
|
||||||
|
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(
|
||||||
|
location.getTime() != -1L ? location.getTime() : trackData.importTime);
|
||||||
|
}
|
||||||
|
trackData.tripStatisticsUpdater.addLocation(location, minRecordingDistance);
|
||||||
|
|
||||||
|
trackData.bufferedLocations[trackData.numBufferedLocations] = location;
|
||||||
|
trackData.numBufferedLocations++;
|
||||||
|
trackData.numberOfLocations++;
|
||||||
|
|
||||||
|
if (trackData.numBufferedLocations >= MAX_BUFFERED_LOCATIONS) {
|
||||||
|
flushLocations(trackData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the locations to the database.
|
||||||
|
*
|
||||||
|
* @param data the track data
|
||||||
|
*/
|
||||||
|
private void flushLocations(TrackData data) {
|
||||||
|
if (data.numBufferedLocations <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
myTracksProviderUtils.bulkInsertTrackPoint(
|
||||||
|
data.bufferedLocations, data.numBufferedLocations, data.track.getId());
|
||||||
|
data.numBufferedLocations = 0;
|
||||||
|
if (data.track.getStartId() == -1L) {
|
||||||
|
data.track.setStartId(myTracksProviderUtils.getFirstTrackPointId(data.track.getId()));
|
||||||
|
}
|
||||||
|
data.track.setStopId(myTracksProviderUtils.getLastTrackPointId(data.track.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts the first waypoint, the track statistics waypoint.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
*/
|
||||||
|
private void insertFirstWaypoint(Track track) {
|
||||||
|
String waypointName = context.getString(R.string.marker_split_name_format, 0);
|
||||||
|
String waypointCategory = "";
|
||||||
|
TripStatisticsUpdater updater = new TripStatisticsUpdater(
|
||||||
|
track.getTripStatistics().getStartTime());
|
||||||
|
TripStatistics tripStatistics = updater.getTripStatistics();
|
||||||
|
String waypointDescription = new DescriptionGeneratorImpl(context).generateWaypointDescription(
|
||||||
|
tripStatistics);
|
||||||
|
String icon = context.getString(R.string.marker_statistics_icon_url);
|
||||||
|
double length = 0.0;
|
||||||
|
long duration = 0L;
|
||||||
|
Location waypointLocation = new Location("");
|
||||||
|
waypointLocation.setLatitude(100);
|
||||||
|
waypointLocation.setLongitude(180);
|
||||||
|
Waypoint waypoint = new Waypoint(waypointName, waypointDescription, waypointCategory, icon,
|
||||||
|
track.getId(), WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation,
|
||||||
|
tripStatistics);
|
||||||
|
myTracksProviderUtils.insertWaypoint(waypoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an exception.
|
||||||
|
*
|
||||||
|
* @param e the exception
|
||||||
|
*/
|
||||||
|
private void handleException(Exception e) throws Exception {
|
||||||
|
for (long trackId : trackIds) {
|
||||||
|
myTracksProviderUtils.deleteTrack(trackId);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,93 +16,26 @@
|
|||||||
|
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
|
||||||
import com.google.android.apps.mytracks.content.DescriptionGeneratorImpl;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
|
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
|
||||||
import com.google.android.apps.mytracks.services.TrackRecordingService;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsUpdater;
|
|
||||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.TrackIconUtils;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
import javax.xml.parsers.SAXParser;
|
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
|
||||||
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.Locator;
|
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports GPX file to My Tracks.
|
* Imports a GPX file.
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Jimmy Shih
|
||||||
* @author Steffen Horlacher
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
*/
|
||||||
public class GpxImporter extends DefaultHandler {
|
public class GpxImporter extends AbstractImporter {
|
||||||
|
|
||||||
/**
|
|
||||||
* Data for the current track.
|
|
||||||
*
|
|
||||||
* @author Jimmy Shih
|
|
||||||
*/
|
|
||||||
private class TrackData {
|
|
||||||
|
|
||||||
// The current track
|
|
||||||
Track track = new Track();
|
|
||||||
|
|
||||||
// The parsed depth for the current track
|
|
||||||
int parsedDepth = 0;
|
|
||||||
|
|
||||||
// The number of segments processed for the current track
|
|
||||||
int numberOfSegments = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The last location in the current segment. Null if the current segment
|
|
||||||
* doesn't have a last location.
|
|
||||||
*/
|
|
||||||
Location lastLocationInCurrentSegment;
|
|
||||||
|
|
||||||
// The number of locations processed for the current track
|
|
||||||
int numberOfLocations = 0;
|
|
||||||
|
|
||||||
// The trip statistics updater for the current track
|
|
||||||
TripStatisticsUpdater tripStatisticsUpdater;
|
|
||||||
|
|
||||||
// The default location time for the current track. -1L to not use it.
|
|
||||||
long defaultLocationTime = -1L;
|
|
||||||
|
|
||||||
// The buffered locations
|
|
||||||
Location[] bufferedLocations = new Location[MAX_BUFFERED_LOCATIONS];
|
|
||||||
|
|
||||||
// The number of buffered locations
|
|
||||||
int numBufferedLocations = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GPX tags
|
|
||||||
private static final String TAG_ALTITUDE = "ele";
|
|
||||||
private static final String TAG_DESCRIPTION = "desc";
|
private static final String TAG_DESCRIPTION = "desc";
|
||||||
private static final String TAG_COMMENT = "cmt";
|
private static final String TAG_COMMENT = "cmt";
|
||||||
|
private static final String TAG_ELEVATION = "ele";
|
||||||
private static final String TAG_GPX = "gpx";
|
private static final String TAG_GPX = "gpx";
|
||||||
private static final String TAG_NAME = "name";
|
private static final String TAG_NAME = "name";
|
||||||
private static final String TAG_TIME = "time";
|
private static final String TAG_TIME = "time";
|
||||||
@@ -112,150 +45,65 @@ public class GpxImporter extends DefaultHandler {
|
|||||||
private static final String TAG_TYPE = "type";
|
private static final String TAG_TYPE = "type";
|
||||||
private static final String TAG_WAYPOINT = "wpt";
|
private static final String TAG_WAYPOINT = "wpt";
|
||||||
|
|
||||||
// GPX attributes
|
private static final String ATTRIBUTE_LAT = "lat";
|
||||||
private static final String ATT_LAT = "lat";
|
private static final String ATTRIBUTE_LON = "lon";
|
||||||
private static final String ATT_LON = "lon";
|
|
||||||
|
|
||||||
// The maximum number of buffered locations for bulk-insertion
|
public GpxImporter(Context context) {
|
||||||
private static final int MAX_BUFFERED_LOCATIONS = 512;
|
super(context);
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
|
||||||
private final int minRecordingDistance;
|
|
||||||
private final List<Long> tracksIds;
|
|
||||||
|
|
||||||
// The SAX locator to get the current line information
|
|
||||||
private Locator locator;
|
|
||||||
|
|
||||||
// The current element content
|
|
||||||
private String content;
|
|
||||||
|
|
||||||
// The current track data
|
|
||||||
private TrackData trackData = null;
|
|
||||||
|
|
||||||
// The current waypoint
|
|
||||||
private Waypoint currentWaypoint;
|
|
||||||
|
|
||||||
// The list of waypoints
|
|
||||||
private List<Waypoint> waypoints = new ArrayList<Waypoint>();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The current location, used for both track point location and waypoint
|
|
||||||
* location.
|
|
||||||
*/
|
|
||||||
private Location location;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads GPS tracks from a GPX file and writes tracks, waypoints, and track
|
|
||||||
* points to the database.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
* @param inputStream the input stream for the GPX file
|
|
||||||
* @param myTracksProviderUtils my tracks provider utils
|
|
||||||
* @param minRecordingDistance the min recording distance
|
|
||||||
* @return long[] array of track ids written to the database.
|
|
||||||
*/
|
|
||||||
public static long[] importGPXFile(Context context, InputStream inputStream,
|
|
||||||
MyTracksProviderUtils myTracksProviderUtils, int minRecordingDistance)
|
|
||||||
throws ParserConfigurationException, SAXException, IOException {
|
|
||||||
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
|
|
||||||
SAXParser saxParser = saxParserFactory.newSAXParser();
|
|
||||||
GpxImporter gpxImporter = new GpxImporter(context, myTracksProviderUtils, minRecordingDistance);
|
|
||||||
long[] trackIds = new long[0];
|
|
||||||
|
|
||||||
try {
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
|
|
||||||
saxParser.parse(inputStream, gpxImporter);
|
|
||||||
|
|
||||||
long end = System.currentTimeMillis();
|
|
||||||
Log.d(Constants.TAG, "Total import time: " + (end - start) + "ms");
|
|
||||||
|
|
||||||
trackIds = gpxImporter.getImportedTrackIds();
|
|
||||||
if (trackIds.length == 0) {
|
|
||||||
throw new IOException("No track imported.");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
// Delete the current track if not finished
|
|
||||||
gpxImporter.rollbackUnfinishedTracks();
|
|
||||||
}
|
|
||||||
return trackIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public GpxImporter(
|
@VisibleForTesting
|
||||||
Context context, MyTracksProviderUtils myTracksProviderUtils, int minRecordingDistance) {
|
public GpxImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
|
||||||
this.context = context;
|
super(context, myTracksProviderUtils);
|
||||||
this.myTracksProviderUtils = myTracksProviderUtils;
|
|
||||||
this.minRecordingDistance = minRecordingDistance;
|
|
||||||
this.tracksIds = new ArrayList<Long>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
public void startElement(String uri, String localName, String tag, Attributes attributes)
|
||||||
String newContent = new String(ch, start, length);
|
|
||||||
if (content == null) {
|
|
||||||
content = newContent;
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* In 99% of the cases, a single call to this method will be made for each
|
|
||||||
* sequence of characters we're interested in, so we'll rarely be
|
|
||||||
* concatenating strings, thus not justifying the use of a StringBuilder.
|
|
||||||
*/
|
|
||||||
content += newContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startElement(String uri, String localName, String name, Attributes attributes)
|
|
||||||
throws SAXException {
|
throws SAXException {
|
||||||
if (trackData != null) {
|
if (tag.equals(TAG_WAYPOINT)) {
|
||||||
trackData.parsedDepth++;
|
onWaypointStart(attributes);
|
||||||
if (localName.equals(TAG_TRACK)) {
|
} else if (tag.equals(TAG_TRACK)) {
|
||||||
throw new SAXException(createErrorMessage("Invalid GPX. Already inside a track."));
|
onTrackStart();
|
||||||
} else if (localName.equals(TAG_TRACK_SEGMENT)) {
|
} else if (tag.equals(TAG_TRACK_SEGMENT)) {
|
||||||
onTrackSegmentElementStart();
|
onTrackSegmentStart();
|
||||||
} else if (localName.equals(TAG_TRACK_POINT)) {
|
} else if (tag.equals(TAG_TRACK_POINT)) {
|
||||||
onTrackPointElementStart(attributes);
|
onTrackPointStart(attributes);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (localName.equals(TAG_TRACK)) {
|
|
||||||
onTrackElementStart();
|
|
||||||
} else if (localName.equals(TAG_WAYPOINT)) {
|
|
||||||
onWaypointElementStart(attributes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String localName, String name) throws SAXException {
|
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||||
if (localName.equals(TAG_NAME)) {
|
if (tag.equals(TAG_GPX)) {
|
||||||
onNameElementEnd();
|
onFileEnd();
|
||||||
} else if (localName.equals(TAG_DESCRIPTION)) {
|
} else if (tag.equals(TAG_WAYPOINT)) {
|
||||||
onDescriptionElementEnd();
|
onWaypointEnd();
|
||||||
} else if (localName.equals(TAG_TYPE)) {
|
} else if (tag.equals(TAG_TRACK)) {
|
||||||
onTypeElementEnd();
|
onTrackEnd();
|
||||||
} else if (localName.equals(TAG_ALTITUDE)) {
|
} else if (tag.equals(TAG_TRACK_POINT)) {
|
||||||
onAltitudeElementEnd();
|
onTrackPointEnd();
|
||||||
} else if (localName.equals(TAG_TIME)) {
|
} else if (tag.equals(TAG_NAME)) {
|
||||||
onTimeElementEnd();
|
if (content != null) {
|
||||||
} else if (localName.equals(TAG_GPX)) {
|
name = content.trim();
|
||||||
onGpxElementEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackData != null) {
|
|
||||||
trackData.parsedDepth--;
|
|
||||||
if (localName.equals(TAG_TRACK)) {
|
|
||||||
onTrackElementEnd();
|
|
||||||
} else if (localName.equals(TAG_TRACK_SEGMENT)) {
|
|
||||||
onTrackSegmentElementEnd();
|
|
||||||
} else if (localName.equals(TAG_TRACK_POINT)) {
|
|
||||||
onTrackPointElementEnd();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else if (tag.equals(TAG_DESCRIPTION)) {
|
||||||
if (localName.equals(TAG_WAYPOINT)) {
|
if (content != null) {
|
||||||
onWaypointElementEnd();
|
description = content.trim();
|
||||||
} else if (localName.equals(TAG_COMMENT)) {
|
}
|
||||||
onCommentElementEnd();
|
} else if (tag.equals(TAG_TYPE)) {
|
||||||
|
if (content != null) {
|
||||||
|
category = content.trim();
|
||||||
|
}
|
||||||
|
} else if (tag.equals(TAG_TIME)) {
|
||||||
|
if (content != null) {
|
||||||
|
time = content.trim();
|
||||||
|
}
|
||||||
|
} else if (tag.equals(TAG_ELEVATION)) {
|
||||||
|
if (content != null) {
|
||||||
|
altitude = content.trim();
|
||||||
|
}
|
||||||
|
} else if (tag.equals(TAG_COMMENT)) {
|
||||||
|
if (content != null) {
|
||||||
|
waypointType = content.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,486 +112,57 @@ public class GpxImporter extends DefaultHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDocumentLocator(Locator locator) {
|
protected void onTrackStart() {
|
||||||
this.locator = locator;
|
super.onTrackStart();
|
||||||
|
name = null;
|
||||||
|
description = null;
|
||||||
|
category = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rolls back last track if possible.
|
* On track point start.
|
||||||
*/
|
|
||||||
public void rollbackUnfinishedTracks() {
|
|
||||||
if (trackData != null) {
|
|
||||||
myTracksProviderUtils.deleteTrack(trackData.track.getId());
|
|
||||||
trackData = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On gpx element end.
|
|
||||||
*/
|
|
||||||
private void onGpxElementEnd() {
|
|
||||||
int size = tracksIds.size();
|
|
||||||
if (size > 0) {
|
|
||||||
long trackId = tracksIds.get(size - 1);
|
|
||||||
Track track = myTracksProviderUtils.getTrack(trackId);
|
|
||||||
if (track != null) {
|
|
||||||
addWaypoints(track);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track element start.
|
|
||||||
*/
|
|
||||||
private void onTrackElementStart() {
|
|
||||||
trackData = new TrackData();
|
|
||||||
Uri uri = myTracksProviderUtils.insertTrack(trackData.track);
|
|
||||||
trackData.track.setId(Long.parseLong(uri.getLastPathSegment()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track element end.
|
|
||||||
*/
|
|
||||||
private void onTrackElementEnd() {
|
|
||||||
flushPoints(trackData);
|
|
||||||
if (trackData.tripStatisticsUpdater == null) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(now);
|
|
||||||
trackData.tripStatisticsUpdater.updateTime(now);
|
|
||||||
}
|
|
||||||
trackData.track.setTripStatistics(trackData.tripStatisticsUpdater.getTripStatistics());
|
|
||||||
trackData.track.setNumberOfPoints(trackData.numberOfLocations);
|
|
||||||
myTracksProviderUtils.updateTrack(trackData.track);
|
|
||||||
tracksIds.add(trackData.track.getId());
|
|
||||||
insertFirstWaypoint(trackData.track);
|
|
||||||
trackData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track segment start.
|
|
||||||
*/
|
|
||||||
private void onTrackSegmentElementStart() {
|
|
||||||
trackData.numberOfSegments++;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If not the first segment, add a pause separator if there is at least one
|
|
||||||
* location in the last segment.
|
|
||||||
*/
|
|
||||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment != null) {
|
|
||||||
insertPoint(createNewLocation(TrackRecordingService.PAUSE_LATITUDE, 0.0,
|
|
||||||
trackData.lastLocationInCurrentSegment.getTime()));
|
|
||||||
}
|
|
||||||
trackData.lastLocationInCurrentSegment = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track segment element end.
|
|
||||||
*/
|
|
||||||
private void onTrackSegmentElementEnd() {
|
|
||||||
// Nothing needs to be done
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On track point element start.
|
|
||||||
*
|
*
|
||||||
* @param attributes the attributes
|
* @param attributes the attributes
|
||||||
*/
|
*/
|
||||||
private void onTrackPointElementStart(Attributes attributes) throws SAXException {
|
private void onTrackPointStart(Attributes attributes) {
|
||||||
parseLocation(attributes);
|
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
||||||
|
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||||
|
altitude = DEFAULT_ALTITUDE;
|
||||||
|
time = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On track point element end.
|
* On track point end.
|
||||||
*/
|
*/
|
||||||
private void onTrackPointElementEnd() throws SAXException {
|
private void onTrackPointEnd() throws SAXException {
|
||||||
if (!LocationUtils.isValidLocation(location)) {
|
Location location = getTrackPoint();
|
||||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
if (location == null) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
insertTrackPoint(location);
|
||||||
if (trackData.numberOfSegments > 1 && trackData.lastLocationInCurrentSegment == null) {
|
|
||||||
/*
|
|
||||||
* If not the first segment, add a resume separator before adding the
|
|
||||||
* first location.
|
|
||||||
*/
|
|
||||||
insertPoint(
|
|
||||||
createNewLocation(TrackRecordingService.RESUME_LATITUDE, 0.0, location.getTime()));
|
|
||||||
}
|
|
||||||
|
|
||||||
insertPoint(location);
|
|
||||||
|
|
||||||
if (trackData.track.getStartId() == -1L) {
|
|
||||||
// Flush the location to set the track start id and the track end id
|
|
||||||
flushPoints(trackData);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackData.lastLocationInCurrentSegment = location;
|
|
||||||
location = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On waypoint element start.
|
* On waypoint start.
|
||||||
*
|
*
|
||||||
* @param attributes the attributes
|
* @param attributes the attributes
|
||||||
*/
|
*/
|
||||||
private void onWaypointElementStart(Attributes attributes) throws SAXException {
|
private void onWaypointStart(Attributes attributes) {
|
||||||
if (currentWaypoint != null) {
|
name = null;
|
||||||
throw new SAXException(createErrorMessage("Found waypoint inside another waypoint."));
|
description = null;
|
||||||
}
|
category = null;
|
||||||
parseLocation(attributes);
|
latitude = attributes.getValue(ATTRIBUTE_LAT);
|
||||||
currentWaypoint = new Waypoint();
|
longitude = attributes.getValue(ATTRIBUTE_LON);
|
||||||
|
altitude = DEFAULT_ALTITUDE;
|
||||||
|
time = null;
|
||||||
|
waypointType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On waypoint element end.
|
* On waypoint end.
|
||||||
*/
|
*/
|
||||||
private void onWaypointElementEnd() throws SAXException {
|
private void onWaypointEnd() throws SAXException {
|
||||||
if (!LocationUtils.isValidLocation(location)) {
|
addWaypoint(WaypointType.STATISTICS.name().equals(waypointType) ? WaypointType.STATISTICS
|
||||||
throw new SAXException(createErrorMessage("Invalid location detected: " + location));
|
: WaypointType.WAYPOINT);
|
||||||
}
|
|
||||||
currentWaypoint.setLocation(location);
|
|
||||||
waypoints.add(currentWaypoint);
|
|
||||||
location = null;
|
|
||||||
currentWaypoint = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On name element end.
|
|
||||||
*/
|
|
||||||
private void onNameElementEnd() {
|
|
||||||
if (content != null) {
|
|
||||||
String name = content.toString().trim();
|
|
||||||
if (trackData != null && trackData.parsedDepth == 1) {
|
|
||||||
trackData.track.setName(name);
|
|
||||||
} else if (currentWaypoint != null) {
|
|
||||||
currentWaypoint.setName(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On description element end.
|
|
||||||
*/
|
|
||||||
private void onDescriptionElementEnd() {
|
|
||||||
if (content != null) {
|
|
||||||
String description = content.toString().trim();
|
|
||||||
if (trackData != null && trackData.parsedDepth == 1) {
|
|
||||||
trackData.track.setDescription(description);
|
|
||||||
} else if (currentWaypoint != null) {
|
|
||||||
currentWaypoint.setDescription(description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On type element end.
|
|
||||||
*/
|
|
||||||
private void onTypeElementEnd() {
|
|
||||||
if (content != null) {
|
|
||||||
String type = content.toString().trim();
|
|
||||||
if (trackData != null && trackData.parsedDepth == 1) {
|
|
||||||
trackData.track.setCategory(type);
|
|
||||||
trackData.track.setIcon(TrackIconUtils.getIconValue(context, type));
|
|
||||||
} else if (currentWaypoint != null) {
|
|
||||||
currentWaypoint.setCategory(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On altitude element end.
|
|
||||||
*/
|
|
||||||
private void onAltitudeElementEnd() throws SAXException {
|
|
||||||
if (location == null || content == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
location.setAltitude(Double.parseDouble(content));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new SAXException(createErrorMessage("Unable to parse altitude: " + content), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On time element end. Sets location time and doing additional calculations
|
|
||||||
* as this is the last value required for the location. Also sets the start
|
|
||||||
* time for the trip statistics builder as there is no start time in the track
|
|
||||||
* root element.
|
|
||||||
*/
|
|
||||||
private void onTimeElementEnd() throws SAXException {
|
|
||||||
if (location == null || content == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the time
|
|
||||||
long time;
|
|
||||||
try {
|
|
||||||
time = StringUtils.getTime(content.trim());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new SAXException(createErrorMessage("Unable to parse time: " + content), e);
|
|
||||||
}
|
|
||||||
location.setTime(time);
|
|
||||||
|
|
||||||
// Calculate derived attributes from the previous point
|
|
||||||
if (trackData != null && trackData.lastLocationInCurrentSegment != null
|
|
||||||
&& trackData.lastLocationInCurrentSegment.getTime() != 0) {
|
|
||||||
long timeDifference = time - trackData.lastLocationInCurrentSegment.getTime();
|
|
||||||
|
|
||||||
// Check for negative time change
|
|
||||||
if (timeDifference <= 0) {
|
|
||||||
Log.w(Constants.TAG, "Time difference not postive.");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We don't have a speed and bearing in GPX, make something up from the
|
|
||||||
* last two points. GPS points tend to have some inherent imprecision,
|
|
||||||
* speed and bearing will likely be off, so the statistics for things
|
|
||||||
* like max speed will also be off.
|
|
||||||
*/
|
|
||||||
float speed = trackData.lastLocationInCurrentSegment.distanceTo(location) * 1000.0f
|
|
||||||
/ timeDifference;
|
|
||||||
location.setSpeed(speed);
|
|
||||||
}
|
|
||||||
location.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(location));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On comment element end.
|
|
||||||
*/
|
|
||||||
private void onCommentElementEnd() {
|
|
||||||
if (content != null) {
|
|
||||||
String comment = content.toString().trim();
|
|
||||||
if (currentWaypoint != null) {
|
|
||||||
WaypointType waypointType = WaypointType.STATISTICS.name().equals(comment) ? WaypointType.STATISTICS
|
|
||||||
: WaypointType.WAYPOINT;
|
|
||||||
currentWaypoint.setType(waypointType);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a location.
|
|
||||||
*
|
|
||||||
* @param attributes the attributes
|
|
||||||
*/
|
|
||||||
private void parseLocation(Attributes attributes) throws SAXException {
|
|
||||||
if (location != null) {
|
|
||||||
throw new SAXException(createErrorMessage("Found a location inside another one."));
|
|
||||||
}
|
|
||||||
String latitude = attributes.getValue(ATT_LAT);
|
|
||||||
String longitude = attributes.getValue(ATT_LON);
|
|
||||||
|
|
||||||
if (latitude == null || longitude == null) {
|
|
||||||
throw new SAXException(createErrorMessage("Point with no longitude or latitude."));
|
|
||||||
}
|
|
||||||
double latitudeValue;
|
|
||||||
double longitudeValue;
|
|
||||||
try {
|
|
||||||
latitudeValue = Double.parseDouble(latitude);
|
|
||||||
longitudeValue = Double.parseDouble(longitude);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw new SAXException(
|
|
||||||
createErrorMessage("Unable to parse latitude/longitude: " + latitude + "/" + longitude),
|
|
||||||
e);
|
|
||||||
}
|
|
||||||
location = createNewLocation(latitudeValue, longitudeValue, -1L);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new location
|
|
||||||
*
|
|
||||||
* @param latitude location latitude
|
|
||||||
* @param longitude location longitude
|
|
||||||
* @param time location time
|
|
||||||
*/
|
|
||||||
private Location createNewLocation(double latitude, double longitude, long time) {
|
|
||||||
Location newLocation = new Location(LocationManager.GPS_PROVIDER);
|
|
||||||
newLocation.setLatitude(latitude);
|
|
||||||
newLocation.setLongitude(longitude);
|
|
||||||
newLocation.setAltitude(0.0f);
|
|
||||||
newLocation.setTime(time);
|
|
||||||
newLocation.removeAccuracy();
|
|
||||||
newLocation.removeBearing();
|
|
||||||
newLocation.removeSpeed();
|
|
||||||
return newLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a point.
|
|
||||||
*
|
|
||||||
* @param newLocation the location
|
|
||||||
*/
|
|
||||||
private void insertPoint(Location newLocation) {
|
|
||||||
if (trackData.tripStatisticsUpdater == null) {
|
|
||||||
// Set the default location time if the newLocation does not have a time
|
|
||||||
trackData.defaultLocationTime = newLocation.getTime() == -1L ? System.currentTimeMillis()
|
|
||||||
: -1L;
|
|
||||||
trackData.tripStatisticsUpdater = new TripStatisticsUpdater(
|
|
||||||
newLocation.getTime() != -1L ? newLocation.getTime() : trackData.defaultLocationTime);
|
|
||||||
}
|
|
||||||
if (trackData.defaultLocationTime != -1L) {
|
|
||||||
newLocation.setTime(trackData.defaultLocationTime);
|
|
||||||
}
|
|
||||||
trackData.tripStatisticsUpdater.addLocation(newLocation, minRecordingDistance);
|
|
||||||
|
|
||||||
trackData.bufferedLocations[trackData.numBufferedLocations] = newLocation;
|
|
||||||
trackData.numBufferedLocations++;
|
|
||||||
trackData.numberOfLocations++;
|
|
||||||
|
|
||||||
if (trackData.numBufferedLocations >= MAX_BUFFERED_LOCATIONS) {
|
|
||||||
flushPoints(trackData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes the points to the database.
|
|
||||||
*
|
|
||||||
* @param data the track data
|
|
||||||
*/
|
|
||||||
private void flushPoints(TrackData data) {
|
|
||||||
if (data.numBufferedLocations <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
myTracksProviderUtils.bulkInsertTrackPoint(
|
|
||||||
data.bufferedLocations, data.numBufferedLocations, data.track.getId());
|
|
||||||
data.numBufferedLocations = 0;
|
|
||||||
if (data.track.getStartId() == -1L) {
|
|
||||||
data.track.setStartId(myTracksProviderUtils.getFirstTrackPointId(data.track.getId()));
|
|
||||||
}
|
|
||||||
data.track.setStopId(myTracksProviderUtils.getLastTrackPointId(data.track.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the imported track ids.
|
|
||||||
*/
|
|
||||||
private long[] getImportedTrackIds() {
|
|
||||||
long[] result = new long[tracksIds.size()];
|
|
||||||
for (int i = 0; i < result.length; i++) {
|
|
||||||
result[i] = tracksIds.get(i);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an error message.
|
|
||||||
*
|
|
||||||
* @param message the message
|
|
||||||
*/
|
|
||||||
private String createErrorMessage(String message) {
|
|
||||||
return String.format(Locale.US, "Parsing error at line: %d column: %d. %s",
|
|
||||||
locator.getLineNumber(), locator.getColumnNumber(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts the first track waypoint, the track statistics waypoint.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
*/
|
|
||||||
private void insertFirstWaypoint(Track track) {
|
|
||||||
String name = context.getString(R.string.marker_split_name_format, 0);
|
|
||||||
String category = "";
|
|
||||||
TripStatisticsUpdater updater = new TripStatisticsUpdater(
|
|
||||||
track.getTripStatistics().getStartTime());
|
|
||||||
TripStatistics tripStatistics = updater.getTripStatistics();
|
|
||||||
String description = new DescriptionGeneratorImpl(context).generateWaypointDescription(
|
|
||||||
tripStatistics);
|
|
||||||
String icon = context.getString(R.string.marker_statistics_icon_url);
|
|
||||||
double length = 0.0;
|
|
||||||
long duration = 0L;
|
|
||||||
Location waypointLocation = new Location("");
|
|
||||||
waypointLocation.setLatitude(100);
|
|
||||||
waypointLocation.setLongitude(180);
|
|
||||||
Waypoint waypoint = new Waypoint(name, description, category, icon, track.getId(),
|
|
||||||
WaypointType.STATISTICS, length, duration, -1L, -1L, waypointLocation, tripStatistics);
|
|
||||||
myTracksProviderUtils.insertWaypoint(waypoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds waypoints to a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
*/
|
|
||||||
private void addWaypoints(Track track) {
|
|
||||||
Cursor trackPointCursor = null;
|
|
||||||
try {
|
|
||||||
trackPointCursor = myTracksProviderUtils.getTrackPointCursor(track.getId(), -1L, -1, false);
|
|
||||||
if (trackPointCursor == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int waypointPosition = -1;
|
|
||||||
Waypoint waypoint = null;
|
|
||||||
int trackPointPosition = -1;
|
|
||||||
Location trackPoint = null;
|
|
||||||
TripStatisticsUpdater trackTripStatisticstrackUpdater = new TripStatisticsUpdater(
|
|
||||||
track.getTripStatistics().getStartTime());
|
|
||||||
TripStatisticsUpdater markerTripStatisticsUpdater = new TripStatisticsUpdater(
|
|
||||||
track.getTripStatistics().getStartTime());
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (waypoint == null) {
|
|
||||||
waypointPosition++;
|
|
||||||
waypoint = waypointPosition < waypoints.size() ? waypoints.get(waypointPosition) : null;
|
|
||||||
if (waypoint == null) {
|
|
||||||
// No more waypoints
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (trackPoint == null) {
|
|
||||||
trackPointPosition++;
|
|
||||||
trackPoint = trackPointCursor.moveToPosition(trackPointPosition) ? myTracksProviderUtils
|
|
||||||
.createTrackPoint(trackPointCursor)
|
|
||||||
: null;
|
|
||||||
if (trackPoint == null) {
|
|
||||||
// No more track points. Ignore the rest of the waypoints.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
trackTripStatisticstrackUpdater.addLocation(trackPoint, minRecordingDistance);
|
|
||||||
markerTripStatisticsUpdater.addLocation(trackPoint, minRecordingDistance);
|
|
||||||
}
|
|
||||||
if (waypoint.getLocation().getTime() > trackPoint.getTime()) {
|
|
||||||
trackPoint = null;
|
|
||||||
} else if (waypoint.getLocation().getTime() < trackPoint.getTime()) {
|
|
||||||
waypoint = null;
|
|
||||||
} else {
|
|
||||||
// The waypoint location time matches the track point time
|
|
||||||
if (trackPoint.getLatitude() == waypoint.getLocation().getLatitude()
|
|
||||||
&& trackPoint.getLongitude() == waypoint.getLocation().getLongitude()) {
|
|
||||||
|
|
||||||
// Get tripStatistics, description, and icon
|
|
||||||
TripStatistics tripStatistics;
|
|
||||||
String description;
|
|
||||||
String icon;
|
|
||||||
if (waypoint.getType() == WaypointType.STATISTICS) {
|
|
||||||
tripStatistics = markerTripStatisticsUpdater.getTripStatistics();
|
|
||||||
markerTripStatisticsUpdater = new TripStatisticsUpdater(trackPoint.getTime());
|
|
||||||
description = new DescriptionGeneratorImpl(context).generateWaypointDescription(
|
|
||||||
tripStatistics);
|
|
||||||
icon = context.getString(R.string.marker_statistics_icon_url);
|
|
||||||
} else {
|
|
||||||
tripStatistics = null;
|
|
||||||
description = waypoint.getDescription();
|
|
||||||
icon = context.getString(R.string.marker_waypoint_icon_url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get length and duration
|
|
||||||
double length = trackTripStatisticstrackUpdater.getTripStatistics().getTotalDistance();
|
|
||||||
long duration = trackTripStatisticstrackUpdater.getTripStatistics().getTotalTime();
|
|
||||||
|
|
||||||
// Insert waypoint
|
|
||||||
Waypoint newWaypoint = new Waypoint(waypoint.getName(), description,
|
|
||||||
waypoint.getCategory(), icon, track.getId(), waypoint.getType(), length, duration,
|
|
||||||
-1L, -1L, trackPoint, tripStatistics);
|
|
||||||
myTracksProviderUtils.insertWaypoint(newWaypoint);
|
|
||||||
}
|
|
||||||
waypoint = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (trackPointCursor != null) {
|
|
||||||
trackPointCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,36 +35,39 @@ import android.support.v4.app.TaskStackBuilder;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity to import GPX files from the SD card. Optionally to import one
|
* An activity to import files from the external storage. Optionally to import
|
||||||
* GPX file and display it in My Tracks.
|
* one specific file.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class ImportActivity extends Activity {
|
public class ImportActivity extends Activity {
|
||||||
|
|
||||||
public static final String EXTRA_IMPORT_ALL = "import_all";
|
public static final String EXTRA_IMPORT_ALL = "import_all";
|
||||||
|
public static final String EXTRA_TRACK_FILE_FORMAT = "track_file_format";
|
||||||
|
|
||||||
private static final String TAG = ImportActivity.class.getSimpleName();
|
private static final String TAG = ImportActivity.class.getSimpleName();
|
||||||
|
|
||||||
private static final int DIALOG_PROGRESS_ID = 0;
|
private static final int DIALOG_PROGRESS_ID = 0;
|
||||||
private static final int DIALOG_RESULT_ID = 1;
|
private static final int DIALOG_RESULT_ID = 1;
|
||||||
|
|
||||||
private ImportAsyncTask importAsyncTask;
|
private ImportAsyncTask importAsyncTask;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
private boolean importAll;
|
private boolean importAll;
|
||||||
|
private TrackFileFormat trackFileFormat;
|
||||||
// path on the SD card to import
|
|
||||||
|
// the path on the external storage to import
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
// number of succesfully imported files
|
// the number of successfully imported files
|
||||||
private int successCount;
|
private int successCount;
|
||||||
|
|
||||||
// number of files to import
|
// the number of files to import
|
||||||
private int totalCount;
|
private int totalCount;
|
||||||
|
|
||||||
// last successfully imported track id
|
// the last successfully imported track id
|
||||||
private long trackId;
|
private long trackId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -73,8 +76,25 @@ public class ImportActivity extends Activity {
|
|||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false);
|
importAll = intent.getBooleanExtra(EXTRA_IMPORT_ALL, false);
|
||||||
|
trackFileFormat = intent.getParcelableExtra(EXTRA_TRACK_FILE_FORMAT);
|
||||||
|
if (trackFileFormat == null) {
|
||||||
|
trackFileFormat = TrackFileFormat.GPX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FileUtils.isExternalStorageAvailable()) {
|
||||||
|
Toast.makeText(this, R.string.external_storage_not_available, Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (importAll) {
|
if (importAll) {
|
||||||
path = FileUtils.buildExternalDirectoryPath("gpx");
|
path = FileUtils.buildExternalDirectoryPath(
|
||||||
|
trackFileFormat == TrackFileFormat.KML ? "kml" : "gpx");
|
||||||
|
if (!FileUtils.isDirectory(new File(path))) {
|
||||||
|
Toast.makeText(this, getString(R.string.import_no_directory, path), Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) {
|
if (!(Intent.ACTION_ATTACH_DATA.equals(action) || Intent.ACTION_VIEW.equals(action))) {
|
||||||
@@ -97,7 +117,7 @@ public class ImportActivity extends Activity {
|
|||||||
importAsyncTask = (ImportAsyncTask) retained;
|
importAsyncTask = (ImportAsyncTask) retained;
|
||||||
importAsyncTask.setActivity(this);
|
importAsyncTask.setActivity(this);
|
||||||
} else {
|
} else {
|
||||||
importAsyncTask = new ImportAsyncTask(this, importAll, path);
|
importAsyncTask = new ImportAsyncTask(this, importAll, trackFileFormat, path);
|
||||||
importAsyncTask.execute();
|
importAsyncTask.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,41 +132,36 @@ public class ImportActivity extends Activity {
|
|||||||
protected Dialog onCreateDialog(int id) {
|
protected Dialog onCreateDialog(int id) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case DIALOG_PROGRESS_ID:
|
case DIALOG_PROGRESS_ID:
|
||||||
progressDialog = DialogUtils.createHorizontalProgressDialog(this,
|
progressDialog = DialogUtils.createHorizontalProgressDialog(
|
||||||
R.string.external_storage_import_progress_message,
|
this, R.string.import_progress_message, new DialogInterface.OnCancelListener() {
|
||||||
new DialogInterface.OnCancelListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel(DialogInterface dialog) {
|
public void onCancel(DialogInterface dialog) {
|
||||||
importAsyncTask.cancel(true);
|
importAsyncTask.cancel(true);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
});
|
}, path);
|
||||||
return progressDialog;
|
return progressDialog;
|
||||||
case DIALOG_RESULT_ID:
|
case DIALOG_RESULT_ID:
|
||||||
String message;
|
String message;
|
||||||
if (successCount == 0) {
|
if (totalCount == 0) {
|
||||||
message = getString(R.string.external_storage_import_error_no_file, path);
|
message = getString(R.string.import_no_file, path);
|
||||||
} else {
|
} else {
|
||||||
String totalFiles = getResources()
|
String totalFiles = getResources()
|
||||||
.getQuantityString(R.plurals.importGpxFiles, totalCount, totalCount);
|
.getQuantityString(R.plurals.importFiles, totalCount, totalCount);
|
||||||
message = getString(
|
message = getString(R.string.import_success, successCount, totalFiles, path);
|
||||||
R.string.external_storage_import_success_count, successCount, totalFiles, path);
|
|
||||||
}
|
}
|
||||||
return new AlertDialog.Builder(this)
|
return new AlertDialog.Builder(this).setCancelable(true)
|
||||||
.setCancelable(true)
|
.setMessage(message).setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
.setMessage(message)
|
@Override
|
||||||
.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
|
||||||
@Override
|
|
||||||
public void onCancel(DialogInterface dialog) {
|
public void onCancel(DialogInterface dialog) {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
})
|
}).setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
||||||
.setPositiveButton(R.string.generic_ok, new DialogInterface.OnClickListener() {
|
@Override
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
if (!importAll && trackId != -1L) {
|
if (!importAll && trackId != -1L) {
|
||||||
Intent intent = IntentUtils
|
Intent intent = IntentUtils.newIntent(
|
||||||
.newIntent(ImportActivity.this, TrackDetailActivity.class)
|
ImportActivity.this, TrackDetailActivity.class)
|
||||||
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
.putExtra(TrackDetailActivity.EXTRA_TRACK_ID, trackId);
|
||||||
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(ImportActivity.this);
|
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(ImportActivity.this);
|
||||||
taskStackBuilder.addNextIntent(intent);
|
taskStackBuilder.addNextIntent(intent);
|
||||||
@@ -154,8 +169,7 @@ public class ImportActivity extends Activity {
|
|||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
})
|
}).create();
|
||||||
.create();
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -163,23 +177,17 @@ public class ImportActivity extends Activity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes when the associated AsyncTask completes.
|
* Invokes when the associated AsyncTask completes.
|
||||||
*
|
*
|
||||||
* @param success true if the AsyncTask is successful
|
* @param aSuccessCount the number of files successfully imported
|
||||||
* @param imported the number of files successfully imported
|
* @param aTotalCount the total number of files to import
|
||||||
* @param total the total number of files to import
|
* @param aTrackId the last successfully imported track id
|
||||||
* @param id the last successfully imported track id
|
|
||||||
*/
|
*/
|
||||||
public void onAsyncTaskCompleted(boolean success, int imported, int total, long id) {
|
public void onAsyncTaskCompleted(int aSuccessCount, int aTotalCount, long aTrackId) {
|
||||||
successCount = imported;
|
successCount = aSuccessCount;
|
||||||
totalCount = total;
|
totalCount = aTotalCount;
|
||||||
trackId = id;
|
trackId = aTrackId;
|
||||||
removeDialog(DIALOG_PROGRESS_ID);
|
removeDialog(DIALOG_PROGRESS_ID);
|
||||||
if (success) {
|
showDialog(DIALOG_RESULT_ID);
|
||||||
showDialog(DIALOG_RESULT_ID);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, R.string.external_storage_import_error, Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,7 +199,7 @@ public class ImportActivity extends Activity {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the progress dialog value.
|
* Sets the progress dialog value.
|
||||||
*
|
*
|
||||||
* @param number the number of files imported
|
* @param number the number of files imported
|
||||||
* @param max the maximum number of files
|
* @param max the maximum number of files
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
|
||||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
||||||
import com.google.android.apps.mytracks.util.SystemUtils;
|
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -28,18 +26,12 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
|
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AsyncTask to import GPX files from the SD card.
|
* AsyncTask to import files from the external storage.
|
||||||
*
|
*
|
||||||
* @author Jimmy Shih
|
* @author Jimmy Shih
|
||||||
*/
|
*/
|
||||||
public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
||||||
@@ -48,20 +40,17 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
private ImportActivity importActivity;
|
private ImportActivity importActivity;
|
||||||
private final boolean importAll;
|
private final boolean importAll;
|
||||||
|
private final TrackFileFormat trackFileFormat;
|
||||||
private final String path;
|
private final String path;
|
||||||
private final MyTracksProviderUtils myTracksProviderUtils;
|
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
|
|
||||||
// true if the AsyncTask result is success
|
|
||||||
private boolean success;
|
|
||||||
|
|
||||||
// true if the AsyncTask has completed
|
// true if the AsyncTask has completed
|
||||||
private boolean completed;
|
private boolean completed;
|
||||||
|
|
||||||
// number of files successfully imported
|
// the number of files successfully imported
|
||||||
private int successCount;
|
private int successCount;
|
||||||
|
|
||||||
// number of files to import
|
// the number of files to import
|
||||||
private int totalCount;
|
private int totalCount;
|
||||||
|
|
||||||
// the last successfully imported track id
|
// the last successfully imported track id
|
||||||
@@ -69,18 +58,19 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an AsyncTask.
|
* Creates an AsyncTask.
|
||||||
*
|
*
|
||||||
* @param importActivity the activity currently associated with this AsyncTask
|
* @param importActivity the activity currently associated with this AsyncTask
|
||||||
* @param importAll true to import all GPX files
|
* @param importAll true to import all GPX files
|
||||||
|
* @param trackFileFormat the track file format
|
||||||
* @param path path to import GPX files
|
* @param path path to import GPX files
|
||||||
*/
|
*/
|
||||||
public ImportAsyncTask(ImportActivity importActivity, boolean importAll, String path) {
|
public ImportAsyncTask(ImportActivity importActivity, boolean importAll,
|
||||||
|
TrackFileFormat trackFileFormat, String path) {
|
||||||
this.importActivity = importActivity;
|
this.importActivity = importActivity;
|
||||||
this.importAll = importAll;
|
this.importAll = importAll;
|
||||||
|
this.trackFileFormat = trackFileFormat;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
|
|
||||||
myTracksProviderUtils = MyTracksProviderUtils.Factory.get(importActivity);
|
|
||||||
|
|
||||||
// Get the wake lock if not recording or paused
|
// Get the wake lock if not recording or paused
|
||||||
if (PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key)
|
if (PreferencesUtils.getLong(importActivity, R.string.recording_track_id_key)
|
||||||
== PreferencesUtils.RECORDING_TRACK_ID_DEFAULT || PreferencesUtils.getBoolean(
|
== PreferencesUtils.RECORDING_TRACK_ID_DEFAULT || PreferencesUtils.getBoolean(
|
||||||
@@ -89,7 +79,6 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(importActivity, wakeLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
success = false;
|
|
||||||
completed = false;
|
completed = false;
|
||||||
successCount = 0;
|
successCount = 0;
|
||||||
totalCount = 0;
|
totalCount = 0;
|
||||||
@@ -98,13 +87,13 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current {@link ImportActivity} associated with this AyncTask.
|
* Sets the current {@link ImportActivity} associated with this AyncTask.
|
||||||
*
|
*
|
||||||
* @param importActivity the current {@link ImportActivity}, can be null
|
* @param importActivity the current {@link ImportActivity}, can be null
|
||||||
*/
|
*/
|
||||||
public void setActivity(ImportActivity importActivity) {
|
public void setActivity(ImportActivity importActivity) {
|
||||||
this.importActivity = importActivity;
|
this.importActivity = importActivity;
|
||||||
if (completed && importActivity != null) {
|
if (completed && importActivity != null) {
|
||||||
importActivity.onAsyncTaskCompleted(success, successCount, totalCount, trackId);
|
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,10 +107,6 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... params) {
|
protected Boolean doInBackground(Void... params) {
|
||||||
try {
|
try {
|
||||||
if (!FileUtils.isSdCardAvailable()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<File> files = getFiles();
|
List<File> files = getFiles();
|
||||||
totalCount = files.size();
|
totalCount = files.size();
|
||||||
if (totalCount == 0) {
|
if (totalCount == 0) {
|
||||||
@@ -133,8 +118,7 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
// If cancelled, return true to show the number of files imported
|
// If cancelled, return true to show the number of files imported
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
File file = files.get(i);
|
if (importFile(files.get(i))) {
|
||||||
if (importFile(file)) {
|
|
||||||
successCount++;
|
successCount++;
|
||||||
}
|
}
|
||||||
publishProgress(i + 1, totalCount);
|
publishProgress(i + 1, totalCount);
|
||||||
@@ -157,46 +141,36 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
success = result;
|
|
||||||
completed = true;
|
completed = true;
|
||||||
if (importActivity != null) {
|
if (importActivity != null) {
|
||||||
importActivity.onAsyncTaskCompleted(success, successCount, totalCount, trackId);
|
importActivity.onAsyncTaskCompleted(successCount, totalCount, trackId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports a GPX file.
|
* Imports a file.
|
||||||
*
|
*
|
||||||
* @param file the file
|
* @param file the file
|
||||||
*/
|
*/
|
||||||
private boolean importFile(final File file) {
|
private boolean importFile(final File file) {
|
||||||
try {
|
try {
|
||||||
int minRecordingDistance = PreferencesUtils.getInt(importActivity,
|
AbstractImporter importer = trackFileFormat == TrackFileFormat.KML ? new KmlImporter(
|
||||||
R.string.min_recording_distance_key, PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
importActivity)
|
||||||
long trackIds[] = GpxImporter.importGPXFile(importActivity,
|
: new GpxImporter(importActivity);
|
||||||
new FileInputStream(file), myTracksProviderUtils, minRecordingDistance);
|
long trackIds[] = importer.importFile(new FileInputStream(file));
|
||||||
int length = trackIds.length;
|
int length = trackIds.length;
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
trackId = trackIds[length - 1];
|
trackId = trackIds[length - 1];
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (Exception e) {
|
||||||
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
|
|
||||||
return false;
|
|
||||||
} catch (ParserConfigurationException e) {
|
|
||||||
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
|
|
||||||
return false;
|
|
||||||
} catch (SAXException e) {
|
|
||||||
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
|
|
||||||
return false;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
|
Log.d(TAG, "file: " + file.getAbsolutePath(), e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of GPX files. If importAll is true, returns a list of GPX files
|
* Gets a list of files. If importAll is true, returns a list of the files
|
||||||
* under the path directory. If importAll is false, returns a list containing
|
* under the path directory. If importAll is false, returns a list containing
|
||||||
* just the path file.
|
* just the path file.
|
||||||
*/
|
*/
|
||||||
@@ -207,7 +181,8 @@ public class ImportAsyncTask extends AsyncTask<Void, Integer, Boolean> {
|
|||||||
File[] candidates = file.listFiles();
|
File[] candidates = file.listFiles();
|
||||||
if (candidates != null) {
|
if (candidates != null) {
|
||||||
for (File candidate : candidates) {
|
for (File candidate : candidates) {
|
||||||
if (!candidate.isDirectory() && candidate.getName().endsWith(".gpx")) {
|
if (!candidate.isDirectory() && candidate.getName()
|
||||||
|
.endsWith(trackFileFormat == TrackFileFormat.KML ? ".kml" : ".gpx")) {
|
||||||
files.add(candidate);
|
files.add(candidate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
|
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||||
|
import com.google.android.apps.mytracks.content.Waypoint.WaypointType;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.xml.sax.Attributes;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports a KML file.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class KmlImporter extends AbstractImporter {
|
||||||
|
|
||||||
|
private static final String CADENCE = "cadence";
|
||||||
|
private static final String HEART_RATE = "heart_rate";
|
||||||
|
private static final String POWER = "power";
|
||||||
|
|
||||||
|
private static final String STATISTICS_STYLE = "#statistics";
|
||||||
|
private static final String WAYPOINT_STYLE = "#waypoint";
|
||||||
|
|
||||||
|
private static final String TAG_COORDINATES = "coordinates";
|
||||||
|
private static final String TAG_DESCRIPTION = "description";
|
||||||
|
private static final String TAG_GX_COORD = "gx:coord";
|
||||||
|
private static final String TAG_GX_MULTI_TRACK = "gx:MultiTrack";
|
||||||
|
private static final String TAG_GX_SIMPLE_ARRAY_DATA = "gx:SimpleArrayData";
|
||||||
|
private static final String TAG_GX_TRACK = "gx:Track";
|
||||||
|
private static final String TAG_GX_VALUE = "gx:value";
|
||||||
|
private static final String TAG_KML = "kml";
|
||||||
|
private static final String TAG_NAME = "name";
|
||||||
|
private static final String TAG_PLACEMARK = "Placemark";
|
||||||
|
private static final String TAG_STYLE_URL = "styleUrl";
|
||||||
|
private static final String TAG_VALUE = "value";
|
||||||
|
private static final String TAG_WHEN = "when";
|
||||||
|
|
||||||
|
private static final String ATTRIBUTE_NAME = "name";
|
||||||
|
|
||||||
|
private String sensorName;
|
||||||
|
private ArrayList<Location> locationList;
|
||||||
|
private ArrayList<Integer> cadenceList;
|
||||||
|
private ArrayList<Integer> heartRateList;
|
||||||
|
private ArrayList<Integer> powerList;
|
||||||
|
|
||||||
|
public KmlImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public KmlImporter(Context context, MyTracksProviderUtils myTracksProviderUtils) {
|
||||||
|
super(context, myTracksProviderUtils);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startElement(String uri, String localName, String tag, Attributes attributes)
|
||||||
|
throws SAXException {
|
||||||
|
if (tag.equals(TAG_PLACEMARK)) {
|
||||||
|
onWaypointStart();
|
||||||
|
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
|
||||||
|
onTrackStart();
|
||||||
|
} else if (tag.equals(TAG_GX_TRACK)) {
|
||||||
|
onTrackSegmentStart();
|
||||||
|
} else if (tag.equals(TAG_GX_SIMPLE_ARRAY_DATA)) {
|
||||||
|
onSensorDataStart(attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endElement(String uri, String localName, String tag) throws SAXException {
|
||||||
|
if (tag.equals(TAG_KML)) {
|
||||||
|
onFileEnd();
|
||||||
|
} else if (tag.equals(TAG_PLACEMARK)) {
|
||||||
|
onWaypointEnd();
|
||||||
|
} else if (localName.equals(TAG_COORDINATES)) {
|
||||||
|
onWaypointLocationEnd();
|
||||||
|
} else if (tag.equals(TAG_GX_MULTI_TRACK)) {
|
||||||
|
onTrackEnd();
|
||||||
|
} else if (tag.equals(TAG_GX_TRACK)) {
|
||||||
|
onTrackSegmentEnd();
|
||||||
|
} else if (tag.equals(TAG_GX_COORD)) {
|
||||||
|
onTrackPointEnd();
|
||||||
|
} else if (tag.equals(TAG_GX_VALUE)) {
|
||||||
|
onSensorValueEnd();
|
||||||
|
} else if (tag.equals(TAG_NAME)) {
|
||||||
|
if (content != null) {
|
||||||
|
name = content.trim();
|
||||||
|
}
|
||||||
|
} else if (localName.equals(TAG_DESCRIPTION)) {
|
||||||
|
if (content != null) {
|
||||||
|
description = content.trim();
|
||||||
|
}
|
||||||
|
} else if (localName.equals(TAG_VALUE)) {
|
||||||
|
if (content != null) {
|
||||||
|
category = content.trim();
|
||||||
|
}
|
||||||
|
} else if (localName.equals(TAG_WHEN)) {
|
||||||
|
if (content != null) {
|
||||||
|
time = content.trim();
|
||||||
|
}
|
||||||
|
} else if (localName.equals(TAG_STYLE_URL)) {
|
||||||
|
if (content != null) {
|
||||||
|
waypointType = content.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset element content
|
||||||
|
content = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On waypoint start.
|
||||||
|
*/
|
||||||
|
private void onWaypointStart() {
|
||||||
|
// Reset all Placemark variables
|
||||||
|
name = null;
|
||||||
|
description = null;
|
||||||
|
category = null;
|
||||||
|
latitude = null;
|
||||||
|
longitude = null;
|
||||||
|
altitude = DEFAULT_ALTITUDE;
|
||||||
|
time = null;
|
||||||
|
waypointType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On waypoint end.
|
||||||
|
*/
|
||||||
|
private void onWaypointEnd() throws SAXException {
|
||||||
|
// Add a waypoint if the waypointType matches
|
||||||
|
WaypointType type = null;
|
||||||
|
if (WAYPOINT_STYLE.equals(waypointType)) {
|
||||||
|
type = WaypointType.WAYPOINT;
|
||||||
|
} else if (STATISTICS_STYLE.equals(waypointType)) {
|
||||||
|
type = WaypointType.STATISTICS;
|
||||||
|
}
|
||||||
|
if (type == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addWaypoint(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On waypoint location end.
|
||||||
|
*/
|
||||||
|
private void onWaypointLocationEnd() {
|
||||||
|
if (content != null) {
|
||||||
|
String parts[] = content.trim().split(",");
|
||||||
|
if (parts.length != 2 && parts.length != 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
longitude = parts[0];
|
||||||
|
latitude = parts[1];
|
||||||
|
if (parts.length == 3) {
|
||||||
|
altitude = parts[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTrackSegmentStart() {
|
||||||
|
super.onTrackSegmentStart();
|
||||||
|
locationList = new ArrayList<Location>();
|
||||||
|
powerList = new ArrayList<Integer>();
|
||||||
|
cadenceList = new ArrayList<Integer>();
|
||||||
|
heartRateList = new ArrayList<Integer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On track segment end.
|
||||||
|
*/
|
||||||
|
private void onTrackSegmentEnd() {
|
||||||
|
// Close a track segment by inserting the segment locations
|
||||||
|
boolean hasPower = powerList.size() == locationList.size();
|
||||||
|
boolean hasCadence = cadenceList.size() == locationList.size();
|
||||||
|
boolean hasHeartRate = heartRateList.size() == locationList.size();
|
||||||
|
|
||||||
|
for (int i = 0; i < locationList.size(); i++) {
|
||||||
|
Location location = locationList.get(i);
|
||||||
|
|
||||||
|
if (!hasPower && !hasCadence && !hasHeartRate) {
|
||||||
|
insertTrackPoint(location);
|
||||||
|
} else {
|
||||||
|
SensorDataSet.Builder builder = Sensor.SensorDataSet.newBuilder();
|
||||||
|
if (hasPower) {
|
||||||
|
builder.setPower(Sensor.SensorData.newBuilder()
|
||||||
|
.setValue(powerList.get(i)).setState(Sensor.SensorState.SENDING));
|
||||||
|
}
|
||||||
|
if (hasCadence) {
|
||||||
|
builder.setCadence(Sensor.SensorData.newBuilder()
|
||||||
|
.setValue(cadenceList.get(i)).setState(Sensor.SensorState.SENDING));
|
||||||
|
}
|
||||||
|
if (hasHeartRate) {
|
||||||
|
builder.setHeartRate(Sensor.SensorData.newBuilder()
|
||||||
|
.setValue(heartRateList.get(i)).setState(Sensor.SensorState.SENDING));
|
||||||
|
}
|
||||||
|
SensorDataSet sensorDataSet = builder.setCreationTime(location.getTime()).build();
|
||||||
|
MyTracksLocation myTracksLocation = new MyTracksLocation(location, sensorDataSet);
|
||||||
|
insertTrackPoint(myTracksLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On track point end. gx:coord end tag.
|
||||||
|
*/
|
||||||
|
private void onTrackPointEnd() throws SAXException {
|
||||||
|
// Add location to locationList
|
||||||
|
if (content == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String parts[] = content.trim().split(" ");
|
||||||
|
if (parts.length != 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
longitude = parts[0];
|
||||||
|
latitude = parts[1];
|
||||||
|
altitude = parts[2];
|
||||||
|
Location location = getTrackPoint();
|
||||||
|
if (location == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locationList.add(location);
|
||||||
|
time = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On sensor data start. gx:SimpleArrayData start tag.
|
||||||
|
*
|
||||||
|
* @param attributes
|
||||||
|
*/
|
||||||
|
private void onSensorDataStart(Attributes attributes) {
|
||||||
|
sensorName = attributes.getValue(ATTRIBUTE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On sensor value end. gx:value end tag.
|
||||||
|
*/
|
||||||
|
private void onSensorValueEnd() throws SAXException {
|
||||||
|
if (content != null) {
|
||||||
|
int value;
|
||||||
|
try {
|
||||||
|
value = Integer.parseInt(content.trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new SAXException(createErrorMessage("Unable to parse gx:value:" + content), e);
|
||||||
|
}
|
||||||
|
if (POWER.equals(sensorName)) {
|
||||||
|
powerList.add(value);
|
||||||
|
} else if (HEART_RATE.equals(sensorName)) {
|
||||||
|
heartRateList.add(value);
|
||||||
|
} else if (CADENCE.equals(sensorName)) {
|
||||||
|
cadenceList.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -303,6 +303,8 @@ public class KmlTrackWriter implements TrackFormatWriter {
|
|||||||
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
printWriter.println("<name>" + StringUtils.formatCData(name) + "</name>");
|
||||||
printWriter.println(
|
printWriter.println(
|
||||||
"<description>" + StringUtils.formatCData(description) + "</description>");
|
"<description>" + StringUtils.formatCData(description) + "</description>");
|
||||||
|
printWriter.println("<TimeStamp><when>"
|
||||||
|
+ StringUtils.formatDateTimeIso8601(location.getTime()) + "</when></TimeStamp>");
|
||||||
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
|
printWriter.println("<styleUrl>#" + styleName + "</styleUrl>");
|
||||||
writeCategory(category);
|
writeCategory(category);
|
||||||
printWriter.println("<Point>");
|
printWriter.println("<Point>");
|
||||||
|
|||||||
@@ -194,9 +194,8 @@ class TrackWriterImpl implements TrackWriter {
|
|||||||
* Checks and returns whether we're ready to create the output file.
|
* Checks and returns whether we're ready to create the output file.
|
||||||
*/
|
*/
|
||||||
protected boolean canWriteFile() {
|
protected boolean canWriteFile() {
|
||||||
if (!FileUtils.isSdCardAvailable()) {
|
if (!FileUtils.isExternalStorageWriteable()) {
|
||||||
Log.i(Constants.TAG, "Could not find SD card.");
|
errorMessage = R.string.external_storage_not_writeable;
|
||||||
errorMessage = R.string.external_storage_error_no_storage;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,29 +37,45 @@ public class FileUtils {
|
|||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int MAX_FAT32_PATH_LENGTH = 260;
|
static final int MAX_FAT32_PATH_LENGTH = 260;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the SD card is available.
|
* Returns true if the external storage is available.
|
||||||
*/
|
*/
|
||||||
public static boolean isSdCardAvailable() {
|
public static boolean isExternalStorageAvailable() {
|
||||||
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
|
String state = Environment.getExternalStorageState();
|
||||||
|
return Environment.MEDIA_MOUNTED.equals(state)
|
||||||
|
|| Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the external storage is writable.
|
||||||
|
*/
|
||||||
|
public static boolean isExternalStorageWriteable() {
|
||||||
|
String state = Environment.getExternalStorageState();
|
||||||
|
return Environment.MEDIA_MOUNTED.equals(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the directory exists.
|
||||||
|
*
|
||||||
|
* @param dir the directory
|
||||||
|
*/
|
||||||
|
public static boolean isDirectory(File dir) {
|
||||||
|
return dir.exists() && dir.isDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures the given directory exists by creating it and its parents if
|
* Ensures the directory exists by creating it and its parents if
|
||||||
* necessary.
|
* necessary.
|
||||||
*
|
*
|
||||||
* @return whether the directory exists (either already existed or was
|
* @return whether the directory exists (either already existed or was
|
||||||
* successfully created)
|
* successfully created)
|
||||||
*/
|
*/
|
||||||
public static boolean ensureDirectoryExists(File dir) {
|
public static boolean ensureDirectoryExists(File dir) {
|
||||||
if (dir.exists() && dir.isDirectory()) {
|
if (isDirectory(dir)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (dir.mkdirs()) {
|
return dir.mkdirs();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.eq;
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.expect;
|
||||||
|
|
||||||
|
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.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
||||||
|
import com.google.android.testing.mocking.AndroidMock;
|
||||||
|
import com.google.android.testing.mocking.UsesMocks;
|
||||||
|
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.SimpleTimeZone;
|
||||||
|
|
||||||
|
import org.easymock.Capture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for testing importer.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih.
|
||||||
|
*/
|
||||||
|
public class AbstractTestImporter extends AndroidTestCase {
|
||||||
|
|
||||||
|
protected static final String TRACK_NAME_0 = "blablub";
|
||||||
|
protected static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
||||||
|
|
||||||
|
protected static final String TRACK_NAME_1 = "another track";
|
||||||
|
protected static final String TRACK_DESCRIPTION_1 = "another description";
|
||||||
|
|
||||||
|
protected static final double TRACK_LATITUDE = 48.768364;
|
||||||
|
protected static final double TRACK_LONGITUDE = 9.177886;
|
||||||
|
protected static final double TRACK_ELEVATION = 324.0;
|
||||||
|
|
||||||
|
protected static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
||||||
|
protected static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
|
||||||
|
protected static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
|
||||||
|
protected static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
|
||||||
|
|
||||||
|
protected static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat(
|
||||||
|
"yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
||||||
|
protected static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat(
|
||||||
|
"yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
||||||
|
|
||||||
|
static {
|
||||||
|
/*
|
||||||
|
* We can't omit the timezones in the test, otherwise it'll use the local
|
||||||
|
* timezone and fail depending on where the test runner is.
|
||||||
|
*/
|
||||||
|
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
|
||||||
|
DATE_FORMAT_0.setTimeZone(utc);
|
||||||
|
DATE_FORMAT_1.setTimeZone(utc);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final long TRACK_ID_0 = 1;
|
||||||
|
protected static final long TRACK_ID_1 = 2;
|
||||||
|
protected static final long TRACK_POINT_ID_0 = 1;
|
||||||
|
protected static final long TRACK_POINT_ID_1 = 2;
|
||||||
|
protected static final long TRACK_POINT_ID_3 = 4;
|
||||||
|
protected static final long WAYPOINT_ID_0 = 1;
|
||||||
|
|
||||||
|
protected static final Uri TRACK_ID_0_URI = ContentUris.appendId(
|
||||||
|
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
|
||||||
|
protected static final Uri TRACK_ID_1_URI = ContentUris.appendId(
|
||||||
|
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build();
|
||||||
|
protected static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(
|
||||||
|
WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
||||||
|
|
||||||
|
protected MyTracksProviderUtils myTracksProviderUtils;
|
||||||
|
|
||||||
|
private Factory oldMyTracksProviderUtilsFactory;
|
||||||
|
|
||||||
|
@UsesMocks(MyTracksProviderUtils.class)
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
myTracksProviderUtils = AndroidMock.createMock(MyTracksProviderUtils.class);
|
||||||
|
oldMyTracksProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(
|
||||||
|
myTracksProviderUtils);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
TestingProviderUtilsFactory.restoreOldFactory(oldMyTracksProviderUtilsFactory);
|
||||||
|
super.tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Location createLocation(int index, long time) {
|
||||||
|
Location location = new Location(LocationManager.GPS_PROVIDER);
|
||||||
|
location.setLatitude(TRACK_LATITUDE + index);
|
||||||
|
location.setLongitude(TRACK_LONGITUDE + index);
|
||||||
|
location.setAltitude(TRACK_ELEVATION + index);
|
||||||
|
location.setTime(time);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expects the first track point to be added.
|
||||||
|
*
|
||||||
|
* @param location the location
|
||||||
|
* @param trackId the track id
|
||||||
|
* @param trackPointId the track point id
|
||||||
|
*/
|
||||||
|
protected void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
|
||||||
|
expect(myTracksProviderUtils.bulkInsertTrackPoint(
|
||||||
|
location != null ? LocationsMatcher.eqLoc(location) : (Location[]) AndroidMock.anyObject(),
|
||||||
|
eq(1), eq(trackId))).andReturn(1);
|
||||||
|
expect(myTracksProviderUtils.getFirstTrackPointId(trackId)).andReturn(trackPointId);
|
||||||
|
expect(myTracksProviderUtils.getLastTrackPointId(trackId)).andReturn(trackPointId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expects the track to be updated.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @param lastTrack true if it is the last track in the gpx
|
||||||
|
* @param trackId the track id
|
||||||
|
*/
|
||||||
|
protected void expectUpdateTrack(Capture<Track> track, boolean lastTrack, long trackId) {
|
||||||
|
myTracksProviderUtils.updateTrack(AndroidMock.capture(track));
|
||||||
|
expect(myTracksProviderUtils.insertWaypoint((Waypoint) AndroidMock.anyObject()))
|
||||||
|
.andReturn(WAYPOINT_ID_O_URI);
|
||||||
|
if (lastTrack) {
|
||||||
|
// Return null to not add waypoints
|
||||||
|
expect(myTracksProviderUtils.getTrack(trackId)).andReturn(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void verifyTrack(Track track, String name, String description, long time) {
|
||||||
|
assertEquals(name, track.getName());
|
||||||
|
assertEquals(description, track.getDescription());
|
||||||
|
if (time != -1L) {
|
||||||
|
assertEquals(time, track.getTripStatistics().getStartTime());
|
||||||
|
}
|
||||||
|
assertNotSame(-1, track.getStartId());
|
||||||
|
assertNotSame(-1, track.getStopId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,78 +13,29 @@
|
|||||||
* License for the specific language governing permissions and limitations under
|
* License for the specific language governing permissions and limitations under
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
import static com.google.android.testing.mocking.AndroidMock.eq;
|
import static com.google.android.testing.mocking.AndroidMock.eq;
|
||||||
import static com.google.android.testing.mocking.AndroidMock.expect;
|
import static com.google.android.testing.mocking.AndroidMock.expect;
|
||||||
|
|
||||||
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.Track;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
|
||||||
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
|
||||||
import com.google.android.apps.mytracks.util.PreferencesUtils;
|
|
||||||
import com.google.android.testing.mocking.AndroidMock;
|
import com.google.android.testing.mocking.AndroidMock;
|
||||||
import com.google.android.testing.mocking.UsesMocks;
|
|
||||||
|
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.SimpleTimeZone;
|
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
|
|
||||||
import org.easymock.Capture;
|
import org.easymock.Capture;
|
||||||
import org.easymock.IArgumentMatcher;
|
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the GPX importer.
|
* Tests for {@link GpxImporter}.
|
||||||
*
|
*
|
||||||
* @author Steffen Horlacher
|
* @author Steffen Horlacher
|
||||||
*/
|
*/
|
||||||
public class GpxImporterTest extends AndroidTestCase {
|
public class GpxImporterTest extends AbstractTestImporter {
|
||||||
|
|
||||||
private static final String TRACK_NAME_0 = "blablub";
|
|
||||||
private static final String TRACK_DESCRIPTION_0 = "s'Laebe isch koi Schlotzer";
|
|
||||||
|
|
||||||
private static final String TRACK_NAME_1 = "another track";
|
|
||||||
private static final String TRACK_DESCRIPTION_1 = "another description";
|
|
||||||
|
|
||||||
private static final double TRACK_LATITUDE = 48.768364;
|
|
||||||
private static final double TRACK_LONGITUDE = 9.177886;
|
|
||||||
private static final double TRACK_ELEVATION = 324.0;
|
|
||||||
|
|
||||||
private static final String TRACK_TIME_0 = "2010-04-22T18:21:00Z";
|
|
||||||
private static final String TRACK_TIME_1 = "2010-04-22T18:21:50.123";
|
|
||||||
private static final String TRACK_TIME_2 = "2010-04-22T18:23:00.123";
|
|
||||||
private static final String TRACK_TIME_3 = "2010-04-22T18:24:50.123";
|
|
||||||
|
|
||||||
private static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
|
|
||||||
private static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
|
|
||||||
|
|
||||||
static {
|
|
||||||
/*
|
|
||||||
* We can't omit the timezones in the test, otherwise it'll use the local
|
|
||||||
* timezone and fail depending on where the test runner is.
|
|
||||||
*/
|
|
||||||
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC");
|
|
||||||
DATE_FORMAT_0.setTimeZone(utc);
|
|
||||||
DATE_FORMAT_1.setTimeZone(utc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getNameAndDescription(String name, String description) {
|
private static String getNameAndDescription(String name, String description) {
|
||||||
return "<name><![CDATA[" + name + "]]></name>" + "<desc><![CDATA[" + description + "]]></desc>";
|
return "<name><![CDATA[" + name + "]]></name>" + "<desc><![CDATA[" + description + "]]></desc>";
|
||||||
@@ -134,40 +85,6 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(
|
private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(
|
||||||
Double.toString(TRACK_LONGITUDE), "invalid");
|
Double.toString(TRACK_LONGITUDE), "invalid");
|
||||||
|
|
||||||
private static final long TRACK_ID_0 = 1;
|
|
||||||
private static final long TRACK_ID_1 = 2;
|
|
||||||
private static final long TRACK_POINT_ID_0 = 1;
|
|
||||||
private static final long TRACK_POINT_ID_1 = 2;
|
|
||||||
private static final long TRACK_POINT_ID_3 = 4;
|
|
||||||
private static final long WAYPOINT_ID_0 = 1;
|
|
||||||
|
|
||||||
private static final Uri TRACK_ID_0_URI = ContentUris.appendId(
|
|
||||||
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
|
|
||||||
private static final Uri TRACK_ID_1_URI = ContentUris.appendId(
|
|
||||||
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_1).build();
|
|
||||||
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(
|
|
||||||
WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
|
|
||||||
|
|
||||||
private MyTracksProviderUtils myTracksProviderUtils;
|
|
||||||
|
|
||||||
private Factory oldMyTracksProviderUtilsFactory;
|
|
||||||
|
|
||||||
@UsesMocks(MyTracksProviderUtils.class)
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
myTracksProviderUtils = AndroidMock.createMock(MyTracksProviderUtils.class);
|
|
||||||
oldMyTracksProviderUtilsFactory = TestingProviderUtilsFactory.installWithInstance(
|
|
||||||
myTracksProviderUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() throws Exception {
|
|
||||||
TestingProviderUtilsFactory.restoreOldFactory(oldMyTracksProviderUtilsFactory);
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests one track with one segment.
|
* Tests one track with one segment.
|
||||||
*/
|
*/
|
||||||
@@ -190,8 +107,8 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
AndroidMock.replay(myTracksProviderUtils);
|
AndroidMock.replay(myTracksProviderUtils);
|
||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||||
long[] trackIds = GpxImporter.importGPXFile(getContext(), inputStream, myTracksProviderUtils,
|
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
|
||||||
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
long[] trackIds = gpxImporter.importFile(inputStream);
|
||||||
assertEquals(1, trackIds.length);
|
assertEquals(1, trackIds.length);
|
||||||
assertEquals(TRACK_ID_0, trackIds[0]);
|
assertEquals(TRACK_ID_0, trackIds[0]);
|
||||||
|
|
||||||
@@ -223,8 +140,8 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
AndroidMock.replay(myTracksProviderUtils);
|
AndroidMock.replay(myTracksProviderUtils);
|
||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||||
long[] trackIds = GpxImporter.importGPXFile(getContext(), inputStream, myTracksProviderUtils,
|
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
|
||||||
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
long[] trackIds = gpxImporter.importFile(inputStream);
|
||||||
assertEquals(1, trackIds.length);
|
assertEquals(1, trackIds.length);
|
||||||
assertEquals(TRACK_ID_0, trackIds[0]);
|
assertEquals(TRACK_ID_0, trackIds[0]);
|
||||||
|
|
||||||
@@ -260,8 +177,9 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(
|
InputStream inputStream = new ByteArrayInputStream(
|
||||||
VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
|
VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
|
||||||
long[] trackIds = GpxImporter.importGPXFile(getContext(), inputStream, myTracksProviderUtils,
|
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
|
||||||
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
long[] trackIds = gpxImporter.importFile(inputStream);
|
||||||
|
|
||||||
assertEquals(1, trackIds.length);
|
assertEquals(1, trackIds.length);
|
||||||
assertEquals(TRACK_ID_0, trackIds[0]);
|
assertEquals(TRACK_ID_0, trackIds[0]);
|
||||||
|
|
||||||
@@ -294,8 +212,8 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
AndroidMock.replay(myTracksProviderUtils);
|
AndroidMock.replay(myTracksProviderUtils);
|
||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(VALID_TWO_TRACKS_GPX.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(VALID_TWO_TRACKS_GPX.getBytes());
|
||||||
long[] trackIds = GpxImporter.importGPXFile(getContext(), inputStream, myTracksProviderUtils,
|
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
|
||||||
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
long[] trackIds = gpxImporter.importFile(inputStream);
|
||||||
assertEquals(2, trackIds.length);
|
assertEquals(2, trackIds.length);
|
||||||
assertEquals(TRACK_ID_0, trackIds[0]);
|
assertEquals(TRACK_ID_0, trackIds[0]);
|
||||||
assertEquals(TRACK_ID_1, trackIds[1]);
|
assertEquals(TRACK_ID_1, trackIds[1]);
|
||||||
@@ -306,46 +224,46 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
/**
|
/**
|
||||||
* Test an invalid xml input.
|
* Test an invalid xml input.
|
||||||
*/
|
*/
|
||||||
public void testInvalidXml() throws ParserConfigurationException, IOException {
|
public void testInvalidXml() throws Exception {
|
||||||
testInvalidGpx(INVALID_XML_GPX);
|
testInvalidGpx(INVALID_XML_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an invalid location.
|
* Test an invalid location.
|
||||||
*/
|
*/
|
||||||
public void testInvalidLocation() throws ParserConfigurationException, IOException {
|
public void testInvalidLocation() throws Exception {
|
||||||
testInvalidGpx(INVALID_LOCATION_GPX);
|
testInvalidGpx(INVALID_LOCATION_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an invalid time.
|
* Test an invalid time.
|
||||||
*/
|
*/
|
||||||
public void testInvalidTime() throws ParserConfigurationException, IOException {
|
public void testInvalidTime() throws Exception {
|
||||||
testInvalidGpx(INVALID_TIME_GPX);
|
testInvalidGpx(INVALID_TIME_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an invalid altitude.
|
* Test an invalid altitude.
|
||||||
*/
|
*/
|
||||||
public void testInvalidAltitude() throws ParserConfigurationException, IOException {
|
public void testInvalidAltitude() throws Exception {
|
||||||
testInvalidGpx(INVALID_ALTITUDE_GPX);
|
testInvalidGpx(INVALID_ALTITUDE_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an invalid latitude.
|
* Test an invalid latitude.
|
||||||
*/
|
*/
|
||||||
public void testInvalidLatitude() throws ParserConfigurationException, IOException {
|
public void testInvalidLatitude() throws Exception {
|
||||||
testInvalidGpx(INVALID_LATITUDE_GPX);
|
testInvalidGpx(INVALID_LATITUDE_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test an invalid longitude.
|
* Test an invalid longitude.
|
||||||
*/
|
*/
|
||||||
public void testInvalidLongitude() throws ParserConfigurationException, IOException {
|
public void testInvalidLongitude() throws Exception {
|
||||||
testInvalidGpx(INVALID_LONGITUDE_GPX);
|
testInvalidGpx(INVALID_LONGITUDE_GPX);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testInvalidGpx(String xml) throws ParserConfigurationException, IOException {
|
private void testInvalidGpx(String xml) throws Exception {
|
||||||
expect(myTracksProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
|
expect(myTracksProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
|
||||||
.andReturn(TRACK_ID_0_URI);
|
.andReturn(TRACK_ID_0_URI);
|
||||||
|
|
||||||
@@ -361,116 +279,11 @@ public class GpxImporterTest extends AndroidTestCase {
|
|||||||
|
|
||||||
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
|
||||||
try {
|
try {
|
||||||
GpxImporter.importGPXFile(getContext(), inputStream, myTracksProviderUtils,
|
GpxImporter gpxImporter = new GpxImporter(getContext(), myTracksProviderUtils);
|
||||||
PreferencesUtils.MIN_RECORDING_DISTANCE_DEFAULT);
|
gpxImporter.importFile(inputStream);
|
||||||
} catch (SAXException e) {
|
} catch (SAXException e) {
|
||||||
// expected
|
// expected
|
||||||
}
|
}
|
||||||
AndroidMock.verify(myTracksProviderUtils);
|
AndroidMock.verify(myTracksProviderUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Location createLocation(int index, long time) {
|
|
||||||
Location location = new Location(LocationManager.GPS_PROVIDER);
|
|
||||||
location.setLatitude(TRACK_LATITUDE + index);
|
|
||||||
location.setLongitude(TRACK_LONGITUDE + index);
|
|
||||||
location.setAltitude(TRACK_ELEVATION + index);
|
|
||||||
location.setTime(time);
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expects the first track point to be added.
|
|
||||||
*
|
|
||||||
* @param location the location
|
|
||||||
* @param trackId the track id
|
|
||||||
* @param trackPointId the track point id
|
|
||||||
*/
|
|
||||||
private void expectFirstTrackPoint(Location location, long trackId, long trackPointId) {
|
|
||||||
expect(myTracksProviderUtils.bulkInsertTrackPoint(
|
|
||||||
location != null ? LocationsMatcher.eqLoc(location) : (Location[]) AndroidMock.anyObject(),
|
|
||||||
eq(1), eq(trackId))).andReturn(1);
|
|
||||||
expect(myTracksProviderUtils.getFirstTrackPointId(trackId)).andReturn(trackPointId);
|
|
||||||
expect(myTracksProviderUtils.getLastTrackPointId(trackId)).andReturn(trackPointId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expects the track to be updated.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @param lastTrack true if it is the last track in the gpx
|
|
||||||
* @param trackId the track id
|
|
||||||
*/
|
|
||||||
private void expectUpdateTrack(Capture<Track> track, boolean lastTrack, long trackId) {
|
|
||||||
myTracksProviderUtils.updateTrack(AndroidMock.capture(track));
|
|
||||||
expect(myTracksProviderUtils.insertWaypoint((Waypoint) AndroidMock.anyObject()))
|
|
||||||
.andReturn(WAYPOINT_ID_O_URI);
|
|
||||||
if (lastTrack) {
|
|
||||||
// Return null to not add waypoints
|
|
||||||
expect(myTracksProviderUtils.getTrack(trackId)).andReturn(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyTrack(Track track, String name, String description, long time) {
|
|
||||||
assertEquals(name, track.getName());
|
|
||||||
assertEquals(description, track.getDescription());
|
|
||||||
if (time != -1L) {
|
|
||||||
assertEquals(time, track.getTripStatistics().getStartTime());
|
|
||||||
}
|
|
||||||
assertNotSame(-1, track.getStartId());
|
|
||||||
assertNotSame(-1, track.getStopId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Workaround because of capture bug 2617107 in easymock:
|
|
||||||
* http://sourceforge.net
|
|
||||||
* /tracker/?func=detail&aid=2617107&group_id=82958&atid=567837
|
|
||||||
*/
|
|
||||||
private static class LocationsMatcher implements IArgumentMatcher {
|
|
||||||
private final Location[] matchLocs;
|
|
||||||
|
|
||||||
private LocationsMatcher(Location[] expected) {
|
|
||||||
this.matchLocs = expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Location[] eqLoc(Location[] expected) {
|
|
||||||
IArgumentMatcher matcher = new LocationsMatcher(expected);
|
|
||||||
AndroidMock.reportMatcher(matcher);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Location[] eqLoc(Location expected) {
|
|
||||||
return eqLoc(new Location[] { expected });
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendTo(StringBuffer buf) {
|
|
||||||
buf.append("eqLoc(").append(Arrays.toString(matchLocs)).append(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(Object obj) {
|
|
||||||
if (!(obj instanceof Location[])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Location[] locs = (Location[]) obj;
|
|
||||||
if (locs.length < matchLocs.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only check the first elements (those that will be taken into account)
|
|
||||||
for (int i = 0; i < matchLocs.length; i++) {
|
|
||||||
if (!locationsMatch(locs[i], matchLocs[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean locationsMatch(Location loc1, Location loc2) {
|
|
||||||
return (loc1.getTime() == loc2.getTime()) && (loc1.getLatitude() == loc2.getLatitude())
|
|
||||||
&& (loc1.getLongitude() == loc2.getLongitude())
|
|
||||||
&& (loc1.getAltitude() == loc2.getAltitude());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.eq;
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.expect;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.testing.mocking.AndroidMock;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.easymock.Capture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link KmlImporter}.
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class KmlImporterTest extends AbstractTestImporter {
|
||||||
|
|
||||||
|
private static String getNameAndDescription(String name, String description) {
|
||||||
|
return "<name><![CDATA[" + name + "]]></name><description><![CDATA[" + description
|
||||||
|
+ "]]></description>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getTrackPoint(int index, String time) {
|
||||||
|
String latitude = Double.toString(TRACK_LATITUDE + index);
|
||||||
|
String longitude = Double.toString(TRACK_LONGITUDE + index);
|
||||||
|
String altitude = Double.toString(TRACK_ELEVATION + index);
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
buffer.append("<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " "
|
||||||
|
+ altitude + "</gx:coord>");
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX =
|
||||||
|
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||||
|
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
||||||
|
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
|
||||||
|
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
|
||||||
|
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX =
|
||||||
|
"<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
|
||||||
|
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
|
||||||
|
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
|
||||||
|
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
|
||||||
|
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests one track with one segment.
|
||||||
|
*/
|
||||||
|
public void testOneTrackOneSegment() throws Exception {
|
||||||
|
Capture<Track> track = new Capture<Track>();
|
||||||
|
|
||||||
|
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
|
Location location1 = createLocation(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
|
||||||
|
|
||||||
|
expect(myTracksProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
|
||||||
|
.andReturn(TRACK_ID_0_URI);
|
||||||
|
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
|
// A flush happens at the end
|
||||||
|
expect(myTracksProviderUtils.bulkInsertTrackPoint(
|
||||||
|
LocationsMatcher.eqLoc(location1), eq(1), eq(TRACK_ID_0))).andReturn(1);
|
||||||
|
expect(myTracksProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_1);
|
||||||
|
|
||||||
|
expectUpdateTrack(track, true, TRACK_ID_0);
|
||||||
|
AndroidMock.replay(myTracksProviderUtils);
|
||||||
|
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
|
||||||
|
KmlImporter kmlImporter = new KmlImporter(getContext(), myTracksProviderUtils);
|
||||||
|
long[] trackIds = kmlImporter.importFile(inputStream);
|
||||||
|
assertEquals(1, trackIds.length);
|
||||||
|
assertEquals(TRACK_ID_0, trackIds[0]);
|
||||||
|
|
||||||
|
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||||
|
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||||
|
assertEquals(time1 - time0, track.getValue().getTripStatistics().getTotalTime());
|
||||||
|
AndroidMock.verify(myTracksProviderUtils);
|
||||||
|
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests one track with two segments.
|
||||||
|
*/
|
||||||
|
public void testOneTrackTwoSegments() throws Exception {
|
||||||
|
Capture<Track> track = new Capture<Track>();
|
||||||
|
|
||||||
|
Location location0 = createLocation(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
|
|
||||||
|
expect(myTracksProviderUtils.insertTrack((Track) AndroidMock.anyObject()))
|
||||||
|
.andReturn(TRACK_ID_0_URI);
|
||||||
|
expectFirstTrackPoint(location0, TRACK_ID_0, TRACK_POINT_ID_0);
|
||||||
|
|
||||||
|
// A flush happens at the end
|
||||||
|
expect(myTracksProviderUtils.bulkInsertTrackPoint(
|
||||||
|
(Location[]) AndroidMock.anyObject(), eq(5), eq(TRACK_ID_0))).andStubReturn(5);
|
||||||
|
expect(myTracksProviderUtils.getLastTrackPointId(TRACK_ID_0)).andReturn(TRACK_POINT_ID_3);
|
||||||
|
|
||||||
|
expectUpdateTrack(track, true, TRACK_ID_0);
|
||||||
|
AndroidMock.replay(myTracksProviderUtils);
|
||||||
|
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
|
||||||
|
KmlImporter kmlImporter = new KmlImporter(getContext(), myTracksProviderUtils);
|
||||||
|
long[] trackIds = kmlImporter.importFile(inputStream);
|
||||||
|
assertEquals(1, trackIds.length);
|
||||||
|
assertEquals(TRACK_ID_0, trackIds[0]);
|
||||||
|
|
||||||
|
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
|
||||||
|
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
|
||||||
|
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
|
||||||
|
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
|
||||||
|
assertEquals(
|
||||||
|
time1 - time0 + time3 - time2, track.getValue().getTripStatistics().getTotalTime());
|
||||||
|
|
||||||
|
AndroidMock.verify(myTracksProviderUtils);
|
||||||
|
verifyTrack(track.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0,
|
||||||
|
DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import com.google.android.testing.mocking.AndroidMock;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.easymock.IArgumentMatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locations matcher. Workaround because of capture bug 2617107 in easymock:
|
||||||
|
* http://sourceforge.net/tracker/?func=detail&aid=2617107&group_id=82958&atid=567837
|
||||||
|
*
|
||||||
|
* @author Jimmy Shih
|
||||||
|
*/
|
||||||
|
public class LocationsMatcher implements IArgumentMatcher {
|
||||||
|
|
||||||
|
public static Location[] eqLoc(Location expected) {
|
||||||
|
return eqLoc(new Location[] { expected });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Location[] eqLoc(Location[] expected) {
|
||||||
|
IArgumentMatcher matcher = new LocationsMatcher(expected);
|
||||||
|
AndroidMock.reportMatcher(matcher);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Location[] expectedLocations;
|
||||||
|
|
||||||
|
private LocationsMatcher(Location[] expected) {
|
||||||
|
this.expectedLocations = expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTo(StringBuffer buf) {
|
||||||
|
buf.append("eqLoc(").append(Arrays.toString(expectedLocations)).append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Object obj) {
|
||||||
|
if (!(obj instanceof Location[])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Location[] locations = (Location[]) obj;
|
||||||
|
if (locations.length < expectedLocations.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only check the first elements (those that will be taken into account)
|
||||||
|
for (int i = 0; i < expectedLocations.length; i++) {
|
||||||
|
if (!matchLocation(locations[i], expectedLocations[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchLocation(Location location1, Location location2) {
|
||||||
|
return (location1.getTime() == location2.getTime())
|
||||||
|
&& (location1.getLatitude() == location2.getLatitude())
|
||||||
|
&& (location1.getLongitude() == location2.getLongitude())
|
||||||
|
&& (location1.getAltitude() == location2.getAltitude());
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -106,7 +106,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
|||||||
// No file to imported.
|
// No file to imported.
|
||||||
EndToEndTestUtils.findMenuItem(activityMyTracks.getString(R.string.menu_import), true);
|
EndToEndTestUtils.findMenuItem(activityMyTracks.getString(R.string.menu_import), true);
|
||||||
EndToEndTestUtils.SOLO.waitForText(activityMyTracks.getString(
|
EndToEndTestUtils.SOLO.waitForText(activityMyTracks.getString(
|
||||||
R.string.external_storage_import_error_no_file,
|
R.string.import_no_file,
|
||||||
FileUtils.buildExternalDirectoryPath(EndToEndTestUtils.GPX)));
|
FileUtils.buildExternalDirectoryPath(EndToEndTestUtils.GPX)));
|
||||||
EndToEndTestUtils.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
EndToEndTestUtils.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ public class ExportAndImportTest extends ActivityInstrumentationTestCase2<TrackL
|
|||||||
// Wait for the prefix of import success string is much faster than wait
|
// Wait for the prefix of import success string is much faster than wait
|
||||||
// the whole string.
|
// the whole string.
|
||||||
EndToEndTestUtils.SOLO.waitForText(
|
EndToEndTestUtils.SOLO.waitForText(
|
||||||
activityMyTracks.getString(R.string.external_storage_import_success_count).split("%")[0]);
|
activityMyTracks.getString(R.string.import_success).split("%")[0]);
|
||||||
// Check import tracks should be equal with the sum of trackNumber and
|
// Check import tracks should be equal with the sum of trackNumber and
|
||||||
// gpxFilesNumber;
|
// gpxFilesNumber;
|
||||||
EndToEndTestUtils.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
EndToEndTestUtils.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ public class SettingTest extends ActivityInstrumentationTestCase2<TrackListActiv
|
|||||||
EndToEndTestUtils
|
EndToEndTestUtils
|
||||||
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
.getButtonOnScreen(activityMyTracks.getString(R.string.generic_ok), true, true);
|
||||||
assertTrue(EndToEndTestUtils.SOLO.waitForText(
|
assertTrue(EndToEndTestUtils.SOLO.waitForText(
|
||||||
activityMyTracks.getString(R.string.external_storage_import_success), 0,
|
activityMyTracks.getString(R.string.restore_success), 0,
|
||||||
EndToEndTestUtils.SUPER_LONG_WAIT_TIME));
|
EndToEndTestUtils.SUPER_LONG_WAIT_TIME));
|
||||||
// Check restore track.
|
// Check restore track.
|
||||||
assertTrue(EndToEndTestUtils.SOLO.searchText(EndToEndTestUtils.trackName));
|
assertTrue(EndToEndTestUtils.SOLO.searchText(EndToEndTestUtils.trackName));
|
||||||
|
|||||||
Reference in New Issue
Block a user