mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-10 21:13:11 +02:00
Merge with head.
This commit is contained in:
+14
-13
@@ -1,13 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
|
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/google-common.jar"/>
|
<classpathentry kind="lib" path="libs/google-common.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/accounts.jar"/>
|
<classpathentry kind="lib" path="libs/accounts.jar"/>
|
||||||
<classpathentry kind="lib" path="libs/google-api-client-1.2.2-alpha.jar"/>
|
<classpathentry kind="lib" path="libs/google-api-client-1.2.2-alpha.jar"/>
|
||||||
<classpathentry kind="src" path="src"/>
|
<classpathentry kind="lib" path="libs/libGoogleAnalytics.jar"/>
|
||||||
<classpathentry kind="src" path="gen"/>
|
<classpathentry kind="src" path="src"/>
|
||||||
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
|
<classpathentry kind="src" path="gen"/>
|
||||||
<classpathentry kind="src" path="MyTracksLib_src"/>
|
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
|
||||||
<classpathentry kind="output" path="bin"/>
|
<classpathentry kind="src" path="MyTracksLib_src"/>
|
||||||
</classpath>
|
<classpathentry kind="output" path="bin"/>
|
||||||
|
</classpath>
|
||||||
|
|||||||
+107
-41
@@ -17,8 +17,8 @@ limitations under the License.
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.google.android.maps.mytracks"
|
package="com.google.android.maps.mytracks"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="27"
|
android:versionCode="30"
|
||||||
android:versionName="1.1.4">
|
android:versionName="1.1.7">
|
||||||
<application android:icon="@drawable/arrow_icon"
|
<application android:icon="@drawable/arrow_icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
|
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
|
||||||
@@ -44,6 +44,109 @@ limitations under the License.
|
|||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.MapActivity"
|
||||||
|
android:label="MyTracks"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.TAB" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.MyMapsList"
|
||||||
|
android:label="@string/map_list_title"
|
||||||
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!-- TODO: VIEW and PICK for track cursor dir -->
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.TrackList"
|
||||||
|
android:label="Recorded Tracks" />
|
||||||
|
|
||||||
|
<!-- TODO: VIEW and PICK for waypoint cursor dir -->
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.WaypointsList"
|
||||||
|
android:label="Recorded Waypoints" />
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.TrackDetails"
|
||||||
|
android:label="@string/trackdetails_title">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.EDIT" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.WaypointDetails"
|
||||||
|
android:label="@string/waypointdetails_title">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.EDIT" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.google.waypoint" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.SettingsActivity"
|
||||||
|
android:label="@string/mytracks_settings" >
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.PREFERENCE" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.AggregatedStatsActivity"
|
||||||
|
android:label="@string/aggregated_stats" />
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
|
||||||
|
android:label="@string/statistics"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.TAB" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.SensorStateActivity"
|
||||||
|
android:label="@string/sensor_state" />
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
|
||||||
|
android:label="@string/elevation_label"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.TAB" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.SendActivity"
|
||||||
|
android:label="@string/sendtogoogle_title"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.io.file.SaveActivity"
|
||||||
|
android:label="@string/tracklist_write_to_sd"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
android:noHistory="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="@string/save_intent_action" />
|
||||||
|
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.apps.mytracks.io.file.ImportActivity"
|
||||||
|
android:label="@string/import_title"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
android:noHistory="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.ATTACH_DATA" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="file" />
|
<data android:scheme="file" />
|
||||||
<data android:mimeType="application/gpx+xml" />
|
<data android:mimeType="application/gpx+xml" />
|
||||||
<data android:mimeType="application/gpx" />
|
<data android:mimeType="application/gpx" />
|
||||||
@@ -52,44 +155,6 @@ limitations under the License.
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.MapActivity"
|
|
||||||
android:label="MyTracks"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.MyMapsList"
|
|
||||||
android:label="@string/map_list_title"
|
|
||||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.TrackList"
|
|
||||||
android:label="Recorded Tracks" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.WaypointsList"
|
|
||||||
android:label="Recorded Waypoints" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.TrackDetails"
|
|
||||||
android:label="@string/trackdetails_title" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.WaypointDetails"
|
|
||||||
android:label="@string/waypointdetails_title" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.SettingsActivity"
|
|
||||||
android:label="@string/mytracks_settings" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.AggregatedStatsActivity"
|
|
||||||
android:label="@string/aggregated_stats" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
|
|
||||||
android:label="@string/statistics"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.SensorStateActivity"
|
|
||||||
android:label="@string/sensor_state" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
|
|
||||||
android:label="@string/elevation_label"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.WelcomeActivity"
|
<activity android:name="com.google.android.apps.mytracks.WelcomeActivity"
|
||||||
android:label="@string/welcome"
|
android:label="@string/welcome"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@@ -104,7 +169,7 @@ limitations under the License.
|
|||||||
<receiver android:name="com.google.android.apps.mytracks.BootReceiver">
|
<receiver android:name="com.google.android.apps.mytracks.BootReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
@@ -114,6 +179,7 @@ limitations under the License.
|
|||||||
<!-- Permissions for recording tracks and showing maps -->
|
<!-- Permissions for recording tracks and showing maps -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
|||||||
Binary file not shown.
@@ -159,8 +159,8 @@ limitations under the License.
|
|||||||
$2 : Attempted imports
|
$2 : Attempted imports
|
||||||
$3 : MyTracks gpx path
|
$3 : MyTracks gpx path
|
||||||
-->
|
-->
|
||||||
<string name="import_done">%3$s からGPXファイル %2$d件のうち %1$d件をインポートしました</string>
|
<string name="import_multi_done">%3$s からGPXファイル %2$d件のうち %1$d件をインポートしました</string>
|
||||||
<string name="import_empty">%s ではGPXファイルがありません.SDカードを読み込めないかディレクトリにGPXファイルが無いかも知れません.</string>
|
<string name="import_multi_empty">%s ではGPXファイルがありません.SDカードを読み込めないかディレクトリにGPXファイルが無いかも知れません.</string>
|
||||||
<string name="import_error">ファイル %s のインポートエラー</string>
|
<string name="import_error">ファイル %s のインポートエラー</string>
|
||||||
|
|
||||||
<!-- Waypoints list -->
|
<!-- Waypoints list -->
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ limitations under the License.
|
|||||||
<item>typ mieszany</item>
|
<item>typ mieszany</item>
|
||||||
<item>inny</item>
|
<item>inny</item>
|
||||||
|
|
||||||
<item>bieanie</item>
|
<item>bieganie</item>
|
||||||
<item>bieganie uliczne</item>
|
<item>bieganie uliczne</item>
|
||||||
<item>bieganie na torze</item>
|
<item>bieganie na torze</item>
|
||||||
<item>bieganie terenowe</item>
|
<item>bieganie terenowe</item>
|
||||||
@@ -170,10 +170,10 @@ limitations under the License.
|
|||||||
<item>łyżwiarstwo torowe</item>
|
<item>łyżwiarstwo torowe</item>
|
||||||
<item>deskorolka</item>
|
<item>deskorolka</item>
|
||||||
|
|
||||||
<item>wędrówka po sniegu</item>
|
<item>wędrówka po śniegu</item>
|
||||||
<item>narciarstwo</item>
|
<item>narciarstwo</item>
|
||||||
<item>snowboard</item>
|
<item>snowboard</item>
|
||||||
<item>saneczkastwo</item>
|
<item>saneczkarstwo</item>
|
||||||
<item>żeglarstwo lodowe</item>
|
<item>żeglarstwo lodowe</item>
|
||||||
|
|
||||||
<item>żeglowanie</item>
|
<item>żeglowanie</item>
|
||||||
@@ -237,7 +237,7 @@ limitations under the License.
|
|||||||
<item>kominek</item>
|
<item>kominek</item>
|
||||||
<item>remiza strażacka</item>
|
<item>remiza strażacka</item>
|
||||||
<item>baza wędkarska</item>
|
<item>baza wędkarska</item>
|
||||||
<item>stcja paliw</item>
|
<item>stacja paliw</item>
|
||||||
<item>pole golfowe</item>
|
<item>pole golfowe</item>
|
||||||
<item>sklep spożywczy</item>
|
<item>sklep spożywczy</item>
|
||||||
<item>fryzjer</item>
|
<item>fryzjer</item>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ limitations under the License.
|
|||||||
<string name="error_invalid_gpx_format">Plik nie jest poprawnym plikiem GPX.</string>
|
<string name="error_invalid_gpx_format">Plik nie jest poprawnym plikiem GPX.</string>
|
||||||
<string name="error_generic">Wystąpił błąd. Spróbuj ponownie.</string>
|
<string name="error_generic">Wystąpił błąd. Spróbuj ponownie.</string>
|
||||||
<string name="error_track_does_not_exist">Trasa nie istnieje.</string>
|
<string name="error_track_does_not_exist">Trasa nie istnieje.</string>
|
||||||
<string name="error_out_of_memory">Plik jest za duży (musi być mniejszy nić 500 KB). Przerywam.</string>
|
<string name="error_out_of_memory">Plik jest za duży (musi być mniejszy niż 500 KB). Przerywam.</string>
|
||||||
<string name="error_unable_to_insert_marker">Nie można wstawić markera. Brak poprawnych danych o lokalizacji.</string>
|
<string name="error_unable_to_insert_marker">Nie można wstawić markera. Brak poprawnych danych o lokalizacji.</string>
|
||||||
|
|
||||||
<!-- Status messages -->
|
<!-- Status messages -->
|
||||||
@@ -334,7 +334,6 @@ limitations under the License.
|
|||||||
<string name="about_team">Zespół:</string>
|
<string name="about_team">Zespół:</string>
|
||||||
<string name="about_version">Wersja:</string>
|
<string name="about_version">Wersja:</string>
|
||||||
<string name="about_support">Wsparcie:</string>
|
<string name="about_support">Wsparcie:</string>
|
||||||
<string name="about_bart">Bartłomiej Niechwiej</string>
|
|
||||||
|
|
||||||
<!-- Send to Google -->
|
<!-- Send to Google -->
|
||||||
<string name="sendtogoogle_title">Wyślij do...</string>
|
<string name="sendtogoogle_title">Wyślij do...</string>
|
||||||
@@ -385,4 +384,31 @@ limitations under the License.
|
|||||||
<!-- Check Units -->
|
<!-- Check Units -->
|
||||||
<string name="check_units_title">Jednostki miary</string>
|
<string name="check_units_title">Jednostki miary</string>
|
||||||
<string name="check_units_message">Domyślnie używane są jednostki metryczne. Chcesz to zmienić?</string>
|
<string name="check_units_message">Domyślnie używane są jednostki metryczne. Chcesz to zmienić?</string>
|
||||||
|
|
||||||
|
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
|
||||||
|
<string name="method_bluetooth">Bluetooth</string>
|
||||||
|
<string name="bluetooth_options">Opcje Bluetooth</string>
|
||||||
|
<string name="bluetooth_pairing">Parowanie urządzenia</string>
|
||||||
|
<string name="bluetooth_pairing_summary">Przejdź do ustawień Bluetooth Androida aby sparować urządzenie</string>
|
||||||
|
<string name="sensor_bluetooth_options_key">bluetoothSensorsEnabled</string>
|
||||||
|
<string name="ant_options">Opce ANT</string>
|
||||||
|
<string name="sensor_options">Opcje Czujników</string>
|
||||||
|
|
||||||
|
<!-- Bluetooth Sensor -->
|
||||||
|
<string name="bluetooth_connected">Połączony z %s</string>
|
||||||
|
<string name="bluetooth_connecting">Łączenie urządzenia Bluetooth w toku...</string>
|
||||||
|
<string name="bluetooth_not_connected">Urządzenie Bluetooth niepołączone</string>
|
||||||
|
|
||||||
|
<!-- Sensor State -->
|
||||||
|
<string name="sensor_data_time">Czas Danych Czujnika</string>
|
||||||
|
<string name="power_meter_enabled">Miernik mocy aktywny</string>
|
||||||
|
<string name="heart_rate_monitor_enabled">Pulsometr aktywny</string>
|
||||||
|
<string name="no_sensor_manager">Brak Ustawień Czujników</string>
|
||||||
|
<string name="enabled">Aktywny</string>
|
||||||
|
<string name="connecting">Łączenie</string>
|
||||||
|
<string name="connected">Podłączony</string>
|
||||||
|
<string name="disconnected">Rozłączony</string>
|
||||||
|
<string name="no_sensor_service">Brak Serwisu Czujników</string>
|
||||||
|
<string name="searching">Wykrywanie</string>
|
||||||
|
<string name="sending">Przesyłanie</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -162,8 +162,8 @@ limitations under the License.
|
|||||||
$2 : Attempted imports
|
$2 : Attempted imports
|
||||||
$3 : MyTracks gpx path
|
$3 : MyTracks gpx path
|
||||||
-->
|
-->
|
||||||
<string name="import_done">Импортировано %1$d из %2$d GPX файлов с %3$s</string>
|
<string name="import_multi_done">Импортировано %1$d из %2$d GPX файлов с %3$s</string>
|
||||||
<string name="import_empty">GPX файлы не найденны в %s. Возможно не читается SD-карта или каталог не содержит GPX файлы.</string>
|
<string name="import_multi_empty">GPX файлы не найденны в %s. Возможно не читается SD-карта или каталог не содержит GPX файлы.</string>
|
||||||
<string name="import_error">Ошибка импорта файла %s</string>
|
<string name="import_error">Ошибка импорта файла %s</string>
|
||||||
|
|
||||||
<!-- Waypoints list -->
|
<!-- Waypoints list -->
|
||||||
|
|||||||
@@ -170,6 +170,11 @@ limitations under the License.
|
|||||||
<item>inline skating</item>
|
<item>inline skating</item>
|
||||||
<item>skate boarding</item>
|
<item>skate boarding</item>
|
||||||
|
|
||||||
|
<item>roller skiing (classic)</item>
|
||||||
|
<item>roller skiing (skate)</item>
|
||||||
|
<item>cross-country skiing (classic)</item>
|
||||||
|
<item>cross-country skiing (skate)</item>
|
||||||
|
|
||||||
<item>snow shoeing</item>
|
<item>snow shoeing</item>
|
||||||
<item>skiing</item>
|
<item>skiing</item>
|
||||||
<item>snow boarding</item>
|
<item>snow boarding</item>
|
||||||
|
|||||||
@@ -38,16 +38,6 @@ limitations under the License.
|
|||||||
<item>100</item>
|
<item>100</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="min_recording_distance_values">
|
|
||||||
<item>1</item>
|
|
||||||
<item>2</item>
|
|
||||||
<item>3</item>
|
|
||||||
<item>4</item>
|
|
||||||
<item>5</item>
|
|
||||||
<item>10</item>
|
|
||||||
<item>100</item>
|
|
||||||
</string-array>
|
|
||||||
|
|
||||||
<string-array name="max_recording_distance_values">
|
<string-array name="max_recording_distance_values">
|
||||||
<item>50</item>
|
<item>50</item>
|
||||||
<item>100</item>
|
<item>100</item>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ limitations under the License.
|
|||||||
<string name="about_weburl">mytracks.appspot.com</string>
|
<string name="about_weburl">mytracks.appspot.com</string>
|
||||||
<string name="about_privacy_weburl">m.google.com/privacy</string>
|
<string name="about_privacy_weburl">m.google.com/privacy</string>
|
||||||
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
|
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
|
||||||
<string name="about_bart">Bartlomiej Niechwiej</string>
|
<string name="about_bart">Bartłomiej Niechwiej</string>
|
||||||
<string name="about_dylan">Dylan Casey</string>
|
<string name="about_dylan">Dylan Casey</string>
|
||||||
<string name="about_leif">Leif Hendrik Wilden</string>
|
<string name="about_leif">Leif Hendrik Wilden</string>
|
||||||
<string name="about_rdamazio">Rodrigo Damazio Bovendorp</string>
|
<string name="about_rdamazio">Rodrigo Damazio Bovendorp</string>
|
||||||
@@ -40,4 +40,10 @@ limitations under the License.
|
|||||||
<string name="sendtogoogle_google_fusiontables_url">google.com/fusiontables</string>
|
<string name="sendtogoogle_google_fusiontables_url">google.com/fusiontables</string>
|
||||||
<string name="sendtogoogle_google_mymaps_url">maps.google.com</string>
|
<string name="sendtogoogle_google_mymaps_url">maps.google.com</string>
|
||||||
<string name="sendtogoogle_google_docs_url">docs.google.com</string>
|
<string name="sendtogoogle_google_docs_url">docs.google.com</string>
|
||||||
|
|
||||||
|
<!-- ID for Google Analytics -->
|
||||||
|
<string name="google_analytics_id">UA-7222692-2</string>
|
||||||
|
|
||||||
|
<!-- Intent actions -->
|
||||||
|
<string name="save_intent_action">android.intent.action.SAVE</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -163,9 +163,12 @@ limitations under the License.
|
|||||||
$2 : Attempted imports
|
$2 : Attempted imports
|
||||||
$3 : MyTracks gpx path
|
$3 : MyTracks gpx path
|
||||||
-->
|
-->
|
||||||
<string name="import_done">Imported %1$d of %2$d GPX files from %3$s</string>
|
<string name="import_multi_done">Imported %1$d of %2$d GPX files from %3$s</string>
|
||||||
<string name="import_empty">No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.</string>
|
<string name="import_multi_empty">No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.</string>
|
||||||
<string name="import_error">Import error for file %s</string>
|
<string name="import_error">Import error for file %s</string>
|
||||||
|
<string name="import_title">Import into My Tracks</string>
|
||||||
|
<string name="import_success">Successfully imported %1$d track(s) from file.</string>
|
||||||
|
<string name="import_show_track">Show track</string>
|
||||||
|
|
||||||
<!-- Waypoints list -->
|
<!-- Waypoints list -->
|
||||||
<string name="waypointslist_btn_delete_all">Delete all markers</string>
|
<string name="waypointslist_btn_delete_all">Delete all markers</string>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ limitations under the License.
|
|||||||
android:summary="@string/settings_announcement_frequency_summary"
|
android:summary="@string/settings_announcement_frequency_summary"
|
||||||
android:entries="@array/task_frequency_options"
|
android:entries="@array/task_frequency_options"
|
||||||
android:entryValues="@array/task_frequency_values"
|
android:entryValues="@array/task_frequency_values"
|
||||||
android:defaultValue="-1" />
|
android:defaultValue="0" />
|
||||||
|
|
||||||
<com.google.android.apps.mytracks.IntegerListPreference
|
<com.google.android.apps.mytracks.IntegerListPreference
|
||||||
android:key="@string/split_frequency_key"
|
android:key="@string/split_frequency_key"
|
||||||
|
|||||||
@@ -15,22 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package com.dsi.ant;
|
package com.dsi.ant;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import com.dsi.ant.exception.AntInterfaceException;
|
||||||
|
import com.dsi.ant.exception.AntRemoteException;
|
||||||
|
import com.dsi.ant.exception.AntServiceNotConnectedException;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.dsi.ant.exception.*;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public API for controlling the Ant Service. AntInterface is a proxy
|
* Public API for controlling the Ant Service. AntInterface is a proxy
|
||||||
@@ -63,10 +61,10 @@ public class AntInterface {
|
|||||||
private static Object INSTANCE_LOCK = new Object();
|
private static Object INSTANCE_LOCK = new Object();
|
||||||
|
|
||||||
/** The context to use. */
|
/** The context to use. */
|
||||||
private static Context sContext = null;
|
private Context sContext = null;
|
||||||
|
|
||||||
/** Listens to changes to service connection status. */
|
/** Listens to changes to service connection status. */
|
||||||
private static ServiceListener sServiceListener;
|
private ServiceListener sServiceListener;
|
||||||
|
|
||||||
/** Is the ANT Radio Proxy Service connected. */
|
/** Is the ANT Radio Proxy Service connected. */
|
||||||
private static boolean sServiceConnected = false;
|
private static boolean sServiceConnected = false;
|
||||||
@@ -212,7 +210,7 @@ public class AntInterface {
|
|||||||
/**
|
/**
|
||||||
* Class for interacting with the ANT interface.
|
* Class for interacting with the ANT interface.
|
||||||
*/
|
*/
|
||||||
private static ServiceConnection sIAntConnection = new ServiceConnection() {
|
private final ServiceConnection sIAntConnection = new ServiceConnection() {
|
||||||
public void onServiceConnected(ComponentName pClassName, IBinder pService) {
|
public void onServiceConnected(ComponentName pClassName, IBinder pService) {
|
||||||
// This is called when the connection with the service has been
|
// This is called when the connection with the service has been
|
||||||
// established, giving us the service object we can use to
|
// established, giving us the service object we can use to
|
||||||
@@ -1225,7 +1223,7 @@ public class AntInterface {
|
|||||||
*/
|
*/
|
||||||
public boolean requestForceClaimInterface(String appName) throws AntInterfaceException
|
public boolean requestForceClaimInterface(String appName) throws AntInterfaceException
|
||||||
{
|
{
|
||||||
if((null == appName) || ("" == appName))
|
if((null == appName) || ("".equals(appName)))
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -29,7 +29,7 @@ import android.util.Log;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class AccountChooser {
|
public class AccountChooser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last selected account.
|
* The last selected account.
|
||||||
*/
|
*/
|
||||||
@@ -44,7 +44,7 @@ public class AccountChooser {
|
|||||||
* Handle the account being selected.
|
* Handle the account being selected.
|
||||||
* @param account The selected account or null if none could be found
|
* @param account The selected account or null if none could be found
|
||||||
*/
|
*/
|
||||||
public void handleAccountSelected(Account account);
|
public void onAccountSelected(Account account);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +52,7 @@ public class AccountChooser {
|
|||||||
* If no account is found the user will be alerted.
|
* If no account is found the user will be alerted.
|
||||||
* If only one account is found that will be used.
|
* If only one account is found that will be used.
|
||||||
* If multiple accounts are found the user will be allowed to choose.
|
* If multiple accounts are found the user will be allowed to choose.
|
||||||
*
|
*
|
||||||
* @param activity The parent activity
|
* @param activity The parent activity
|
||||||
* @param handler The handler to be notified when an account has been selected
|
* @param handler The handler to be notified when an account has been selected
|
||||||
*/
|
*/
|
||||||
@@ -65,16 +65,16 @@ public class AccountChooser {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (accounts.length == 1) {
|
if (accounts.length == 1) {
|
||||||
handler.handleAccountSelected(accounts[0]);
|
handler.onAccountSelected(accounts[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This should be read out of a preference.
|
// TODO This should be read out of a preference.
|
||||||
if (selectedAccount != null) {
|
if (selectedAccount != null) {
|
||||||
handler.handleAccountSelected(selectedAccount);
|
handler.onAccountSelected(selectedAccount);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let the user choose.
|
// Let the user choose.
|
||||||
Log.e(Constants.TAG, "Multiple matching accounts found.");
|
Log.e(Constants.TAG, "Multiple matching accounts found.");
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
@@ -83,14 +83,20 @@ public class AccountChooser {
|
|||||||
builder.setPositiveButton(R.string.ok,
|
builder.setPositiveButton(R.string.ok,
|
||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
selectedAccount = accounts[selectedAccountIndex];
|
dialog.dismiss();
|
||||||
handler.handleAccountSelected(selectedAccount);
|
|
||||||
|
if (selectedAccountIndex >= 0) {
|
||||||
|
selectedAccount = accounts[selectedAccountIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.onAccountSelected(selectedAccount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.cancel,
|
builder.setNegativeButton(R.string.cancel,
|
||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
handler.handleAccountSelected(null);
|
dialog.dismiss();
|
||||||
|
handler.onAccountSelected(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
String[] choices = new String[accounts.length];
|
String[] choices = new String[accounts.length];
|
||||||
@@ -120,7 +126,8 @@ public class AccountChooser {
|
|||||||
builder.setNegativeButton(R.string.ok,
|
builder.setNegativeButton(R.string.ok,
|
||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
handler.handleAccountSelected(null);
|
dialog.dismiss();
|
||||||
|
handler.onAccountSelected(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.show();
|
builder.show();
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for viewing the combined statistics for all the recorded tracks.
|
* Activity for viewing the combined statistics for all the recorded tracks.
|
||||||
*
|
*
|
||||||
* Other features to add - menu items to change setings.
|
* Other features to add - menu items to change setings.
|
||||||
*
|
*
|
||||||
* @author Fergus Nelson
|
* @author Fergus Nelson
|
||||||
*/
|
*/
|
||||||
public class AggregatedStatsActivity extends Activity implements
|
public class AggregatedStatsActivity extends Activity implements
|
||||||
@@ -43,7 +43,7 @@ public class AggregatedStatsActivity extends Activity implements
|
|||||||
Log.d(Constants.TAG, "StatsActivity: onSharedPreferences changed "
|
Log.d(Constants.TAG, "StatsActivity: onSharedPreferences changed "
|
||||||
+ key);
|
+ key);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
if (key.equals(R.string.metric_units_key)) {
|
if (key.equals(getString(R.string.metric_units_key))) {
|
||||||
metricUnits = sharedPreferences.getBoolean(
|
metricUnits = sharedPreferences.getBoolean(
|
||||||
getString(R.string.metric_units_key), true);
|
getString(R.string.metric_units_key), true);
|
||||||
utils.setMetricUnits(metricUnits);
|
utils.setMetricUnits(metricUnits);
|
||||||
@@ -86,8 +86,8 @@ public class AggregatedStatsActivity extends Activity implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. Reads tracks from the db
|
* 1. Reads tracks from the db
|
||||||
* 2. Merges the trip stats from the tracks
|
* 2. Merges the trip stats from the tracks
|
||||||
* 3. Updates the view
|
* 3. Updates the view
|
||||||
*/
|
*/
|
||||||
private void loadAggregatedStats() {
|
private void loadAggregatedStats() {
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ package com.google.android.apps.mytracks;
|
|||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.ChartView.Mode;
|
import com.google.android.apps.mytracks.ChartView.Mode;
|
||||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||||
import com.google.android.apps.mytracks.content.Sensor;
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||||
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
import com.google.android.apps.mytracks.content.Sensor.SensorDataSet;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||||
@@ -33,6 +35,9 @@ import com.google.android.apps.mytracks.util.UnitConversions;
|
|||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -56,6 +61,7 @@ import java.util.EnumSet;
|
|||||||
*/
|
*/
|
||||||
public class ChartActivity extends Activity implements TrackDataListener {
|
public class ChartActivity extends Activity implements TrackDataListener {
|
||||||
|
|
||||||
|
private static final int CHART_SETTINGS_DIALOG = 1;
|
||||||
private final DoubleBuffer elevationBuffer =
|
private final DoubleBuffer elevationBuffer =
|
||||||
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||||
private final DoubleBuffer speedBuffer =
|
private final DoubleBuffer speedBuffer =
|
||||||
@@ -103,8 +109,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
Log.w(TAG, "ChartActivity.onCreate");
|
Log.w(TAG, "ChartActivity.onCreate");
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
MyTracks.getInstance().setChartActivity(this);
|
|
||||||
dataHub = MyTracks.getInstance().getDataHub();
|
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
int volumeStream =
|
int volumeStream =
|
||||||
@@ -137,11 +141,13 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onResume() {
|
||||||
super.onStart();
|
super.onResume();
|
||||||
|
|
||||||
|
dataHub = TrackDataHub.getStartedInstance();
|
||||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||||
|
ListenerDataType.TRACK_UPDATES,
|
||||||
ListenerDataType.POINT_UPDATES,
|
ListenerDataType.POINT_UPDATES,
|
||||||
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
|
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
|
||||||
ListenerDataType.WAYPOINT_UPDATES,
|
ListenerDataType.WAYPOINT_UPDATES,
|
||||||
@@ -149,10 +155,11 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onPause() {
|
||||||
dataHub.unregisterTrackDataListener(this);
|
dataHub.unregisterTrackDataListener(this);
|
||||||
|
dataHub = null;
|
||||||
|
|
||||||
super.onStop();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void zoomIn() {
|
private void zoomIn() {
|
||||||
@@ -184,10 +191,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
runOnUiThread(updateChart);
|
runOnUiThread(updateChart);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSeriesEnabled(int index) {
|
|
||||||
return chartView.getChartValueSeries(index).isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
@@ -202,13 +205,52 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case Constants.MENU_CHART_SETTINGS:
|
case Constants.MENU_CHART_SETTINGS:
|
||||||
MyTracks.getInstance().getDialogManager().showDialogSafely(
|
showDialog(CHART_SETTINGS_DIALOG);
|
||||||
DialogManager.DIALOG_CHART_SETTINGS);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
if (id == CHART_SETTINGS_DIALOG) {
|
||||||
|
final ChartSettingsDialog settingsDialog = new ChartSettingsDialog(this);
|
||||||
|
settingsDialog.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface arg0, int which) {
|
||||||
|
if (which != DialogInterface.BUTTON_POSITIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
||||||
|
chartView.getChartValueSeries(i).setEnabled(settingsDialog.isSeriesEnabled(i));
|
||||||
|
}
|
||||||
|
setMode(settingsDialog.getMode());
|
||||||
|
chartView.postInvalidate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return settingsDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||||
|
super.onPrepareDialog(id, dialog);
|
||||||
|
|
||||||
|
if (id == CHART_SETTINGS_DIALOG) {
|
||||||
|
prepareSettingsDialog((ChartSettingsDialog) dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareSettingsDialog(final ChartSettingsDialog settingsDialog) {
|
||||||
|
settingsDialog.setMode(mode);
|
||||||
|
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
||||||
|
settingsDialog.setSeriesEnabled(i, chartView.getChartValueSeries(i).isEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a location, creates a new data point for the chart. A data point is
|
* Given a location, creates a new data point for the chart. A data point is
|
||||||
* an array double[3 or 6], where:
|
* an array double[3 or 6], where:
|
||||||
@@ -257,13 +299,13 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
// TODO: Account for segment splits?
|
// TODO: Account for segment splits?
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case BY_DISTANCE:
|
case BY_DISTANCE:
|
||||||
timeOrDistance = profileLength;
|
timeOrDistance = profileLength / 1000.0;
|
||||||
if (lastLocation != null) {
|
if (lastLocation != null) {
|
||||||
double d = lastLocation.distanceTo(location);
|
double d = lastLocation.distanceTo(location);
|
||||||
if (metricUnits) {
|
if (metricUnits) {
|
||||||
profileLength += d / 1000.0;
|
profileLength += d;
|
||||||
} else {
|
} else {
|
||||||
profileLength += d * UnitConversions.KM_TO_MI / 1000.0;
|
profileLength += d * UnitConversions.KM_TO_MI;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -336,7 +378,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
public void onSelectedTrackChanged(Track track, boolean isRecording) {
|
||||||
Log.e(TAG, "Visible", new Throwable());
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -347,6 +388,11 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(Track track) {
|
public void onTrackUpdated(Track track) {
|
||||||
|
if (track == null || track.getStatistics() == null) {
|
||||||
|
trackMaxSpeed = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
trackMaxSpeed = track.getStatistics().getMaxSpeed();
|
trackMaxSpeed = track.getStatistics().getMaxSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,9 +402,16 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
lastLocation = null;
|
lastLocation = null;
|
||||||
startTime = -1;
|
startTime = -1;
|
||||||
elevationBuffer.reset();
|
elevationBuffer.reset();
|
||||||
speedBuffer.reset();
|
|
||||||
chartView.reset();
|
chartView.reset();
|
||||||
|
speedBuffer.reset();
|
||||||
pendingPoints.clear();
|
pendingPoints.clear();
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
chartView.resetScroll();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -405,15 +458,21 @@ public class ChartActivity extends Activity implements TrackDataListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onUnitsChanged(boolean metric) {
|
public boolean onUnitsChanged(boolean metric) {
|
||||||
|
boolean changed = metric != this.metricUnits;
|
||||||
|
if (!changed) return false;
|
||||||
|
|
||||||
this.metricUnits = metric;
|
this.metricUnits = metric;
|
||||||
|
|
||||||
chartView.setMetricUnits(metric);
|
chartView.setMetricUnits(metric);
|
||||||
|
|
||||||
return true; // Reload data
|
return true; // Reload data
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onReportSpeedChanged(boolean reportSpeed) {
|
public boolean onReportSpeedChanged(boolean reportSpeed) {
|
||||||
|
boolean changed = reportSpeed != this.reportSpeed;
|
||||||
|
if (!changed) return false;
|
||||||
|
|
||||||
this.reportSpeed = reportSpeed;
|
this.reportSpeed = reportSpeed;
|
||||||
|
|
||||||
chartView.setReportSpeed(reportSpeed, this);
|
chartView.setReportSpeed(reportSpeed, this);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Google Inc.
|
* Copyright 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -36,6 +36,7 @@ import android.widget.RadioGroup;
|
|||||||
public class ChartSettingsDialog extends Dialog {
|
public class ChartSettingsDialog extends Dialog {
|
||||||
private RadioButton distance;
|
private RadioButton distance;
|
||||||
private CheckBox[] series;
|
private CheckBox[] series;
|
||||||
|
private OnClickListener clickListener;
|
||||||
|
|
||||||
public ChartSettingsDialog(Context context) {
|
public ChartSettingsDialog(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -44,23 +45,31 @@ public class ChartSettingsDialog extends Dialog {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
setContentView(R.layout.chart_settings);
|
setContentView(R.layout.chart_settings);
|
||||||
Button cancel = (Button) findViewById(R.id.chart_settings_cancel);
|
Button cancel = (Button) findViewById(R.id.chart_settings_cancel);
|
||||||
cancel.setOnClickListener(new View.OnClickListener() {
|
cancel.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
if (clickListener != null) {
|
||||||
|
clickListener.onClick(ChartSettingsDialog.this, BUTTON_NEGATIVE);
|
||||||
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Button ok = (Button) findViewById(R.id.chart_settings_ok);
|
|
||||||
ok.setOnClickListener(new View.OnClickListener() {
|
Button okButton = (Button) findViewById(R.id.chart_settings_ok);
|
||||||
|
okButton.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
handleOk();
|
if (clickListener != null) {
|
||||||
|
clickListener.onClick(ChartSettingsDialog.this, BUTTON_POSITIVE);
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
distance = (RadioButton) findViewById(R.id.chart_settings_by_distance);
|
distance = (RadioButton) findViewById(R.id.chart_settings_by_distance);
|
||||||
|
|
||||||
series = new CheckBox[ChartView.NUM_SERIES];
|
series = new CheckBox[ChartView.NUM_SERIES];
|
||||||
series[ChartView.ELEVATION_SERIES] =
|
series[ChartView.ELEVATION_SERIES] =
|
||||||
(CheckBox) findViewById(R.id.chart_settings_elevation);
|
(CheckBox) findViewById(R.id.chart_settings_elevation);
|
||||||
@@ -74,29 +83,30 @@ public class ChartSettingsDialog extends Dialog {
|
|||||||
(CheckBox) findViewById(R.id.chart_settings_heart_rate);
|
(CheckBox) findViewById(R.id.chart_settings_heart_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setup(ChartActivity chart) {
|
public void setMode(Mode mode) {
|
||||||
if (chart == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
|
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
|
||||||
rd.check(chart.getMode() == Mode.BY_DISTANCE
|
rd.check(mode == Mode.BY_DISTANCE
|
||||||
? R.id.chart_settings_by_distance
|
? R.id.chart_settings_by_distance
|
||||||
: R.id.chart_settings_by_time);
|
: R.id.chart_settings_by_time);
|
||||||
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
|
||||||
series[i].setChecked(chart.isSeriesEnabled(i));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleOk() {
|
public void setSeriesEnabled(int seriesIdx, boolean enabled) {
|
||||||
ChartActivity chart = MyTracks.getInstance().getChartActivity();
|
series[seriesIdx].setChecked(enabled);
|
||||||
chart.setMode(distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME);
|
}
|
||||||
|
|
||||||
// TODO: check that something is visible.
|
public Mode getMode() {
|
||||||
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
|
if (distance == null) return Mode.BY_DISTANCE;
|
||||||
chart.setSeriesEnabled(i, series[i].isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss();
|
return distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSeriesEnabled(int seriesIdx) {
|
||||||
|
if (series == null) return true;
|
||||||
|
|
||||||
|
return series[seriesIdx].isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnClickListener(OnClickListener clickListener) {
|
||||||
|
this.clickListener = clickListener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Google Inc.
|
* Copyright 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -48,7 +48,7 @@ public class ChartValueSeries {
|
|||||||
private double spread;
|
private double spread;
|
||||||
private int interval;
|
private int interval;
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
|
* This class controls how effective min/max values of a {@link ChartValueSeries} are calculated.
|
||||||
*/
|
*/
|
||||||
@@ -70,10 +70,10 @@ public class ChartValueSeries {
|
|||||||
this.intervals = intervals;
|
this.intervals = intervals;
|
||||||
this.absoluteMin = absoluteMin;
|
this.absoluteMin = absoluteMin;
|
||||||
this.absoluteMax = absoluteMax;
|
this.absoluteMax = absoluteMax;
|
||||||
this.zoomLevels = zoomLevels;
|
this.zoomLevels = zoomLevels;
|
||||||
checkArgs();
|
checkArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkArgs() {
|
private void checkArgs() {
|
||||||
if (intervals <= 0 || zoomLevels == null || zoomLevels.length == 0) {
|
if (intervals <= 0 || zoomLevels == null || zoomLevels.length == 0) {
|
||||||
throw new IllegalArgumentException("Expecing positive intervals and non-empty zoom levels");
|
throw new IllegalArgumentException("Expecing positive intervals and non-empty zoom levels");
|
||||||
@@ -100,12 +100,12 @@ public class ChartValueSeries {
|
|||||||
public int[] getZoomLevels() {
|
public int[] getZoomLevels() {
|
||||||
return zoomLevels;
|
return zoomLevels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the interval between markings given the min and max values.
|
* Calculates the interval between markings given the min and max values.
|
||||||
* This function attempts to find the smallest zoom level that fits [min,max] after rounding
|
* This function attempts to find the smallest zoom level that fits [min,max] after rounding
|
||||||
* it to the current zoom level.
|
* it to the current zoom level.
|
||||||
*
|
*
|
||||||
* @param min the minimum value in the series
|
* @param min the minimum value in the series
|
||||||
* @param max the maximum value in the series
|
* @param max the maximum value in the series
|
||||||
* @return the calculated interval for the given range
|
* @return the calculated interval for the given range
|
||||||
@@ -136,7 +136,7 @@ public class ChartValueSeries {
|
|||||||
* @param fill The paint for filling the chart
|
* @param fill The paint for filling the chart
|
||||||
* @param stroke The paint for stroking the outside the chart, optional
|
* @param stroke The paint for stroking the outside the chart, optional
|
||||||
* @param zoomSettings The settings related to zooming
|
* @param zoomSettings The settings related to zooming
|
||||||
*
|
*
|
||||||
* TODO: Get rid of Context and inject appropriate values instead.
|
* TODO: Get rid of Context and inject appropriate values instead.
|
||||||
*/
|
*/
|
||||||
public ChartValueSeries(Context context, String formatString, int fillColor, int strokeColor,
|
public ChartValueSeries(Context context, String formatString, int fillColor, int strokeColor,
|
||||||
@@ -207,7 +207,7 @@ public class ChartValueSeries {
|
|||||||
}
|
}
|
||||||
min = Math.min(min, zoomSettings.getAbsoluteMin());
|
min = Math.min(min, zoomSettings.getAbsoluteMin());
|
||||||
max = Math.max(max, zoomSettings.getAbsoluteMax());
|
max = Math.max(max, zoomSettings.getAbsoluteMax());
|
||||||
|
|
||||||
this.interval = zoomSettings.calculateInterval(min, max);
|
this.interval = zoomSettings.calculateInterval(min, max);
|
||||||
// Round it up.
|
// Round it up.
|
||||||
effectiveMax = ((int) (max / interval)) * interval + interval;
|
effectiveMax = ((int) (max / interval)) * interval + interval;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -28,9 +28,9 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.DashPathEffect;
|
import android.graphics.DashPathEffect;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
|
||||||
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Align;
|
||||||
import android.graphics.Paint.Style;
|
import android.graphics.Paint.Style;
|
||||||
|
import android.graphics.Path;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.VelocityTracker;
|
import android.view.VelocityTracker;
|
||||||
@@ -49,6 +49,7 @@ import java.util.ArrayList;
|
|||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class ChartView extends View {
|
public class ChartView extends View {
|
||||||
|
private static final int MIN_ZOOM_LEVEL = 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Scrolling logic:
|
* Scrolling logic:
|
||||||
@@ -62,40 +63,41 @@ public class ChartView extends View {
|
|||||||
* Zoom logic:
|
* Zoom logic:
|
||||||
*/
|
*/
|
||||||
private int zoomLevel = 1;
|
private int zoomLevel = 1;
|
||||||
private final int minZoomLevel = 1;
|
|
||||||
private int maxZoomLevel = 10;
|
private int maxZoomLevel = 10;
|
||||||
|
|
||||||
private static final int MAX_INTERVALS = 5;
|
private static final int MAX_INTERVALS = 5;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Borders, margins, dimensions (in pixels):
|
* Borders, margins, dimensions (in pixels):
|
||||||
*/
|
*/
|
||||||
private int leftBorder = -1;
|
private int leftBorder = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unscaled top border of the chart.
|
* Unscaled top border of the chart.
|
||||||
*/
|
*/
|
||||||
private static final int TOP_BORDER = 15;
|
private static final int TOP_BORDER = 15;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device scaled top border of the chart.
|
* Device scaled top border of the chart.
|
||||||
*/
|
*/
|
||||||
private int topBorder;
|
private int topBorder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unscaled bottom border of the chart.
|
* Unscaled bottom border of the chart.
|
||||||
*/
|
*/
|
||||||
private static final float BOTTOM_BORDER = 40;
|
private static final float BOTTOM_BORDER = 40;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device scaled bottom border of the chart.
|
* Device scaled bottom border of the chart.
|
||||||
*/
|
*/
|
||||||
private int bottomBorder;
|
private int bottomBorder;
|
||||||
|
|
||||||
private static final int RIGHT_BORDER = 17;
|
private static final int RIGHT_BORDER = 17;
|
||||||
|
|
||||||
/** Space to leave for drawing the unit labels */
|
/** Space to leave for drawing the unit labels */
|
||||||
private static final int UNIT_BORDER = 15;
|
private static final int UNIT_BORDER = 15;
|
||||||
private static final int FONT_HEIGHT = 10;
|
private static final int FONT_HEIGHT = 10;
|
||||||
|
|
||||||
private int w = 0;
|
private int w = 0;
|
||||||
private int h = 0;
|
private int h = 0;
|
||||||
private int effectiveWidth = 0;
|
private int effectiveWidth = 0;
|
||||||
@@ -118,11 +120,11 @@ public class ChartView extends View {
|
|||||||
private ChartValueSeries[] series;
|
private ChartValueSeries[] series;
|
||||||
|
|
||||||
private final ExtremityMonitor xMonitor = new ExtremityMonitor();
|
private final ExtremityMonitor xMonitor = new ExtremityMonitor();
|
||||||
private final NumberFormat xFormat = new DecimalFormat("###,###");
|
private static final NumberFormat X_FORMAT = new DecimalFormat("###,###");
|
||||||
private final NumberFormat xShortFormat = new DecimalFormat("#.0");
|
private static final NumberFormat X_SHORT_FORMAT = new DecimalFormat("#.0");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Paints etc. used when drawing the histogram:
|
* Paints etc. used when drawing the chart:
|
||||||
*/
|
*/
|
||||||
private final Paint borderPaint = new Paint();
|
private final Paint borderPaint = new Paint();
|
||||||
private final Paint labelPaint = new Paint();
|
private final Paint labelPaint = new Paint();
|
||||||
@@ -174,7 +176,7 @@ public class ChartView extends View {
|
|||||||
|
|
||||||
gridBarPaint.set(gridPaint);
|
gridBarPaint.set(gridPaint);
|
||||||
gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
|
gridBarPaint.setPathEffect(new DashPathEffect(new float[] {3, 2}, 0));
|
||||||
|
|
||||||
clearPaint.setStyle(Style.FILL);
|
clearPaint.setStyle(Style.FILL);
|
||||||
clearPaint.setColor(context.getResources().getColor(R.color.white));
|
clearPaint.setColor(context.getResources().getColor(R.color.white));
|
||||||
clearPaint.setAntiAlias(false);
|
clearPaint.setAntiAlias(false);
|
||||||
@@ -271,49 +273,12 @@ public class ChartView extends View {
|
|||||||
: R.string.pace_label));
|
: R.string.pace_label));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the data that is displayed by the chart.
|
|
||||||
*
|
|
||||||
* @return an array list with data points
|
|
||||||
*/
|
|
||||||
public ArrayList<double[]> getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the data that is to be displayed by the chart.
|
|
||||||
*
|
|
||||||
* @param theData an array list of data points
|
|
||||||
*/
|
|
||||||
public synchronized void setDataPoints(ArrayList<double[]> theData) {
|
|
||||||
scrollTo(0, 0);
|
|
||||||
zoomLevel = 1;
|
|
||||||
data.clear();
|
|
||||||
xMonitor.reset();
|
|
||||||
for (ChartValueSeries cvs : series) {
|
|
||||||
cvs.reset();
|
|
||||||
}
|
|
||||||
addDataPoints(theData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new data point to the chart.
|
|
||||||
*
|
|
||||||
* @param theData a data point
|
|
||||||
*/
|
|
||||||
public synchronized void addDataPoint(double[] theData) {
|
|
||||||
data.add(theData);
|
|
||||||
addDataPointInternal(theData);
|
|
||||||
updateDimensions();
|
|
||||||
setUpPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addDataPointInternal(double[] theData) {
|
private void addDataPointInternal(double[] theData) {
|
||||||
xMonitor.update(theData[0]);
|
xMonitor.update(theData[0]);
|
||||||
int min = Math.min(series.length, theData.length - 1);
|
int min = Math.min(series.length, theData.length - 1);
|
||||||
for (int i = 0; i < min; i++) {
|
for (int i = 1; i <= min; i++) {
|
||||||
if (!Double.isNaN(theData[i + 1])) {
|
if (!Double.isNaN(theData[i])) {
|
||||||
series[i].update(theData[i + 1]);
|
series[i - 1].update(theData[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Fill in the extra's if needed.
|
// Fill in the extra's if needed.
|
||||||
@@ -329,25 +294,32 @@ public class ChartView extends View {
|
|||||||
*
|
*
|
||||||
* @param theData an array list of data points to be added
|
* @param theData an array list of data points to be added
|
||||||
*/
|
*/
|
||||||
public synchronized void addDataPoints(ArrayList<double[]> theData) {
|
public void addDataPoints(ArrayList<double[]> theData) {
|
||||||
data.addAll(theData);
|
synchronized (data) {
|
||||||
for (int i = 0; i < theData.size(); i++) {
|
data.addAll(theData);
|
||||||
double d[] = theData.get(i);
|
for (int i = 0; i < theData.size(); i++) {
|
||||||
addDataPointInternal(d);
|
double d[] = theData.get(i);
|
||||||
|
addDataPointInternal(d);
|
||||||
|
}
|
||||||
|
updateDimensions();
|
||||||
|
setUpPath();
|
||||||
}
|
}
|
||||||
updateDimensions();
|
|
||||||
setUpPath();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all data.
|
* Clears all data.
|
||||||
* Call this only from the UI thread!
|
|
||||||
*/
|
*/
|
||||||
public synchronized void reset() {
|
public void reset() {
|
||||||
data.clear();
|
synchronized (data) {
|
||||||
zoomLevel = 1;
|
data.clear();
|
||||||
|
xMonitor.reset();
|
||||||
|
zoomLevel = 1;
|
||||||
|
updateDimensions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetScroll() {
|
||||||
scrollTo(0, 0);
|
scrollTo(0, 0);
|
||||||
updateDimensions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -361,7 +333,7 @@ public class ChartView extends View {
|
|||||||
* @return true if the chart can be zoomed out
|
* @return true if the chart can be zoomed out
|
||||||
*/
|
*/
|
||||||
public boolean canZoomOut() {
|
public boolean canZoomOut() {
|
||||||
return zoomLevel > minZoomLevel;
|
return zoomLevel > MIN_ZOOM_LEVEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -392,13 +364,6 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the current zoom level (1 equals to showing all data points)
|
|
||||||
*/
|
|
||||||
public int getZoomLevel() {
|
|
||||||
return zoomLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates flinging.
|
* Initiates flinging.
|
||||||
*
|
*
|
||||||
@@ -427,14 +392,6 @@ public class ChartView extends View {
|
|||||||
scrollTo(scrollX, 0);
|
scrollTo(scrollX, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the scroll position of the chart. This will trigger a redraw.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void scrollTo(int x, int y) {
|
|
||||||
super.scrollTo(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current display mode (by distance, by time)
|
* @return the current display mode (by distance, by time)
|
||||||
*/
|
*/
|
||||||
@@ -551,63 +508,60 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized void onDraw(Canvas c) {
|
protected void onDraw(Canvas c) {
|
||||||
if (w != c.getWidth() || h != c.getHeight()) {
|
synchronized (data) {
|
||||||
// Dimensions have changed (for example due to orientation change).
|
updateEffectiveDimensionsIfChanged(c);
|
||||||
w = c.getWidth();
|
|
||||||
h = c.getHeight();
|
|
||||||
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
|
||||||
effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
|
|
||||||
setUpPath();
|
|
||||||
}
|
|
||||||
c.save();
|
|
||||||
c.drawColor(Color.WHITE);
|
|
||||||
if (data.size() < 1) {
|
|
||||||
drawXAxis(c);
|
|
||||||
drawYAxis(c);
|
|
||||||
c.restore();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
c.save();
|
|
||||||
c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
|
|
||||||
w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
|
|
||||||
|
|
||||||
drawGrid(c);
|
// Keep original state.
|
||||||
|
|
||||||
// Draw the data series.
|
|
||||||
for (ChartValueSeries cvs : series) {
|
|
||||||
if (cvs.isEnabled() && cvs.hasData()) {
|
|
||||||
cvs.drawPath(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the waypoints.
|
|
||||||
for (int i = 1; i < waypoints.size(); i++) {
|
|
||||||
final Waypoint waypoint = waypoints.get(i);
|
|
||||||
if (waypoint.getLocation() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
c.save();
|
c.save();
|
||||||
|
|
||||||
final float x = getWaypointX(waypoint);
|
c.drawColor(Color.WHITE);
|
||||||
c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
|
|
||||||
c.translate(x - markerWidth / 2, markerHeight);
|
|
||||||
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
|
|
||||||
statsMarker.draw(c);
|
|
||||||
} else {
|
|
||||||
waypointMarker.draw(c);
|
|
||||||
}
|
|
||||||
c.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
c.restore();
|
if (data.isEmpty()) {
|
||||||
|
// No data, draw only axes
|
||||||
// Draw the axis and labels.
|
drawXAxis(c);
|
||||||
|
drawYAxis(c);
|
||||||
|
c.restore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip to graph drawing space
|
||||||
|
c.save();
|
||||||
|
clipToGraphSpace(c);
|
||||||
|
|
||||||
|
// Draw the grid and the data on it.
|
||||||
|
drawGrid(c);
|
||||||
|
drawDataSeries(c);
|
||||||
|
drawWaypoints(c);
|
||||||
|
|
||||||
|
// Go back to full canvas drawing.
|
||||||
|
c.restore();
|
||||||
|
|
||||||
|
// Draw the axes and their labels.
|
||||||
|
drawAxesAndLabels(c);
|
||||||
|
|
||||||
|
// Go back to original state.
|
||||||
|
c.restore();
|
||||||
|
|
||||||
|
// Draw the pointer
|
||||||
|
if (showPointer) {
|
||||||
|
drawPointer(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clips the given canvas to the area where the graph lines should be drawn. */
|
||||||
|
private void clipToGraphSpace(Canvas c) {
|
||||||
|
c.clipRect(leftBorder + 1 + getScrollX(), topBorder + 1,
|
||||||
|
w - RIGHT_BORDER + getScrollX() - 1, h - bottomBorder - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Draws the axes and their labels into th e given canvas. */
|
||||||
|
private void drawAxesAndLabels(Canvas c) {
|
||||||
drawXLabels(c);
|
drawXLabels(c);
|
||||||
drawXAxis(c);
|
drawXAxis(c);
|
||||||
drawSeriesTitles(c);
|
drawSeriesTitles(c);
|
||||||
|
|
||||||
c.translate(getScrollX(), 0);
|
c.translate(getScrollX(), 0);
|
||||||
drawYAxis(c);
|
drawYAxis(c);
|
||||||
float density = getContext().getResources().getDisplayMetrics().density;
|
float density = getContext().getResources().getDisplayMetrics().density;
|
||||||
@@ -618,15 +572,47 @@ public class ChartView extends View {
|
|||||||
x -= drawYLabels(cvs, c, x) + spacer;
|
x -= drawYLabels(cvs, c, x) + spacer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.restore();
|
}
|
||||||
if (showPointer && !data.isEmpty()) {
|
|
||||||
c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
|
/** Draws the current pointer into the given canvas. */
|
||||||
getY(series[0], data.get(data.size() - 1)[1])
|
private void drawPointer(Canvas c) {
|
||||||
- pointer.getIntrinsicHeight() / 2 - 12);
|
c.translate(getX(maxX) - pointer.getIntrinsicWidth() / 2,
|
||||||
pointer.draw(c);
|
getY(series[0], data.get(data.size() - 1)[1])
|
||||||
|
- pointer.getIntrinsicHeight() / 2 - 12);
|
||||||
|
pointer.draw(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Draws the waypoints into the given canvas. */
|
||||||
|
private void drawWaypoints(Canvas c) {
|
||||||
|
for (int i = 1; i < waypoints.size(); i++) {
|
||||||
|
final Waypoint waypoint = waypoints.get(i);
|
||||||
|
if (waypoint.getLocation() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
c.save();
|
||||||
|
|
||||||
|
final float x = getWaypointX(waypoint);
|
||||||
|
c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
|
||||||
|
c.translate(x - (float) markerWidth / 2.0f, (float) markerHeight);
|
||||||
|
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
|
||||||
|
statsMarker.draw(c);
|
||||||
|
} else {
|
||||||
|
waypointMarker.draw(c);
|
||||||
|
}
|
||||||
|
c.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws the data series into the given canvas. */
|
||||||
|
private void drawDataSeries(Canvas c) {
|
||||||
|
for (ChartValueSeries cvs : series) {
|
||||||
|
if (cvs.isEnabled() && cvs.hasData()) {
|
||||||
|
cvs.drawPath(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Draws the colored titles for the data series. */
|
||||||
private void drawSeriesTitles(Canvas c) {
|
private void drawSeriesTitles(Canvas c) {
|
||||||
int sections = 1;
|
int sections = 1;
|
||||||
for (ChartValueSeries cvs : series) {
|
for (ChartValueSeries cvs : series) {
|
||||||
@@ -644,17 +630,24 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the path that is used to draw the histogram in onDraw(). The path
|
* Sets up the path that is used to draw the chart in onDraw(). The path
|
||||||
* needs to be updated any time after the data or histogram dimensions change.
|
* needs to be updated any time after the data or histogram dimensions change.
|
||||||
*/
|
*/
|
||||||
private synchronized void setUpPath() {
|
private void setUpPath() {
|
||||||
for (ChartValueSeries cvs : series) {
|
synchronized (data) {
|
||||||
cvs.getPath().reset();
|
for (ChartValueSeries cvs : series) {
|
||||||
}
|
cvs.getPath().reset();
|
||||||
if (data.isEmpty()) {
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
drawPaths();
|
||||||
|
closePaths();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Actually draws the data points as a path. */
|
||||||
|
private void drawPaths() {
|
||||||
// All of the data points to the respective series.
|
// All of the data points to the respective series.
|
||||||
// TODO: Come up with a better sampling than Math.max(1, (maxZoomLevel - zoomLevel + 1) / 2);
|
// TODO: Come up with a better sampling than Math.max(1, (maxZoomLevel - zoomLevel + 1) / 2);
|
||||||
int sampling = 1;
|
int sampling = 1;
|
||||||
@@ -673,7 +666,10 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes the drawn path so it looks like a solid graph. */
|
||||||
|
private void closePaths() {
|
||||||
// Close the path.
|
// Close the path.
|
||||||
int yCorner = topBorder + effectiveHeight;
|
int yCorner = topBorder + effectiveHeight;
|
||||||
int xCorner = getX(data.get(0)[0]);
|
int xCorner = getX(data.get(0)[0]);
|
||||||
@@ -692,12 +688,13 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the index of the first point which has a series populated.
|
* Finds the index of the first point which has a series populated.
|
||||||
* @param seriesIndex The index of the value series to search for.
|
*
|
||||||
|
* @param seriesIndex The index of the value series to search for
|
||||||
* @return The index in the first data for the point in the series that has series
|
* @return The index in the first data for the point in the series that has series
|
||||||
* index value populated or -1 if none is found.
|
* index value populated or -1 if none is found
|
||||||
*/
|
*/
|
||||||
private int getFirstPointPopulatedIndex(int seriesIndex) {
|
private int getFirstPointPopulatedIndex(int seriesIndex) {
|
||||||
for (int i = 0; i < data.size(); i++) {
|
for (int i = 0; i < data.size(); i++) {
|
||||||
@@ -709,7 +706,7 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the histogram dimensions.
|
* Updates the chart dimensions.
|
||||||
*/
|
*/
|
||||||
private void updateDimensions() {
|
private void updateDimensions() {
|
||||||
maxX = xMonitor.getMax();
|
maxX = xMonitor.getMax();
|
||||||
@@ -730,10 +727,29 @@ public class ChartView extends View {
|
|||||||
float density = getContext().getResources().getDisplayMetrics().density;
|
float density = getContext().getResources().getDisplayMetrics().density;
|
||||||
maxLength = Math.max(maxLength, 1);
|
maxLength = Math.max(maxLength, 1);
|
||||||
leftBorder = (int) (density * (4 + 8 * maxLength));
|
leftBorder = (int) (density * (4 + 8 * maxLength));
|
||||||
effectiveWidth = w - leftBorder - RIGHT_BORDER;
|
|
||||||
bottomBorder = (int) (density * BOTTOM_BORDER);
|
bottomBorder = (int) (density * BOTTOM_BORDER);
|
||||||
topBorder = (int) (density * TOP_BORDER);
|
topBorder = (int) (density * TOP_BORDER);
|
||||||
effectiveHeight = h - topBorder - bottomBorder;
|
updateEffectiveDimensions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the effective dimensions where the graph will be drawn. */
|
||||||
|
private void updateEffectiveDimensions() {
|
||||||
|
effectiveWidth = Math.max(0, w - leftBorder - RIGHT_BORDER);
|
||||||
|
effectiveHeight = Math.max(0, h - topBorder - bottomBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the effective dimensions where the graph will be drawn, only if the
|
||||||
|
* dimensions of the given canvas have changed since the last call.
|
||||||
|
*/
|
||||||
|
private void updateEffectiveDimensionsIfChanged(Canvas c) {
|
||||||
|
if (w != c.getWidth() || h != c.getHeight()) {
|
||||||
|
// Dimensions have changed (for example due to orientation change).
|
||||||
|
w = c.getWidth();
|
||||||
|
h = c.getHeight();
|
||||||
|
updateEffectiveDimensions();
|
||||||
|
setUpPath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getX(double distance) {
|
private int getX(double distance) {
|
||||||
@@ -746,6 +762,7 @@ public class ChartView extends View {
|
|||||||
- (int) ((y - cvs.getMin()) * effectiveHeight / effectiveSpread);
|
- (int) ((y - cvs.getMin()) * effectiveHeight / effectiveSpread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws the labels on the X axis into the given canvas. */
|
||||||
private void drawXLabels(Canvas c) {
|
private void drawXLabels(Canvas c) {
|
||||||
double interval = (int) (maxX / zoomLevel / 4);
|
double interval = (int) (maxX / zoomLevel / 4);
|
||||||
boolean shortFormat = false;
|
boolean shortFormat = false;
|
||||||
@@ -770,6 +787,7 @@ public class ChartView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws the labels on the Y axis into the given canvas. */
|
||||||
private float drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
|
private float drawYLabels(ChartValueSeries cvs, Canvas c, int x) {
|
||||||
int interval = cvs.getInterval();
|
int interval = cvs.getInterval();
|
||||||
float maxTextWidth = 0;
|
float maxTextWidth = 0;
|
||||||
@@ -779,13 +797,14 @@ public class ChartView extends View {
|
|||||||
return maxTextWidth;
|
return maxTextWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws a single label on the X axis. */
|
||||||
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
|
private void drawXLabel(Canvas c, double x, boolean shortFormat) {
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String s =
|
String s =
|
||||||
(mode == Mode.BY_DISTANCE)
|
(mode == Mode.BY_DISTANCE)
|
||||||
? (shortFormat ? xShortFormat.format(x) : xFormat.format(x))
|
? (shortFormat ? X_SHORT_FORMAT.format(x) : X_FORMAT.format(x))
|
||||||
: StringUtils.formatTime((long) x);
|
: StringUtils.formatTime((long) x);
|
||||||
c.drawText(s,
|
c.drawText(s,
|
||||||
getX(x),
|
getX(x),
|
||||||
@@ -793,6 +812,7 @@ public class ChartView extends View {
|
|||||||
labelPaint);
|
labelPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws a single label on the Y axis. */
|
||||||
private float drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
|
private float drawYLabel(ChartValueSeries cvs, Canvas c, int x, int y) {
|
||||||
int desiredY = (int) ((y - cvs.getMin()) * effectiveHeight /
|
int desiredY = (int) ((y - cvs.getMin()) * effectiveHeight /
|
||||||
(cvs.getInterval() * MAX_INTERVALS));
|
(cvs.getInterval() * MAX_INTERVALS));
|
||||||
@@ -804,6 +824,7 @@ public class ChartView extends View {
|
|||||||
return p.measureText(text);
|
return p.measureText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws the actual X axis line and its label. */
|
||||||
private void drawXAxis(Canvas canvas) {
|
private void drawXAxis(Canvas canvas) {
|
||||||
float rightEdge = getX(maxX);
|
float rightEdge = getX(maxX);
|
||||||
final int y = effectiveHeight + topBorder;
|
final int y = effectiveHeight + topBorder;
|
||||||
@@ -815,6 +836,7 @@ public class ChartView extends View {
|
|||||||
canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
|
canvas.drawText(s, rightEdge, effectiveHeight + .2f * UNIT_BORDER + topBorder, labelPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Draws the actual Y axis line and its label. */
|
||||||
private void drawYAxis(Canvas canvas) {
|
private void drawYAxis(Canvas canvas) {
|
||||||
canvas.drawRect(0, 0,
|
canvas.drawRect(0, 0,
|
||||||
leftBorder - 1, effectiveHeight + topBorder + UNIT_BORDER + 1,
|
leftBorder - 1, effectiveHeight + topBorder + UNIT_BORDER + 1,
|
||||||
@@ -826,18 +848,15 @@ public class ChartView extends View {
|
|||||||
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
|
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
|
||||||
canvas.drawLine(leftBorder - 5, y, leftBorder, y, gridPaint);
|
canvas.drawLine(leftBorder - 5, y, leftBorder, y, gridPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
Context c = getContext();
|
Context c = getContext();
|
||||||
// TODO: This should really show units for all series.
|
// TODO: This should really show units for all series.
|
||||||
String s = metricUnits ? c.getString(R.string.meter) : c.getString(R.string.feet);
|
String s = metricUnits ? c.getString(R.string.meter) : c.getString(R.string.feet);
|
||||||
canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
|
canvas.drawText(s, leftBorder - UNIT_BORDER * .2f, UNIT_BORDER * .8f + topBorder, labelPaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void drawGrid(Canvas c) {
|
/** Draws the grid for the graph. */
|
||||||
if (data.isEmpty()) {
|
private void drawGrid(Canvas c) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float rightEdge = getX(maxX);
|
float rightEdge = getX(maxX);
|
||||||
for (int i = 1; i < MAX_INTERVALS; ++i) {
|
for (int i = 1; i < MAX_INTERVALS; ++i) {
|
||||||
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
|
int y = i * effectiveHeight / MAX_INTERVALS + topBorder;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public abstract class Constants {
|
|||||||
public static final int AUTHENTICATE_TO_DOCLIST = 10;
|
public static final int AUTHENTICATE_TO_DOCLIST = 10;
|
||||||
public static final int AUTHENTICATE_TO_TRIX = 11;
|
public static final int AUTHENTICATE_TO_TRIX = 11;
|
||||||
public static final int DELETE_TRACK = 13;
|
public static final int DELETE_TRACK = 13;
|
||||||
public static final int SEND_TO_GOOGLE_DIALOG = 14;
|
public static final int SEND_TO_GOOGLE = 14;
|
||||||
public static final int SHARE_LINK = 15;
|
public static final int SHARE_LINK = 15;
|
||||||
public static final int SHARE_GPX_FILE = 16;
|
public static final int SHARE_GPX_FILE = 16;
|
||||||
public static final int SHARE_KML_FILE = 17;
|
public static final int SHARE_KML_FILE = 17;
|
||||||
@@ -187,18 +187,10 @@ public abstract class Constants {
|
|||||||
public static final String RESUME_TRACK_EXTRA_NAME =
|
public static final String RESUME_TRACK_EXTRA_NAME =
|
||||||
"com.google.android.apps.mytracks.RESUME_TRACK";
|
"com.google.android.apps.mytracks.RESUME_TRACK";
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the extra intent property that holds the track information
|
|
||||||
* when sharing with friends
|
|
||||||
*/
|
|
||||||
public static final String TRACK_EXTRA =
|
|
||||||
"com.google.android.apps.mytracks.TRACK";
|
|
||||||
|
|
||||||
|
|
||||||
public static int getActionFromMenuId(int menuId) {
|
public static int getActionFromMenuId(int menuId) {
|
||||||
switch (menuId) {
|
switch (menuId) {
|
||||||
case Constants.MENU_SEND_TO_GOOGLE:
|
case Constants.MENU_SEND_TO_GOOGLE:
|
||||||
return Constants.SEND_TO_GOOGLE_DIALOG;
|
return Constants.SEND_TO_GOOGLE;
|
||||||
case Constants.MENU_EDIT:
|
case Constants.MENU_EDIT:
|
||||||
return Constants.EDIT_DETAILS;
|
return Constants.EDIT_DETAILS;
|
||||||
case Constants.MENU_DELETE:
|
case Constants.MENU_DELETE:
|
||||||
|
|||||||
@@ -17,14 +17,8 @@ package com.google.android.apps.mytracks;
|
|||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.WindowManager.BadTokenException;
|
import android.view.WindowManager.BadTokenException;
|
||||||
|
|
||||||
@@ -35,145 +29,16 @@ import android.view.WindowManager.BadTokenException;
|
|||||||
*/
|
*/
|
||||||
public class DialogManager {
|
public class DialogManager {
|
||||||
|
|
||||||
public static final int DIALOG_CHART_SETTINGS = 1;
|
|
||||||
public static final int DIALOG_IMPORT_PROGRESS = 2;
|
|
||||||
public static final int DIALOG_PROGRESS = 3;
|
|
||||||
public static final int DIALOG_SEND_TO_GOOGLE = 4;
|
|
||||||
|
|
||||||
private ProgressDialog progressDialog;
|
|
||||||
private ProgressDialog importProgressDialog;
|
|
||||||
private SendDialog sendToGoogleDialog;
|
|
||||||
private ChartSettingsDialog chartSettingsDialog;
|
|
||||||
|
|
||||||
private MyTracks activity;
|
|
||||||
|
|
||||||
public DialogManager(MyTracks activity) {
|
|
||||||
this.activity = activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Dialog onCreateDialog(int id, Bundle args) {
|
|
||||||
switch (id) {
|
|
||||||
case DIALOG_CHART_SETTINGS:
|
|
||||||
chartSettingsDialog = new ChartSettingsDialog(activity);
|
|
||||||
return chartSettingsDialog;
|
|
||||||
case DIALOG_IMPORT_PROGRESS:
|
|
||||||
importProgressDialog = new ProgressDialog(activity);
|
|
||||||
importProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
importProgressDialog.setTitle(
|
|
||||||
activity.getString(R.string.progress_title));
|
|
||||||
importProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
|
||||||
importProgressDialog.setMessage(
|
|
||||||
activity.getString(R.string.import_progress_message));
|
|
||||||
return importProgressDialog;
|
|
||||||
case DIALOG_PROGRESS:
|
|
||||||
progressDialog = new ProgressDialog(activity);
|
|
||||||
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
progressDialog.setTitle(activity.getString(R.string.progress_title));
|
|
||||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
||||||
progressDialog.setMessage("");
|
|
||||||
progressDialog.setMax(100);
|
|
||||||
progressDialog.setProgress(10);
|
|
||||||
return progressDialog;
|
|
||||||
case DIALOG_SEND_TO_GOOGLE:
|
|
||||||
sendToGoogleDialog = new SendDialog(activity);
|
|
||||||
return sendToGoogleDialog;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
|
||||||
switch (id) {
|
|
||||||
case DIALOG_SEND_TO_GOOGLE:
|
|
||||||
activity.resetSendToGoogleStatus();
|
|
||||||
break;
|
|
||||||
case DIALOG_CHART_SETTINGS:
|
|
||||||
Log.d(TAG, "MyTracks.onPrepare chart dialog");
|
|
||||||
chartSettingsDialog.setup(activity.getChartActivity());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgressMessage(final String message) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
synchronized (this) {
|
|
||||||
if (progressDialog != null) {
|
|
||||||
progressDialog.setMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgressValue(final int percent) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
synchronized (this) {
|
|
||||||
if (progressDialog != null) {
|
|
||||||
progressDialog.setProgress(percent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the sendToGoogleDialog
|
|
||||||
*/
|
|
||||||
public SendDialog getSendToGoogleDialog() {
|
|
||||||
return sendToGoogleDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a dialog with the given message.
|
|
||||||
* Does it on the UI thread.
|
|
||||||
*
|
|
||||||
* @param success if true, displays an info icon/title, otherwise an error
|
|
||||||
* icon/title
|
|
||||||
* @param message resource string id
|
|
||||||
*/
|
|
||||||
public void showMessageDialog(final int message, final boolean success) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
AlertDialog dialog = null;
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
||||||
builder.setMessage(activity.getString(message));
|
|
||||||
builder.setNegativeButton(activity.getString(R.string.ok), null);
|
|
||||||
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
|
|
||||||
android.R.drawable.ic_dialog_alert);
|
|
||||||
builder.setTitle(success ? R.string.success : R.string.error);
|
|
||||||
dialog = builder.create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Just like showDialog, but will catch a {@link BadTokenException} that
|
|
||||||
* sometimes (very rarely) gets thrown. This might happen if the user hits
|
|
||||||
* the "back" button immediately after sending tracks to google.
|
|
||||||
*
|
|
||||||
* @param id the dialog id
|
|
||||||
*/
|
|
||||||
public void showDialogSafely(final int id) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
activity.showDialog(id);
|
|
||||||
} catch (BadTokenException e) {
|
|
||||||
Log.w(TAG, "Could not display dialog with id " + id, e);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.w(TAG, "Could not display dialog with id " + id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The equivalent of {@link #showDialogSafely(int)}, but for a specific
|
* The equivalent of {@link #showDialogSafely(int)}, but for a specific
|
||||||
* dialog instance.
|
* dialog instance.
|
||||||
*/
|
*/
|
||||||
public static void showDialogSafely(Activity activity, final Dialog dialog) {
|
public static void showDialogSafely(Activity activity, final Dialog dialog) {
|
||||||
|
if (activity.isFinishing()) {
|
||||||
|
Log.w(TAG, "Activity finishing - not showing dialog");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
activity.runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
@@ -187,26 +52,16 @@ public class DialogManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismisses the progress dialog if it is showing. Executed on the UI thread.
|
|
||||||
*/
|
|
||||||
public void dismissDialogSafely(final int id) {
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
activity.dismissDialog(id);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// This will be thrown if this dialog was not shown before.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The equivalent of {@link #dismissDialogSafely(int)}, but for a specific
|
* The equivalent of {@link #dismissDialogSafely(int)}, but for a specific
|
||||||
* dialog instance.
|
* dialog instance.
|
||||||
*/
|
*/
|
||||||
public static void dismissDialogSafely(Activity activity, final Dialog dialog) {
|
public static void dismissDialogSafely(Activity activity, final Dialog dialog) {
|
||||||
|
if (activity.isFinishing()) {
|
||||||
|
Log.w(TAG, "Activity finishing - not dismissing dialog");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
activity.runOnUiThread(new Runnable() {
|
activity.runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
@@ -217,4 +72,6 @@ public class DialogManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DialogManager() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Google Inc.
|
* Copyright 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -29,8 +29,6 @@ import android.app.ProgressDialog;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@@ -91,12 +89,7 @@ public class ExportAllTracks {
|
|||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
HandlerThread handlerThread;
|
new Thread(runner, "SendToMyMaps").start();
|
||||||
Handler handler;
|
|
||||||
handlerThread = new HandlerThread("SendToMyMaps");
|
|
||||||
handlerThread.start();
|
|
||||||
handler = new Handler(handlerThread.getLooper());
|
|
||||||
handler.post(runner);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -117,7 +110,7 @@ public class ExportAllTracks {
|
|||||||
long recordingTrackId = -1;
|
long recordingTrackId = -1;
|
||||||
if (prefs != null) {
|
if (prefs != null) {
|
||||||
recordingTrackId =
|
recordingTrackId =
|
||||||
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
|
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
|
||||||
}
|
}
|
||||||
if (recordingTrackId != -1) {
|
if (recordingTrackId != -1) {
|
||||||
wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock);
|
wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock);
|
||||||
@@ -133,7 +126,7 @@ public class ExportAllTracks {
|
|||||||
Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock.");
|
Log.i(Constants.TAG, "ExportAllTracks: Releasing wake lock.");
|
||||||
}
|
}
|
||||||
Log.i(Constants.TAG, "ExportAllTracks: Done");
|
Log.i(Constants.TAG, "ExportAllTracks: Done");
|
||||||
Toast.makeText(activity, R.string.export_done, Toast.LENGTH_SHORT).show();
|
showToast(R.string.export_done, Toast.LENGTH_SHORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeProgressDialog(final int trackCount) {
|
private void makeProgressDialog(final int trackCount) {
|
||||||
@@ -189,12 +182,16 @@ public class ExportAllTracks {
|
|||||||
Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id);
|
Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id);
|
||||||
TrackWriter writer =
|
TrackWriter writer =
|
||||||
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
|
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
|
||||||
|
if (writer == null) {
|
||||||
|
showToast(R.string.error_track_does_not_exist, Toast.LENGTH_LONG);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
writer.writeTrack();
|
writer.writeTrack();
|
||||||
|
|
||||||
if (!writer.wasSuccess()) {
|
if (!writer.wasSuccess()) {
|
||||||
// Abort the whole export on the first error.
|
// Abort the whole export on the first error.
|
||||||
int error = writer.getErrorMessage();
|
showToast(writer.getErrorMessage(), Toast.LENGTH_LONG);
|
||||||
Toast.makeText(activity, error, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,4 +207,13 @@ public class ExportAllTracks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showToast(final int messageId, final int length) {
|
||||||
|
activity.runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(activity, messageId, length).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011 Google Inc.
|
* Copyright 2011 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -25,8 +25,6 @@ import android.app.Activity;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.os.PowerManager.WakeLock;
|
import android.os.PowerManager.WakeLock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@@ -45,7 +43,7 @@ import org.xml.sax.SAXException;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that will import all GPX tracks in /sdcard/MyTracks/gpx/
|
* A class that will import all GPX tracks in /sdcard/MyTracks/gpx/
|
||||||
*
|
*
|
||||||
* @author David Piggott
|
* @author David Piggott
|
||||||
*/
|
*/
|
||||||
public class ImportAllTracks {
|
public class ImportAllTracks {
|
||||||
@@ -63,12 +61,8 @@ public class ImportAllTracks {
|
|||||||
Log.i(Constants.TAG, "ImportAllTracks: Starting");
|
Log.i(Constants.TAG, "ImportAllTracks: Starting");
|
||||||
fileUtils = new FileUtils();
|
fileUtils = new FileUtils();
|
||||||
gpxPath = fileUtils.buildExternalDirectoryPath("gpx");
|
gpxPath = fileUtils.buildExternalDirectoryPath("gpx");
|
||||||
HandlerThread handlerThread;
|
|
||||||
Handler handler;
|
new Thread(runner).start();
|
||||||
handlerThread = new HandlerThread("ImportAllTracks");
|
|
||||||
handlerThread.start();
|
|
||||||
handler = new Handler(handlerThread.getLooper());
|
|
||||||
handler.post(runner);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Runnable runner = new Runnable() {
|
private final Runnable runner = new Runnable() {
|
||||||
@@ -104,9 +98,9 @@ public class ImportAllTracks {
|
|||||||
Log.i(Constants.TAG, "ImportAllTracks: Done");
|
Log.i(Constants.TAG, "ImportAllTracks: Done");
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
if (gpxFileCount == 0) {
|
if (gpxFileCount == 0) {
|
||||||
builder.setMessage(activity.getString(R.string.import_empty, gpxPath + "/"));
|
builder.setMessage(activity.getString(R.string.import_multi_empty, gpxPath + "/"));
|
||||||
} else {
|
} else {
|
||||||
builder.setMessage(activity.getString(R.string.import_done, importSuccessCount, gpxFileCount,
|
builder.setMessage(activity.getString(R.string.import_multi_done, importSuccessCount, gpxFileCount,
|
||||||
gpxPath + "/"));
|
gpxPath + "/"));
|
||||||
}
|
}
|
||||||
builder.setPositiveButton(R.string.ok, null);
|
builder.setPositiveButton(R.string.ok, null);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -17,10 +17,14 @@ package com.google.android.apps.mytracks;
|
|||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
|
||||||
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.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
|
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||||
|
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
|
||||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
@@ -33,7 +37,6 @@ import com.google.android.maps.mytracks.R;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -50,6 +53,7 @@ import android.widget.ImageButton;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
@@ -120,7 +124,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
// ------------------------
|
// ------------------------
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
protected void onCreate(Bundle bundle) {
|
||||||
Log.d(TAG, "MapActivity.onCreate");
|
Log.d(TAG, "MapActivity.onCreate");
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
@@ -129,8 +133,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
||||||
setVolumeControlStream(volumeStream);
|
setVolumeControlStream(volumeStream);
|
||||||
|
|
||||||
dataHub = MyTracks.getInstance().getDataHub();
|
|
||||||
|
|
||||||
// We don't need a window title bar:
|
// We don't need a window title bar:
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
|
||||||
@@ -176,10 +178,11 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onResume() {
|
||||||
Log.d(TAG, "MapActivity.onStart");
|
Log.d(TAG, "MapActivity.onResume");
|
||||||
super.onStart();
|
super.onResume();
|
||||||
|
|
||||||
|
dataHub = TrackDataHub.getStartedInstance();
|
||||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||||
ListenerDataType.POINT_UPDATES,
|
ListenerDataType.POINT_UPDATES,
|
||||||
@@ -199,12 +202,13 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onPause() {
|
||||||
Log.d(TAG, "MapActivity.onStop");
|
Log.d(TAG, "MapActivity.onPause");
|
||||||
|
|
||||||
dataHub.unregisterTrackDataListener(this);
|
dataHub.unregisterTrackDataListener(this);
|
||||||
|
dataHub = null;
|
||||||
|
|
||||||
super.onStop();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility functions:
|
// Utility functions:
|
||||||
@@ -253,17 +257,18 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
* the current location is outside the visible area.
|
* the current location is outside the visible area.
|
||||||
*/
|
*/
|
||||||
private void showCurrentLocation() {
|
private void showCurrentLocation() {
|
||||||
if (currentLocation == null || mapOverlay == null || mapView == null) {
|
if (mapOverlay == null || mapView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOverlay.setMyLocation(currentLocation);
|
mapOverlay.setMyLocation(currentLocation);
|
||||||
mapView.postInvalidate();
|
mapView.postInvalidate();
|
||||||
if (keepMyLocationVisible && !locationIsVisible(currentLocation)) {
|
|
||||||
|
if (currentLocation != null && keepMyLocationVisible && !locationIsVisible(currentLocation)) {
|
||||||
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
|
GeoPoint geoPoint = LocationUtils.getGeoPoint(currentLocation);
|
||||||
MapController controller = mapView.getController();
|
MapController controller = mapView.getController();
|
||||||
controller.animateTo(geoPoint);
|
controller.animateTo(geoPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -334,9 +339,9 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
|
|
||||||
if (trackSelected) {
|
if (trackSelected) {
|
||||||
busyPane.setVisibility(View.VISIBLE);
|
busyPane.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
zoomMapToBoundaries(track);
|
zoomMapToBoundaries(track);
|
||||||
|
|
||||||
mapOverlay.setShowEndMarker(!isRecording);
|
mapOverlay.setShowEndMarker(!isRecording);
|
||||||
busyPane.setVisibility(View.GONE);
|
busyPane.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -389,13 +394,27 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||||
if (!super.onMenuItemSelected(featureId, item)) {
|
switch (item.getItemId()) {
|
||||||
MyTracks.getInstance().onActivityResult(
|
case Constants.MENU_SEND_TO_GOOGLE:
|
||||||
Constants.getActionFromMenuId(item.getItemId()), RESULT_OK,
|
SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), false);
|
||||||
new Intent());
|
return true;
|
||||||
return true;
|
case Constants.MENU_SHARE_LINK:
|
||||||
|
SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), true);
|
||||||
|
return true;
|
||||||
|
case Constants.MENU_SAVE_GPX_FILE:
|
||||||
|
case Constants.MENU_SAVE_KML_FILE:
|
||||||
|
case Constants.MENU_SAVE_CSV_FILE:
|
||||||
|
case Constants.MENU_SAVE_TCX_FILE:
|
||||||
|
case Constants.MENU_SHARE_GPX_FILE:
|
||||||
|
case Constants.MENU_SHARE_KML_FILE:
|
||||||
|
case Constants.MENU_SHARE_CSV_FILE:
|
||||||
|
case Constants.MENU_SHARE_TCX_FILE:
|
||||||
|
SaveActivity.handleExportTrackAction(this, dataHub.getSelectedTrackId(),
|
||||||
|
Constants.getActionFromMenuId(item.getItemId()));
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onMenuItemSelected(featureId, item);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -462,21 +481,21 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onProviderStateChange(ProviderState state) {
|
public void onProviderStateChange(ProviderState state) {
|
||||||
final int messageId;
|
final int messageId;
|
||||||
final boolean bindClick;
|
final boolean isGpsDisabled;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
messageId = R.string.status_enable_gps;
|
messageId = R.string.status_enable_gps;
|
||||||
bindClick = true;
|
isGpsDisabled = true;
|
||||||
break;
|
break;
|
||||||
case NO_FIX:
|
case NO_FIX:
|
||||||
case BAD_FIX:
|
case BAD_FIX:
|
||||||
messageId = R.string.wait_for_fix;
|
messageId = R.string.wait_for_fix;
|
||||||
bindClick = false;
|
isGpsDisabled = false;
|
||||||
break;
|
break;
|
||||||
case GOOD_FIX:
|
case GOOD_FIX:
|
||||||
// Nothing to show.
|
// Nothing to show.
|
||||||
messageId = -1;
|
messageId = -1;
|
||||||
bindClick = false;
|
isGpsDisabled = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unexpected state: " + state);
|
throw new IllegalArgumentException("Unexpected state: " + state);
|
||||||
@@ -489,7 +508,13 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
messageText.setText(messageId);
|
messageText.setText(messageId);
|
||||||
messagePane.setVisibility(View.VISIBLE);
|
messagePane.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
if (bindClick) {
|
if (isGpsDisabled) {
|
||||||
|
// Give a warning about this state.
|
||||||
|
Toast.makeText(MapActivity.this,
|
||||||
|
R.string.error_no_gps_location_provider,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
// Make clicking take the user to the location settings.
|
||||||
messagePane.setOnClickListener(MapActivity.this);
|
messagePane.setOnClickListener(MapActivity.this);
|
||||||
} else {
|
} else {
|
||||||
messagePane.setOnClickListener(null);
|
messagePane.setOnClickListener(null);
|
||||||
@@ -505,11 +530,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCurrentLocationChanged(Location location) {
|
public void onCurrentLocationChanged(Location location) {
|
||||||
if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
|
|
||||||
Log.d(TAG,
|
|
||||||
"MapActivity: Network location update received (provider '" + location.getProvider() + "'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
currentLocation = location;
|
currentLocation = location;
|
||||||
showCurrentLocation();
|
showCurrentLocation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.util.LocationUtils;
|
import com.google.android.apps.mytracks.util.LocationUtils;
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
@@ -33,6 +35,7 @@ import android.graphics.Point;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -71,7 +74,7 @@ public class MapOverlay extends Overlay {
|
|||||||
private GeoPoint lastReferencePoint;
|
private GeoPoint lastReferencePoint;
|
||||||
private Rect lastViewRect;
|
private Rect lastViewRect;
|
||||||
private Path lastPath;
|
private Path lastPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a pre-processed {@code Location} to speed up drawing.
|
* Represents a pre-processed {@code Location} to speed up drawing.
|
||||||
* This class is more like a data object and doesn't provide accessors.
|
* This class is more like a data object and doesn't provide accessors.
|
||||||
@@ -93,13 +96,13 @@ public class MapOverlay extends Overlay {
|
|||||||
*/
|
*/
|
||||||
public CachedLocation(Location location) {
|
public CachedLocation(Location location) {
|
||||||
this.valid = LocationUtils.isValidLocation(location);
|
this.valid = LocationUtils.isValidLocation(location);
|
||||||
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
|
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public MapOverlay(Context context) {
|
public MapOverlay(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
this.waypoints = new ArrayList<Waypoint>();
|
this.waypoints = new ArrayList<Waypoint>();
|
||||||
this.points = new ArrayList<CachedLocation>(1024);
|
this.points = new ArrayList<CachedLocation>(1024);
|
||||||
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
|
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
|
||||||
@@ -164,7 +167,7 @@ public class MapOverlay extends Overlay {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a location to the map overlay.
|
* Add a location to the map overlay.
|
||||||
*
|
*
|
||||||
* NOTE: This method doesn't take ownership of the given location, so it is
|
* NOTE: This method doesn't take ownership of the given location, so it is
|
||||||
* safe to reuse the same location while calling this method.
|
* safe to reuse the same location while calling this method.
|
||||||
*
|
*
|
||||||
@@ -172,14 +175,18 @@ public class MapOverlay extends Overlay {
|
|||||||
*/
|
*/
|
||||||
public void addLocation(Location l) {
|
public void addLocation(Location l) {
|
||||||
// Queue up in the pending queue until it's merged with {@code #points}.
|
// Queue up in the pending queue until it's merged with {@code #points}.
|
||||||
pendingPoints.offer(new CachedLocation(l));
|
if (!pendingPoints.offer(new CachedLocation(l))) {
|
||||||
|
Log.e(TAG, "Unable to add pending points");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a segment split to the map overlay.
|
* Adds a segment split to the map overlay.
|
||||||
*/
|
*/
|
||||||
public void addSegmentSplit() {
|
public void addSegmentSplit() {
|
||||||
pendingPoints.offer(new CachedLocation());
|
if (!pendingPoints.offer(new CachedLocation())) {
|
||||||
|
Log.e(TAG, "Unable to add pending points");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWaypoint(Waypoint wpt) {
|
public void addWaypoint(Waypoint wpt) {
|
||||||
@@ -203,7 +210,7 @@ public class MapOverlay extends Overlay {
|
|||||||
return waypoints.size();
|
return waypoints.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearPoints() {
|
public void clearPoints() {
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
points.clear();
|
points.clear();
|
||||||
@@ -235,10 +242,15 @@ public class MapOverlay extends Overlay {
|
|||||||
|
|
||||||
// It's safe to keep projection within a single draw operation.
|
// It's safe to keep projection within a single draw operation.
|
||||||
final Projection projection = getMapProjection(mapView);
|
final Projection projection = getMapProjection(mapView);
|
||||||
|
if (projection == null) {
|
||||||
|
Log.w(TAG, "No projection, unable to draw");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the current viewing window.
|
// Get the current viewing window.
|
||||||
if (trackDrawingEnabled) {
|
if (trackDrawingEnabled) {
|
||||||
Rect viewRect = getMapViewRect(mapView);
|
Rect viewRect = getMapViewRect(mapView);
|
||||||
|
|
||||||
// Draw the selected track:
|
// Draw the selected track:
|
||||||
drawTrack(canvas, projection, viewRect);
|
drawTrack(canvas, projection, viewRect);
|
||||||
|
|
||||||
@@ -249,12 +261,12 @@ public class MapOverlay extends Overlay {
|
|||||||
// Draw the current location
|
// Draw the current location
|
||||||
drawMyLocation(canvas, projection);
|
drawMyLocation(canvas, projection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
// Visible for testing.
|
||||||
Projection getMapProjection(MapView mapView) {
|
Projection getMapProjection(MapView mapView) {
|
||||||
return mapView.getProjection();
|
return mapView.getProjection();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
// Visible for testing.
|
||||||
Rect getMapViewRect(MapView mapView) {
|
Rect getMapViewRect(MapView mapView) {
|
||||||
int w = mapView.getLongitudeSpan();
|
int w = mapView.getLongitudeSpan();
|
||||||
@@ -263,12 +275,12 @@ public class MapOverlay extends Overlay {
|
|||||||
int cy = mapView.getMapCenter().getLatitudeE6();
|
int cy = mapView.getMapCenter().getLatitudeE6();
|
||||||
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
|
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
// Visible for testing.
|
||||||
Path newPath() {
|
Path newPath() {
|
||||||
return new Path();
|
return new Path();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
// Visible for testing.
|
||||||
Path getLastPath() {
|
Path getLastPath() {
|
||||||
return lastPath;
|
return lastPath;
|
||||||
@@ -304,9 +316,9 @@ public class MapOverlay extends Overlay {
|
|||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
// Merge the pending points with the list of cached locations.
|
// Merge the pending points with the list of cached locations.
|
||||||
final GeoPoint referencePoint = projection.fromPixels(0, 0);
|
final GeoPoint referencePoint = projection.fromPixels(0, 0);
|
||||||
int newPoints = pendingPoints.drainTo(points);
|
int newPoints = pendingPoints.drainTo(points);
|
||||||
boolean newProjection = !viewRect.equals(lastViewRect) ||
|
boolean newProjection = !viewRect.equals(lastViewRect) ||
|
||||||
!referencePoint.equals(lastReferencePoint);
|
!referencePoint.equals(lastReferencePoint);
|
||||||
if (newPoints == 0 && lastPath != null && !newProjection) {
|
if (newPoints == 0 && lastPath != null && !newProjection) {
|
||||||
// No need to recreate path (same points and viewing area).
|
// No need to recreate path (same points and viewing area).
|
||||||
path = lastPath;
|
path = lastPath;
|
||||||
@@ -318,12 +330,12 @@ public class MapOverlay extends Overlay {
|
|||||||
} else if (lastPath != null && !newProjection) {
|
} else if (lastPath != null && !newProjection) {
|
||||||
// Incremental update of the path, without repositioning the view.
|
// Incremental update of the path, without repositioning the view.
|
||||||
path = lastPath;
|
path = lastPath;
|
||||||
updatePath(projection, viewRect, path, numPoints - newPoints);
|
updatePath(projection, viewRect, path, numPoints - newPoints);
|
||||||
} else {
|
} else {
|
||||||
// The view has changed so we have to start from scratch.
|
// The view has changed so we have to start from scratch.
|
||||||
path = newPath();
|
path = newPath();
|
||||||
path.incReserve(numPoints);
|
path.incReserve(numPoints);
|
||||||
updatePath(projection, viewRect, path, 0);
|
updatePath(projection, viewRect, path, 0);
|
||||||
}
|
}
|
||||||
lastPath = path;
|
lastPath = path;
|
||||||
}
|
}
|
||||||
@@ -344,7 +356,7 @@ public class MapOverlay extends Overlay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the "Start" marker.
|
// Draw the "Start" marker.
|
||||||
for (int i = 0; i < points.size(); ++i) {
|
for (int i = 0; i < points.size(); ++i) {
|
||||||
if (points.get(i).valid) {
|
if (points.get(i).valid) {
|
||||||
@@ -354,7 +366,7 @@ public class MapOverlay extends Overlay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePath(Projection projection, Rect viewRect, Path path,
|
private void updatePath(Projection projection, Rect viewRect, Path path,
|
||||||
int startLocationIdx) {
|
int startLocationIdx) {
|
||||||
// Whether to start a new segment on new valid and visible point.
|
// Whether to start a new segment on new valid and visible point.
|
||||||
@@ -370,7 +382,7 @@ public class MapOverlay extends Overlay {
|
|||||||
newSegment = true;
|
newSegment = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final GeoPoint geoPoint = loc.geoPoint;
|
final GeoPoint geoPoint = loc.geoPoint;
|
||||||
// Check if this breaks the existing segment.
|
// Check if this breaks the existing segment.
|
||||||
boolean visible = alwaysVisible || viewRect.contains(
|
boolean visible = alwaysVisible || viewRect.contains(
|
||||||
@@ -380,7 +392,7 @@ public class MapOverlay extends Overlay {
|
|||||||
newSegment = true;
|
newSegment = true;
|
||||||
}
|
}
|
||||||
lastVisible = visible;
|
lastVisible = visible;
|
||||||
|
|
||||||
// Either move to beginning of a new segment or continue the old one.
|
// Either move to beginning of a new segment or continue the old one.
|
||||||
projection.toPixels(geoPoint, pt);
|
projection.toPixels(geoPoint, pt);
|
||||||
if (newSegment) {
|
if (newSegment) {
|
||||||
@@ -391,7 +403,7 @@ public class MapOverlay extends Overlay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
// Visible for testing.
|
||||||
Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
|
Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
|
||||||
Drawable element, int offsetX, int offsetY) {
|
Drawable element, int offsetX, int offsetY) {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011 Google Inc.
|
* Copyright 2011 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.accounts.Account;
|
import com.google.android.accounts.Account;
|
||||||
import com.google.android.apps.mytracks.io.AuthManager;
|
import com.google.android.apps.mytracks.io.AuthManager;
|
||||||
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||||
@@ -25,11 +27,8 @@ import com.google.android.maps.mytracks.R;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.ContextMenu;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnCreateContextMenuListener;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
@@ -42,29 +41,15 @@ import android.widget.TextView;
|
|||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class MyMapsList extends Activity implements MapsFacade.MapsListCallback {
|
public class MyMapsList extends Activity implements MapsFacade.MapsListCallback {
|
||||||
private static final int MENU_OPEN = 0;
|
|
||||||
private static final int MENU_SHARE = 2;
|
|
||||||
private static final int GET_LOGIN = 1;
|
private static final int GET_LOGIN = 1;
|
||||||
|
|
||||||
|
public static final String EXTRA_ACCOUNT_NAME = "accountName";
|
||||||
|
public static final String EXTRA_ACCOUNT_TYPE = "accountType";
|
||||||
|
|
||||||
private MapsFacade mapsClient;
|
private MapsFacade mapsClient;
|
||||||
private AuthManager auth;
|
private AuthManager auth;
|
||||||
private MyMapsListAdapter listAdapter;
|
private MyMapsListAdapter listAdapter;
|
||||||
|
|
||||||
private int contextPosition;
|
|
||||||
|
|
||||||
private final OnCreateContextMenuListener contextMenuListener =
|
|
||||||
new OnCreateContextMenuListener() {
|
|
||||||
@Override
|
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
|
||||||
ContextMenuInfo menuInfo) {
|
|
||||||
AdapterView.AdapterContextMenuInfo info =
|
|
||||||
(AdapterView.AdapterContextMenuInfo) menuInfo;
|
|
||||||
contextPosition = info.position;
|
|
||||||
menu.add(0, MENU_OPEN, 0, R.string.open_map);
|
|
||||||
menu.add(0, MENU_SHARE, 0, R.string.share_map);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final OnItemClickListener clickListener =
|
private final OnItemClickListener clickListener =
|
||||||
new OnItemClickListener() {
|
new OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -91,7 +76,6 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
|
|||||||
|
|
||||||
ListView list = (ListView) findViewById(R.id.maplist);
|
ListView list = (ListView) findViewById(R.id.maplist);
|
||||||
list.setOnItemClickListener(clickListener);
|
list.setOnItemClickListener(clickListener);
|
||||||
list.setOnCreateContextMenuListener(contextMenuListener);
|
|
||||||
list.setAdapter(listAdapter);
|
list.setAdapter(listAdapter);
|
||||||
|
|
||||||
startLogin();
|
startLogin();
|
||||||
@@ -101,19 +85,18 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
|
|||||||
// Starts in the UI thread.
|
// Starts in the UI thread.
|
||||||
// TODO fix this for non-froyo devices.
|
// TODO fix this for non-froyo devices.
|
||||||
if (AuthManagerFactory.useModernAuthManager()) {
|
if (AuthManagerFactory.useModernAuthManager()) {
|
||||||
MyTracks.getInstance().getAccountChooser().chooseAccount(
|
Intent intent = getIntent();
|
||||||
MyMapsList.this,
|
String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
|
||||||
new AccountChooser.AccountHandler() {
|
String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
|
||||||
@Override
|
if (accountName == null || accountType == null) {
|
||||||
public void handleAccountSelected(Account account) {
|
Log.e(TAG, "Didn't receive account name or type");
|
||||||
// Account selection happens in the UI thread.
|
setResult(RESULT_CANCELED);
|
||||||
if (account != null) {
|
finish();
|
||||||
// The user did not quit and there was a valid google
|
return;
|
||||||
// account.
|
}
|
||||||
doLogin(account);
|
|
||||||
}
|
Account account = new Account(accountName, accountType);
|
||||||
}
|
doLogin(account);
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
doLogin(null);
|
doLogin(null);
|
||||||
}
|
}
|
||||||
@@ -183,27 +166,4 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
|
|||||||
}
|
}
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case MENU_OPEN:
|
|
||||||
clickListener.onItemClick(null, null, contextPosition, 0);
|
|
||||||
return true;
|
|
||||||
case MENU_SHARE:
|
|
||||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
|
||||||
shareIntent.setType("text/plain");
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
|
|
||||||
getText(R.string.share_map_subject));
|
|
||||||
String[] listItem = (String[]) listAdapter.getMapListingArray(contextPosition);
|
|
||||||
shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
|
|
||||||
getText(R.string.share_map_body_format).toString(),
|
|
||||||
listItem[1],
|
|
||||||
MapsFacade.buildMapUrl(listItem[0])));
|
|
||||||
startActivity(Intent.createChooser(shareIntent,
|
|
||||||
getText(R.string.share_map).toString()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,10 @@
|
|||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.Sensor;
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||||
|
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||||
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
|
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
@@ -57,6 +59,11 @@ public class SensorStateActivity extends Activity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection to the recording service.
|
||||||
|
*/
|
||||||
|
private TrackRecordingServiceConnection serviceConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task which will update the U/I.
|
* A task which will update the U/I.
|
||||||
*/
|
*/
|
||||||
@@ -79,35 +86,45 @@ public class SensorStateActivity extends Activity {
|
|||||||
|
|
||||||
setContentView(R.layout.sensor_state);
|
setContentView(R.layout.sensor_state);
|
||||||
|
|
||||||
|
// TODO: Allow reading from sensors even if service is not running.
|
||||||
|
serviceConnection = new TrackRecordingServiceConnection(this, stateUpdater);
|
||||||
|
serviceConnection.bindIfRunning();
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
serviceConnection.bindIfRunning();
|
||||||
|
|
||||||
timer = new Timer();
|
timer = new Timer();
|
||||||
timer.schedule(new RefreshTask(), REFRESH_PERIOD_MS, REFRESH_PERIOD_MS);
|
timer.schedule(new RefreshTask(), REFRESH_PERIOD_MS, REFRESH_PERIOD_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
timer.purge();
|
timer.purge();
|
||||||
timer = null;
|
timer = null;
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
serviceConnection.unbind();
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateState() {
|
protected void updateState() {
|
||||||
MyTracks mt = MyTracks.getInstance();
|
ITrackRecordingService service = serviceConnection.getServiceIfBound();
|
||||||
|
|
||||||
ITrackRecordingService service =
|
|
||||||
mt == null ? null : mt.getTrackRecordingService();
|
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
Log.d(Constants.TAG, "Could not get track recording service.");
|
Log.d(Constants.TAG, "Could not get track recording service.");
|
||||||
updateSensorState(Sensor.SensorState.NONE);
|
updateSensorState(Sensor.SensorState.NONE);
|
||||||
updateSensorData(null);
|
updateSensorData(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sensor.SensorDataSet sds = null;
|
Sensor.SensorDataSet sds = null;
|
||||||
try {
|
try {
|
||||||
byte[] buff = service.getSensorData();
|
byte[] buff = service.getSensorData();
|
||||||
@@ -153,8 +170,8 @@ public class SensorStateActivity extends Activity {
|
|||||||
|
|
||||||
if (sds.hasPower() && sds.getPower().hasValue()
|
if (sds.hasPower() && sds.getPower().hasValue()
|
||||||
&& sds.getPower().getState() == Sensor.SensorState.SENDING) {
|
&& sds.getPower().getState() == Sensor.SensorState.SENDING) {
|
||||||
utils.setText(R.id.power_state_register,
|
utils.setText(R.id.power_state_register,
|
||||||
Integer.toString(sds.getPower().getValue()));
|
Integer.toString(sds.getPower().getValue()));
|
||||||
} else {
|
} else {
|
||||||
utils.setText(R.id.power_state_register,
|
utils.setText(R.id.power_state_register,
|
||||||
SensorUtils.getStateAsString(
|
SensorUtils.getStateAsString(
|
||||||
@@ -180,14 +197,14 @@ public class SensorStateActivity extends Activity {
|
|||||||
if (sds.hasHeartRate() && sds.getHeartRate().hasValue()
|
if (sds.hasHeartRate() && sds.getHeartRate().hasValue()
|
||||||
&& sds.getHeartRate().getState() == Sensor.SensorState.SENDING) {
|
&& sds.getHeartRate().getState() == Sensor.SensorState.SENDING) {
|
||||||
utils.setText(R.id.heart_rate_register,
|
utils.setText(R.id.heart_rate_register,
|
||||||
Integer.toString(sds.getHeartRate().getValue()));
|
Integer.toString(sds.getHeartRate().getValue()));
|
||||||
} else {
|
} else {
|
||||||
utils.setText(R.id.heart_rate_register,
|
utils.setText(R.id.heart_rate_register,
|
||||||
SensorUtils.getStateAsString(
|
SensorUtils.getStateAsString(
|
||||||
sds.hasHeartRate()
|
sds.hasHeartRate()
|
||||||
? sds.getHeartRate().getState()
|
? sds.getHeartRate().getState()
|
||||||
: Sensor.SensorState.NONE,
|
: Sensor.SensorState.NONE,
|
||||||
this));
|
this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,18 @@ package com.google.android.apps.mytracks;
|
|||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataListener;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
|
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||||
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
import com.google.android.apps.mytracks.util.ApiFeatures;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
@@ -44,6 +48,18 @@ import java.util.EnumSet;
|
|||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class StatsActivity extends Activity implements TrackDataListener {
|
public class StatsActivity extends Activity implements TrackDataListener {
|
||||||
|
/**
|
||||||
|
* A runnable for posting to the UI thread. Will update the total time field.
|
||||||
|
*/
|
||||||
|
private final Runnable updateResults = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (dataHub != null && dataHub.isRecordingSelected()) {
|
||||||
|
utils.setTime(R.id.total_time_register,
|
||||||
|
System.currentTimeMillis() - startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private StatsUtilities utils;
|
private StatsUtilities utils;
|
||||||
private UIUpdateThread thread;
|
private UIUpdateThread thread;
|
||||||
|
|
||||||
@@ -59,18 +75,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
private boolean showCurrentSegment = false;
|
private boolean showCurrentSegment = false;
|
||||||
|
|
||||||
private TrackDataHub dataHub;
|
private TrackDataHub dataHub;
|
||||||
|
private SharedPreferences preferences;
|
||||||
/**
|
|
||||||
* A runnable for posting to the UI thread. Will update the total time field.
|
|
||||||
*/
|
|
||||||
private final Runnable updateResults = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (dataHub.isRecordingSelected()) {
|
|
||||||
utils.setTime(R.id.total_time_register,
|
|
||||||
System.currentTimeMillis() - startTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread that updates the total time field every second.
|
* A thread that updates the total time field every second.
|
||||||
@@ -85,7 +90,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Log.i(TAG, "Started UI update thread");
|
Log.i(TAG, "Started UI update thread");
|
||||||
while (MyTracks.getInstance().isRecording()) {
|
while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
|
||||||
runOnUiThread(updateResults);
|
runOnUiThread(updateResults);
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000L);
|
Thread.sleep(1000L);
|
||||||
@@ -100,10 +105,10 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
dataHub = MyTracks.getInstance().getDataHub();
|
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
utils = new StatsUtilities(this);
|
utils = new StatsUtilities(this);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
@@ -129,19 +134,21 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
dataHub = TrackDataHub.getStartedInstance();
|
||||||
dataHub.registerTrackDataListener(this, EnumSet.of(
|
dataHub.registerTrackDataListener(this, EnumSet.of(
|
||||||
ListenerDataType.SELECTED_TRACK_CHANGED,
|
ListenerDataType.SELECTED_TRACK_CHANGED,
|
||||||
ListenerDataType.TRACK_UPDATES,
|
ListenerDataType.TRACK_UPDATES,
|
||||||
ListenerDataType.LOCATION_UPDATES,
|
ListenerDataType.LOCATION_UPDATES,
|
||||||
ListenerDataType.DISPLAY_PREFERENCES));
|
ListenerDataType.DISPLAY_PREFERENCES));
|
||||||
|
|
||||||
super.onStart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onPause() {
|
||||||
dataHub.unregisterTrackDataListener(this);
|
dataHub.unregisterTrackDataListener(this);
|
||||||
|
dataHub = null;
|
||||||
|
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
@@ -153,6 +160,9 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onUnitsChanged(boolean metric) {
|
public boolean onUnitsChanged(boolean metric) {
|
||||||
|
// Ignore if unchanged.
|
||||||
|
if (metric == utils.isMetricUnits()) return false;
|
||||||
|
|
||||||
utils.setMetricUnits(metric);
|
utils.setMetricUnits(metric);
|
||||||
updateLabels();
|
updateLabels();
|
||||||
|
|
||||||
@@ -161,9 +171,12 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onReportSpeedChanged(boolean displaySpeed) {
|
public boolean onReportSpeedChanged(boolean displaySpeed) {
|
||||||
|
// Ignore if unchanged.
|
||||||
|
if (displaySpeed == utils.isReportSpeed()) return false;
|
||||||
|
|
||||||
utils.setReportSpeed(displaySpeed);
|
utils.setReportSpeed(displaySpeed);
|
||||||
updateLabels();
|
updateLabels();
|
||||||
|
|
||||||
return true; // Reload data
|
return true; // Reload data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,21 +261,21 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
thread = null;
|
thread = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track == null || track.getStatistics() == null) {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
utils.setAllToUnknown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCurrentLocationChanged(Location loc) {
|
public void onCurrentLocationChanged(final Location loc) {
|
||||||
if (dataHub.isRecordingSelected()) {
|
if (dataHub.isRecordingSelected()) {
|
||||||
showLocation(loc);
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (loc != null) {
|
||||||
|
showLocation(loc);
|
||||||
|
} else {
|
||||||
|
showUnknownLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,18 +289,29 @@ public class StatsActivity extends Activity implements TrackDataListener {
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
case NO_FIX:
|
case NO_FIX:
|
||||||
showUnknownLocation();
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
showUnknownLocation();
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackUpdated(final Track track) {
|
public void onTrackUpdated(final Track track) {
|
||||||
|
final boolean recordingSelected = dataHub.isRecordingSelected();
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (track == null || track.getStatistics() == null) {
|
||||||
|
utils.setAllToUnknown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
startTime = track.getStatistics().getStartTime();
|
startTime = track.getStatistics().getStartTime();
|
||||||
if (!dataHub.isRecordingSelected()) {
|
if (!recordingSelected) {
|
||||||
utils.setTime(R.id.total_time_register,
|
utils.setTime(R.id.total_time_register,
|
||||||
track.getStatistics().getTotalTime());
|
track.getStatistics().getTotalTime());
|
||||||
showUnknownLocation();
|
showUnknownLocation();
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ public class StatsUtilities {
|
|||||||
public void setText(int id, double d, NumberFormat format) {
|
public void setText(int id, double d, NumberFormat format) {
|
||||||
if (!Double.isNaN(d) && !Double.isInfinite(d)) {
|
if (!Double.isNaN(d) && !Double.isInfinite(d)) {
|
||||||
setText(id, format.format(d));
|
setText(id, format.format(d));
|
||||||
|
} else {
|
||||||
|
setUnknown(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +220,7 @@ public class StatsUtilities {
|
|||||||
setGrade(R.id.max_grade_register, maxGrade);
|
setGrade(R.id.max_grade_register, maxGrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllStats(TripStatistics stats) {
|
public void setAllStats(TripStatistics stats) {
|
||||||
setTime(R.id.moving_time_register, stats.getMovingTime());
|
setTime(R.id.moving_time_register, stats.getMovingTime());
|
||||||
setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000);
|
setDistance(R.id.total_distance_register, stats.getTotalDistance() / 1000);
|
||||||
setSpeed(R.id.average_speed_register, stats.getAverageSpeed() * 3.6);
|
setSpeed(R.id.average_speed_register, stats.getAverageSpeed() * 3.6);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -16,6 +16,10 @@
|
|||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.io.file.SaveActivity;
|
||||||
|
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
|
||||||
|
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||||
|
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
import com.google.android.apps.mytracks.util.UnitConversions;
|
import com.google.android.apps.mytracks.util.UnitConversions;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
@@ -76,8 +80,7 @@ public class TrackList extends ListActivity
|
|||||||
R.string.tracklist_show_track);
|
R.string.tracklist_show_track);
|
||||||
menu.add(0, Constants.MENU_EDIT, 0,
|
menu.add(0, Constants.MENU_EDIT, 0,
|
||||||
R.string.tracklist_edit_track);
|
R.string.tracklist_edit_track);
|
||||||
if (!MyTracks.getInstance().isRecording()
|
if (!isRecording() || trackId != recordingTrackId) {
|
||||||
|| trackId != recordingTrackId) {
|
|
||||||
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
|
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
|
||||||
R.string.tracklist_send_to_google);
|
R.string.tracklist_send_to_google);
|
||||||
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
|
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
|
||||||
@@ -109,6 +112,16 @@ public class TrackList extends ListActivity
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final Runnable serviceBindingChanged = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateButtonsEnabled();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private TrackRecordingServiceConnection serviceConnection;
|
||||||
|
private SharedPreferences preferences;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(
|
public void onSharedPreferenceChanged(
|
||||||
SharedPreferences sharedPreferences, String key) {
|
SharedPreferences sharedPreferences, String key) {
|
||||||
@@ -138,32 +151,46 @@ public class TrackList extends ListActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||||
if (!super.onMenuItemSelected(featureId, item)) {
|
switch (item.getItemId()) {
|
||||||
switch (item.getItemId()) {
|
case Constants.MENU_SHOW: {
|
||||||
case Constants.MENU_SHOW: {
|
onListItemClick(null, null, 0, trackId);
|
||||||
onListItemClick(null, null, 0, trackId);
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
case Constants.MENU_EDIT: {
|
||||||
case Constants.MENU_EDIT: {
|
Intent intent = new Intent(this, TrackDetails.class);
|
||||||
Intent intent = new Intent(this, TrackDetails.class);
|
intent.putExtra("trackid", trackId);
|
||||||
intent.putExtra("trackid", trackId);
|
startActivity(intent);
|
||||||
startActivity(intent);
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
case Constants.MENU_SHARE:
|
||||||
case Constants.MENU_SHARE:
|
case Constants.MENU_WRITE_TO_SD_CARD:
|
||||||
case Constants.MENU_WRITE_TO_SD_CARD:
|
return false;
|
||||||
return false;
|
case Constants.MENU_SEND_TO_GOOGLE:
|
||||||
default: {
|
SendActivity.sendToGoogle(this, trackId, false);
|
||||||
Intent result = new Intent();
|
return true;
|
||||||
result.putExtra("trackid", trackId);
|
case Constants.MENU_SHARE_LINK:
|
||||||
setResult(
|
SendActivity.sendToGoogle(this, trackId, true);
|
||||||
Constants.getActionFromMenuId(item.getItemId()), result);
|
return true;
|
||||||
finish();
|
case Constants.MENU_SAVE_GPX_FILE:
|
||||||
return true;
|
case Constants.MENU_SAVE_KML_FILE:
|
||||||
}
|
case Constants.MENU_SAVE_CSV_FILE:
|
||||||
|
case Constants.MENU_SAVE_TCX_FILE:
|
||||||
|
case Constants.MENU_SHARE_GPX_FILE:
|
||||||
|
case Constants.MENU_SHARE_KML_FILE:
|
||||||
|
case Constants.MENU_SHARE_CSV_FILE:
|
||||||
|
case Constants.MENU_SHARE_TCX_FILE:
|
||||||
|
SaveActivity.handleExportTrackAction(this, trackId,
|
||||||
|
Constants.getActionFromMenuId(item.getItemId()));
|
||||||
|
return true;
|
||||||
|
default: {
|
||||||
|
Intent result = new Intent();
|
||||||
|
result.putExtra("trackid", trackId);
|
||||||
|
setResult(
|
||||||
|
Constants.getActionFromMenuId(item.getItemId()), result);
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -197,17 +224,19 @@ public class TrackList extends ListActivity
|
|||||||
listView = getListView();
|
listView = getListView();
|
||||||
listView.setOnCreateContextMenuListener(contextMenuListener);
|
listView.setOnCreateContextMenuListener(contextMenuListener);
|
||||||
|
|
||||||
|
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
|
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindingChanged);
|
||||||
|
|
||||||
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
|
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
|
||||||
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
|
||||||
boolean notRecording = !MyTracks.getInstance().isRecording();
|
|
||||||
deleteAll.setOnClickListener(this);
|
deleteAll.setOnClickListener(this);
|
||||||
deleteAll.setEnabled(notRecording);
|
|
||||||
|
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
||||||
exportAll.setOnClickListener(this);
|
exportAll.setOnClickListener(this);
|
||||||
exportAll.setEnabled(notRecording);
|
|
||||||
|
updateButtonsEnabled();
|
||||||
|
|
||||||
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
|
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
|
||||||
|
|
||||||
SharedPreferences preferences =
|
|
||||||
getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
|
||||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
metricUnits =
|
metricUnits =
|
||||||
preferences.getBoolean(getString(R.string.metric_units_key), true);
|
preferences.getBoolean(getString(R.string.metric_units_key), true);
|
||||||
@@ -220,6 +249,29 @@ public class TrackList extends ListActivity
|
|||||||
setListAdapter();
|
setListAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
serviceConnection.bindIfRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
serviceConnection.unbind();
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtonsEnabled() {
|
||||||
|
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
|
||||||
|
View exportAll = findViewById(R.id.tracklist_btn_export_all);
|
||||||
|
|
||||||
|
boolean notRecording = !isRecording();
|
||||||
|
deleteAll.setEnabled(notRecording);
|
||||||
|
exportAll.setEnabled(notRecording);
|
||||||
|
}
|
||||||
|
|
||||||
private void setListAdapter() {
|
private void setListAdapter() {
|
||||||
// Get a cursor with all tracks
|
// Get a cursor with all tracks
|
||||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
||||||
@@ -283,4 +335,8 @@ public class TrackList extends ListActivity
|
|||||||
});
|
});
|
||||||
setListAdapter(adapter);
|
setListAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isRecording() {
|
||||||
|
return ServiceUtils.isRecording(TrackList.this, serviceConnection.getServiceIfBound(), preferences);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Google Inc.
|
* Copyright 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -15,10 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
|
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||||
|
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
@@ -43,6 +47,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.SimpleCursorAdapter;
|
import android.widget.SimpleCursorAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity which shows the list of waypoints in a track.
|
* Activity which shows the list of waypoints in a track.
|
||||||
@@ -60,6 +65,7 @@ public class WaypointsList extends ListActivity
|
|||||||
private Button insertStatisticsButton = null;
|
private Button insertStatisticsButton = null;
|
||||||
private long recordingTrackId = -1;
|
private long recordingTrackId = -1;
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
private TrackRecordingServiceConnection serviceConnection;
|
||||||
|
|
||||||
private Cursor waypointsCursor = null;
|
private Cursor waypointsCursor = null;
|
||||||
|
|
||||||
@@ -73,15 +79,18 @@ public class WaypointsList extends ListActivity
|
|||||||
contextPosition = info.position;
|
contextPosition = info.position;
|
||||||
waypointId = WaypointsList.this.listView.getAdapter()
|
waypointId = WaypointsList.this.listView.getAdapter()
|
||||||
.getItemId(contextPosition);
|
.getItemId(contextPosition);
|
||||||
int type = providerUtils.getWaypoint(info.id).getType();
|
Waypoint waypoint = providerUtils.getWaypoint(info.id);
|
||||||
menu.add(0, Constants.MENU_SHOW, 0,
|
if (waypoint != null) {
|
||||||
R.string.waypointslist_show_waypoint);
|
int type = waypoint.getType();
|
||||||
menu.add(0, Constants.MENU_EDIT, 0,
|
menu.add(0, Constants.MENU_SHOW, 0,
|
||||||
R.string.waypointslist_edit_waypoint);
|
R.string.waypointslist_show_waypoint);
|
||||||
menu.add(0, Constants.MENU_DELETE, 0,
|
menu.add(0, Constants.MENU_EDIT, 0,
|
||||||
R.string.waypointslist_delete_waypoint).setEnabled(
|
R.string.waypointslist_edit_waypoint);
|
||||||
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
|
menu.add(0, Constants.MENU_DELETE, 0,
|
||||||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
|
R.string.waypointslist_delete_waypoint).setEnabled(
|
||||||
|
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
|
||||||
|
info.id != providerUtils.getLastWaypointId(recordingTrackId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,6 +131,7 @@ public class WaypointsList extends ListActivity
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
serviceConnection = new TrackRecordingServiceConnection(this, null);
|
||||||
|
|
||||||
// We don't need a window title bar:
|
// We don't need a window title bar:
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
@@ -159,7 +169,7 @@ public class WaypointsList extends ListActivity
|
|||||||
trackId = -1;
|
trackId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final long firstWaypointId = providerUtils.getFirstWaypointId(trackId);
|
final long firstWaypointId = providerUtils.getFirstWaypointId(trackId);
|
||||||
waypointsCursor = getContentResolver().query(
|
waypointsCursor = getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI, null,
|
WaypointsColumns.CONTENT_URI, null,
|
||||||
WaypointsColumns.TRACKID + "=" + trackId + " AND "
|
WaypointsColumns.TRACKID + "=" + trackId + " AND "
|
||||||
@@ -168,6 +178,20 @@ public class WaypointsList extends ListActivity
|
|||||||
setListAdapter();
|
setListAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
serviceConnection.bindIfRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
serviceConnection.unbind();
|
||||||
|
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
WaypointCreationRequest request;
|
WaypointCreationRequest request;
|
||||||
@@ -181,17 +205,10 @@ public class WaypointsList extends ListActivity
|
|||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long id;
|
long id = insertWaypoint(request);
|
||||||
try {
|
|
||||||
id = MyTracks.getInstance().insertWaypoint(request);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
Log.e(Constants.TAG, "Cannot insert marker.", e);
|
|
||||||
return;
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.e(Constants.TAG, "Cannot insert marker.", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (id < 0) {
|
if (id < 0) {
|
||||||
|
Toast.makeText(this, R.string.error_unable_to_insert_marker,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
Log.e(Constants.TAG, "Failed to insert marker.");
|
Log.e(Constants.TAG, "Failed to insert marker.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -200,6 +217,28 @@ public class WaypointsList extends ListActivity
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long insertWaypoint(WaypointCreationRequest request) {
|
||||||
|
try {
|
||||||
|
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
|
||||||
|
if (trackRecordingService != null) {
|
||||||
|
long waypointId = trackRecordingService.insertWaypoint(request);
|
||||||
|
if (waypointId >= 0) {
|
||||||
|
Toast.makeText(this, R.string.status_statistics_inserted,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
return waypointId;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Not connected to service, not inserting waypoint");
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(Constants.TAG, "Cannot insert marker.", e);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.e(Constants.TAG, "Cannot insert marker.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
private void setListAdapter() {
|
private void setListAdapter() {
|
||||||
// Get a cursor with all tracks
|
// Get a cursor with all tracks
|
||||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
|
||||||
@@ -209,7 +248,7 @@ public class WaypointsList extends ListActivity
|
|||||||
new String[] { WaypointsColumns.NAME, WaypointsColumns.TIME,
|
new String[] { WaypointsColumns.NAME, WaypointsColumns.TIME,
|
||||||
WaypointsColumns.CATEGORY, WaypointsColumns.TYPE },
|
WaypointsColumns.CATEGORY, WaypointsColumns.TYPE },
|
||||||
new int[] { R.id.waypointslist_item_name,
|
new int[] { R.id.waypointslist_item_name,
|
||||||
R.id.waypointslist_item_time,
|
R.id.waypointslist_item_time,
|
||||||
R.id.waypointslist_item_category,
|
R.id.waypointslist_item_category,
|
||||||
R.id.waypointslist_item_icon });
|
R.id.waypointslist_item_icon });
|
||||||
|
|
||||||
@@ -219,7 +258,7 @@ public class WaypointsList extends ListActivity
|
|||||||
waypointsCursor.getColumnIndexOrThrow(WaypointsColumns.TYPE);
|
waypointsCursor.getColumnIndexOrThrow(WaypointsColumns.TYPE);
|
||||||
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
|
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
|
||||||
@Override
|
@Override
|
||||||
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
|
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
|
||||||
if (columnIndex == timeIdx) {
|
if (columnIndex == timeIdx) {
|
||||||
long time = cursor.getLong(timeIdx);
|
long time = cursor.getLong(timeIdx);
|
||||||
TextView textView = (TextView) view;
|
TextView textView = (TextView) view;
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.location.LocationProvider;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External data source manager, which converts system-level events into My Tracks data events.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
class DataSourceManager {
|
||||||
|
|
||||||
|
/** Single interface for receiving system events that were registered for. */
|
||||||
|
interface DataSourceListener {
|
||||||
|
void notifyTrackUpdated();
|
||||||
|
void notifyWaypointUpdated();
|
||||||
|
void notifyPointsUpdated();
|
||||||
|
void notifyPreferenceChanged(String key);
|
||||||
|
void notifyLocationProviderEnabled(boolean enabled);
|
||||||
|
void notifyLocationChanged(Location loc);
|
||||||
|
void notifyHeadingChanged(float heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DataSourceListener listener;
|
||||||
|
|
||||||
|
/** Observer for when the tracks table is updated. */
|
||||||
|
private class TrackObserver extends ContentObserver {
|
||||||
|
public TrackObserver() {
|
||||||
|
super(contentHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
listener.notifyTrackUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Observer for when the waypoints table is updated. */
|
||||||
|
private class WaypointObserver extends ContentObserver {
|
||||||
|
public WaypointObserver() {
|
||||||
|
super(contentHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
listener.notifyWaypointUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Observer for when the points table is updated. */
|
||||||
|
private class PointObserver extends ContentObserver {
|
||||||
|
public PointObserver() {
|
||||||
|
super(contentHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange) {
|
||||||
|
listener.notifyPointsUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listener for when preferences change. */
|
||||||
|
private class HubSharedPreferenceListener implements OnSharedPreferenceChangeListener {
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
listener.notifyPreferenceChanged(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listener for the current location (independent from track data). */
|
||||||
|
private class CurrentLocationListener implements
|
||||||
|
LocationListener {
|
||||||
|
@Override
|
||||||
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
|
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||||
|
|
||||||
|
listener.notifyLocationProviderEnabled(status == LocationProvider.AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderEnabled(String provider) {
|
||||||
|
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||||
|
|
||||||
|
listener.notifyLocationProviderEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderDisabled(String provider) {
|
||||||
|
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
|
||||||
|
|
||||||
|
listener.notifyLocationProviderEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationChanged(Location location) {
|
||||||
|
listener.notifyLocationChanged(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listener for compass readings. */
|
||||||
|
private class CompassListener implements
|
||||||
|
SensorEventListener {
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
listener.notifyHeadingChanged(event.values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wrapper for registering internal listeners. */
|
||||||
|
private final DataSourcesWrapper dataSources;
|
||||||
|
|
||||||
|
// Internal listeners (to receive data from the system)
|
||||||
|
private final Set<ListenerDataType> registeredListeners =
|
||||||
|
EnumSet.noneOf(ListenerDataType.class);
|
||||||
|
private final Handler contentHandler;
|
||||||
|
private final ContentObserver pointObserver;
|
||||||
|
private final ContentObserver waypointObserver;
|
||||||
|
private final ContentObserver trackObserver;
|
||||||
|
private final LocationListener locationListener;
|
||||||
|
private final OnSharedPreferenceChangeListener preferenceListener;
|
||||||
|
private final SensorEventListener compassListener;
|
||||||
|
|
||||||
|
DataSourceManager(DataSourceListener listener, DataSourcesWrapper dataSources) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.dataSources = dataSources;
|
||||||
|
|
||||||
|
contentHandler = new Handler();
|
||||||
|
pointObserver = new PointObserver();
|
||||||
|
waypointObserver = new WaypointObserver();
|
||||||
|
trackObserver = new TrackObserver();
|
||||||
|
|
||||||
|
compassListener = new CompassListener();
|
||||||
|
locationListener = new CurrentLocationListener();
|
||||||
|
preferenceListener = new HubSharedPreferenceListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the internal (sensor, position, etc) listeners. */
|
||||||
|
void updateAllListeners(EnumSet<ListenerDataType> externallyNeededListeners) {
|
||||||
|
EnumSet<ListenerDataType> neededListeners = EnumSet.copyOf(externallyNeededListeners);
|
||||||
|
|
||||||
|
// Special case - map sampled-out points type to points type since they
|
||||||
|
// correspond to the same internal listener.
|
||||||
|
if (neededListeners.contains(ListenerDataType.SAMPLED_OUT_POINT_UPDATES)) {
|
||||||
|
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
|
||||||
|
neededListeners.add(ListenerDataType.POINT_UPDATES);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Updating internal listeners to types " + neededListeners);
|
||||||
|
|
||||||
|
// Unnecessary = registered - needed
|
||||||
|
Set<ListenerDataType> unnecessaryListeners = EnumSet.copyOf(registeredListeners);
|
||||||
|
unnecessaryListeners.removeAll(neededListeners);
|
||||||
|
|
||||||
|
// Missing = needed - registered
|
||||||
|
Set<ListenerDataType> missingListeners = EnumSet.copyOf(neededListeners);
|
||||||
|
missingListeners.removeAll(registeredListeners);
|
||||||
|
|
||||||
|
// Remove all unnecessary listeners.
|
||||||
|
for (ListenerDataType type : unnecessaryListeners) {
|
||||||
|
unregisterListener(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all missing listeners.
|
||||||
|
for (ListenerDataType type : missingListeners) {
|
||||||
|
registerListener(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now all needed types are registered.
|
||||||
|
registeredListeners.clear();
|
||||||
|
registeredListeners.addAll(neededListeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerListener(ListenerDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case COMPASS_UPDATES: {
|
||||||
|
// Listen to compass
|
||||||
|
Sensor compass = dataSources.getSensor(Sensor.TYPE_ORIENTATION);
|
||||||
|
if (compass != null) {
|
||||||
|
Log.d(TAG, "TrackDataHub: Now registering sensor listener.");
|
||||||
|
dataSources.registerSensorListener(compassListener, compass, SensorManager.SENSOR_DELAY_UI);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LOCATION_UPDATES:
|
||||||
|
dataSources.requestLocationUpdates(locationListener);
|
||||||
|
break;
|
||||||
|
case POINT_UPDATES:
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
TrackPointsColumns.CONTENT_URI, false, pointObserver);
|
||||||
|
break;
|
||||||
|
case TRACK_UPDATES:
|
||||||
|
dataSources.registerContentObserver(TracksColumns.CONTENT_URI, false, trackObserver);
|
||||||
|
break;
|
||||||
|
case WAYPOINT_UPDATES:
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
WaypointsColumns.CONTENT_URI, false, waypointObserver);
|
||||||
|
break;
|
||||||
|
case DISPLAY_PREFERENCES:
|
||||||
|
dataSources.registerOnSharedPreferenceChangeListener(preferenceListener);
|
||||||
|
break;
|
||||||
|
case SAMPLED_OUT_POINT_UPDATES:
|
||||||
|
throw new IllegalArgumentException("Should have been mapped to point updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterListener(ListenerDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case COMPASS_UPDATES:
|
||||||
|
dataSources.unregisterSensorListener(compassListener);
|
||||||
|
break;
|
||||||
|
case LOCATION_UPDATES:
|
||||||
|
dataSources.removeLocationUpdates(locationListener);
|
||||||
|
break;
|
||||||
|
case POINT_UPDATES:
|
||||||
|
dataSources.unregisterContentObserver(pointObserver);
|
||||||
|
break;
|
||||||
|
case TRACK_UPDATES:
|
||||||
|
dataSources.unregisterContentObserver(trackObserver);
|
||||||
|
break;
|
||||||
|
case WAYPOINT_UPDATES:
|
||||||
|
dataSources.unregisterContentObserver(waypointObserver);
|
||||||
|
break;
|
||||||
|
case DISPLAY_PREFERENCES:
|
||||||
|
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
||||||
|
break;
|
||||||
|
case SAMPLED_OUT_POINT_UPDATES:
|
||||||
|
throw new IllegalArgumentException("Should have been mapped to point updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unregisters all internal (sensor, position, etc.) listeners. */
|
||||||
|
void unregisterAllListeners() {
|
||||||
|
dataSources.removeLocationUpdates(locationListener);
|
||||||
|
dataSources.unregisterSensorListener(compassListener);
|
||||||
|
dataSources.unregisterContentObserver(trackObserver);
|
||||||
|
dataSources.unregisterContentObserver(waypointObserver);
|
||||||
|
dataSources.unregisterContentObserver(pointObserver);
|
||||||
|
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -13,7 +13,7 @@
|
|||||||
* 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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
@@ -24,11 +24,11 @@ import android.location.LocationListener;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for abstracting external data sources.
|
* Interface for abstracting registration of external data source listeners.
|
||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
interface TrackDataSources {
|
interface DataSourcesWrapper {
|
||||||
// Preferences
|
// Preferences
|
||||||
void registerOnSharedPreferenceChangeListener(
|
void registerOnSharedPreferenceChangeListener(
|
||||||
OnSharedPreferenceChangeListener listener);
|
OnSharedPreferenceChangeListener listener);
|
||||||
+4
-3
@@ -13,11 +13,12 @@
|
|||||||
* 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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
|
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
|
||||||
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
|
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
@@ -41,7 +42,7 @@ import android.widget.Toast;
|
|||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
class TrackDataSourcesImpl implements TrackDataSources {
|
class DataSourcesWrapperImpl implements DataSourcesWrapper {
|
||||||
// System services
|
// System services
|
||||||
private final SensorManager sensorManager;
|
private final SensorManager sensorManager;
|
||||||
private final LocationManager locationManager;
|
private final LocationManager locationManager;
|
||||||
@@ -49,7 +50,7 @@ class TrackDataSourcesImpl implements TrackDataSources {
|
|||||||
private final SharedPreferences sharedPreferences;
|
private final SharedPreferences sharedPreferences;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
TrackDataSourcesImpl(Context context, SharedPreferences sharedPreferences) {
|
DataSourcesWrapperImpl(Context context, SharedPreferences sharedPreferences) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
this.sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -154,12 +154,12 @@ public class MyTracksProvider extends ContentProvider {
|
|||||||
// Sensor data.
|
// Sensor data.
|
||||||
if (oldVersion <= 17) {
|
if (oldVersion <= 17) {
|
||||||
Log.w(TAG, "Upgrade DB: Adding sensor column.");
|
Log.w(TAG, "Upgrade DB: Adding sensor column.");
|
||||||
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
|
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
|
||||||
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
|
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
|
||||||
}
|
}
|
||||||
if (oldVersion <= 18) {
|
if (oldVersion <= 18) {
|
||||||
Log.w(TAG, "Upgrade DB: Adding tableid column.");
|
Log.w(TAG, "Upgrade DB: Adding tableid column.");
|
||||||
db.execSQL("ALTER TABLE " + TRACKS_TABLE
|
db.execSQL("ALTER TABLE " + TRACKS_TABLE
|
||||||
+ " ADD " + TracksColumns.TABLEID + " STRING");
|
+ " ADD " + TracksColumns.TABLEID + " STRING");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,11 @@ public class MyTracksProvider extends ContentProvider {
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
|
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
|
||||||
db = dbHelper.getWritableDatabase();
|
try {
|
||||||
|
db = dbHelper.getWritableDatabase();
|
||||||
|
} catch (SQLiteException e) {
|
||||||
|
Log.e(TAG, "Unable to open database for writing", e);
|
||||||
|
}
|
||||||
return db != null;
|
return db != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +387,7 @@ public class MyTracksProvider extends ContentProvider {
|
|||||||
|
|
||||||
if (ApiFeatures.getInstance().canReuseSQLiteQueryBuilder()) {
|
if (ApiFeatures.getInstance().canReuseSQLiteQueryBuilder()) {
|
||||||
Log.i(Constants.TAG,
|
Log.i(Constants.TAG,
|
||||||
"Build query: " + qb.buildQuery(projection, selection, selectionArgs,
|
"Build query: " + qb.buildQuery(projection, selection, selectionArgs,
|
||||||
null, null, sortOrder, null));
|
null, null, sortOrder, null));
|
||||||
}
|
}
|
||||||
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
|
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
|
||||||
|
|||||||
+364
-465
File diff suppressed because it is too large
Load Diff
+1
-3
@@ -13,7 +13,7 @@
|
|||||||
* 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;
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
@@ -23,8 +23,6 @@ import android.location.Location;
|
|||||||
/**
|
/**
|
||||||
* Listener for track data, for both initial and incremental loading.
|
* Listener for track data, for both initial and incremental loading.
|
||||||
*
|
*
|
||||||
* TODO: Allow listening to just part of the data received here.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public interface TrackDataListener {
|
public interface TrackDataListener {
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager for the external data listeners and their listening types.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
class TrackDataListeners {
|
||||||
|
|
||||||
|
/** Internal representation of a listener's registration. */
|
||||||
|
static class ListenerRegistration {
|
||||||
|
final TrackDataListener listener;
|
||||||
|
final EnumSet<ListenerDataType> types;
|
||||||
|
|
||||||
|
// State that was last notified to the listener, for resuming after a pause.
|
||||||
|
long lastTrackId;
|
||||||
|
long lastPointId;
|
||||||
|
int lastSamplingFrequency;
|
||||||
|
int numLoadedPoints;
|
||||||
|
|
||||||
|
public ListenerRegistration(TrackDataListener listener,
|
||||||
|
EnumSet<ListenerDataType> types) {
|
||||||
|
this.listener = listener;
|
||||||
|
this.types = types;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInterestedIn(ListenerDataType type) {
|
||||||
|
return types.contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ListenerRegistration [listener=" + listener + ", types=" + types
|
||||||
|
+ ", lastTrackId=" + lastTrackId + ", lastPointId=" + lastPointId
|
||||||
|
+ ", lastSamplingFrequency=" + lastSamplingFrequency
|
||||||
|
+ ", numLoadedPoints=" + numLoadedPoints + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Map of external listener to its registration details. */
|
||||||
|
private final Map<TrackDataListener, ListenerRegistration> registeredListeners =
|
||||||
|
new HashMap<TrackDataListener, ListenerRegistration>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of external paused listener to its registration details.
|
||||||
|
* This will automatically discard listeners which are GCed.
|
||||||
|
*/
|
||||||
|
private final WeakHashMap<TrackDataListener, ListenerRegistration> oldListeners =
|
||||||
|
new WeakHashMap<TrackDataListener, ListenerRegistration>();
|
||||||
|
|
||||||
|
/** Map of data type to external listeners interested in it. */
|
||||||
|
private final Map<ListenerDataType, Set<TrackDataListener>> listenerSetsPerType =
|
||||||
|
new EnumMap<ListenerDataType, Set<TrackDataListener>>(ListenerDataType.class);
|
||||||
|
|
||||||
|
public TrackDataListeners() {
|
||||||
|
// Create sets for all data types at startup.
|
||||||
|
for (ListenerDataType type : ListenerDataType.values()) {
|
||||||
|
listenerSetsPerType.put(type, new LinkedHashSet<TrackDataListener>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a listener to send data to.
|
||||||
|
* It is ok to call this method before {@link start}, and in that case
|
||||||
|
* the data will only be passed to listeners when {@link start} is called.
|
||||||
|
*
|
||||||
|
* @param listener the listener to register
|
||||||
|
* @param dataTypes the type of data that the listener is interested in
|
||||||
|
*/
|
||||||
|
public ListenerRegistration registerTrackDataListener(final TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
|
||||||
|
Log.d(TAG, "Registered track data listener: " + listener);
|
||||||
|
if (registeredListeners.containsKey(listener)) {
|
||||||
|
throw new IllegalStateException("Listener already registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenerRegistration registration = oldListeners.remove(listener);
|
||||||
|
if (registration == null) {
|
||||||
|
registration = new ListenerRegistration(listener, dataTypes);
|
||||||
|
}
|
||||||
|
registeredListeners.put(listener, registration);
|
||||||
|
|
||||||
|
for (ListenerDataType type : dataTypes) {
|
||||||
|
// This is guaranteed not to be null.
|
||||||
|
Set<TrackDataListener> typeSet = listenerSetsPerType.get(type);
|
||||||
|
typeSet.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a listener to send data to.
|
||||||
|
*
|
||||||
|
* @param listener the listener to unregister
|
||||||
|
*/
|
||||||
|
public void unregisterTrackDataListener(TrackDataListener listener) {
|
||||||
|
Log.d(TAG, "Unregistered track data listener: " + listener);
|
||||||
|
// Remove and keep the corresponding registration.
|
||||||
|
ListenerRegistration match = registeredListeners.remove(listener);
|
||||||
|
if (match == null) {
|
||||||
|
Log.w(TAG, "Tried to unregister listener which is not registered.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove it from the per-type sets
|
||||||
|
for (ListenerDataType type : match.types) {
|
||||||
|
listenerSetsPerType.get(type).remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep it around in case it's re-registered soon
|
||||||
|
oldListeners.put(listener, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenerRegistration getRegistration(TrackDataListener listener) {
|
||||||
|
ListenerRegistration registration = registeredListeners.get(listener);
|
||||||
|
if (registration == null) {
|
||||||
|
registration = oldListeners.get(listener);
|
||||||
|
}
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<TrackDataListener> getListenersFor(ListenerDataType type) {
|
||||||
|
return listenerSetsPerType.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnumSet<ListenerDataType> getAllRegisteredTypes() {
|
||||||
|
EnumSet<ListenerDataType> listeners = EnumSet.noneOf(ListenerDataType.class);
|
||||||
|
for (ListenerRegistration registration : this.registeredListeners.values()) {
|
||||||
|
listeners.addAll(registration.types);
|
||||||
|
}
|
||||||
|
return listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasListeners() {
|
||||||
|
return !registeredListeners.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumListeners() {
|
||||||
|
return registeredListeners.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -20,11 +20,10 @@ import android.content.Intent;
|
|||||||
/**
|
/**
|
||||||
* This interface describes a class that will fetch and maintain a Google
|
* This interface describes a class that will fetch and maintain a Google
|
||||||
* authentication token.
|
* authentication token.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public interface AuthManager {
|
public interface AuthManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the login process. The user should be asked to login if they
|
* Initializes the login process. The user should be asked to login if they
|
||||||
* haven't already. The {@link Runnable} provided will be executed when the
|
* haven't already. The {@link Runnable} provided will be executed when the
|
||||||
@@ -34,7 +33,7 @@ public interface AuthManager {
|
|||||||
* has been successfully fetched and is available via
|
* has been successfully fetched and is available via
|
||||||
* {@link #getAuthToken()}
|
* {@link #getAuthToken()}
|
||||||
*/
|
*/
|
||||||
public abstract void doLogin(Runnable whenFinished, Object o);
|
void doLogin(Runnable whenFinished, Object o);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link android.app.Activity} owner of this class should call this
|
* The {@link android.app.Activity} owner of this class should call this
|
||||||
@@ -53,7 +52,7 @@ public interface AuthManager {
|
|||||||
* the auth token, or False if there was an error or the request was
|
* the auth token, or False if there was an error or the request was
|
||||||
* canceled
|
* canceled
|
||||||
*/
|
*/
|
||||||
public abstract boolean authResult(int resultCode, Intent results);
|
boolean authResult(int resultCode, Intent results);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current auth token. Response may be null if no valid auth
|
* Returns the current auth token. Response may be null if no valid auth
|
||||||
@@ -62,7 +61,7 @@ public interface AuthManager {
|
|||||||
* @return The current auth token or null if no auth token has been
|
* @return The current auth token or null if no auth token has been
|
||||||
* fetched
|
* fetched
|
||||||
*/
|
*/
|
||||||
public abstract String getAuthToken();
|
String getAuthToken();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates the existing auth token and request a new one. The
|
* Invalidates the existing auth token and request a new one. The
|
||||||
@@ -72,6 +71,5 @@ public interface AuthManager {
|
|||||||
* @param whenFinished A {@link Runnable} to execute when a new auth token
|
* @param whenFinished A {@link Runnable} to execute when a new auth token
|
||||||
* is successfully fetched
|
* is successfully fetched
|
||||||
*/
|
*/
|
||||||
public abstract void invalidateAndRefresh(Runnable whenFinished);
|
void invalidateAndRefresh(Runnable whenFinished);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import com.google.android.accounts.AccountManagerFuture;
|
|||||||
import com.google.android.accounts.AuthenticatorException;
|
import com.google.android.accounts.AuthenticatorException;
|
||||||
import com.google.android.accounts.OperationCanceledException;
|
import com.google.android.accounts.OperationCanceledException;
|
||||||
import com.google.android.apps.mytracks.AccountChooser;
|
import com.google.android.apps.mytracks.AccountChooser;
|
||||||
import com.google.android.apps.mytracks.MyTracks;
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -183,10 +182,12 @@ public class ModernAuthManager implements AuthManager {
|
|||||||
public void run() {
|
public void run() {
|
||||||
accountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
|
accountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
|
||||||
authToken);
|
authToken);
|
||||||
MyTracks.getInstance().getAccountChooser().chooseAccount(activity,
|
|
||||||
|
AccountChooser accountChooser = new AccountChooser();
|
||||||
|
accountChooser.chooseAccount(activity,
|
||||||
new AccountChooser.AccountHandler() {
|
new AccountChooser.AccountHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleAccountSelected(Account account) {
|
public void onAccountSelected(Account account) {
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
doLogin(whenFinished, account);
|
doLogin(whenFinished, account);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io;
|
package com.google.android.apps.mytracks.io;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracks;
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||||
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.Track;
|
||||||
import com.google.android.apps.mytracks.io.docs.DocsHelper;
|
import com.google.android.apps.mytracks.io.docs.DocsHelper;
|
||||||
@@ -25,15 +25,13 @@ import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
|
|||||||
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
import com.google.android.common.gdata.AndroidXmlParserFactory;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
import com.google.wireless.gdata.client.GDataClient;
|
import com.google.wireless.gdata.client.GDataClient;
|
||||||
|
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||||
import com.google.wireless.gdata.docs.DocumentsClient;
|
import com.google.wireless.gdata.docs.DocumentsClient;
|
||||||
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
import com.google.wireless.gdata.docs.SpreadsheetsClient;
|
||||||
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
|
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
|
||||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -46,17 +44,15 @@ import java.io.IOException;
|
|||||||
public class SendToDocs {
|
public class SendToDocs {
|
||||||
/** The GData service name for Google Spreadsheets (aka Trix) */
|
/** The GData service name for Google Spreadsheets (aka Trix) */
|
||||||
public static final String GDATA_SERVICE_NAME_TRIX = "wise";
|
public static final String GDATA_SERVICE_NAME_TRIX = "wise";
|
||||||
|
|
||||||
/** The GData service name for the Google Docs Document List */
|
/** The GData service name for the Google Docs Document List */
|
||||||
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
|
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
|
||||||
|
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final AuthManager trixAuth;
|
private final AuthManager trixAuth;
|
||||||
private final AuthManager docListAuth;
|
private final AuthManager docListAuth;
|
||||||
private final long trackId;
|
private final ProgressIndicator progressIndicator;
|
||||||
private final boolean metricUnits;
|
private final boolean metricUnits;
|
||||||
private final HandlerThread handlerThread;
|
|
||||||
private final Handler handler;
|
|
||||||
|
|
||||||
private boolean createdNewSpreadSheet = false;
|
private boolean createdNewSpreadSheet = false;
|
||||||
|
|
||||||
@@ -64,12 +60,13 @@ public class SendToDocs {
|
|||||||
private String statusMessage = "";
|
private String statusMessage = "";
|
||||||
private Runnable onCompletion = null;
|
private Runnable onCompletion = null;
|
||||||
|
|
||||||
|
|
||||||
public SendToDocs(Activity activity, AuthManager trixAuth,
|
public SendToDocs(Activity activity, AuthManager trixAuth,
|
||||||
AuthManager docListAuth, long trackId) {
|
AuthManager docListAuth, ProgressIndicator progressIndicator) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.trixAuth = trixAuth;
|
this.trixAuth = trixAuth;
|
||||||
this.docListAuth = docListAuth;
|
this.docListAuth = docListAuth;
|
||||||
this.trackId = trackId;
|
this.progressIndicator = progressIndicator;
|
||||||
|
|
||||||
SharedPreferences preferences = activity.getSharedPreferences(
|
SharedPreferences preferences = activity.getSharedPreferences(
|
||||||
Constants.SETTINGS_NAME, 0);
|
Constants.SETTINGS_NAME, 0);
|
||||||
@@ -80,24 +77,20 @@ public class SendToDocs {
|
|||||||
} else {
|
} else {
|
||||||
metricUnits = true;
|
metricUnits = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendToDocs(final long trackId) {
|
||||||
Log.d(Constants.TAG,
|
Log.d(Constants.TAG,
|
||||||
"Sending to Google Docs: trackId = " + trackId);
|
"Sending to Google Docs: trackId = " + trackId);
|
||||||
handlerThread = new HandlerThread("SendToGoogleDocs");
|
new Thread("SendToGoogleDocs") {
|
||||||
handlerThread.start();
|
|
||||||
handler = new Handler(handlerThread.getLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
handler.post(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
doUpload();
|
doUpload(trackId);
|
||||||
}
|
}
|
||||||
});
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doUpload() {
|
private void doUpload(long trackId) {
|
||||||
// TODO
|
// TODO
|
||||||
statusMessage = activity.getString(R.string.error_sending_to_fusiontables);
|
statusMessage = activity.getString(R.string.error_sending_to_fusiontables);
|
||||||
success = false;
|
success = false;
|
||||||
@@ -162,7 +155,7 @@ public class SendToDocs {
|
|||||||
GDataWrapper<GDataServiceClient> docListWrapper = new GDataWrapper<GDataServiceClient>();
|
GDataWrapper<GDataServiceClient> docListWrapper = new GDataWrapper<GDataServiceClient>();
|
||||||
docListWrapper.setAuthManager(docListAuth);
|
docListWrapper.setAuthManager(docListAuth);
|
||||||
docListWrapper.setRetryOnAuthFailure(true);
|
docListWrapper.setRetryOnAuthFailure(true);
|
||||||
|
|
||||||
GDataWrapper<GDataServiceClient> trixWrapper = new GDataWrapper<GDataServiceClient>();
|
GDataWrapper<GDataServiceClient> trixWrapper = new GDataWrapper<GDataServiceClient>();
|
||||||
trixWrapper.setAuthManager(trixAuth);
|
trixWrapper.setAuthManager(trixAuth);
|
||||||
trixWrapper.setRetryOnAuthFailure(true);
|
trixWrapper.setRetryOnAuthFailure(true);
|
||||||
@@ -190,7 +183,7 @@ public class SendToDocs {
|
|||||||
// First try to find the spreadsheet:
|
// First try to find the spreadsheet:
|
||||||
String spreadsheetId = null;
|
String spreadsheetId = null;
|
||||||
try {
|
try {
|
||||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||||
sheetTitle);
|
sheetTitle);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.i(Constants.TAG, "Spreadsheet lookup failed.", e);
|
Log.i(Constants.TAG, "Spreadsheet lookup failed.", e);
|
||||||
@@ -198,7 +191,7 @@ public class SendToDocs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spreadsheetId == null) {
|
if (spreadsheetId == null) {
|
||||||
MyTracks.getInstance().setProgressValue(65);
|
progressIndicator.setProgressValue(65);
|
||||||
// Waiting a few seconds and trying again. Maybe the server just had a
|
// Waiting a few seconds and trying again. Maybe the server just had a
|
||||||
// hickup (unfortunately that happens quite a lot...).
|
// hickup (unfortunately that happens quite a lot...).
|
||||||
try {
|
try {
|
||||||
@@ -206,18 +199,18 @@ public class SendToDocs {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(Constants.TAG, "Sleep interrupted", e);
|
Log.e(Constants.TAG, "Sleep interrupted", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||||
sheetTitle);
|
sheetTitle);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.i(Constants.TAG, "2nd spreadsheet lookup failed.", e);
|
Log.i(Constants.TAG, "2nd spreadsheet lookup failed.", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We were unable to find an existing spreadsheet, so create a new one.
|
// We were unable to find an existing spreadsheet, so create a new one.
|
||||||
MyTracks.getInstance().setProgressValue(70);
|
progressIndicator.setProgressValue(70);
|
||||||
if (spreadsheetId == null) {
|
if (spreadsheetId == null) {
|
||||||
Log.i(Constants.TAG, "Creating new spreadsheet: " + sheetTitle);
|
Log.i(Constants.TAG, "Creating new spreadsheet: " + sheetTitle);
|
||||||
|
|
||||||
@@ -229,11 +222,11 @@ public class SendToDocs {
|
|||||||
+ sheetTitle, e);
|
+ sheetTitle, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MyTracks.getInstance().setProgressValue(80);
|
progressIndicator.setProgressValue(80);
|
||||||
createdNewSpreadSheet = true;
|
createdNewSpreadSheet = true;
|
||||||
|
|
||||||
if (spreadsheetId == null) {
|
if (spreadsheetId == null) {
|
||||||
MyTracks.getInstance().setProgressValue(85);
|
progressIndicator.setProgressValue(85);
|
||||||
// The previous creation might have succeeded even though GData
|
// The previous creation might have succeeded even though GData
|
||||||
// reported an error. Seems to be a know bug,
|
// reported an error. Seems to be a know bug,
|
||||||
// see http://code.google.com/p/gdata-issues/issues/detail?id=929
|
// see http://code.google.com/p/gdata-issues/issues/detail?id=929
|
||||||
@@ -247,7 +240,7 @@ public class SendToDocs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||||
sheetTitle);
|
sheetTitle);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.i(Constants.TAG, "Failed create-failed lookup", e);
|
Log.i(Constants.TAG, "Failed create-failed lookup", e);
|
||||||
@@ -255,16 +248,16 @@ public class SendToDocs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spreadsheetId == null) {
|
if (spreadsheetId == null) {
|
||||||
MyTracks.getInstance().setProgressValue(87);
|
progressIndicator.setProgressValue(87);
|
||||||
// Re-try
|
// Re-try
|
||||||
try {
|
try {
|
||||||
Thread.sleep(5000);
|
Thread.sleep(5000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(Constants.TAG, "Sleep interrupted", e);
|
Log.e(Constants.TAG, "Sleep interrupted", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
|
||||||
sheetTitle);
|
sheetTitle);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.i(Constants.TAG, "Failed create-failed relookup", e);
|
Log.i(Constants.TAG, "Failed create-failed relookup", e);
|
||||||
@@ -290,9 +283,9 @@ public class SendToDocs {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MyTracks.getInstance().setProgressValue(90);
|
progressIndicator.setProgressValue(90);
|
||||||
|
|
||||||
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
|
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
|
||||||
track, metricUnits);
|
track, metricUnits);
|
||||||
Log.i(Constants.TAG, "Done uploading to docs.");
|
Log.i(Constants.TAG, "Done uploading to docs.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io;
|
package com.google.android.apps.mytracks.io;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.ProgressIndicator;
|
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
@@ -49,6 +50,7 @@ import android.util.Log;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -156,6 +158,11 @@ public class SendToFusionTables implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getMapVisualizationUrl(Track track) {
|
public static String getMapVisualizationUrl(Track track) {
|
||||||
|
if (track == null || track.getStatistics() == null || track.getTableId() == null) {
|
||||||
|
Log.w(TAG, "Unable to get track URL");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(leifhendrik): Determine correct bounding box and zoom level that will show the entire track.
|
// TODO(leifhendrik): Determine correct bounding box and zoom level that will show the entire track.
|
||||||
TripStatistics stats = track.getStatistics();
|
TripStatistics stats = track.getStatistics();
|
||||||
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
|
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
|
||||||
@@ -179,6 +186,11 @@ public class SendToFusionTables implements Runnable {
|
|||||||
|
|
||||||
// Get the track meta-data
|
// Get the track meta-data
|
||||||
Track track = providerUtils.getTrack(trackId);
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
Log.w(Constants.TAG, "Cannot get track.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String originalDescription = track.getDescription();
|
String originalDescription = track.getDescription();
|
||||||
|
|
||||||
// Create a new table:
|
// Create a new table:
|
||||||
@@ -207,7 +219,6 @@ public class SendToFusionTables implements Runnable {
|
|||||||
Log.d(Constants.TAG, "SendToFusionTables: Done: " + success);
|
Log.d(Constants.TAG, "SendToFusionTables: Done: " + success);
|
||||||
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
|
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
final boolean finalSuccess = success;
|
final boolean finalSuccess = success;
|
||||||
final int finalStatusMessageId = statusMessageId;
|
final int finalStatusMessageId = statusMessageId;
|
||||||
context.runOnUiThread(new Runnable() {
|
context.runOnUiThread(new Runnable() {
|
||||||
@@ -304,7 +315,7 @@ public class SendToFusionTables implements Runnable {
|
|||||||
|
|
||||||
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||||
try {
|
try {
|
||||||
if (!locationsCursor.moveToFirst()) {
|
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||||
Log.w(Constants.TAG, "Unable to get any points to upload");
|
Log.w(Constants.TAG, "Unable to get any points to upload");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -391,7 +402,9 @@ public class SendToFusionTables implements Runnable {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
locationsCursor.close();
|
if (locationsCursor != null) {
|
||||||
|
locationsCursor.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,10 +433,10 @@ public class SendToFusionTables implements Runnable {
|
|||||||
* @return the kml.
|
* @return the kml.
|
||||||
*/
|
*/
|
||||||
private String getKmlPoint(Location location) {
|
private String getKmlPoint(Location location) {
|
||||||
StringBuilder builder = new StringBuilder("<Point><coordinates>");
|
StringBuilder builder = new StringBuilder("<Point><coordinates>");
|
||||||
appendCoordinate(location, builder);
|
appendCoordinate(location, builder);
|
||||||
builder.append("</coordinates></Point>");
|
builder.append("</coordinates></Point>");
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -599,7 +612,7 @@ public class SendToFusionTables implements Runnable {
|
|||||||
// I am leaving the number of waypoints very high which should not be a
|
// I am leaving the number of waypoints very high which should not be a
|
||||||
// problem because we don't try to load them into objects all at the
|
// problem because we don't try to load them into objects all at the
|
||||||
// same time.
|
// same time.
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
try {
|
try {
|
||||||
c = providerUtils.getWaypointsCursor(
|
c = providerUtils.getWaypointsCursor(
|
||||||
@@ -613,9 +626,9 @@ public class SendToFusionTables implements Runnable {
|
|||||||
Waypoint wpt = providerUtils.createWaypoint(c);
|
Waypoint wpt = providerUtils.createWaypoint(c);
|
||||||
Log.d(Constants.TAG, "SendToFusionTables: Creating waypoint.");
|
Log.d(Constants.TAG, "SendToFusionTables: Creating waypoint.");
|
||||||
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
|
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
|
||||||
MARKER_TYPE_WAYPOINT);
|
MARKER_TYPE_WAYPOINT);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -637,7 +650,7 @@ public class SendToFusionTables implements Runnable {
|
|||||||
// to fit the completion percentage range alloted to track data upload.
|
// to fit the completion percentage range alloted to track data upload.
|
||||||
double totalPercentage =
|
double totalPercentage =
|
||||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
||||||
/ (totalLocations * 3);
|
/ (totalLocations * 3.0);
|
||||||
|
|
||||||
double scaledPercentage = totalPercentage
|
double scaledPercentage = totalPercentage
|
||||||
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
|
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
|
||||||
@@ -681,8 +694,8 @@ public class SendToFusionTables implements Runnable {
|
|||||||
boolean success = response.isSuccessStatusCode;
|
boolean success = response.isSuccessStatusCode;
|
||||||
if (success) {
|
if (success) {
|
||||||
byte[] result = new byte[1024];
|
byte[] result = new byte[1024];
|
||||||
response.getContent().read(result);
|
int read = response.getContent().read(result);
|
||||||
String s = Strings.fromBytesUtf8(result);
|
String s = new String(result, 0, read, Charset.forName("UTF8"));
|
||||||
String[] lines = s.split(Strings.LINE_SEPARATOR);
|
String[] lines = s.split(Strings.LINE_SEPARATOR);
|
||||||
if (lines[0].equals("tableid")) {
|
if (lines[0].equals("tableid")) {
|
||||||
tableId = lines[1];
|
tableId = lines[1];
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -76,7 +76,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
public interface OnSendCompletedListener {
|
public interface OnSendCompletedListener {
|
||||||
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||||
long trackId, ProgressIndicator progressIndicator,
|
long trackId, ProgressIndicator progressIndicator,
|
||||||
OnSendCompletedListener onCompletion) {
|
OnSendCompletedListener onCompletion) {
|
||||||
@@ -100,18 +100,22 @@ public class SendToMyMaps implements Runnable {
|
|||||||
int statusMessageId = R.string.error_sending_to_mymap;
|
int statusMessageId = R.string.error_sending_to_mymap;
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
try {
|
try {
|
||||||
progressIndicator.setProgressValue(1);
|
|
||||||
progressIndicator.setProgressMessage(
|
progressIndicator.setProgressMessage(
|
||||||
R.string.progress_message_reading_track);
|
R.string.progress_message_reading_track);
|
||||||
|
|
||||||
// Get the track meta-data
|
// Get the track meta-data
|
||||||
Track track = providerUtils.getTrack(trackId);
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
Log.w(Constants.TAG, "Cannot get track.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String originalDescription = track.getDescription();
|
String originalDescription = track.getDescription();
|
||||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||||
|
|
||||||
mapsClient = new MapsFacade(context, auth);
|
mapsClient = new MapsFacade(context, auth);
|
||||||
|
|
||||||
// Create a new map if necessary:
|
// Create a new map if necessary:
|
||||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
||||||
if (isNewMap) {
|
if (isNewMap) {
|
||||||
@@ -166,7 +170,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
Log.w(TAG, "SendToMyMaps: upload waypoints failed.");
|
Log.w(TAG, "SendToMyMaps: upload waypoints failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
statusMessageId = isNewMap
|
statusMessageId = isNewMap
|
||||||
? R.string.status_new_mymap_has_been_created
|
? R.string.status_new_mymap_has_been_created
|
||||||
@@ -178,7 +182,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
if (mapsClient != null) {
|
if (mapsClient != null) {
|
||||||
mapsClient.cleanUp();
|
mapsClient.cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean finalSuccess = success;
|
final boolean finalSuccess = success;
|
||||||
final int finalStatusMessageId = statusMessageId;
|
final int finalStatusMessageId = statusMessageId;
|
||||||
context.runOnUiThread(new Runnable() {
|
context.runOnUiThread(new Runnable() {
|
||||||
@@ -206,17 +210,17 @@ public class SendToMyMaps implements Runnable {
|
|||||||
Cursor locationsCursor =
|
Cursor locationsCursor =
|
||||||
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
||||||
try {
|
try {
|
||||||
if (!locationsCursor.moveToFirst()) {
|
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
||||||
Log.w(TAG, "Unable to get any points to upload");
|
Log.w(TAG, "Unable to get any points to upload");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalLocationsRead = 0;
|
totalLocationsRead = 0;
|
||||||
totalLocationsPrepared = 0;
|
totalLocationsPrepared = 0;
|
||||||
totalLocationsUploaded = 0;
|
totalLocationsUploaded = 0;
|
||||||
totalLocations = locationsCursor.getCount();
|
totalLocations = locationsCursor.getCount();
|
||||||
totalSegmentsUploaded = 0;
|
totalSegmentsUploaded = 0;
|
||||||
|
|
||||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
// Limit the number of elevation readings. Ideally we would want around 250.
|
||||||
int elevationSamplingFrequency =
|
int elevationSamplingFrequency =
|
||||||
Math.max(1, (int) (totalLocations / 250.0));
|
Math.max(1, (int) (totalLocations / 250.0));
|
||||||
@@ -224,12 +228,12 @@ public class SendToMyMaps implements Runnable {
|
|||||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
"Using elevation sampling factor: " + elevationSamplingFrequency
|
||||||
+ " on " + totalLocations);
|
+ " on " + totalLocations);
|
||||||
double totalDistance = 0;
|
double totalDistance = 0;
|
||||||
|
|
||||||
Vector<Double> distances = new Vector<Double>();
|
Vector<Double> distances = new Vector<Double>();
|
||||||
Vector<Double> elevations = new Vector<Double>();
|
Vector<Double> elevations = new Vector<Double>();
|
||||||
DoubleBuffer elevationBuffer =
|
DoubleBuffer elevationBuffer =
|
||||||
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
|
||||||
|
|
||||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
||||||
progressIndicator.setProgressMessage(
|
progressIndicator.setProgressMessage(
|
||||||
R.string.progress_message_reading_track);
|
R.string.progress_message_reading_track);
|
||||||
@@ -238,15 +242,15 @@ public class SendToMyMaps implements Runnable {
|
|||||||
if (totalLocationsRead % 100 == 0) {
|
if (totalLocationsRead % 100 == 0) {
|
||||||
updateProgress();
|
updateProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
Location loc = providerUtils.createLocation(locationsCursor);
|
Location loc = providerUtils.createLocation(locationsCursor);
|
||||||
locations.add(loc);
|
locations.add(loc);
|
||||||
|
|
||||||
if (totalLocationsRead == 0) {
|
if (totalLocationsRead == 0) {
|
||||||
// Put a marker at the first point of the first valid segment:
|
// Put a marker at the first point of the first valid segment:
|
||||||
mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), loc, true);
|
mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), loc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to the elevation profile.
|
// Add to the elevation profile.
|
||||||
if (loc != null && LocationUtils.isValidLocation(loc)) {
|
if (loc != null && LocationUtils.isValidLocation(loc)) {
|
||||||
// All points go into the smoothing buffer...
|
// All points go into the smoothing buffer...
|
||||||
@@ -256,19 +260,19 @@ public class SendToMyMaps implements Runnable {
|
|||||||
double dist = lastLocation.distanceTo(loc);
|
double dist = lastLocation.distanceTo(loc);
|
||||||
totalDistance += dist;
|
totalDistance += dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...but only a few points are really used to keep the url short.
|
// ...but only a few points are really used to keep the url short.
|
||||||
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
||||||
distances.add(totalDistance);
|
distances.add(totalDistance);
|
||||||
elevations.add(elevationBuffer.getAverage());
|
elevations.add(elevationBuffer.getAverage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the location was not valid, it's a segment split, so make sure the
|
// If the location was not valid, it's a segment split, so make sure the
|
||||||
// distance between the previous segment and the new one is not accounted
|
// distance between the previous segment and the new one is not accounted
|
||||||
// for in the next iteration.
|
// for in the next iteration.
|
||||||
lastLocation = loc;
|
lastLocation = loc;
|
||||||
|
|
||||||
// Every now and then, upload the accumulated points
|
// Every now and then, upload the accumulated points
|
||||||
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
|
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
|
||||||
== MAX_POINTS_PER_UPLOAD - 1) {
|
== MAX_POINTS_PER_UPLOAD - 1) {
|
||||||
@@ -276,28 +280,30 @@ public class SendToMyMaps implements Runnable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
totalLocationsRead++;
|
totalLocationsRead++;
|
||||||
} while (locationsCursor.moveToNext());
|
} while (locationsCursor.moveToNext());
|
||||||
|
|
||||||
// Do a final upload with what's left
|
// Do a final upload with what's left
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
if (!prepareAndUploadPoints(track, locations)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put an end marker at the last point of the last valid segment:
|
// Put an end marker at the last point of the last valid segment:
|
||||||
if (lastLocation != null) {
|
if (lastLocation != null) {
|
||||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
track.setDescription("<p>" + originalDescription + "</p><p>"
|
||||||
+ stringUtils.generateTrackDescription(
|
+ stringUtils.generateTrackDescription(
|
||||||
track, distances, elevations)
|
track, distances, elevations)
|
||||||
+ "</p>");
|
+ "</p>");
|
||||||
return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
|
return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
|
||||||
lastLocation, false);
|
lastLocation, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} finally {
|
} finally {
|
||||||
locationsCursor.close();
|
if (locationsCursor != null) {
|
||||||
|
locationsCursor.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,17 +312,17 @@ public class SendToMyMaps implements Runnable {
|
|||||||
progressIndicator.setProgressMessage(
|
progressIndicator.setProgressMessage(
|
||||||
R.string.progress_message_preparing_track);
|
R.string.progress_message_preparing_track);
|
||||||
updateProgress();
|
updateProgress();
|
||||||
|
|
||||||
int numLocations = locations.size();
|
int numLocations = locations.size();
|
||||||
if (numLocations < 2) {
|
if (numLocations < 2) {
|
||||||
Log.d(TAG, "Not preparing/uploading too few points");
|
Log.d(TAG, "Not preparing/uploading too few points");
|
||||||
totalLocationsUploaded += numLocations;
|
totalLocationsUploaded += numLocations;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare/pre-process the points
|
// Prepare/pre-process the points
|
||||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
||||||
|
|
||||||
// Start uploading them
|
// Start uploading them
|
||||||
progressIndicator.setProgressMessage(
|
progressIndicator.setProgressMessage(
|
||||||
R.string.progress_message_sending_mymaps);
|
R.string.progress_message_sending_mymaps);
|
||||||
@@ -330,7 +336,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
Log.d(TAG,
|
Log.d(TAG,
|
||||||
"SendToMyMaps: Prepared feature for upload w/ "
|
"SendToMyMaps: Prepared feature for upload w/ "
|
||||||
+ splitTrack.getLocations().size() + " points.");
|
+ splitTrack.getLocations().size() + " points.");
|
||||||
|
|
||||||
// Transmit tracks via GData feed:
|
// Transmit tracks via GData feed:
|
||||||
// -------------------------------
|
// -------------------------------
|
||||||
Log.d(TAG,
|
Log.d(TAG,
|
||||||
@@ -340,7 +346,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
locations.clear();
|
locations.clear();
|
||||||
totalLocationsUploaded += numLocations;
|
totalLocationsUploaded += numLocations;
|
||||||
updateProgress();
|
updateProgress();
|
||||||
@@ -358,7 +364,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
private ArrayList<Track> prepareLocations(
|
private ArrayList<Track> prepareLocations(
|
||||||
Track track, Iterable<Location> locations) {
|
Track track, Iterable<Location> locations) {
|
||||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||||
|
|
||||||
// Create segments from each full track:
|
// Create segments from each full track:
|
||||||
Track segment = new Track();
|
Track segment = new Track();
|
||||||
TripStatistics segmentStats = segment.getStatistics();
|
TripStatistics segmentStats = segment.getStatistics();
|
||||||
@@ -377,11 +383,11 @@ public class SendToMyMaps implements Runnable {
|
|||||||
if (loc.getLatitude() > 90) {
|
if (loc.getLatitude() > 90) {
|
||||||
startNewTrackSegment = true;
|
startNewTrackSegment = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startNewTrackSegment) {
|
if (startNewTrackSegment) {
|
||||||
// Close up the last segment.
|
// Close up the last segment.
|
||||||
prepareTrackSegment(segment, splitTracks);
|
prepareTrackSegment(segment, splitTracks);
|
||||||
|
|
||||||
Log.d(TAG,
|
Log.d(TAG,
|
||||||
"MyTracksSendToMyMaps: Starting new track segment...");
|
"MyTracksSendToMyMaps: Starting new track segment...");
|
||||||
startNewTrackSegment = false;
|
startNewTrackSegment = false;
|
||||||
@@ -391,7 +397,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
segment.setDescription(/* track.getDescription() */ "");
|
segment.setDescription(/* track.getDescription() */ "");
|
||||||
segment.setCategory(track.getCategory());
|
segment.setCategory(track.getCategory());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loc.getLatitude() <= 90) {
|
if (loc.getLatitude() <= 90) {
|
||||||
segment.addLocation(loc);
|
segment.addLocation(loc);
|
||||||
if (segmentStats.getStartTime() < 0) {
|
if (segmentStats.getStartTime() < 0) {
|
||||||
@@ -400,9 +406,9 @@ public class SendToMyMaps implements Runnable {
|
|||||||
}
|
}
|
||||||
totalLocationsPrepared++;
|
totalLocationsPrepared++;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
prepareTrackSegment(segment, splitTracks);
|
||||||
|
|
||||||
return splitTracks;
|
return splitTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,13 +431,13 @@ public class SendToMyMaps implements Runnable {
|
|||||||
&& segment.getLocations().size() > 0) {
|
&& segment.getLocations().size() > 0) {
|
||||||
segmentStats.setStopTime(segment.getLocations().size() - 1);
|
segmentStats.setStopTime(segment.getLocations().size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Decimate to 2 meter precision. Mapshop doesn't like too many
|
* Decimate to 2 meter precision. Mapshop doesn't like too many
|
||||||
* points:
|
* points:
|
||||||
*/
|
*/
|
||||||
LocationUtils.decimate(segment, 2.0);
|
LocationUtils.decimate(segment, 2.0);
|
||||||
|
|
||||||
/* It the track still has > 500 points, split it in pieces: */
|
/* It the track still has > 500 points, split it in pieces: */
|
||||||
if (segment.getLocations().size() > 500) {
|
if (segment.getLocations().size() > 500) {
|
||||||
splitTracks.addAll(LocationUtils.split(segment, 500));
|
splitTracks.addAll(LocationUtils.split(segment, 500));
|
||||||
@@ -447,7 +453,7 @@ public class SendToMyMaps implements Runnable {
|
|||||||
// The percent of the total that represents the completed part of this
|
// The percent of the total that represents the completed part of this
|
||||||
// segment.
|
// segment.
|
||||||
int totalPercentage =
|
int totalPercentage =
|
||||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded) * 100
|
||||||
/ (totalLocations * 3);
|
/ (totalLocations * 3);
|
||||||
totalPercentage = Math.min(99, totalPercentage);
|
totalPercentage = Math.min(99, totalPercentage);
|
||||||
progressIndicator.setProgressValue(totalPercentage);
|
progressIndicator.setProgressValue(totalPercentage);
|
||||||
|
|||||||
@@ -202,8 +202,6 @@ public class BackupActivityHelper {
|
|||||||
Log.e(Constants.TAG, "Failed to restore backup", e);
|
Log.e(Constants.TAG, "Failed to restore backup", e);
|
||||||
showToast(R.string.io_read_failed);
|
showToast(R.string.io_read_failed);
|
||||||
} finally {
|
} finally {
|
||||||
dismissDialog(progressDialog);
|
|
||||||
|
|
||||||
// Data may have been restored, "reboot" the app to catch it
|
// Data may have been restored, "reboot" the app to catch it
|
||||||
restartApplication();
|
restartApplication();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io.backup;
|
package com.google.android.apps.mytracks.io.backup;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
@@ -205,7 +207,9 @@ class ExternalFileBackup {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// We tried to delete the partially created file, but do nothing
|
// We tried to delete the partially created file, but do nothing
|
||||||
// if that also fails.
|
// if that also fails.
|
||||||
outputFile.delete();
|
if (!outputFile.delete()) {
|
||||||
|
Log.w(TAG, "Failed to delete file " + outputFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -363,8 +363,8 @@ public class DocsHelper {
|
|||||||
wr.close();
|
wr.close();
|
||||||
rd.close();
|
rd.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IOException newIOException(GDataWrapper wrapper,
|
private static IOException newIOException(GDataWrapper<GDataServiceClient> wrapper,
|
||||||
String message) {
|
String message) {
|
||||||
return new IOException(String.format("%s: %d: %s", message,
|
return new IOException(String.format("%s: %d: %s", message,
|
||||||
wrapper.getErrorType(), wrapper.getErrorMessage()));
|
wrapper.getErrorType(), wrapper.getErrorMessage()));
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileForm
|
|||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
import com.google.android.apps.mytracks.util.StringUtils;
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -85,7 +86,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
|
|||||||
pw.println("<?xml-stylesheet type=\"text/xsl\" href=\"details.xsl\"?>");
|
pw.println("<?xml-stylesheet type=\"text/xsl\" href=\"details.xsl\"?>");
|
||||||
pw.println("<gpx");
|
pw.println("<gpx");
|
||||||
pw.println(" version=\"1.1\"");
|
pw.println(" version=\"1.1\"");
|
||||||
pw.println(" creator=\"My Tracks for the G1 running Android\"");
|
pw.format(" creator=\"My Tracks running on %s\"\n", Build.MODEL);
|
||||||
pw.println(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
pw.println(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
|
||||||
pw.println(" xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
pw.println(" xmlns=\"http://www.topografix.com/GPX/1/1\"");
|
||||||
pw.print(" xmlns:topografix=\"http://www.topografix.com/GPX/Private/"
|
pw.print(" xmlns:topografix=\"http://www.topografix.com/GPX/Private/"
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.util.UriUtils;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.AlertDialog.Builder;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper activity which imports tracks from a file.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class ImportActivity extends Activity {
|
||||||
|
|
||||||
|
private static final int PROGRESS_DIALOG = 1;
|
||||||
|
private static final int SUCCESS_DIALOG = 2;
|
||||||
|
private static final int FAILURE_DIALOG = 3;
|
||||||
|
|
||||||
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
private int resultMessage;
|
||||||
|
private long importedTrackIds[];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedState) {
|
||||||
|
super.onCreate(savedState);
|
||||||
|
|
||||||
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (!(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_ATTACH_DATA.equals(action))
|
||||||
|
|| !UriUtils.isFileUri(data)) {
|
||||||
|
Log.e(TAG, "Received an intent with unsupported action or data: " + intent);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Importing GPX file at " + data);
|
||||||
|
startTrackImport(data.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case PROGRESS_DIALOG:
|
||||||
|
progressDialog = new ProgressDialog(this);
|
||||||
|
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||||
|
progressDialog.setTitle(R.string.progress_title);
|
||||||
|
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
progressDialog.setMessage(getString(R.string.import_progress_message));
|
||||||
|
return progressDialog;
|
||||||
|
case SUCCESS_DIALOG:
|
||||||
|
final Builder dialogBuilder = new AlertDialog.Builder(this);
|
||||||
|
dialogBuilder.setCancelable(true);
|
||||||
|
dialogBuilder.setPositiveButton(android.R.string.ok, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface arg0, int arg1) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialogBuilder.setNeutralButton(R.string.import_show_track, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface arg0, int arg1) {
|
||||||
|
showImportedTrack();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return dialogBuilder.create();
|
||||||
|
case FAILURE_DIALOG:
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setNeutralButton(R.string.ok, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface arg0, int arg1) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
||||||
|
builder.setTitle(R.string.error);
|
||||||
|
return builder.create();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||||
|
super.onPrepareDialog(id, dialog);
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
case SUCCESS_DIALOG:
|
||||||
|
AlertDialog successDialog = (AlertDialog) dialog;
|
||||||
|
successDialog.setMessage(getString(R.string.import_success, importedTrackIds.length));
|
||||||
|
break;
|
||||||
|
case FAILURE_DIALOG:
|
||||||
|
AlertDialog failureDialog = (AlertDialog) dialog;
|
||||||
|
failureDialog.setMessage(getString(resultMessage));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showImportedTrack() {
|
||||||
|
long lastTrackId = importedTrackIds[importedTrackIds.length - 1];
|
||||||
|
Uri trackUri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, lastTrackId);
|
||||||
|
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setDataAndType(trackUri, TracksColumns.CONTENT_ITEMTYPE);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startTrackImport(final String fileName) {
|
||||||
|
showDialog(PROGRESS_DIALOG);
|
||||||
|
|
||||||
|
Thread t = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
resultMessage = R.string.success;
|
||||||
|
|
||||||
|
importedTrackIds = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
InputStream is = new FileInputStream(fileName);
|
||||||
|
importedTrackIds = GpxImporter.importGPXFile(is, providerUtils);
|
||||||
|
} catch (SAXException e) {
|
||||||
|
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||||
|
resultMessage = R.string.error_generic;
|
||||||
|
} catch (ParserConfigurationException e) {
|
||||||
|
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||||
|
resultMessage = R.string.error_generic;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||||
|
resultMessage = R.string.error_unable_to_read_file;
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||||
|
resultMessage = R.string.error_invalid_gpx_format;
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
Log.e(TAG, "Caught an unexpected exception.", e);
|
||||||
|
resultMessage = R.string.error_out_of_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = (importedTrackIds != null && importedTrackIds.length > 0);
|
||||||
|
showImportResult(success);
|
||||||
|
} finally {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
dismissDialog(PROGRESS_DIALOG);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showImportResult(boolean success) {
|
||||||
|
final int dialogToShow = success ? SUCCESS_DIALOG : FAILURE_DIALOG;
|
||||||
|
|
||||||
|
// Show a dialog telling the user about the import, and asking if he wishes
|
||||||
|
// to open the track right away.
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
showDialog(dialogToShow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import com.google.android.apps.mytracks.util.StringUtils;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -70,8 +71,9 @@ public class KmlTrackWriter implements TrackFormatWriter {
|
|||||||
pw.print(" xmlns=\"http://earth.google.com/kml/2.0\"");
|
pw.print(" xmlns=\"http://earth.google.com/kml/2.0\"");
|
||||||
pw.println(" xmlns:atom=\"http://www.w3.org/2005/Atom\">");
|
pw.println(" xmlns:atom=\"http://www.w3.org/2005/Atom\">");
|
||||||
pw.println("<Document>");
|
pw.println("<Document>");
|
||||||
pw.println("<atom:author><atom:name>My Tracks running on Android"
|
pw.format("<atom:author><atom:name>My Tracks running on %s"
|
||||||
+ "</atom:name></atom:author>");
|
+ "</atom:name></atom:author>\n", Build.MODEL);
|
||||||
|
|
||||||
pw.println("<name>" + StringUtils.stringAsCData(track.getName())
|
pw.println("<name>" + StringUtils.stringAsCData(track.getName())
|
||||||
+ "</name>");
|
+ "</name>");
|
||||||
pw.println("<description>"
|
pw.println("<description>"
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
|
||||||
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.UriUtils;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnCancelListener;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity for saving a track to a file (and optionally sending that file).
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class SaveActivity extends Activity {
|
||||||
|
public static final String EXTRA_SHARE_FILE = "share_file";
|
||||||
|
public static final String EXTRA_FILE_FORMAT = "file_format";
|
||||||
|
private static final int RESULT_DIALOG = 1;
|
||||||
|
/* VisibleForTesting */
|
||||||
|
static final int PROGRESS_DIALOG = 2;
|
||||||
|
|
||||||
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
private long trackId;
|
||||||
|
private TrackWriter writer;
|
||||||
|
private boolean shareFile;
|
||||||
|
private TrackFileFormat format;
|
||||||
|
private WriteProgressController controller;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
String type = intent.getType();
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (!getString(R.string.save_intent_action).equals(action) ||
|
||||||
|
!TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
|
||||||
|
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
|
||||||
|
Log.e(TAG, "Got bad save intent: " + intent);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackId = ContentUris.parseId(data);
|
||||||
|
|
||||||
|
int formatIdx = intent.getIntExtra(EXTRA_FILE_FORMAT, -1);
|
||||||
|
format = TrackFileFormat.values()[formatIdx];
|
||||||
|
shareFile = intent.getBooleanExtra(EXTRA_SHARE_FILE, false);
|
||||||
|
|
||||||
|
writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
|
||||||
|
if (writer == null) {
|
||||||
|
Log.e(TAG, "Unable to build writer");
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shareFile) {
|
||||||
|
// If the file is for sending, save it to a temporary location instead.
|
||||||
|
FileUtils fileUtils = new FileUtils();
|
||||||
|
String extension = format.getExtension();
|
||||||
|
String dirName = fileUtils.buildExternalDirectoryPath(extension, "tmp");
|
||||||
|
|
||||||
|
File dir = new File(dirName);
|
||||||
|
writer.setDirectory(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
controller = new WriteProgressController(this, writer, PROGRESS_DIALOG);
|
||||||
|
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
onWriteComplete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controller.startWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onWriteComplete() {
|
||||||
|
if (shareFile) {
|
||||||
|
shareWrittenFile();
|
||||||
|
} else {
|
||||||
|
showResultDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shareWrittenFile() {
|
||||||
|
if (!writer.wasSuccess()) {
|
||||||
|
showResultDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Share the file.
|
||||||
|
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||||
|
getResources().getText(R.string.send_track_subject).toString());
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT,
|
||||||
|
getResources().getText(R.string.send_track_body_format)
|
||||||
|
.toString());
|
||||||
|
shareIntent.setType(format.getMimeType());
|
||||||
|
Uri u = Uri.fromFile(new File(writer.getAbsolutePath()));
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_STREAM, u);
|
||||||
|
startActivity(Intent.createChooser(shareIntent,
|
||||||
|
getResources().getText(R.string.share_track).toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showResultDialog() {
|
||||||
|
removeDialog(RESULT_DIALOG);
|
||||||
|
showDialog(RESULT_DIALOG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case RESULT_DIALOG:
|
||||||
|
return createResultDialog();
|
||||||
|
case PROGRESS_DIALOG:
|
||||||
|
if (controller != null) {
|
||||||
|
return controller.createProgressDialog();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return super.onCreateDialog(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dialog createResultDialog() {
|
||||||
|
boolean success = writer.wasSuccess();
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(writer.getErrorMessage());
|
||||||
|
builder.setNeutralButton(R.string.ok, new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int arg1) {
|
||||||
|
dialog.dismiss();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setOnCancelListener(new OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
dialog.dismiss();
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
|
||||||
|
android.R.drawable.ic_dialog_alert);
|
||||||
|
builder.setTitle(success ? R.string.success : R.string.error);
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleExportTrackAction(Context ctx, long trackId, int actionCode) {
|
||||||
|
if (trackId < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackFileFormat exportFormat = null;
|
||||||
|
switch (actionCode) {
|
||||||
|
case Constants.SAVE_GPX_FILE:
|
||||||
|
case Constants.SHARE_GPX_FILE:
|
||||||
|
exportFormat = TrackFileFormat.GPX;
|
||||||
|
break;
|
||||||
|
case Constants.SAVE_KML_FILE:
|
||||||
|
case Constants.SHARE_KML_FILE:
|
||||||
|
exportFormat = TrackFileFormat.KML;
|
||||||
|
break;
|
||||||
|
case Constants.SAVE_CSV_FILE:
|
||||||
|
case Constants.SHARE_CSV_FILE:
|
||||||
|
exportFormat = TrackFileFormat.CSV;
|
||||||
|
break;
|
||||||
|
case Constants.SAVE_TCX_FILE:
|
||||||
|
case Constants.SHARE_TCX_FILE:
|
||||||
|
exportFormat = TrackFileFormat.TCX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Warning unhandled action code: " + actionCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shareFile = false;
|
||||||
|
switch (actionCode) {
|
||||||
|
case Constants.SHARE_GPX_FILE:
|
||||||
|
case Constants.SHARE_KML_FILE:
|
||||||
|
case Constants.SHARE_CSV_FILE:
|
||||||
|
case Constants.SHARE_TCX_FILE:
|
||||||
|
shareFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctx, SaveActivity.class);
|
||||||
|
intent.setAction(ctx.getString(R.string.save_intent_action));
|
||||||
|
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
|
||||||
|
intent.putExtra(EXTRA_FILE_FORMAT, exportFormat.ordinal());
|
||||||
|
intent.putExtra(EXTRA_SHARE_FILE, shareFile);
|
||||||
|
ctx.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -187,7 +187,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
|
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
|
||||||
&& sensorData.getCadence().hasValue()) {
|
&& sensorData.getCadence().hasValue()) {
|
||||||
pw.print(" <Cadence>");
|
pw.print(" <Cadence>");
|
||||||
pw.print(sensorData.getCadence().getValue());
|
pw.print(Math.min(254, sensorData.getCadence().getValue()));
|
||||||
pw.println("</Cadence>");
|
pw.println("</Cadence>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
|
|||||||
}
|
}
|
||||||
pw.println(" </Lap>");
|
pw.println(" </Lap>");
|
||||||
pw.print(" <Creator xsi:type=\"Device_t\">");
|
pw.print(" <Creator xsi:type=\"Device_t\">");
|
||||||
pw.format("<Name>%s</Name>\n", Build.MODEL);
|
pw.format("<Name>My Tracks running on %s</Name>\n", Build.MODEL);
|
||||||
|
|
||||||
// The following code is correct. ID is inconsistently capitalized in the
|
// The following code is correct. ID is inconsistently capitalized in the
|
||||||
// TCX schema.
|
// TCX schema.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -15,7 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -24,18 +27,18 @@ import java.io.File;
|
|||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class TempFileCleaner {
|
public class TempFileCleaner {
|
||||||
|
|
||||||
private long currentTimeMillis;
|
private long currentTimeMillis;
|
||||||
|
|
||||||
public static void clean() {
|
public static void clean() {
|
||||||
(new TempFileCleaner(System.currentTimeMillis())).cleanImpl();
|
(new TempFileCleaner(System.currentTimeMillis())).cleanImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @VisibleForTesting
|
// @VisibleForTesting
|
||||||
TempFileCleaner(long time) {
|
TempFileCleaner(long time) {
|
||||||
currentTimeMillis = time;
|
currentTimeMillis = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanImpl() {
|
private void cleanImpl() {
|
||||||
if (!Environment.getExternalStorageState().equals(
|
if (!Environment.getExternalStorageState().equals(
|
||||||
Environment.MEDIA_MOUNTED)) {
|
Environment.MEDIA_MOUNTED)) {
|
||||||
@@ -64,7 +67,9 @@ public class TempFileCleaner {
|
|||||||
long oldest = currentTimeMillis - 1000 * 3600;
|
long oldest = currentTimeMillis - 1000 * 3600;
|
||||||
for (File f : dir.listFiles()) {
|
for (File f : dir.listFiles()) {
|
||||||
if (f.lastModified() < oldest) {
|
if (f.lastModified() < oldest) {
|
||||||
f.delete();
|
if (!f.delete()) {
|
||||||
|
Log.w(TAG, "Failed to delete file " + f.getAbsolutePath());
|
||||||
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,13 @@
|
|||||||
|
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory to produce track writers for any format.
|
* A factory to produce track writers for any format.
|
||||||
@@ -90,6 +93,11 @@ public class TrackWriterFactory {
|
|||||||
MyTracksProviderUtils providerUtils,
|
MyTracksProviderUtils providerUtils,
|
||||||
long trackId, TrackFileFormat format) {
|
long trackId, TrackFileFormat format) {
|
||||||
Track track = providerUtils.getTrack(trackId);
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
if (track == null) {
|
||||||
|
Log.w(TAG, "Trying to create a writer for an invalid track, id=" + trackId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return newWriter(context, providerUtils, track, format);
|
return newWriter(context, providerUtils, track, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io.file;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
import com.google.android.apps.mytracks.content.MyTracksLocation;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
@@ -120,7 +122,9 @@ class TrackWriterImpl implements TrackWriter {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.i(Constants.TAG, "The track write was interrupted");
|
Log.i(Constants.TAG, "The track write was interrupted");
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
file.delete();
|
if (!file.delete()) {
|
||||||
|
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
success = false;
|
success = false;
|
||||||
errorMessage = R.string.error_operation_cancelled;
|
errorMessage = R.string.error_operation_cancelled;
|
||||||
@@ -195,7 +199,7 @@ class TrackWriterImpl implements TrackWriter {
|
|||||||
directory, track.getName(), writer.getExtension());
|
directory, track.getName(), writer.getExtension());
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
Log.e(Constants.TAG,
|
Log.e(Constants.TAG,
|
||||||
"Unable to get a unique filename for " + fileName);
|
"Unable to get a unique filename for " + track.getName());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-16
@@ -13,9 +13,8 @@
|
|||||||
* 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;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.io.file.TrackWriter;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -41,14 +40,15 @@ class WriteProgressController {
|
|||||||
* dialog has been dismissed. Whether the write succeeded can be
|
* dialog has been dismissed. Whether the write succeeded can be
|
||||||
* determined by examining the {@link TrackWriter}.
|
* determined by examining the {@link TrackWriter}.
|
||||||
*/
|
*/
|
||||||
public void onComplete(TrackWriter writer);
|
public void onComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final TrackWriter writer;
|
private final TrackWriter writer;
|
||||||
private final ProgressDialog dialog;
|
private ProgressDialog dialog;
|
||||||
|
|
||||||
private OnCompletionListener onCompletionListener;
|
private OnCompletionListener onCompletionListener;
|
||||||
|
private final int progressDialogId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param activity the activity associated with this write
|
* @param activity the activity associated with this write
|
||||||
@@ -57,17 +57,10 @@ class WriteProgressController {
|
|||||||
* interested in notification upon completion of the write, they should
|
* interested in notification upon completion of the write, they should
|
||||||
* use {@link #setOnCompletionListener}.
|
* use {@link #setOnCompletionListener}.
|
||||||
*/
|
*/
|
||||||
public WriteProgressController(Activity activity, TrackWriter writer) {
|
public WriteProgressController(Activity activity, TrackWriter writer, int progressDialogId) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
|
this.progressDialogId = progressDialogId;
|
||||||
dialog = new ProgressDialog(activity);
|
|
||||||
dialog.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
dialog.setTitle(activity.getString(R.string.progress_title));
|
|
||||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
|
||||||
dialog.setMessage(activity.getString(R.string.write_progress_message));
|
|
||||||
dialog.setIndeterminate(true);
|
|
||||||
dialog.setOnCancelListener(dialogCancelListener);
|
|
||||||
|
|
||||||
writer.setOnCompletionListener(writerCompleteListener);
|
writer.setOnCompletionListener(writerCompleteListener);
|
||||||
writer.setOnWriteListener(writerWriteListener);
|
writer.setOnWriteListener(writerWriteListener);
|
||||||
@@ -78,9 +71,20 @@ class WriteProgressController {
|
|||||||
this.onCompletionListener = onCompletionListener;
|
this.onCompletionListener = onCompletionListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProgressDialog createProgressDialog() {
|
||||||
|
dialog = new ProgressDialog(activity);
|
||||||
|
dialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||||
|
dialog.setTitle(activity.getString(R.string.progress_title));
|
||||||
|
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
dialog.setMessage(activity.getString(R.string.write_progress_message));
|
||||||
|
dialog.setIndeterminate(true);
|
||||||
|
dialog.setOnCancelListener(dialogCancelListener);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
/** Initiate an asynchronous write. */
|
/** Initiate an asynchronous write. */
|
||||||
public void startWrite() {
|
public void startWrite() {
|
||||||
DialogManager.showDialogSafely(activity, dialog);
|
activity.showDialog(progressDialogId);
|
||||||
writer.writeTrackAsync();
|
writer.writeTrackAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,10 +105,10 @@ class WriteProgressController {
|
|||||||
new TrackWriter.OnCompletionListener() {
|
new TrackWriter.OnCompletionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onComplete() {
|
public void onComplete() {
|
||||||
DialogManager.dismissDialogSafely(activity, dialog);
|
activity.dismissDialog(progressDialogId);
|
||||||
|
|
||||||
if (onCompletionListener != null) {
|
if (onCompletionListener != null) {
|
||||||
onCompletionListener.onComplete(writer);
|
onCompletionListener.onComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -137,7 +137,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see GDataClient#encodeUri(java.lang.String)
|
* @see GDataClient#encodeUri(java.lang.String)
|
||||||
*/
|
*/
|
||||||
public String encodeUri(String uri) {
|
public String encodeUri(String uri) {
|
||||||
@@ -155,7 +155,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
|
* @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
|
||||||
*/
|
*/
|
||||||
public QueryParams createQueryParams() {
|
public QueryParams createQueryParams() {
|
||||||
@@ -273,7 +273,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
String errorMessage = null;
|
String errorMessage = null;
|
||||||
HttpEntity entity = response.getEntity();
|
HttpEntity entity = response.getEntity();
|
||||||
try {
|
try {
|
||||||
if (response != null && entity != null) {
|
if (entity != null) {
|
||||||
InputStream in = entity.getContent();
|
InputStream in = entity.getContent();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
byte[] buf = new byte[8192];
|
byte[] buf = new byte[8192];
|
||||||
@@ -329,7 +329,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
|
* @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
|
||||||
*/
|
*/
|
||||||
public InputStream getFeedAsStream(String feedUrl, String authToken)
|
public InputStream getFeedAsStream(String feedUrl, String authToken)
|
||||||
@@ -358,7 +358,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see GDataClient#createEntry
|
* @see GDataClient#createEntry
|
||||||
*/
|
*/
|
||||||
public InputStream createEntry(String feedUrl, String authToken,
|
public InputStream createEntry(String feedUrl, String authToken,
|
||||||
@@ -377,7 +377,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see GDataClient#updateEntry
|
* @see GDataClient#updateEntry
|
||||||
*/
|
*/
|
||||||
public InputStream updateEntry(String editUri, String authToken,
|
public InputStream updateEntry(String editUri, String authToken,
|
||||||
@@ -395,7 +395,7 @@ public class AndroidGDataClient implements GDataClient {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
*
|
*
|
||||||
* @see GDataClient#deleteEntry
|
* @see GDataClient#deleteEntry
|
||||||
*/
|
*/
|
||||||
public void deleteEntry(String editUri, String authToken)
|
public void deleteEntry(String editUri, String authToken)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.google.android.apps.mytracks.io.mymaps;
|
|||||||
|
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.util.Random;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,7 +12,7 @@ import java.util.Vector;
|
|||||||
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
|
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
|
||||||
* transmitted to other activities.
|
* transmitted to other activities.
|
||||||
*/
|
*/
|
||||||
class MyMapsFeature implements Serializable {
|
class MyMapsFeature {
|
||||||
|
|
||||||
private static final long serialVersionUID = 8439035544430497236L;
|
private static final long serialVersionUID = 8439035544430497236L;
|
||||||
|
|
||||||
@@ -49,6 +49,8 @@ class MyMapsFeature implements Serializable {
|
|||||||
/** The metadata of this feature in a format efficient for transmission. */
|
/** The metadata of this feature in a format efficient for transmission. */
|
||||||
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
|
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a valid but empty feature. It will default to a
|
* Initializes a valid but empty feature. It will default to a
|
||||||
* {@link #MARKER} with a blue placemark with a dot as an icon at the
|
* {@link #MARKER} with a blue placemark with a dot as an icon at the
|
||||||
@@ -62,7 +64,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
*
|
*
|
||||||
* @param point The new point to add
|
* @param point The new point to add
|
||||||
*/
|
*/
|
||||||
public synchronized void addPoint(GeoPoint point) {
|
public void addPoint(GeoPoint point) {
|
||||||
latitudeE6.add(point.getLatitudeE6());
|
latitudeE6.add(point.getLatitudeE6());
|
||||||
longitudeE6.add(point.getLongitudeE6());
|
longitudeE6.add(point.getLongitudeE6());
|
||||||
}
|
}
|
||||||
@@ -71,10 +73,10 @@ class MyMapsFeature implements Serializable {
|
|||||||
* Generates a new local id for this feature based on the current time and
|
* Generates a new local id for this feature based on the current time and
|
||||||
* a random number.
|
* a random number.
|
||||||
*/
|
*/
|
||||||
public synchronized void generateAndroidId() {
|
void generateAndroidId() {
|
||||||
Long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
Integer random = (int) (Math.random() * 10000);
|
int rand = random.nextInt(10000);
|
||||||
androidId = time.toString() + "." + random.toString();
|
androidId = time + "." + rand;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,7 +84,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
*
|
*
|
||||||
* @return The local id for this feature
|
* @return The local id for this feature
|
||||||
*/
|
*/
|
||||||
public String getAndroidId() {
|
String getAndroidId() {
|
||||||
return androidId;
|
return androidId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +104,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
*
|
*
|
||||||
* @param description The new description of this feature
|
* @param description The new description of this feature
|
||||||
*/
|
*/
|
||||||
public synchronized void setDescription(String description) {
|
public void setDescription(String description) {
|
||||||
featureInfo.setDescription(description);
|
featureInfo.setDescription(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +198,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
*
|
*
|
||||||
* @param title The new title of this feature
|
* @param title The new title of this feature
|
||||||
*/
|
*/
|
||||||
public synchronized void setTitle(String title) {
|
public void setTitle(String title) {
|
||||||
featureInfo.setTitle(title);
|
featureInfo.setTitle(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +209,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
* @param type The new type of the feature. That type must be one of
|
* @param type The new type of the feature. That type must be one of
|
||||||
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
|
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
|
||||||
*/
|
*/
|
||||||
public synchronized void setType(int type) {
|
public void setType(int type) {
|
||||||
featureInfo.setType(type);
|
featureInfo.setType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +219,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
*
|
*
|
||||||
* @param color The new ARGB color of this feature
|
* @param color The new ARGB color of this feature
|
||||||
*/
|
*/
|
||||||
public synchronized void setColor(int color) {
|
public void setColor(int color) {
|
||||||
featureInfo.setColor(color);
|
featureInfo.setColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +229,7 @@ class MyMapsFeature implements Serializable {
|
|||||||
*
|
*
|
||||||
* @param url The new icon url of the feature
|
* @param url The new icon url of the feature
|
||||||
*/
|
*/
|
||||||
public synchronized void setIconUrl(String url) {
|
public void setIconUrl(String url) {
|
||||||
featureInfo.setIconUrl(url);
|
featureInfo.setIconUrl(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,6 @@ class MyMapsFeatureMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setIconUrl(String url) {
|
public void setIconUrl(String url) {
|
||||||
iconUrl = new String(url);
|
iconUrl = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class MyMapsMapMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setTitle(String title) {
|
public void setTitle(String title) {
|
||||||
this.title = new String(title);
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
@@ -31,7 +31,7 @@ class MyMapsMapMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDescription(String description) {
|
public void setDescription(String description) {
|
||||||
this.description = new String(description);
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getSearchable() {
|
public boolean getSearchable() {
|
||||||
@@ -47,6 +47,6 @@ class MyMapsMapMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setGDataEditUri(String editUri) {
|
public void setGDataEditUri(String editUri) {
|
||||||
this.gdataEditUri = new String(editUri);
|
this.gdataEditUri = editUri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -85,8 +85,8 @@ class XmlMapsGDataParserFactory implements GDataParserFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GDataSerializer createSerializer(Entry en) {
|
public GDataSerializer createSerializer(Entry en) {
|
||||||
if (en.getClass() == MapFeatureEntry.class) {
|
if (en instanceof MapFeatureEntry) {
|
||||||
return new XmlMapsGDataSerializer(xmlFactory, en);
|
return new XmlMapsGDataSerializer(xmlFactory, (MapFeatureEntry) en);
|
||||||
} else {
|
} else {
|
||||||
return new XmlEntryGDataSerializer(xmlFactory, en);
|
return new XmlEntryGDataSerializer(xmlFactory, en);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright 2010 Google Inc. All Rights Reserved.
|
// Copyright 2010 Google Inc. All Rights Reserved.
|
||||||
package com.google.android.apps.mytracks.io.mymaps;
|
package com.google.android.apps.mytracks.io.mymaps;
|
||||||
|
|
||||||
import com.google.wireless.gdata.data.Entry;
|
|
||||||
import com.google.wireless.gdata.data.StringUtils;
|
import com.google.wireless.gdata.data.StringUtils;
|
||||||
import com.google.wireless.gdata.parser.ParseException;
|
import com.google.wireless.gdata.parser.ParseException;
|
||||||
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
|
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
|
||||||
@@ -10,14 +9,14 @@ import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializer of maps data for GData.
|
* Serializer of maps data for GData.
|
||||||
*/
|
*/
|
||||||
@@ -29,11 +28,11 @@ class XmlMapsGDataSerializer extends XmlEntryGDataSerializer {
|
|||||||
private XmlParserFactory factory;
|
private XmlParserFactory factory;
|
||||||
private OutputStream stream;
|
private OutputStream stream;
|
||||||
|
|
||||||
public XmlMapsGDataSerializer(XmlParserFactory factory, Entry entry) {
|
public XmlMapsGDataSerializer(XmlParserFactory factory, MapFeatureEntry entry) {
|
||||||
super(factory, entry);
|
super(factory, entry);
|
||||||
|
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.entry = (MapFeatureEntry) entry;
|
this.entry = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+4
-1
@@ -58,7 +58,8 @@ public class ResultDialogFactory {
|
|||||||
*/
|
*/
|
||||||
public static AlertDialog makeDialog(Activity activity, List<SendResult> results,
|
public static AlertDialog makeDialog(Activity activity, List<SendResult> results,
|
||||||
DialogInterface.OnClickListener onOkClickListener,
|
DialogInterface.OnClickListener onOkClickListener,
|
||||||
DialogInterface.OnClickListener onShareClickListener) {
|
DialogInterface.OnClickListener onShareClickListener,
|
||||||
|
DialogInterface.OnCancelListener onCancelListener) {
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
for (SendResult result : results) {
|
for (SendResult result : results) {
|
||||||
if (!result.isSuccess()) {
|
if (!result.isSuccess()) {
|
||||||
@@ -84,6 +85,8 @@ public class ResultDialogFactory {
|
|||||||
onShareClickListener);
|
onShareClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.setOnCancelListener(onCancelListener);
|
||||||
|
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,896 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.accounts.Account;
|
||||||
|
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
|
||||||
|
import com.google.android.apps.mytracks.AccountChooser;
|
||||||
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
import com.google.android.apps.mytracks.MyMapsList;
|
||||||
|
import com.google.android.apps.mytracks.ProgressIndicator;
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
|
import com.google.android.apps.mytracks.io.AuthManager;
|
||||||
|
import com.google.android.apps.mytracks.io.AuthManagerFactory;
|
||||||
|
import com.google.android.apps.mytracks.io.SendToDocs;
|
||||||
|
import com.google.android.apps.mytracks.io.SendToFusionTables;
|
||||||
|
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
|
||||||
|
import com.google.android.apps.mytracks.io.SendToMyMaps;
|
||||||
|
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
|
||||||
|
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
|
||||||
|
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||||
|
import com.google.android.apps.mytracks.util.UriUtils;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnCancelListener;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper activity for managing the sending of tracks to Google services.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class SendActivity extends Activity implements ProgressIndicator {
|
||||||
|
// Keys for saved state variables.
|
||||||
|
private static final String STATE_ACCOUNT_TYPE = "accountType";
|
||||||
|
private static final String STATE_ACCOUNT_NAME = "accountName";
|
||||||
|
private static final String STATE_TABLE_ID = "tableId";
|
||||||
|
private static final String STATE_MAP_ID = "mapId";
|
||||||
|
private static final String STATE_DOCS_MESSAGE = "docsMsg";
|
||||||
|
private static final String STATE_FUSION_TABLES_MESSAGE = "fusionMsg";
|
||||||
|
private static final String STATE_MAPS_MESSAGE = "mapsMsg";
|
||||||
|
private static final String STATE_DOCS_SUCCESS = "docsSuccess";
|
||||||
|
private static final String STATE_FUSION_SUCCESS = "fusionSuccess";
|
||||||
|
private static final String STATE_MAPS_SUCCESS = "mapsSuccess";
|
||||||
|
private static final String STATE_STATE = "state";
|
||||||
|
private static final String EXTRA_SHARE_LINK = "share_link";
|
||||||
|
|
||||||
|
/** States for the state machine that defines the upload process. */
|
||||||
|
private enum SendState {
|
||||||
|
SEND_OPTIONS,
|
||||||
|
START,
|
||||||
|
AUTHENTICATE_MAPS,
|
||||||
|
PICK_MAP,
|
||||||
|
SEND_TO_MAPS,
|
||||||
|
SEND_TO_MAPS_DONE,
|
||||||
|
AUTHENTICATE_FUSION_TABLES,
|
||||||
|
SEND_TO_FUSION_TABLES,
|
||||||
|
SEND_TO_FUSION_TABLES_DONE,
|
||||||
|
AUTHENTICATE_DOCS,
|
||||||
|
AUTHENTICATE_TRIX,
|
||||||
|
SEND_TO_DOCS,
|
||||||
|
SEND_TO_DOCS_DONE,
|
||||||
|
SHOW_RESULTS,
|
||||||
|
SHARE_LINK,
|
||||||
|
FINISH,
|
||||||
|
DONE,
|
||||||
|
NOT_READY
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int SEND_DIALOG = 1;
|
||||||
|
private static final int PROGRESS_DIALOG = 2;
|
||||||
|
/* @VisibleForTesting */
|
||||||
|
static final int DONE_DIALOG = 3;
|
||||||
|
|
||||||
|
// UI
|
||||||
|
private SendDialog sendDialog;
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
|
// Services
|
||||||
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
private SharedPreferences sharedPreferences;
|
||||||
|
private GoogleAnalyticsTracker tracker;
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
private AuthManager lastAuth;
|
||||||
|
private final HashMap<String, AuthManager> authMap =
|
||||||
|
new HashMap<String, AuthManager>();
|
||||||
|
private final AccountChooser accountChooser = new AccountChooser();
|
||||||
|
private String lastAccountName;
|
||||||
|
private String lastAccountType;
|
||||||
|
|
||||||
|
// Send request information.
|
||||||
|
private boolean shareRequested = false;
|
||||||
|
private long sendTrackId;
|
||||||
|
|
||||||
|
// Send result information, used by results dialog.
|
||||||
|
private boolean sendToMyMapsSuccess = false;
|
||||||
|
private boolean sendToFusionTablesSuccess = false;
|
||||||
|
private boolean sendToDocsSuccess = false;
|
||||||
|
// TODO: Make these be used for showing results
|
||||||
|
private String sendToMyMapsMessage;
|
||||||
|
private String sendToFusionTablesMessage;
|
||||||
|
private String sendToDocsMessage;
|
||||||
|
|
||||||
|
// Send result information, used to share a link.
|
||||||
|
private String sendToMyMapsMapId;
|
||||||
|
private String sendToFusionTablesTableId;
|
||||||
|
|
||||||
|
// Current sending state.
|
||||||
|
private SendState currentState;
|
||||||
|
|
||||||
|
private final OnCancelListener finishOnCancelListener = new OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface arg0) {
|
||||||
|
onAllDone();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.d(TAG, "SendActivity.onCreate");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
|
|
||||||
|
resetState();
|
||||||
|
|
||||||
|
tracker = GoogleAnalyticsTracker.getInstance();
|
||||||
|
// Start the tracker in manual dispatch mode...
|
||||||
|
tracker.start(getString(R.string.google_analytics_id), getApplicationContext());
|
||||||
|
tracker.setProductVersion("android-mytracks", SystemUtils.getMyTracksVersion(this));
|
||||||
|
|
||||||
|
if (!handleIntent()) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
restoreInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
executeStateMachine(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleIntent() {
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
String type = intent.getType();
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (!Intent.ACTION_SEND.equals(action) ||
|
||||||
|
!TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
|
||||||
|
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
|
||||||
|
Log.e(TAG, "Got bad send intent: " + intent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTrackId = ContentUris.parseId(data);
|
||||||
|
shareRequested = intent.getBooleanExtra(EXTRA_SHARE_LINK, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Dialog onCreateDialog(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case SEND_DIALOG:
|
||||||
|
return createSendDialog();
|
||||||
|
case PROGRESS_DIALOG:
|
||||||
|
return createProgressDialog();
|
||||||
|
case DONE_DIALOG:
|
||||||
|
return createDoneDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dialog createSendDialog() {
|
||||||
|
sendDialog = new SendDialog(this);
|
||||||
|
sendDialog.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
if (which != DialogInterface.BUTTON_POSITIVE) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
executeStateMachine(SendState.START);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sendDialog.setOnCancelListener(finishOnCancelListener);
|
||||||
|
return sendDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreInstanceState(Bundle savedInstanceState) {
|
||||||
|
currentState = SendState.values()[savedInstanceState.getInt(STATE_STATE)];
|
||||||
|
|
||||||
|
sendToMyMapsSuccess = savedInstanceState.getBoolean(STATE_MAPS_SUCCESS);
|
||||||
|
sendToFusionTablesSuccess = savedInstanceState.getBoolean(STATE_FUSION_SUCCESS);
|
||||||
|
sendToDocsSuccess = savedInstanceState.getBoolean(STATE_DOCS_SUCCESS);
|
||||||
|
|
||||||
|
sendToMyMapsMessage = savedInstanceState.getString(STATE_MAPS_MESSAGE);
|
||||||
|
sendToFusionTablesMessage = savedInstanceState.getString(STATE_FUSION_TABLES_MESSAGE);
|
||||||
|
sendToDocsMessage = savedInstanceState.getString(STATE_DOCS_MESSAGE);
|
||||||
|
|
||||||
|
sendToMyMapsMapId = savedInstanceState.getString(STATE_MAP_ID);
|
||||||
|
sendToFusionTablesTableId = savedInstanceState.getString(STATE_TABLE_ID);
|
||||||
|
|
||||||
|
lastAccountName = savedInstanceState.getString(STATE_ACCOUNT_NAME);
|
||||||
|
lastAccountType = savedInstanceState.getString(STATE_ACCOUNT_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
outState.putInt(STATE_STATE, currentState.ordinal());
|
||||||
|
|
||||||
|
outState.putBoolean(STATE_MAPS_SUCCESS, sendToMyMapsSuccess);
|
||||||
|
outState.putBoolean(STATE_FUSION_SUCCESS, sendToFusionTablesSuccess);
|
||||||
|
outState.putBoolean(STATE_DOCS_SUCCESS, sendToDocsSuccess);
|
||||||
|
|
||||||
|
outState.putString(STATE_MAPS_MESSAGE, sendToMyMapsMessage);
|
||||||
|
outState.putString(STATE_FUSION_TABLES_MESSAGE, sendToFusionTablesMessage);
|
||||||
|
outState.putString(STATE_DOCS_MESSAGE, sendToDocsMessage);
|
||||||
|
|
||||||
|
outState.putString(STATE_MAP_ID, sendToMyMapsMapId);
|
||||||
|
outState.putString(STATE_TABLE_ID, sendToFusionTablesTableId);
|
||||||
|
|
||||||
|
outState.putString(STATE_ACCOUNT_NAME, lastAccountName);
|
||||||
|
outState.putString(STATE_ACCOUNT_TYPE, lastAccountType);
|
||||||
|
|
||||||
|
// TODO: Ideally we should serialize/restore the authenticator map and lastAuth somehow,
|
||||||
|
// but it's highly unlikely we'll get killed while an auth dialog is displayed.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
Log.d(TAG, "SendActivity.onStop" + currentState);
|
||||||
|
tracker.dispatch();
|
||||||
|
tracker.stop();
|
||||||
|
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
Log.d(TAG, "SendActivity.onDestroy, state=" + currentState);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeStateMachine(SendState startState) {
|
||||||
|
if (startState != null) {
|
||||||
|
currentState = startState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a state handler returns NOT_READY, it means it's waiting for some
|
||||||
|
// event, and will call this method again when it happens.
|
||||||
|
while (currentState != SendState.DONE &&
|
||||||
|
currentState != SendState.NOT_READY) {
|
||||||
|
Log.d(TAG, "Executing state " + currentState);
|
||||||
|
currentState = executeState(currentState);
|
||||||
|
Log.d(TAG, "New state is " + currentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState executeState(SendState state) {
|
||||||
|
switch (state) {
|
||||||
|
case SEND_OPTIONS:
|
||||||
|
return showSendOptions();
|
||||||
|
case START:
|
||||||
|
return startSend();
|
||||||
|
case AUTHENTICATE_MAPS:
|
||||||
|
return authenticateToGoogleMaps();
|
||||||
|
case PICK_MAP:
|
||||||
|
return pickMap();
|
||||||
|
case SEND_TO_MAPS:
|
||||||
|
return sendToGoogleMaps();
|
||||||
|
case SEND_TO_MAPS_DONE:
|
||||||
|
return onSendToGoogleMapsDone();
|
||||||
|
case AUTHENTICATE_FUSION_TABLES:
|
||||||
|
return authenticateToFusionTables();
|
||||||
|
case SEND_TO_FUSION_TABLES:
|
||||||
|
return sendToFusionTables();
|
||||||
|
case SEND_TO_FUSION_TABLES_DONE:
|
||||||
|
return onSendToFusionTablesDone();
|
||||||
|
case AUTHENTICATE_DOCS:
|
||||||
|
return authenticateToGoogleDocs();
|
||||||
|
case AUTHENTICATE_TRIX:
|
||||||
|
return authenticateToGoogleTrix();
|
||||||
|
case SEND_TO_DOCS:
|
||||||
|
return sendToGoogleDocs();
|
||||||
|
case SEND_TO_DOCS_DONE:
|
||||||
|
return onSendToGoogleDocsDone();
|
||||||
|
case SHOW_RESULTS:
|
||||||
|
return onSendToGoogleDone();
|
||||||
|
case SHARE_LINK:
|
||||||
|
return shareLink();
|
||||||
|
case FINISH:
|
||||||
|
return onAllDone();
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Reached a non-executable state");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState showSendOptions() {
|
||||||
|
showDialog(SEND_DIALOG);
|
||||||
|
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates the process to send tracks to google.
|
||||||
|
* This is called once the user has selected sending options via the
|
||||||
|
* SendToGoogleDialog.
|
||||||
|
*/
|
||||||
|
private SendState startSend() {
|
||||||
|
showDialog(PROGRESS_DIALOG);
|
||||||
|
|
||||||
|
if (sendDialog.getSendToMyMaps()) {
|
||||||
|
return SendState.AUTHENTICATE_MAPS;
|
||||||
|
} else if (sendDialog.getSendToFusionTables()) {
|
||||||
|
return SendState.AUTHENTICATE_FUSION_TABLES;
|
||||||
|
} else if (sendDialog.getSendToDocs()) {
|
||||||
|
return SendState.AUTHENTICATE_DOCS;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Nowhere to upload to");
|
||||||
|
return SendState.FINISH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dialog createProgressDialog() {
|
||||||
|
progressDialog = new ProgressDialog(this);
|
||||||
|
progressDialog.setCancelable(false);
|
||||||
|
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
||||||
|
progressDialog.setTitle(R.string.progress_title);
|
||||||
|
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
progressDialog.setMax(100);
|
||||||
|
progressDialog.setProgress(0);
|
||||||
|
|
||||||
|
return progressDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState authenticateToGoogleMaps() {
|
||||||
|
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
|
||||||
|
progressDialog.setProgress(0);
|
||||||
|
progressDialog.setMessage(getString(
|
||||||
|
R.string.progress_message_authenticating_mymaps));
|
||||||
|
authenticate(Constants.AUTHENTICATE_TO_MY_MAPS, MyMapsConstants.SERVICE_NAME);
|
||||||
|
// AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState pickMap() {
|
||||||
|
if (!sendDialog.getCreateNewMap()) {
|
||||||
|
// Ask the user to choose a map to upload into
|
||||||
|
Intent listIntent = new Intent(this, MyMapsList.class);
|
||||||
|
listIntent.putExtra(MyMapsList.EXTRA_ACCOUNT_NAME, lastAccountName);
|
||||||
|
listIntent.putExtra(MyMapsList.EXTRA_ACCOUNT_TYPE, lastAccountType);
|
||||||
|
startActivityForResult(listIntent, Constants.GET_MAP);
|
||||||
|
// The callback for GET_MAP calls authenticateToGoogleMaps
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
} else {
|
||||||
|
return SendState.SEND_TO_MAPS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState sendToGoogleMaps() {
|
||||||
|
tracker.trackPageView("/send/maps");
|
||||||
|
|
||||||
|
SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
|
||||||
|
@Override
|
||||||
|
public void onSendCompleted(String mapId, boolean success, int statusMessage) {
|
||||||
|
// TODO: Use this message
|
||||||
|
sendToMyMapsMessage = getString(statusMessage);
|
||||||
|
sendToMyMapsSuccess = success;
|
||||||
|
if (sendToMyMapsSuccess) {
|
||||||
|
sendToMyMapsMapId = mapId;
|
||||||
|
// Update the map id for this track:
|
||||||
|
try {
|
||||||
|
Track track = providerUtils.getTrack(sendTrackId);
|
||||||
|
if (track != null) {
|
||||||
|
track.setMapId(mapId);
|
||||||
|
providerUtils.updateTrack(track);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Updating map id failed.");
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// If that fails whatever reasons we'll just log an error, but
|
||||||
|
// continue.
|
||||||
|
Log.w(TAG, "Updating map id failed.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeStateMachine(SendState.SEND_TO_MAPS_DONE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sendToMyMapsMapId == null) {
|
||||||
|
sendToMyMapsMapId = SendToMyMaps.NEW_MAP_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
final SendToMyMaps sender = new SendToMyMaps(this, sendToMyMapsMapId, lastAuth,
|
||||||
|
sendTrackId, this /*progressIndicator*/, onCompletion);
|
||||||
|
|
||||||
|
new Thread(sender, "SendToMyMaps").start();
|
||||||
|
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState onSendToGoogleMapsDone() {
|
||||||
|
if (sendDialog.getSendToFusionTables()) {
|
||||||
|
return SendState.AUTHENTICATE_FUSION_TABLES;
|
||||||
|
} else if (sendDialog.getSendToDocs()) {
|
||||||
|
return SendState.AUTHENTICATE_DOCS;
|
||||||
|
} else {
|
||||||
|
return SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState authenticateToFusionTables() {
|
||||||
|
progressDialog.setProgress(0);
|
||||||
|
progressDialog.setMessage(getString(
|
||||||
|
R.string.progress_message_authenticating_fusiontables));
|
||||||
|
authenticate(Constants.AUTHENTICATE_TO_FUSION_TABLES, SendToFusionTables.SERVICE_ID);
|
||||||
|
// AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState sendToFusionTables() {
|
||||||
|
tracker.trackPageView("/send/fusion_tables");
|
||||||
|
|
||||||
|
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
|
||||||
|
@Override
|
||||||
|
public void onSendCompleted(String tableId, boolean success,
|
||||||
|
int statusMessage) {
|
||||||
|
// TODO: Use this message
|
||||||
|
sendToFusionTablesMessage = getString(statusMessage);
|
||||||
|
sendToFusionTablesSuccess = success;
|
||||||
|
if (sendToFusionTablesSuccess) {
|
||||||
|
sendToFusionTablesTableId = tableId;
|
||||||
|
// Update the table id for this track:
|
||||||
|
try {
|
||||||
|
Track track = providerUtils.getTrack(sendTrackId);
|
||||||
|
if (track != null) {
|
||||||
|
track.setTableId(tableId);
|
||||||
|
providerUtils.updateTrack(track);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Updating table id failed.");
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// If that fails whatever reasons we'll just log an error, but
|
||||||
|
// continue.
|
||||||
|
Log.w(TAG, "Updating table id failed.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executeStateMachine(SendState.SEND_TO_FUSION_TABLES_DONE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final SendToFusionTables sender = new SendToFusionTables(this, lastAuth,
|
||||||
|
sendTrackId, this /*progressIndicator*/, onCompletion);
|
||||||
|
|
||||||
|
new Thread(sender, "SendToFusionTables").start();
|
||||||
|
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState onSendToFusionTablesDone() {
|
||||||
|
if (sendDialog.getSendToDocs()) {
|
||||||
|
return SendState.AUTHENTICATE_DOCS;
|
||||||
|
} else {
|
||||||
|
return SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState authenticateToGoogleDocs() {
|
||||||
|
setProgressValue(0);
|
||||||
|
setProgressMessage(
|
||||||
|
R.string.progress_message_authenticating_docs);
|
||||||
|
authenticate(Constants.AUTHENTICATE_TO_DOCLIST, SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
|
||||||
|
// AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState authenticateToGoogleTrix() {
|
||||||
|
setProgressValue(30);
|
||||||
|
setProgressMessage(
|
||||||
|
R.string.progress_message_authenticating_docs);
|
||||||
|
authenticate(Constants.AUTHENTICATE_TO_TRIX, SendToDocs.GDATA_SERVICE_NAME_TRIX);
|
||||||
|
// AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState sendToGoogleDocs() {
|
||||||
|
Log.d(TAG, "Sending to Docs....");
|
||||||
|
tracker.trackPageView("/send/docs");
|
||||||
|
|
||||||
|
setProgressValue(50);
|
||||||
|
setProgressMessage(R.string.progress_message_sending_docs);
|
||||||
|
final SendToDocs sender = new SendToDocs(this,
|
||||||
|
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
|
||||||
|
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST),
|
||||||
|
this);
|
||||||
|
Runnable onCompletion = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
setProgressValue(100);
|
||||||
|
|
||||||
|
// TODO: Use this message
|
||||||
|
sendToDocsMessage = sender.getStatusMessage();
|
||||||
|
sendToDocsSuccess = sender.wasSuccess();
|
||||||
|
|
||||||
|
executeStateMachine(SendState.SEND_TO_DOCS_DONE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sender.setOnCompletion(onCompletion);
|
||||||
|
sender.sendToDocs(sendTrackId);
|
||||||
|
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState onSendToGoogleDocsDone() {
|
||||||
|
return SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState onSendToGoogleDone() {
|
||||||
|
tracker.dispatch();
|
||||||
|
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Log.d(TAG, "Sending to Google done.");
|
||||||
|
dismissDialog(PROGRESS_DIALOG);
|
||||||
|
dismissDialog(SEND_DIALOG);
|
||||||
|
|
||||||
|
// Ensure a new done dialog is created each time.
|
||||||
|
// This is required because the send results must be available at the
|
||||||
|
// time the dialog is created.
|
||||||
|
removeDialog(DONE_DIALOG);
|
||||||
|
showDialog(DONE_DIALOG);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return SendState.NOT_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dialog createDoneDialog() {
|
||||||
|
Log.d(TAG, "Creating done dialog");
|
||||||
|
// We've finished sending the track to the user-selected services. Now
|
||||||
|
// we tell them the results of the upload, and optionally share the track.
|
||||||
|
// There are a few different paths through this code:
|
||||||
|
//
|
||||||
|
// 1. The user pre-requested a share (shareRequested == true). We're going
|
||||||
|
// to display the result dialog *without* the share button (the share
|
||||||
|
// listener will be null). The OK button listener will initiate the
|
||||||
|
// share.
|
||||||
|
//
|
||||||
|
// 2. The user did not pre-request a share, and the set of services to
|
||||||
|
// which we succeeded in uploading the track are compatible with
|
||||||
|
// sharing. We'll display a share button (the share listener will be
|
||||||
|
// non-null), and will share the link if the user clicks it.
|
||||||
|
//
|
||||||
|
// 3. The user did not pre-request a share, and the set of services to
|
||||||
|
// which we succeeded in uploading the track are incompatible with
|
||||||
|
// sharing. We won't display a share button.
|
||||||
|
|
||||||
|
List<SendResult> results = makeSendToGoogleResults();
|
||||||
|
final boolean canShare = sendToFusionTablesTableId != null || sendToMyMapsMapId != null;
|
||||||
|
|
||||||
|
final OnClickListener finishListener = new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
executeStateMachine(SendState.FINISH);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DialogInterface.OnClickListener doShareListener = null;
|
||||||
|
if (canShare) {
|
||||||
|
doShareListener = new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
executeStateMachine(SendState.SHARE_LINK);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DialogInterface.OnClickListener onOkListener = (canShare && shareRequested)
|
||||||
|
? doShareListener : finishListener;
|
||||||
|
DialogInterface.OnClickListener onShareListener = (canShare && !shareRequested)
|
||||||
|
? doShareListener : null;
|
||||||
|
|
||||||
|
return ResultDialogFactory.makeDialog(this, results, onOkListener, onShareListener, finishOnCancelListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState shareLink() {
|
||||||
|
boolean sentToMyMaps = sendDialog.getSendToMyMaps();
|
||||||
|
boolean sentToFusionTables = sendDialog.getSendToFusionTables();
|
||||||
|
if (!shareLinkToMap(sentToMyMaps, sentToFusionTables)) {
|
||||||
|
Log.w(TAG, "Failed to share link");
|
||||||
|
}
|
||||||
|
return SendState.FINISH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SendState onAllDone() {
|
||||||
|
Log.d(TAG, "All sending done.");
|
||||||
|
removeDialog(PROGRESS_DIALOG);
|
||||||
|
removeDialog(SEND_DIALOG);
|
||||||
|
removeDialog(DONE_DIALOG);
|
||||||
|
progressDialog = null;
|
||||||
|
sendDialog = null;
|
||||||
|
finish();
|
||||||
|
return SendState.DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean shareLinkToMap(boolean sentToMyMaps, boolean sentToFusionTables) {
|
||||||
|
String url = null;
|
||||||
|
if (sentToMyMaps && sendToMyMapsSuccess) {
|
||||||
|
// Prefer a link to My Maps
|
||||||
|
url = MapsFacade.buildMapUrl(sendToMyMapsMapId);
|
||||||
|
} else if (sentToFusionTables && sendToFusionTablesSuccess) {
|
||||||
|
// Otherwise try using the link to fusion tables
|
||||||
|
url = getFusionTablesUrl(sendTrackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url != null) {
|
||||||
|
shareLinkToMap(url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shares a link to a My Map or Fusion Table via external app (email, gmail, ...)
|
||||||
|
* A chooser with apps that support text/plain will be shown to the user.
|
||||||
|
*/
|
||||||
|
private void shareLinkToMap(String url) {
|
||||||
|
boolean shareUrlOnly = sharedPreferences.getBoolean(
|
||||||
|
getString(R.string.share_url_only_key), false);
|
||||||
|
String msg = shareUrlOnly ? url : String.format(
|
||||||
|
getResources().getText(R.string.share_map_body_format).toString(), url);
|
||||||
|
|
||||||
|
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||||
|
getResources().getText(R.string.share_map_subject).toString());
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
|
||||||
|
startActivity(Intent.createChooser(shareIntent,
|
||||||
|
getResources().getText(R.string.share_map).toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getFusionTablesUrl(long trackId) {
|
||||||
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
return SendToFusionTables.getMapVisualizationUrl(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a list of {@link SendResult} instances based on the set of
|
||||||
|
* services selected in {@link SendDialog} and the results as known to
|
||||||
|
* this class.
|
||||||
|
*/
|
||||||
|
private List<SendResult> makeSendToGoogleResults() {
|
||||||
|
List<SendResult> results = new ArrayList<SendResult>();
|
||||||
|
if (sendDialog.getSendToMyMaps()) {
|
||||||
|
results.add(new SendResult(SendType.MYMAPS, sendToMyMapsSuccess));
|
||||||
|
}
|
||||||
|
if (sendDialog.getSendToFusionTables()) {
|
||||||
|
results.add(new SendResult(SendType.FUSION_TABLES, sendToFusionTablesSuccess));
|
||||||
|
}
|
||||||
|
if (sendDialog.getSendToDocs()) {
|
||||||
|
results.add(new SendResult(SendType.DOCS, sendToDocsSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the authentication manager which obtains an authentication
|
||||||
|
* token, prompting the user for a login and password if needed.
|
||||||
|
*/
|
||||||
|
private void authenticate(final int requestCode, final String service) {
|
||||||
|
lastAuth = authMap.get(service);
|
||||||
|
if (lastAuth == null) {
|
||||||
|
Log.i(TAG, "Creating a new authentication for service: " + service);
|
||||||
|
lastAuth = AuthManagerFactory.getAuthManager(this,
|
||||||
|
Constants.GET_LOGIN,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
service);
|
||||||
|
authMap.put(service, lastAuth);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Logging in to " + service + "...");
|
||||||
|
if (AuthManagerFactory.useModernAuthManager()) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
chooseAccount(requestCode, service);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
doLogin(requestCode, service, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseAccount(final int requestCode, final String service) {
|
||||||
|
accountChooser.chooseAccount(SendActivity.this,
|
||||||
|
new AccountChooser.AccountHandler() {
|
||||||
|
@Override
|
||||||
|
public void onAccountSelected(Account account) {
|
||||||
|
if (account == null) {
|
||||||
|
dismissDialog(PROGRESS_DIALOG);
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAccountName = account.name;
|
||||||
|
lastAccountType = account.type;
|
||||||
|
doLogin(requestCode, service, account);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doLogin(final int requestCode, final String service, final Object account) {
|
||||||
|
lastAuth.doLogin(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
Log.i(TAG, "Login success for " + service + "!");
|
||||||
|
onActivityResult(requestCode, RESULT_OK, new Intent());
|
||||||
|
}
|
||||||
|
}, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode,
|
||||||
|
final Intent results) {
|
||||||
|
Log.w(TAG, "Result: req=" + requestCode + "; res=" + resultCode + "; result=" + results);
|
||||||
|
SendState nextState = null;
|
||||||
|
switch (requestCode) {
|
||||||
|
case Constants.GET_LOGIN: {
|
||||||
|
// TODO: This is a result from inside the auth manager,
|
||||||
|
// make this return path explicit.
|
||||||
|
if (resultCode != RESULT_OK || lastAuth == null ||
|
||||||
|
!lastAuth.authResult(resultCode, results)) {
|
||||||
|
nextState = SendState.FINISH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Constants.GET_MAP: {
|
||||||
|
// User picked a map to upload to
|
||||||
|
Log.d(TAG, "Get map result: " + resultCode);
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
if (results.hasExtra("mapid")) {
|
||||||
|
sendToMyMapsMapId = results.getStringExtra("mapid");
|
||||||
|
}
|
||||||
|
nextState = SendState.SEND_TO_MAPS;
|
||||||
|
} else {
|
||||||
|
nextState = SendState.FINISH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constants.AUTHENTICATE_TO_MY_MAPS: {
|
||||||
|
// Authenticated with Google My Maps
|
||||||
|
if (results != null && resultCode == RESULT_OK) {
|
||||||
|
nextState = SendState.PICK_MAP;
|
||||||
|
} else {
|
||||||
|
nextState = SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constants.AUTHENTICATE_TO_FUSION_TABLES: {
|
||||||
|
// Authenticated with Google Fusion Tables
|
||||||
|
if (results != null && resultCode == RESULT_OK) {
|
||||||
|
nextState = SendState.SEND_TO_FUSION_TABLES;
|
||||||
|
} else {
|
||||||
|
nextState = SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constants.AUTHENTICATE_TO_DOCLIST: {
|
||||||
|
// Authenticated with Google Docs
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
nextState = SendState.AUTHENTICATE_TRIX;
|
||||||
|
} else {
|
||||||
|
nextState = SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Constants.AUTHENTICATE_TO_TRIX: {
|
||||||
|
// Authenticated with Trix
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
nextState = SendState.SEND_TO_DOCS;
|
||||||
|
} else {
|
||||||
|
nextState = SendState.SHOW_RESULTS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
Log.w(TAG, "Warning unhandled request code: " + requestCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextState != null) {
|
||||||
|
executeStateMachine(nextState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets status information for sending to MyMaps/Docs.
|
||||||
|
*/
|
||||||
|
private void resetState() {
|
||||||
|
currentState = SendState.SEND_OPTIONS;
|
||||||
|
sendToMyMapsMapId = null;
|
||||||
|
sendToMyMapsMessage = "";
|
||||||
|
sendToMyMapsSuccess = true;
|
||||||
|
sendToFusionTablesMessage = "";
|
||||||
|
sendToFusionTablesSuccess = true;
|
||||||
|
sendToDocsMessage = "";
|
||||||
|
sendToDocsSuccess = true;
|
||||||
|
sendToFusionTablesTableId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgressMessage(final int resId) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
progressDialog.setMessage(getString(resId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgressValue(final int percent) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
progressDialog.setProgress(percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearProgressMessage() {
|
||||||
|
progressDialog.setMessage("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendToGoogle(Context ctx, long trackId, boolean shareLink) {
|
||||||
|
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
|
||||||
|
|
||||||
|
Intent intent = new Intent(ctx, SendActivity.class);
|
||||||
|
intent.setAction(Intent.ACTION_SEND);
|
||||||
|
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
|
||||||
|
intent.putExtra(SendActivity.EXTRA_SHARE_LINK, shareLink);
|
||||||
|
ctx.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.DialogManager;
|
|
||||||
import com.google.android.apps.mytracks.MyTracks;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@@ -42,15 +40,13 @@ import android.widget.RadioGroup;
|
|||||||
*/
|
*/
|
||||||
public class SendDialog extends Dialog {
|
public class SendDialog extends Dialog {
|
||||||
|
|
||||||
private RadioGroup groupMyMaps;
|
|
||||||
private RadioButton createNewMapRadioButton;
|
private RadioButton createNewMapRadioButton;
|
||||||
private RadioButton pickMapRadioButton;
|
private RadioButton pickMapRadioButton;
|
||||||
private CheckBox sendToMyMapsCheckBox;
|
private CheckBox sendToMyMapsCheckBox;
|
||||||
private CheckBox sendToFusionTablesCheckBox;
|
private CheckBox sendToFusionTablesCheckBox;
|
||||||
private CheckBox sendToDocsCheckBox;
|
private CheckBox sendToDocsCheckBox;
|
||||||
private RadioButton sendStatsRadioButton;
|
|
||||||
private RadioButton sendStatsAndPointsRadioButton;
|
private RadioButton sendStatsAndPointsRadioButton;
|
||||||
private Button sendButton;
|
private OnClickListener clickListener;
|
||||||
|
|
||||||
public SendDialog(Context context) {
|
public SendDialog(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -62,18 +58,38 @@ public class SendDialog extends Dialog {
|
|||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
setContentView(R.layout.mytracks_send_to_google);
|
setContentView(R.layout.mytracks_send_to_google);
|
||||||
|
|
||||||
|
final Button sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
|
||||||
|
final RadioGroup groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
|
||||||
|
sendToMyMapsCheckBox =
|
||||||
|
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
|
||||||
|
sendToFusionTablesCheckBox =
|
||||||
|
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
|
||||||
|
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
|
||||||
|
createNewMapRadioButton =
|
||||||
|
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
|
||||||
|
pickMapRadioButton =
|
||||||
|
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
|
||||||
|
RadioButton sendStatsRadioButton = (RadioButton) findViewById(R.id.sendtogoogle_send_stats);
|
||||||
|
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
|
||||||
|
R.id.sendtogoogle_send_stats_and_points);
|
||||||
|
|
||||||
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
|
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
|
||||||
cancel.setOnClickListener(new View.OnClickListener() {
|
cancel.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
MyTracks.getInstance().dismissDialog(DialogManager.DIALOG_SEND_TO_GOOGLE);
|
if (clickListener != null) {
|
||||||
|
clickListener.onClick(SendDialog.this, BUTTON_NEGATIVE);
|
||||||
|
}
|
||||||
|
dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Button send = (Button) findViewById(R.id.sendtogoogle_send_now);
|
Button send = (Button) findViewById(R.id.sendtogoogle_send_now);
|
||||||
send.setOnClickListener(new View.OnClickListener() {
|
send.setOnClickListener(new View.OnClickListener() {
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
if (clickListener != null) {
|
||||||
|
clickListener.onClick(SendDialog.this, BUTTON_POSITIVE);
|
||||||
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
MyTracks.getInstance().sendToGoogle();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,25 +102,9 @@ public class SendDialog extends Dialog {
|
|||||||
groupMyMaps.setVisibility(sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
|
groupMyMaps.setVisibility(sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
|
|
||||||
groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
|
|
||||||
sendToMyMapsCheckBox =
|
|
||||||
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
|
|
||||||
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||||
sendToFusionTablesCheckBox =
|
|
||||||
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
|
|
||||||
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||||
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
|
|
||||||
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
|
||||||
createNewMapRadioButton =
|
|
||||||
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
|
|
||||||
pickMapRadioButton =
|
|
||||||
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
|
|
||||||
sendStatsRadioButton =
|
|
||||||
(RadioButton) findViewById(R.id.sendtogoogle_send_stats);
|
|
||||||
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
|
|
||||||
R.id.sendtogoogle_send_stats_and_points);
|
|
||||||
|
|
||||||
SharedPreferences prefs =
|
SharedPreferences prefs =
|
||||||
getContext().getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
getContext().getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
@@ -163,6 +163,10 @@ public class SendDialog extends Dialog {
|
|||||||
super.onStop();
|
super.onStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOnClickListener(OnClickListener clickListener) {
|
||||||
|
this.clickListener = clickListener;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getSendToMyMaps() {
|
public boolean getSendToMyMaps() {
|
||||||
return sendToMyMapsCheckBox.isChecked();
|
return sendToMyMapsCheckBox.isChecked();
|
||||||
}
|
}
|
||||||
@@ -182,4 +186,4 @@ public class SendDialog extends Dialog {
|
|||||||
public boolean getSendStatsAndPoints() {
|
public boolean getSendStatsAndPoints() {
|
||||||
return sendStatsAndPointsRadioButton.isChecked();
|
return sendStatsAndPointsRadioButton.isChecked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,13 +93,13 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(minRecordingDistanceKey)) {
|
if (key == null || key.equals(minRecordingDistanceKey)) {
|
||||||
service.setMinRecordingDistance(
|
int minRecordingDistance = sharedPreferences.getInt(
|
||||||
sharedPreferences.getInt(
|
minRecordingDistanceKey,
|
||||||
minRecordingDistanceKey,
|
Constants.DEFAULT_MIN_RECORDING_DISTANCE);
|
||||||
Constants.DEFAULT_MIN_RECORDING_DISTANCE));
|
service.setMinRecordingDistance(minRecordingDistance);
|
||||||
Log.d(Constants.TAG,
|
Log.d(Constants.TAG,
|
||||||
"TrackRecordingService: minRecordingDistance = "
|
"TrackRecordingService: minRecordingDistance = "
|
||||||
+ service.getMinRecordingDistance());
|
+ minRecordingDistance);
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(maxRecordingDistanceKey)) {
|
if (key == null || key.equals(maxRecordingDistanceKey)) {
|
||||||
service.setMaxRecordingDistance(sharedPreferences.getInt(
|
service.setMaxRecordingDistance(sharedPreferences.getInt(
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.app.ActivityManager.RunningServiceInfo;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for reading service state.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class ServiceUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether we're currently recording.
|
||||||
|
* The checking is done by calling the service, if provided, or alternatively by reading
|
||||||
|
* recording state saved to preferences.
|
||||||
|
*
|
||||||
|
* @param ctx the current context
|
||||||
|
* @param service the service, or null if not bound to it
|
||||||
|
* @param preferences the preferences, or null if not available
|
||||||
|
* @return true if the service is recording (or supposed to be recording), false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean isRecording(Context ctx, ITrackRecordingService service, SharedPreferences preferences) {
|
||||||
|
if (service != null) {
|
||||||
|
try {
|
||||||
|
return service.isRecording();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Failed to check if service is recording", e);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Log.e(TAG, "Failed to check if service is recording", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferences == null) {
|
||||||
|
preferences = ctx.getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
|
}
|
||||||
|
return preferences.getLong(ctx.getString(R.string.recording_track_key), -1) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the recording service is currently running.
|
||||||
|
*
|
||||||
|
* @param ctx the current context
|
||||||
|
* @return true if the service is running, false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean isServiceRunning(Context ctx) {
|
||||||
|
ActivityManager activityManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||||
|
List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
for (RunningServiceInfo serviceInfo : services) {
|
||||||
|
ComponentName componentName = serviceInfo.service;
|
||||||
|
String serviceName = componentName.getClassName();
|
||||||
|
if (serviceName.equals(TrackRecordingService.class.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceUtils() {}
|
||||||
|
}
|
||||||
+749
-745
File diff suppressed because it is too large
Load Diff
+170
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.util.SystemUtils;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.IBinder.DeathRecipient;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for the connection to the track recording service.
|
||||||
|
* This handles connection/disconnection internally, only returning a real
|
||||||
|
* service for use if one is available and connected.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class TrackRecordingServiceConnection {
|
||||||
|
private ITrackRecordingService boundService;
|
||||||
|
|
||||||
|
private final DeathRecipient deathRecipient = new DeathRecipient() {
|
||||||
|
@Override
|
||||||
|
public void binderDied() {
|
||||||
|
Log.d(TAG, "Service died");
|
||||||
|
setBoundService(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
Log.i(TAG, "Connected to service");
|
||||||
|
try {
|
||||||
|
service.linkToDeath(deathRecipient, 0);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
Log.e(TAG, "Failed to bind a death recipient", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBoundService(ITrackRecordingService.Stub.asInterface(service));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
Log.i(TAG, "Disconnected from service");
|
||||||
|
setBoundService(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final Runnable bindChangedCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param context the current context
|
||||||
|
* @param bindChangedCallback a callback to be executed when the state of the
|
||||||
|
* service binding changes
|
||||||
|
*/
|
||||||
|
public TrackRecordingServiceConnection(Context context, Runnable bindChangedCallback) {
|
||||||
|
this.context = context;
|
||||||
|
this.bindChangedCallback = bindChangedCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds to the service, starting it if necessary.
|
||||||
|
*/
|
||||||
|
public void startAndBind() {
|
||||||
|
bindService(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds to the service, only if it's already running.
|
||||||
|
*/
|
||||||
|
public void bindIfRunning() {
|
||||||
|
bindService(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbinds from and stops the service.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
unbind();
|
||||||
|
|
||||||
|
Log.d(TAG, "Stopping service");
|
||||||
|
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||||
|
context.stopService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbinds from the service (but leaves it running).
|
||||||
|
*/
|
||||||
|
public void unbind() {
|
||||||
|
Log.d(TAG, "Unbinding from the service");
|
||||||
|
try {
|
||||||
|
context.unbindService(serviceConnection);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Means we weren't bound, which is ok.
|
||||||
|
}
|
||||||
|
|
||||||
|
setBoundService(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the service if connected to it, or null if not connected.
|
||||||
|
*/
|
||||||
|
public ITrackRecordingService getServiceIfBound() {
|
||||||
|
checkBindingAlive();
|
||||||
|
|
||||||
|
return boundService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBindingAlive() {
|
||||||
|
if (boundService != null &&
|
||||||
|
!boundService.asBinder().isBinderAlive()) {
|
||||||
|
setBoundService(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindService(boolean startIfNeeded) {
|
||||||
|
if (boundService != null) {
|
||||||
|
// Already bound.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startIfNeeded && !ServiceUtils.isServiceRunning(context)) {
|
||||||
|
// Not running, start not requested.
|
||||||
|
Log.d(TAG, "Service not running, not binding to it.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startIfNeeded) {
|
||||||
|
Log.i(TAG, "Starting the service");
|
||||||
|
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Binding to the service");
|
||||||
|
Intent intent = new Intent(context, TrackRecordingService.class);
|
||||||
|
int flags = SystemUtils.isRelease(context) ? 0 : Context.BIND_DEBUG_UNBIND;
|
||||||
|
context.bindService(intent, serviceConnection, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBoundService(ITrackRecordingService service) {
|
||||||
|
boundService = service;
|
||||||
|
if (bindChangedCallback != null) {
|
||||||
|
bindChangedCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-4
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Google Inc.
|
* Copyright 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -45,7 +45,9 @@ public class SensorManagerFactory {
|
|||||||
if (prefs == null) {
|
if (prefs == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context = context.getApplicationContext();
|
||||||
|
|
||||||
String sensor = prefs.getString(context.getString(R.string.sensor_type_key), null);
|
String sensor = prefs.getString(context.getString(R.string.sensor_type_key), null);
|
||||||
Log.i(Constants.TAG, "Creating sensor of type: " + sensor);
|
Log.i(Constants.TAG, "Creating sensor of type: " + sensor);
|
||||||
|
|
||||||
|
|||||||
@@ -24,14 +24,16 @@ import com.google.android.maps.mytracks.R;
|
|||||||
* A collection of methods for message parsers.
|
* A collection of methods for message parsers.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
|
* @author Nico Laum
|
||||||
*/
|
*/
|
||||||
public class SensorUtils {
|
public class SensorUtils {
|
||||||
|
|
||||||
private SensorUtils() {
|
private SensorUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract one unsigned short from a big endian byte array.
|
* Extract one unsigned short from a big endian byte array.
|
||||||
|
*
|
||||||
* @param buffer the buffer to extract the short from
|
* @param buffer the buffer to extract the short from
|
||||||
* @param index the first byte to be interpreted as part of the short
|
* @param index the first byte to be interpreted as part of the short
|
||||||
* @return The unsigned short at the given index in the buffer
|
* @return The unsigned short at the given index in the buffer
|
||||||
@@ -42,6 +44,58 @@ public class SensorUtils {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract one unsigned short from a little endian byte array.
|
||||||
|
*
|
||||||
|
* @param buffer the buffer to extract the short from
|
||||||
|
* @param index the first byte to be interpreted as part of the short
|
||||||
|
* @return The unsigned short at the given index in the buffer
|
||||||
|
*/
|
||||||
|
public static int unsignedShortToIntLittleEndian(byte[] buffer, int index) {
|
||||||
|
int r = buffer[index] & 0xFF;
|
||||||
|
r |= (buffer[index + 1] & 0xFF) << 8;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns CRC8 (polynomial 0x8C) from byte array buffer[start] until
|
||||||
|
* (including) buffer[end]
|
||||||
|
*
|
||||||
|
* @param buffer the byte array of data (payload)
|
||||||
|
* @param start the position in the byte array where the payload begins
|
||||||
|
* @param end the position in the byte array where the payload ends
|
||||||
|
* @return CRC8 value
|
||||||
|
*/
|
||||||
|
public static byte getCrc8(byte[] buffer, int start, int length) {
|
||||||
|
byte crc = 0x0;
|
||||||
|
|
||||||
|
for (int i = start; i < (start + length); i++) {
|
||||||
|
crc = crc8PushByte(crc, buffer[i]);
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a CRC8 value by using the next byte passed to this method
|
||||||
|
*
|
||||||
|
* @param crc int of crc value
|
||||||
|
* @param add the next byte to add to the CRC8 calculation
|
||||||
|
*/
|
||||||
|
private static byte crc8PushByte(byte crc, byte add) {
|
||||||
|
crc = (byte) (crc ^ add);
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if ((crc & 0x1) != 0x0) {
|
||||||
|
// Using a 0xFF bit assures that 0-bits are introduced during the shift operation.
|
||||||
|
// Otherwise, implicit casts to signed int could shift in 1-bits if the signed bit is 1.
|
||||||
|
crc = (byte) (((crc & 0xFF) >> 1) ^ 0x8C);
|
||||||
|
} else {
|
||||||
|
crc = (byte) ((crc & 0xFF) >> 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getStateAsString(Sensor.SensorState state, Context c) {
|
public static String getStateAsString(Sensor.SensorState state, Context c) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case NONE:
|
case NONE:
|
||||||
|
|||||||
+24
-13
@@ -21,12 +21,16 @@ import com.google.android.apps.mytracks.Constants;
|
|||||||
import com.google.android.apps.mytracks.content.Sensor;
|
import com.google.android.apps.mytracks.content.Sensor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of a SensorData parser for Zephyr HRM.
|
* An implementation of a Sensor MessageParser for Zephyr.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class ZephyrMessageParser implements MessageParser {
|
public class ZephyrMessageParser implements MessageParser {
|
||||||
|
|
||||||
|
public static final int ZEPHYR_HXM_BYTE_STX = 0;
|
||||||
|
public static final int ZEPHYR_HXM_BYTE_CRC = 58;
|
||||||
|
public static final int ZEPHYR_HXM_BYTE_ETX = 59;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
|
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
@@ -34,20 +38,24 @@ public class ZephyrMessageParser implements MessageParser {
|
|||||||
sb.append(String.format("%02X", buffer[i]));
|
sb.append(String.format("%02X", buffer[i]));
|
||||||
}
|
}
|
||||||
Log.w(Constants.TAG, "Got zephyr data: " + sb);
|
Log.w(Constants.TAG, "Got zephyr data: " + sb);
|
||||||
// The provided units are 1/16 strides per minute.
|
|
||||||
// TODO: Fix the cadence calculation.
|
|
||||||
// int cadence = SensorUtils.unsignedShortToInt(buffer, 56);
|
|
||||||
// Heart Rate
|
// Heart Rate
|
||||||
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
|
Sensor.SensorData.Builder heartrate = Sensor.SensorData.newBuilder()
|
||||||
.setValue(buffer[12] & 0xFF)
|
.setValue(buffer[12] & 0xFF)
|
||||||
.setState(Sensor.SensorState.SENDING);
|
.setState(Sensor.SensorState.SENDING);
|
||||||
// Cadence
|
// Changes Nico Laum (Power and Cadence)
|
||||||
//.setCadence(cadence / 16)
|
Sensor.SensorData.Builder power = Sensor.SensorData.newBuilder()
|
||||||
//.build();
|
.setValue(buffer[11])
|
||||||
|
.setState(Sensor.SensorState.SENDING);
|
||||||
|
Sensor.SensorData.Builder cadence = Sensor.SensorData.newBuilder()
|
||||||
|
.setValue(SensorUtils.unsignedShortToIntLittleEndian(buffer, 56) / 16)
|
||||||
|
.setState(Sensor.SensorState.SENDING);
|
||||||
|
|
||||||
Sensor.SensorDataSet sds =
|
Sensor.SensorDataSet sds =
|
||||||
Sensor.SensorDataSet.newBuilder()
|
Sensor.SensorDataSet.newBuilder()
|
||||||
.setCreationTime(System.currentTimeMillis())
|
.setCreationTime(System.currentTimeMillis())
|
||||||
.setHeartRate(b)
|
.setPower(power)
|
||||||
|
.setHeartRate(heartrate)
|
||||||
|
.setCadence(cadence)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return sds;
|
return sds;
|
||||||
@@ -55,8 +63,11 @@ public class ZephyrMessageParser implements MessageParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(byte[] buffer) {
|
public boolean isValid(byte[] buffer) {
|
||||||
// TODO crc etc.
|
// Check STX (Start of Text), ETX (End of Text) and CRC Checksum
|
||||||
return buffer[0] == 0x02 && buffer[59] == 0x03;
|
return buffer.length > ZEPHYR_HXM_BYTE_ETX
|
||||||
|
&& buffer[ZEPHYR_HXM_BYTE_STX] == 0x02
|
||||||
|
&& buffer[ZEPHYR_HXM_BYTE_ETX] == 0x03
|
||||||
|
&& SensorUtils.getCrc8(buffer, 3, 55) == buffer[ZEPHYR_HXM_BYTE_CRC];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+11
-2
@@ -130,8 +130,17 @@ public abstract class AntSensorManager extends SensorManager {
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
Log.i(TAG, "destroying AntSensorManager");
|
Log.i(TAG, "destroying AntSensorManager");
|
||||||
context.unregisterReceiver(statusReceiver);
|
try {
|
||||||
context.unregisterReceiver(dataReceiver);
|
context.unregisterReceiver(statusReceiver);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.w(TAG, "Failed to unregister ANT status receiver", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.unregisterReceiver(dataReceiver);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Log.w(TAG, "Failed to unregister ANT data receiver", e);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
antReceiver.releaseInterface();
|
antReceiver.releaseInterface();
|
||||||
|
|||||||
+14
-13
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2011 Google Inc.
|
* Copyright 2011 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -44,34 +44,35 @@ public class FroyoStatusAnnouncerTask extends StatusAnnouncerTask {
|
|||||||
new OnUtteranceCompletedListener() {
|
new OnUtteranceCompletedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onUtteranceCompleted(String utteranceId) {
|
public void onUtteranceCompleted(String utteranceId) {
|
||||||
if (audioManager != null) {
|
int result = audioManager.abandonAudioFocus(null);
|
||||||
Log.d(TAG, "FroyoStatusAnnouncerTask: Abandoning audio focus.");
|
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||||
audioManager.abandonAudioFocus(null);
|
Log.w(TAG, "FroyoStatusAnnouncerTask: Failed to relinquish audio focus");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public FroyoStatusAnnouncerTask(Context context) {
|
public FroyoStatusAnnouncerTask(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onTtsInit(int status) {
|
protected void onTtsReady() {
|
||||||
super.onTtsInit(status);
|
super.onTtsReady();
|
||||||
if (status == TextToSpeech.SUCCESS) {
|
|
||||||
tts.setOnUtteranceCompletedListener(utteranceListener);
|
tts.setOnUtteranceCompletedListener(utteranceListener);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void speakAnnouncment(String announcement) {
|
protected synchronized void speakAnnouncement(String announcement) {
|
||||||
int result = audioManager.requestAudioFocus(null,
|
int result = audioManager.requestAudioFocus(null,
|
||||||
TextToSpeech.Engine.DEFAULT_STREAM,
|
TextToSpeech.Engine.DEFAULT_STREAM,
|
||||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
if (result == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
|
||||||
Log.w(TAG, "FroyoStatusAnnouncerTask: Request audio focus failed.");
|
Log.w(TAG, "FroyoStatusAnnouncerTask: Request for audio focus failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't care about the utterance id.
|
// We don't care about the utterance id.
|
||||||
// It is supplied here to force onUtteranceCompleted to be called.
|
// It is supplied here to force onUtteranceCompleted to be called.
|
||||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, SPEECH_PARAMS);
|
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, SPEECH_PARAMS);
|
||||||
|
|||||||
+7
-7
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -67,7 +67,7 @@ public class PeriodicTaskExecutor {
|
|||||||
if (!service.isRecording()) {
|
if (!service.isRecording()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isTimeFrequency()) {
|
if (!isTimeFrequency()) {
|
||||||
if (timerExecutor != null) {
|
if (timerExecutor != null) {
|
||||||
timerExecutor.shutdown();
|
timerExecutor.shutdown();
|
||||||
@@ -77,7 +77,7 @@ public class PeriodicTaskExecutor {
|
|||||||
if (taskFrequency == 0) {
|
if (taskFrequency == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to make the task.
|
// Try to make the task.
|
||||||
task = factory.create(service);
|
task = factory.create(service);
|
||||||
// Returning null is ok.
|
// Returning null is ok.
|
||||||
@@ -90,7 +90,7 @@ public class PeriodicTaskExecutor {
|
|||||||
if (timerExecutor == null) {
|
if (timerExecutor == null) {
|
||||||
timerExecutor = new TimerTaskExecutor(task, service);
|
timerExecutor = new TimerTaskExecutor(task, service);
|
||||||
}
|
}
|
||||||
timerExecutor.scheduleTask(taskFrequency * 60000);
|
timerExecutor.scheduleTask(taskFrequency * 60000L);
|
||||||
} else {
|
} else {
|
||||||
// For distance based splits.
|
// For distance based splits.
|
||||||
calculateNextTaskDistance();
|
calculateNextTaskDistance();
|
||||||
@@ -119,7 +119,7 @@ public class PeriodicTaskExecutor {
|
|||||||
if (!service.isRecording() || task == null) {
|
if (!service.isRecording() || task == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDistanceFrequency()) {
|
if (!isDistanceFrequency()) {
|
||||||
nextTaskDistance = Double.MAX_VALUE;
|
nextTaskDistance = Double.MAX_VALUE;
|
||||||
Log.d(TAG, "SplitManager: Distance splits disabled.");
|
Log.d(TAG, "SplitManager: Distance splits disabled.");
|
||||||
|
|||||||
+71
-36
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Google Inc.
|
* Copyright 2009 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -66,6 +66,11 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
/**
|
/**
|
||||||
* The response received from the TTS engine after initialization.
|
* The response received from the TTS engine after initialization.
|
||||||
*/
|
*/
|
||||||
|
private int initStatus = TextToSpeech.ERROR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the TTS engine is ready.
|
||||||
|
*/
|
||||||
private boolean ready = false;
|
private boolean ready = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,13 +86,14 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
public void onCallStateChanged(int state, String incomingNumber) {
|
public void onCallStateChanged(int state, String incomingNumber) {
|
||||||
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
|
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
|
||||||
|
|
||||||
if (!speechAllowed && tts.isSpeaking()) {
|
if (!speechAllowed && tts != null && tts.isSpeaking()) {
|
||||||
// If we're already speaking, stop it.
|
// If we're already speaking, stop it.
|
||||||
tts.stop();
|
tts.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
public StatusAnnouncerTask(Context context) {
|
public StatusAnnouncerTask(Context context) {
|
||||||
this(context, new StringUtils(context));
|
this(context, new StringUtils(context));
|
||||||
}
|
}
|
||||||
@@ -97,33 +103,6 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
this.stringUtils = stringUtils;
|
this.stringUtils = stringUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the TTS engine is initialized.
|
|
||||||
*/
|
|
||||||
protected void onTtsInit(int status) {
|
|
||||||
Log.i(TAG, "TrackRecordingService.TTS init: " + status);
|
|
||||||
// TTS should be valid here but NPE exceptions were reported to the market.
|
|
||||||
this.ready = status == TextToSpeech.SUCCESS && tts != null;
|
|
||||||
|
|
||||||
if (ready) {
|
|
||||||
// Force the language to be the same as the string we will be speaking,
|
|
||||||
// if that's available.
|
|
||||||
Locale speechLanguage = Locale.getDefault();
|
|
||||||
int languageAvailability = tts.isLanguageAvailable(speechLanguage);
|
|
||||||
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA ||
|
|
||||||
languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
|
||||||
// English is probably supported.
|
|
||||||
// TODO: Somehow use announcement strings from English too.
|
|
||||||
Log.w(TAG, "Default language not available, using English.");
|
|
||||||
speechLanguage = Locale.ENGLISH;
|
|
||||||
}
|
|
||||||
tts.setLanguage(speechLanguage);
|
|
||||||
|
|
||||||
// Slow down the speed just a bit as it is hard to hear when exercising.
|
|
||||||
tts.setSpeechRate(TTS_SPEECH_RATE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
@@ -144,12 +123,15 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
* to avoid needing to instantiate an entire {@link TrackRecordingService}
|
* to avoid needing to instantiate an entire {@link TrackRecordingService}
|
||||||
* just to test the announcer.
|
* just to test the announcer.
|
||||||
*/
|
*/
|
||||||
protected void runWithStatistics(TripStatistics statistics) {
|
// @VisibleForTesting
|
||||||
|
void runWithStatistics(TripStatistics statistics) {
|
||||||
if (statistics == null) {
|
if (statistics == null) {
|
||||||
Log.e(TAG, "StatusAnnouncer stats not initialized.");
|
Log.e(TAG, "StatusAnnouncer stats not initialized.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!ready || tts == null) {
|
|
||||||
|
checkReady();
|
||||||
|
if (!ready) {
|
||||||
Log.e(TAG, "StatusAnnouncer Tts not ready.");
|
Log.e(TAG, "StatusAnnouncer Tts not ready.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -162,10 +144,10 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
|
|
||||||
String announcement = getAnnouncement(statistics);
|
String announcement = getAnnouncement(statistics);
|
||||||
Log.d(Constants.TAG, "Announcement: " + announcement);
|
Log.d(Constants.TAG, "Announcement: " + announcement);
|
||||||
speakAnnouncment(announcement);
|
speakAnnouncement(announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void speakAnnouncment(String announcement) {
|
protected void speakAnnouncement(String announcement) {
|
||||||
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
|
tts.speak(announcement, TextToSpeech.QUEUE_FLUSH, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +163,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
boolean metricUnits = true;
|
boolean metricUnits = true;
|
||||||
boolean reportSpeed = true;
|
boolean reportSpeed = true;
|
||||||
if (preferences != null) {
|
if (preferences != null) {
|
||||||
metricUnits =
|
metricUnits =
|
||||||
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
||||||
true);
|
true);
|
||||||
reportSpeed =
|
reportSpeed =
|
||||||
@@ -256,6 +238,59 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
|
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the TTS engine is initialized.
|
||||||
|
*/
|
||||||
|
private void onTtsInit(int status) {
|
||||||
|
Log.i(TAG, "TrackRecordingService.TTS init: " + status);
|
||||||
|
synchronized (this) {
|
||||||
|
// TTS should be valid here but NPE exceptions were reported to the market.
|
||||||
|
initStatus = status;
|
||||||
|
checkReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the TTS is ready (finishing its initialization if needed).
|
||||||
|
*/
|
||||||
|
private void checkReady() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (ready) {
|
||||||
|
// Already done;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ready = initStatus == TextToSpeech.SUCCESS && tts != null;
|
||||||
|
Log.d(TAG, "Status announcer ready: " + ready);
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
onTtsReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes the TTS engine initialization.
|
||||||
|
* Called once (and only once) when the TTS engine is ready.
|
||||||
|
*/
|
||||||
|
protected void onTtsReady() {
|
||||||
|
// Force the language to be the same as the string we will be speaking,
|
||||||
|
// if that's available.
|
||||||
|
Locale speechLanguage = Locale.getDefault();
|
||||||
|
int languageAvailability = tts.isLanguageAvailable(speechLanguage);
|
||||||
|
if (languageAvailability == TextToSpeech.LANG_MISSING_DATA ||
|
||||||
|
languageAvailability == TextToSpeech.LANG_NOT_SUPPORTED) {
|
||||||
|
// English is probably supported.
|
||||||
|
// TODO: Somehow use announcement strings from English too.
|
||||||
|
Log.w(TAG, "Default language not available, using English.");
|
||||||
|
speechLanguage = Locale.ENGLISH;
|
||||||
|
}
|
||||||
|
tts.setLanguage(speechLanguage);
|
||||||
|
|
||||||
|
// Slow down the speed just a bit as it is hard to hear when exercising.
|
||||||
|
tts.setSpeechRate(TTS_SPEECH_RATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
// Stop listening to phone state.
|
// Stop listening to phone state.
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package com.google.android.apps.mytracks.stats;
|
|||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
import com.google.android.apps.mytracks.SettingsActivity;
|
|
||||||
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import android.app.Notification;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.HandlerThread;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of methods that may be implemented in a platform specific way.
|
* A set of methods that may be implemented in a platform specific way.
|
||||||
@@ -51,14 +50,6 @@ public interface ApiPlatformAdapter {
|
|||||||
void stopForeground(Service service, NotificationManager notificationManager,
|
void stopForeground(Service service, NotificationManager notificationManager,
|
||||||
int id);
|
int id);
|
||||||
|
|
||||||
/**
|
|
||||||
* Terminates the given handler thread.
|
|
||||||
*
|
|
||||||
* @param handlerThread the thread to be terminated.
|
|
||||||
* @return true whether the handler has been stopped or not.
|
|
||||||
*/
|
|
||||||
boolean stopHandlerThread(HandlerThread handlerThread);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies all changes done to the given preferences editor.
|
* Applies all changes done to the given preferences editor.
|
||||||
* Changes may or may not be applied immediately.
|
* Changes may or may not be applied immediately.
|
||||||
|
|||||||
@@ -16,11 +16,11 @@
|
|||||||
package com.google.android.apps.mytracks.util;
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.Constants.TAG;
|
import static com.google.android.apps.mytracks.Constants.TAG;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.os.HandlerThread;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
@@ -70,12 +70,6 @@ public class CupcakePlatformAdapter implements ApiPlatformAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
|
||||||
// Do nothing, as Cupcake doesn't provide quit().
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyPreferenceChanges(Editor editor) {
|
public void applyPreferenceChanges(Editor editor) {
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import android.app.Notification;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.os.HandlerThread;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Eclair (API level 5) specific implementation of the
|
* The Eclair (API level 5) specific implementation of the
|
||||||
@@ -42,11 +41,6 @@ public class EclairPlatformAdapter implements ApiPlatformAdapter {
|
|||||||
service.stopForeground(id != -1);
|
service.stopForeground(id != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
|
||||||
return handlerThread.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void applyPreferenceChanges(Editor editor) {
|
public void applyPreferenceChanges(Editor editor) {
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -57,10 +57,11 @@ public class ResourceUtils {
|
|||||||
public static void readBinaryFileToOutputStream(
|
public static void readBinaryFileToOutputStream(
|
||||||
Context activity, int id, OutputStream os) {
|
Context activity, int id, OutputStream os) {
|
||||||
BufferedInputStream in = null;
|
BufferedInputStream in = null;
|
||||||
|
BufferedOutputStream out = null;
|
||||||
try {
|
try {
|
||||||
in = new BufferedInputStream(
|
in = new BufferedInputStream(
|
||||||
activity.getResources().openRawResource(id));
|
activity.getResources().openRawResource(id));
|
||||||
BufferedOutputStream out = new BufferedOutputStream(os);
|
out = new BufferedOutputStream(os);
|
||||||
int b;
|
int b;
|
||||||
while ((b = in.read()) != -1) {
|
while ((b = in.read()) != -1) {
|
||||||
out.write(b);
|
out.write(b);
|
||||||
@@ -76,6 +77,14 @@ public class ResourceUtils {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (out != null) {
|
||||||
|
try {
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for dealing with content and other types of URIs.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class UriUtils {
|
||||||
|
|
||||||
|
public static boolean matchesContentUri(Uri uri, Uri baseContentUri) {
|
||||||
|
if (uri == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that scheme and authority are the same.
|
||||||
|
if (!uri.getScheme().equals(baseContentUri.getScheme()) ||
|
||||||
|
!uri.getAuthority().equals(baseContentUri.getAuthority())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks that all the base path components are in the URI.
|
||||||
|
List<String> uriPathSegments = uri.getPathSegments();
|
||||||
|
List<String> basePathSegments = baseContentUri.getPathSegments();
|
||||||
|
if (basePathSegments.size() > uriPathSegments.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < basePathSegments.size(); i++) {
|
||||||
|
if (!uriPathSegments.get(i).equals(basePathSegments.get(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFileUri(Uri uri) {
|
||||||
|
return "file".equals(uri.getScheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UriUtils() {}
|
||||||
|
}
|
||||||
@@ -153,7 +153,7 @@ public class SpreadsheetsClient extends GDataServiceClient {
|
|||||||
* @return a parser for the indicated feed
|
* @return a parser for the indicated feed
|
||||||
* @throws ParseException if the response from the server could not be parsed
|
* @throws ParseException if the response from the server could not be parsed
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("rawtypes")
|
||||||
@Override
|
@Override
|
||||||
public GDataParser getParserForFeed(
|
public GDataParser getParserForFeed(
|
||||||
Class feedEntryClass, String feedUri, String authToken)
|
Class feedEntryClass, String feedUri, String authToken)
|
||||||
@@ -227,7 +227,7 @@ public class SpreadsheetsClient extends GDataServiceClient {
|
|||||||
* Converts an HTTP exception which happened while writing to the equivalent
|
* Converts an HTTP exception which happened while writing to the equivalent
|
||||||
* local exception.
|
* local exception.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("rawtypes")
|
||||||
private void convertHttpExceptionForWrites(
|
private void convertHttpExceptionForWrites(
|
||||||
Class entryClass, String message, HttpException cause)
|
Class entryClass, String message, HttpException cause)
|
||||||
throws ParseException, IOException {
|
throws ParseException, IOException {
|
||||||
@@ -253,7 +253,7 @@ public class SpreadsheetsClient extends GDataServiceClient {
|
|||||||
/**
|
/**
|
||||||
* Parses one entry from the input stream.
|
* Parses one entry from the input stream.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("rawtypes")
|
||||||
private Entry parseEntry(Class entryClass, InputStream is)
|
private Entry parseEntry(Class entryClass, InputStream is)
|
||||||
throws ParseException, IOException {
|
throws ParseException, IOException {
|
||||||
GDataParser parser = null;
|
GDataParser parser = null;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class XmlDocsGDataParserFactory implements GDataParserFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
@Override
|
@Override
|
||||||
public GDataParser createParser(Class cls, InputStream is)
|
public GDataParser createParser(Class cls, InputStream is)
|
||||||
throws ParseException {
|
throws ParseException {
|
||||||
|
|||||||
+58
-18
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -40,7 +40,7 @@ import java.util.NoSuchElementException;
|
|||||||
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||||
|
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
|
||||||
private int defaultCursorBatchSize = 2000;
|
private int defaultCursorBatchSize = 2000;
|
||||||
|
|
||||||
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
|
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
|
||||||
@@ -199,12 +199,12 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
public final int idxLatitude;
|
public final int idxLatitude;
|
||||||
public final int idxLongitude;
|
public final int idxLongitude;
|
||||||
public final int idxAltitude;
|
public final int idxAltitude;
|
||||||
public final int idxTime;
|
public final int idxTime;
|
||||||
public final int idxBearing;
|
public final int idxBearing;
|
||||||
public final int idxAccuracy;
|
public final int idxAccuracy;
|
||||||
public final int idxSpeed;
|
public final int idxSpeed;
|
||||||
public final int idxSensor;
|
public final int idxSensor;
|
||||||
|
|
||||||
public CachedTrackColumnIndices(Cursor cursor) {
|
public CachedTrackColumnIndices(Cursor cursor) {
|
||||||
idxId = cursor.getColumnIndex(TrackPointsColumns._ID);
|
idxId = cursor.getColumnIndex(TrackPointsColumns._ID);
|
||||||
idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
|
idxLatitude = cursor.getColumnIndexOrThrow(TrackPointsColumns.LATITUDE);
|
||||||
@@ -217,11 +217,11 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
|
idxSensor = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillLocation(Cursor cursor, CachedTrackColumnIndices columnIndices,
|
private void fillLocation(Cursor cursor, CachedTrackColumnIndices columnIndices,
|
||||||
Location location) {
|
Location location) {
|
||||||
location.reset();
|
location.reset();
|
||||||
|
|
||||||
if (!cursor.isNull(columnIndices.idxLatitude)) {
|
if (!cursor.isNull(columnIndices.idxLatitude)) {
|
||||||
location.setLatitude(1. * cursor.getInt(columnIndices.idxLatitude) / 1E6);
|
location.setLatitude(1. * cursor.getInt(columnIndices.idxLatitude) / 1E6);
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fillLocation(Cursor cursor, Location location) {
|
public void fillLocation(Cursor cursor, Location location) {
|
||||||
CachedTrackColumnIndices columnIndicies = new CachedTrackColumnIndices(cursor);
|
CachedTrackColumnIndices columnIndicies = new CachedTrackColumnIndices(cursor);
|
||||||
@@ -660,6 +660,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Waypoint getFirstWaypoint(long trackId) {
|
public Waypoint getFirstWaypoint(long trackId) {
|
||||||
|
if (trackId < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
@@ -682,6 +686,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Waypoint getWaypoint(long waypointId) {
|
public Waypoint getWaypoint(long waypointId) {
|
||||||
|
if (waypointId < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
@@ -704,6 +712,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLastLocationId(long trackId) {
|
public long getLastLocationId(long trackId) {
|
||||||
|
if (trackId < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
TrackPointsColumns.CONTENT_URI,
|
TrackPointsColumns.CONTENT_URI,
|
||||||
@@ -728,6 +740,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getFirstWaypointId(long trackId) {
|
public long getFirstWaypointId(long trackId) {
|
||||||
|
if (trackId < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
@@ -752,6 +768,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLastWaypointId(long trackId) {
|
public long getLastWaypointId(long trackId) {
|
||||||
|
if (trackId < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = contentResolver.query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
@@ -815,6 +835,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Location getLocation(long id) {
|
public Location getLocation(long id) {
|
||||||
|
if (id < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String selection = TrackPointsColumns._ID + "=" + id;
|
String selection = TrackPointsColumns._ID + "=" + id;
|
||||||
return findLocationBy(selection);
|
return findLocationBy(selection);
|
||||||
}
|
}
|
||||||
@@ -822,7 +846,11 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor getLocationsCursor(long trackId, long minTrackPointId,
|
public Cursor getLocationsCursor(long trackId, long minTrackPointId,
|
||||||
int maxLocations, boolean descending) {
|
int maxLocations, boolean descending) {
|
||||||
String selection;
|
if (trackId < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String selection;
|
||||||
if (minTrackPointId >= 0) {
|
if (minTrackPointId >= 0) {
|
||||||
selection = String.format("%s=%d AND %s%s%d",
|
selection = String.format("%s=%d AND %s%s%d",
|
||||||
TrackPointsColumns.TRACKID, trackId, TrackPointsColumns._ID,
|
TrackPointsColumns.TRACKID, trackId, TrackPointsColumns._ID,
|
||||||
@@ -842,6 +870,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
int maxWaypoints) {
|
int maxWaypoints) {
|
||||||
|
if (trackId < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String selection;
|
String selection;
|
||||||
if (minWaypointId > 0) {
|
if (minWaypointId > 0) {
|
||||||
selection = String.format("%s=%d AND %s>=%d",
|
selection = String.format("%s=%d AND %s>=%d",
|
||||||
@@ -863,6 +895,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Track getTrack(long id) {
|
public Track getTrack(long id) {
|
||||||
|
if (id < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
String select = TracksColumns._ID + "=" + id;
|
String select = TracksColumns._ID + "=" + id;
|
||||||
return findTrackBy(select);
|
return findTrackBy(select);
|
||||||
}
|
}
|
||||||
@@ -929,6 +965,10 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean trackExists(long id) {
|
public boolean trackExists(long id) {
|
||||||
|
if (id < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
final String[] projection = { TracksColumns._ID };
|
final String[] projection = { TracksColumns._ID };
|
||||||
@@ -967,7 +1007,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
private Cursor cursor = getCursor(startTrackPointId);
|
private Cursor cursor = getCursor(startTrackPointId);
|
||||||
private final CachedTrackColumnIndices columnIndices = cursor != null ?
|
private final CachedTrackColumnIndices columnIndices = cursor != null ?
|
||||||
new CachedTrackColumnIndices(cursor) : null;
|
new CachedTrackColumnIndices(cursor) : null;
|
||||||
|
|
||||||
private Cursor getCursor(long trackPointId) {
|
private Cursor getCursor(long trackPointId) {
|
||||||
return getLocationsCursor(trackId, trackPointId, defaultCursorBatchSize, descending);
|
return getLocationsCursor(trackId, trackPointId, defaultCursorBatchSize, descending);
|
||||||
}
|
}
|
||||||
@@ -979,7 +1019,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
cursor = getCursor(pointId);
|
cursor = getCursor(pointId);
|
||||||
return cursor != null;
|
return cursor != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getLocationId() {
|
public long getLocationId() {
|
||||||
return lastTrackPointId;
|
return lastTrackPointId;
|
||||||
@@ -999,7 +1039,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
return cursor.getCount() == defaultCursorBatchSize &&
|
return cursor.getCount() == defaultCursorBatchSize &&
|
||||||
advanceCursorToNextBatch() && !cursor.isAfterLast();
|
advanceCursorToNextBatch() && !cursor.isAfterLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1009,11 +1049,11 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
!(cursor.moveToNext() || advanceCursorToNextBatch() || cursor.moveToNext())) {
|
!(cursor.moveToNext() || advanceCursorToNextBatch() || cursor.moveToNext())) {
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTrackPointId = cursor.getLong(columnIndices.idxId);
|
lastTrackPointId = cursor.getLong(columnIndices.idxId);
|
||||||
Location location = locationFactory.createLocation();
|
Location location = locationFactory.createLocation();
|
||||||
fillLocation(cursor, columnIndices, location);
|
fillLocation(cursor, columnIndices, location);
|
||||||
|
|
||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1031,7 +1071,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// @VisibleForTesting
|
// @VisibleForTesting
|
||||||
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
|
void setDefaultCursorBatchSize(int defaultCursorBatchSize) {
|
||||||
this.defaultCursorBatchSize = defaultCursorBatchSize;
|
this.defaultCursorBatchSize = defaultCursorBatchSize;
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ public class Track implements Parcelable {
|
|||||||
track.stats = source.readParcelable(classLoader);
|
track.stats = source.readParcelable(classLoader);
|
||||||
|
|
||||||
track.numberOfPoints = source.readInt();
|
track.numberOfPoints = source.readInt();
|
||||||
|
for (int i = 0; i < track.numberOfPoints; ++i) {
|
||||||
|
Location loc = source.readParcelable(classLoader);
|
||||||
|
track.locations.add(loc);
|
||||||
|
}
|
||||||
|
|
||||||
track.tableId = source.readString();
|
track.tableId = source.readString();
|
||||||
return track;
|
return track;
|
||||||
@@ -99,6 +103,9 @@ public class Track implements Parcelable {
|
|||||||
dest.writeParcelable(stats, 0);
|
dest.writeParcelable(stats, 0);
|
||||||
|
|
||||||
dest.writeInt(numberOfPoints);
|
dest.writeInt(numberOfPoints);
|
||||||
|
for (int i = 0; i < numberOfPoints; ++i) {
|
||||||
|
dest.writeParcelable(locations.get(i), 0);
|
||||||
|
}
|
||||||
|
|
||||||
dest.writeString(tableId);
|
dest.writeString(tableId);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-14
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2008 Google Inc.
|
* Copyright 2008 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -66,17 +66,6 @@ interface ITrackRecordingService {
|
|||||||
*/
|
*/
|
||||||
void endCurrentTrack();
|
void endCurrentTrack();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether at least one track has been or is being recorded (and was
|
|
||||||
* not deleted).
|
|
||||||
*/
|
|
||||||
boolean hasRecorded();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all the stored tracks.
|
|
||||||
*/
|
|
||||||
void deleteAllTracks();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current sensor data.
|
* The current sensor data.
|
||||||
* The data is returned as a byte array which is a binary version of a
|
* The data is returned as a byte array which is a binary version of a
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.test.ActivityInstrumentationTestCase2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class DialogManagerTest
|
|
||||||
extends ActivityInstrumentationTestCase2<MyTracks> {
|
|
||||||
|
|
||||||
public DialogManagerTest() {
|
|
||||||
super(MyTracks.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
MyTracks.clearInstance();
|
|
||||||
assertNull(MyTracks.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_onCreateChartSettings() {
|
|
||||||
Dialog d =
|
|
||||||
getActivity().onCreateDialog(DialogManager.DIALOG_CHART_SETTINGS, null);
|
|
||||||
assertNotNull(d);
|
|
||||||
assertTrue(d instanceof ChartSettingsDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_onCreateImportProgress() {
|
|
||||||
Dialog d =
|
|
||||||
getActivity().onCreateDialog(
|
|
||||||
DialogManager.DIALOG_IMPORT_PROGRESS, null);
|
|
||||||
assertNotNull(d);
|
|
||||||
assertTrue(d instanceof ProgressDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_onCreateProgress() {
|
|
||||||
Dialog d =
|
|
||||||
getActivity().onCreateDialog(DialogManager.DIALOG_PROGRESS, null);
|
|
||||||
assertNotNull(d);
|
|
||||||
assertTrue(d instanceof ProgressDialog);
|
|
||||||
ProgressDialog pd = (ProgressDialog) d;
|
|
||||||
assertEquals(100, pd.getMax());
|
|
||||||
assertEquals(10, pd.getProgress());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_onCreateSendToGoogle() {
|
|
||||||
Dialog d =
|
|
||||||
getActivity().onCreateDialog(DialogManager.DIALOG_SEND_TO_GOOGLE, null);
|
|
||||||
assertNotNull(d);
|
|
||||||
assertTrue(d instanceof SendDialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.services.ServiceUtils;
|
||||||
|
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -32,60 +34,53 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A unit test for {@link MyTracks} activity.
|
* A unit test for {@link MyTracks} activity.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
*/
|
*/
|
||||||
public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
||||||
|
private SharedPreferences sharedPreferences;
|
||||||
|
private TrackRecordingServiceConnection serviceConnection;
|
||||||
|
|
||||||
public MyTracksTest() {
|
public MyTracksTest() {
|
||||||
super(MyTracks.class);
|
super(MyTracks.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
MyTracks.clearInstance();
|
|
||||||
assertNull(MyTracks.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void tearDown() throws Exception {
|
protected void tearDown() throws Exception {
|
||||||
clearSelectedAndRecordingTracks();
|
clearSelectedAndRecordingTracks();
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
super.tearDown();
|
super.tearDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInitialization_mainAction() {
|
public void testInitialization_mainAction() {
|
||||||
// Make sure we can start MyTracks and the activity doesn't start recording.
|
// Make sure we can start MyTracks and the activity doesn't start recording.
|
||||||
assertNotNull(getActivity());
|
assertInitialized();
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
// Check if not recording.
|
||||||
assertFalse(getActivity().isRecording());
|
assertFalse(isRecording());
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
assertEquals(-1, getRecordingTrackId());
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
long selectedTrackId = getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
getActivity().getString(R.string.selected_track_key), -1);
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInitialization_viewActionWithNoData() {
|
public void testInitialization_viewActionWithNoData() {
|
||||||
// Simulate start with ACTION_VIEW intent.
|
// Simulate start with ACTION_VIEW intent.
|
||||||
Intent startIntent = new Intent();
|
Intent startIntent = new Intent();
|
||||||
startIntent.setAction(Intent.ACTION_VIEW);
|
startIntent.setAction(Intent.ACTION_VIEW);
|
||||||
setActivityIntent(startIntent);
|
setActivityIntent(startIntent);
|
||||||
|
|
||||||
assertNotNull(getActivity());
|
assertInitialized();
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
// Check if not recording.
|
||||||
assertFalse(getActivity().isRecording());
|
assertFalse(isRecording());
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
assertEquals(-1, getRecordingTrackId());
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
long selectedTrackId = getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
getActivity().getString(R.string.selected_track_key), -1);
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInitialization_viewActionWithValidData() throws Exception {
|
public void testInitialization_viewActionWithValidData() throws Exception {
|
||||||
// Simulate start with ACTION_VIEW intent.
|
// Simulate start with ACTION_VIEW intent.
|
||||||
Intent startIntent = new Intent();
|
Intent startIntent = new Intent();
|
||||||
@@ -93,24 +88,22 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
|||||||
Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx"));
|
Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx"));
|
||||||
|
|
||||||
// TODO: Add a valid GPX.
|
// TODO: Add a valid GPX.
|
||||||
|
|
||||||
startIntent.setData(uri);
|
startIntent.setData(uri);
|
||||||
setActivityIntent(startIntent);
|
setActivityIntent(startIntent);
|
||||||
|
|
||||||
assertNotNull(getActivity());
|
assertInitialized();
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
// Check if not recording.
|
||||||
assertFalse(getActivity().isRecording());
|
assertFalse(isRecording());
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
assertEquals(-1, getRecordingTrackId());
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
long selectedTrackId = getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
getActivity().getString(R.string.selected_track_key), -1);
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
|
|
||||||
// TODO: Finish this test.
|
// TODO: Finish this test.
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInitialization_viewActionWithInvalidData() throws Exception {
|
public void testInitialization_viewActionWithInvalidData() throws Exception {
|
||||||
// Simulate start with ACTION_VIEW intent.
|
// Simulate start with ACTION_VIEW intent.
|
||||||
Intent startIntent = new Intent();
|
Intent startIntent = new Intent();
|
||||||
@@ -118,66 +111,62 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
|||||||
Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx"));
|
Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx"));
|
||||||
startIntent.setData(uri);
|
startIntent.setData(uri);
|
||||||
setActivityIntent(startIntent);
|
setActivityIntent(startIntent);
|
||||||
|
|
||||||
assertNotNull(getActivity());
|
assertInitialized();
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
// Check if not recording.
|
||||||
assertFalse(getActivity().isRecording());
|
assertFalse(isRecording());
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
assertEquals(-1, getRecordingTrackId());
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
long selectedTrackId = getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
getActivity().getString(R.string.selected_track_key), -1);
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
|
|
||||||
// TODO: Finish this test.
|
// TODO: Finish this test.
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRecording_startAndStop() throws Exception {
|
public void testRecording_startAndStop() throws Exception {
|
||||||
// Make sure we can start MyTracks and the activity doesn't start recording.
|
assertInitialized();
|
||||||
assertNotNull(getActivity());
|
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
// Check if not recording.
|
||||||
clearSelectedAndRecordingTracks();
|
clearSelectedAndRecordingTracks();
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
|
|
||||||
assertFalse(getActivity().isRecording());
|
assertFalse(isRecording());
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
assertEquals(-1, getRecordingTrackId());
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
long selectedTrackId = getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
getActivity().getString(R.string.selected_track_key), -1);
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
|
|
||||||
// Start a new track.
|
// Start a new track.
|
||||||
getActivity().startRecording();
|
getActivity().startRecording();
|
||||||
|
serviceConnection.bindIfRunning();
|
||||||
long recordingTrackId = awaitRecordingStatus(5000, true);
|
long recordingTrackId = awaitRecordingStatus(5000, true);
|
||||||
assertTrue(recordingTrackId >= 0);
|
assertTrue(recordingTrackId >= 0);
|
||||||
|
|
||||||
// Wait until we are done and make sure that selectedTrack = recordingTrack.
|
// Wait until we are done and make sure that selectedTrack = recordingTrack.
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong(
|
assertEquals(recordingTrackId, getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.recording_track_key), -1));
|
getActivity().getString(R.string.recording_track_key), -1));
|
||||||
selectedTrackId = getActivity().getSharedPreferences().getLong(
|
selectedTrackId = getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
getActivity().getString(R.string.selected_track_key), -1);
|
||||||
assertEquals(recordingTrackId, selectedTrackId);
|
assertEquals(recordingTrackId, selectedTrackId);
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
|
|
||||||
// Watch for MyTracksDetails activity.
|
// Watch for MyTracksDetails activity.
|
||||||
ActivityMonitor monitor = getInstrumentation().addMonitor(
|
ActivityMonitor monitor = getInstrumentation().addMonitor(
|
||||||
TrackDetails.class.getName(), null, false);
|
TrackDetails.class.getName(), null, false);
|
||||||
|
|
||||||
// Now, stop the track and make sure that it is still selected, but
|
// Now, stop the track and make sure that it is still selected, but
|
||||||
// no longer recording.
|
// no longer recording.
|
||||||
getActivity().stopRecording();
|
getActivity().stopRecording();
|
||||||
|
|
||||||
// Check if we got back MyTracksDetails activity.
|
// Check if we got back MyTracksDetails activity.
|
||||||
Activity activity = getInstrumentation().waitForMonitor(monitor);
|
Activity activity = getInstrumentation().waitForMonitor(monitor);
|
||||||
assertTrue(activity instanceof TrackDetails);
|
assertTrue(activity instanceof TrackDetails);
|
||||||
|
|
||||||
// TODO: Update track name and other properties and test if they were
|
// TODO: Update track name and other properties and test if they were
|
||||||
// properly saved.
|
// properly saved.
|
||||||
|
|
||||||
// Simulate a click on Save button.
|
// Simulate a click on Save button.
|
||||||
Button save = (Button) activity.findViewById(R.id.trackdetails_save);
|
Button save = (Button) activity.findViewById(R.id.trackdetails_save);
|
||||||
save.performClick();
|
save.performClick();
|
||||||
@@ -185,18 +174,24 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
|||||||
// Check the remaining properties.
|
// Check the remaining properties.
|
||||||
recordingTrackId = awaitRecordingStatus(5000, false);
|
recordingTrackId = awaitRecordingStatus(5000, false);
|
||||||
assertEquals(-1, recordingTrackId);
|
assertEquals(-1, recordingTrackId);
|
||||||
assertEquals(recordingTrackId, getActivity().getRecordingTrackId());
|
assertEquals(recordingTrackId, getRecordingTrackId());
|
||||||
assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong(
|
assertEquals(recordingTrackId, getSharedPreferences().getLong(
|
||||||
getActivity().getString(R.string.recording_track_key), -1));
|
getActivity().getString(R.string.recording_track_key), -1));
|
||||||
// Make sure this is the same track as the last recording track ID.
|
// Make sure this is the same track as the last recording track ID.
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertInitialized() {
|
||||||
|
assertNotNull(getActivity());
|
||||||
|
|
||||||
|
serviceConnection = new TrackRecordingServiceConnection(getActivity(), null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until the UI thread becomes idle.
|
* Waits until the UI thread becomes idle.
|
||||||
*/
|
*/
|
||||||
private void waitForIdle() throws InterruptedException {
|
private void waitForIdle() throws InterruptedException {
|
||||||
// Note: We can't use getInstrumentation().waitForIdleSync() here.
|
// Note: We can't use getInstrumentation().waitForIdleSync() here.
|
||||||
final Object semaphore = new Object();
|
final Object semaphore = new Object();
|
||||||
synchronized (semaphore) {
|
synchronized (semaphore) {
|
||||||
final AtomicBoolean isIdle = new AtomicBoolean();
|
final AtomicBoolean isIdle = new AtomicBoolean();
|
||||||
@@ -216,20 +211,20 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears {selected,recording}TrackId in the {@link SharedPreferences}.
|
* Clears {selected,recording}TrackId in the {@link getSharedPreferences()}.
|
||||||
*/
|
*/
|
||||||
private void clearSelectedAndRecordingTracks() {
|
private void clearSelectedAndRecordingTracks() {
|
||||||
Editor editor = getActivity().getSharedPreferences().edit();
|
Editor editor = getSharedPreferences().edit();
|
||||||
editor.putLong(getActivity().getString(R.string.selected_track_key), -1);
|
editor.putLong(getActivity().getString(R.string.selected_track_key), -1);
|
||||||
editor.putLong(getActivity().getString(R.string.recording_track_key), -1);
|
editor.putLong(getActivity().getString(R.string.recording_track_key), -1);
|
||||||
|
|
||||||
editor.clear();
|
editor.clear();
|
||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits until the recording state changes to the given status.
|
* Waits until the recording state changes to the given status.
|
||||||
*
|
*
|
||||||
* @param timeout the maximum time to wait, in milliseconds.
|
* @param timeout the maximum time to wait, in milliseconds.
|
||||||
* @param isRecording the final status to await.
|
* @param isRecording the final status to await.
|
||||||
* @return the recording track ID.
|
* @return the recording track ID.
|
||||||
@@ -237,14 +232,30 @@ public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
|||||||
private long awaitRecordingStatus(long timeout, boolean isRecording)
|
private long awaitRecordingStatus(long timeout, boolean isRecording)
|
||||||
throws TimeoutException, InterruptedException {
|
throws TimeoutException, InterruptedException {
|
||||||
long startTime = System.nanoTime();
|
long startTime = System.nanoTime();
|
||||||
while (getActivity().isRecording() != isRecording) {
|
while (isRecording() != isRecording) {
|
||||||
if (System.nanoTime() - startTime > timeout * 1000000) {
|
if (System.nanoTime() - startTime > timeout * 1000000) {
|
||||||
throw new TimeoutException("Timeout while waiting for recording!");
|
throw new TimeoutException("Timeout while waiting for recording!");
|
||||||
}
|
}
|
||||||
Thread.sleep(20);
|
Thread.sleep(20);
|
||||||
}
|
}
|
||||||
waitForIdle();
|
waitForIdle();
|
||||||
assertEquals(isRecording, getActivity().isRecording());
|
assertEquals(isRecording, isRecording());
|
||||||
return getActivity().getRecordingTrackId();
|
return getRecordingTrackId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getRecordingTrackId() {
|
||||||
|
return getSharedPreferences().getLong(getActivity().getString(R.string.recording_track_key), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedPreferences getSharedPreferences() {
|
||||||
|
if (sharedPreferences == null) {
|
||||||
|
sharedPreferences = getActivity().getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
|
}
|
||||||
|
return sharedPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRecording() {
|
||||||
|
return ServiceUtils.isRecording(getActivity(),
|
||||||
|
serviceConnection.getServiceIfBound(), getSharedPreferences());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,478 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2011 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks;
|
|
||||||
|
|
||||||
import static com.google.android.testing.mocking.AndroidMock.anyInt;
|
|
||||||
import static com.google.android.testing.mocking.AndroidMock.capture;
|
|
||||||
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.isA;
|
|
||||||
import static com.google.android.testing.mocking.AndroidMock.leq;
|
|
||||||
import static com.google.android.testing.mocking.AndroidMock.same;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.TrackDataHub.ListenerDataType;
|
|
||||||
import com.google.android.apps.mytracks.TrackDataListener.ProviderState;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
|
||||||
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
import com.google.android.testing.mocking.AndroidMock;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.database.ContentObserver;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.MatrixCursor;
|
|
||||||
import android.hardware.Sensor;
|
|
||||||
import android.hardware.SensorEvent;
|
|
||||||
import android.hardware.SensorEventListener;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.location.LocationListener;
|
|
||||||
import android.provider.BaseColumns;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
import android.test.RenamingDelegatingContext;
|
|
||||||
import android.test.mock.MockContentResolver;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
|
|
||||||
import org.easymock.Capture;
|
|
||||||
import org.easymock.IAnswer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link TrackDataHub}.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class TrackDataHubTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
private static final long TRACK_ID = 42;
|
|
||||||
private MyTracksProviderUtils providerUtils;
|
|
||||||
private TrackDataHub hub;
|
|
||||||
private TrackDataSources dataSources;
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
private TrackDataListener listener1;
|
|
||||||
private TrackDataListener listener2;
|
|
||||||
private Capture<OnSharedPreferenceChangeListener> preferenceListenerCapture =
|
|
||||||
new Capture<SharedPreferences.OnSharedPreferenceChangeListener>();
|
|
||||||
private MockContext context;
|
|
||||||
private float declination;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
MockContentResolver mockContentResolver = new MockContentResolver();
|
|
||||||
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
|
||||||
getContext(), getContext(), "test.");
|
|
||||||
context = new MockContext(mockContentResolver, targetContext);
|
|
||||||
|
|
||||||
prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
|
||||||
providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class);
|
|
||||||
dataSources = AndroidMock.createNiceMock("dataSources", TrackDataSources.class);
|
|
||||||
|
|
||||||
hub = new TrackDataHub(context, dataSources, prefs, providerUtils) {
|
|
||||||
@Override
|
|
||||||
protected void runInListenerThread(Runnable runnable) {
|
|
||||||
// Run everything in the same thread.
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected float getDeclinationFor(Location location, long timestamp) {
|
|
||||||
return declination;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
listener1 = AndroidMock.createMock("listener1", TrackDataListener.class);
|
|
||||||
listener2 = AndroidMock.createMock("listener2", TrackDataListener.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() throws Exception {
|
|
||||||
AndroidMock.reset(dataSources);
|
|
||||||
|
|
||||||
// Expect everything to be unregistered.
|
|
||||||
if (preferenceListenerCapture.hasCaptured()) {
|
|
||||||
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListenerCapture.getValue());
|
|
||||||
}
|
|
||||||
dataSources.removeLocationUpdates(isA(LocationListener.class));
|
|
||||||
dataSources.unregisterSensorListener(isA(SensorEventListener.class));
|
|
||||||
dataSources.unregisterContentObserver(isA(ContentObserver.class));
|
|
||||||
AndroidMock.expectLastCall().times(3);
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources);
|
|
||||||
|
|
||||||
hub.stop();
|
|
||||||
hub.destroy();
|
|
||||||
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testTrackListen() {
|
|
||||||
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
|
||||||
Track track = new Track();
|
|
||||||
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
|
||||||
.putLong("selectedTrack", TRACK_ID).commit();
|
|
||||||
expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track);
|
|
||||||
expectStart();
|
|
||||||
dataSources.registerContentObserver(
|
|
||||||
eq(TracksColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
|
||||||
|
|
||||||
// Expect the initial loading.
|
|
||||||
// Both listeners (registered before and after start) should get the same data.
|
|
||||||
listener1.onTrackUpdated(track);
|
|
||||||
listener2.onTrackUpdated(track);
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.TRACK_UPDATES));
|
|
||||||
hub.start();
|
|
||||||
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.TRACK_UPDATES));
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources);
|
|
||||||
AndroidMock.reset(listener1, listener2, dataSources);
|
|
||||||
|
|
||||||
ContentObserver observer = observerCapture.getValue();
|
|
||||||
|
|
||||||
// Now expect an update.
|
|
||||||
listener1.onTrackUpdated(track);
|
|
||||||
listener2.onTrackUpdated(track);
|
|
||||||
|
|
||||||
AndroidMock.replay(listener1, listener2, dataSources);
|
|
||||||
|
|
||||||
observer.onChange(false);
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources);
|
|
||||||
AndroidMock.reset(listener1, listener2, dataSources);
|
|
||||||
|
|
||||||
// Unregister one, get another update.
|
|
||||||
listener2.onTrackUpdated(track);
|
|
||||||
|
|
||||||
AndroidMock.replay(listener1, listener2, dataSources);
|
|
||||||
|
|
||||||
hub.unregisterTrackDataListener(listener1);
|
|
||||||
|
|
||||||
observer.onChange(false);
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources);
|
|
||||||
AndroidMock.reset(listener1, listener2, dataSources);
|
|
||||||
|
|
||||||
// Unregister the other, expect internal unregistration
|
|
||||||
dataSources.unregisterContentObserver(observer);
|
|
||||||
|
|
||||||
AndroidMock.replay(listener1, listener2, dataSources);
|
|
||||||
|
|
||||||
hub.unregisterTrackDataListener(listener2);
|
|
||||||
observer.onChange(false);
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FixedSizeCursorAnswer implements IAnswer<Cursor> {
|
|
||||||
private final int size;
|
|
||||||
|
|
||||||
public FixedSizeCursorAnswer(int size) {
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Cursor answer() throws Throwable {
|
|
||||||
MatrixCursor cursor = new MatrixCursor(new String[] { BaseColumns._ID });
|
|
||||||
for (long i = 1; i <= size; i++) {
|
|
||||||
cursor.addRow(new Object[] { i });
|
|
||||||
}
|
|
||||||
return cursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testWaypointListen() {
|
|
||||||
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
|
||||||
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
|
||||||
.putLong("selectedTrack", TRACK_ID).commit();
|
|
||||||
|
|
||||||
Waypoint wpt1 = new Waypoint(),
|
|
||||||
wpt2 = new Waypoint(),
|
|
||||||
wpt3 = new Waypoint(),
|
|
||||||
wpt4 = new Waypoint();
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
loc.setLatitude(10.0);
|
|
||||||
loc.setLongitude(8.0);
|
|
||||||
wpt1.setLocation(loc);
|
|
||||||
wpt2.setLocation(loc);
|
|
||||||
wpt3.setLocation(loc);
|
|
||||||
wpt4.setLocation(loc);
|
|
||||||
|
|
||||||
expect(providerUtils.getWaypointsCursor(
|
|
||||||
eq(TRACK_ID), leq(0L), eq(Constants.MAX_DISPLAYED_WAYPOINTS_POINTS)))
|
|
||||||
.andStubAnswer(new FixedSizeCursorAnswer(2));
|
|
||||||
expect(providerUtils.createWaypoint(isA(Cursor.class)))
|
|
||||||
.andReturn(wpt1)
|
|
||||||
.andReturn(wpt2)
|
|
||||||
.andReturn(wpt1)
|
|
||||||
.andReturn(wpt2);
|
|
||||||
|
|
||||||
expectStart();
|
|
||||||
dataSources.registerContentObserver(
|
|
||||||
eq(WaypointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
|
||||||
|
|
||||||
// Expect the initial loading.
|
|
||||||
// Both listeners (registered before and after start) should get the same data.
|
|
||||||
listener1.clearWaypoints();
|
|
||||||
listener1.onNewWaypoint(wpt1);
|
|
||||||
listener1.onNewWaypoint(wpt2);
|
|
||||||
listener1.onNewWaypointsDone();
|
|
||||||
listener2.clearWaypoints();
|
|
||||||
listener2.onNewWaypoint(wpt1);
|
|
||||||
listener2.onNewWaypoint(wpt2);
|
|
||||||
listener2.onNewWaypointsDone();
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.WAYPOINT_UPDATES));
|
|
||||||
hub.start();
|
|
||||||
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.WAYPOINT_UPDATES));
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources, providerUtils);
|
|
||||||
AndroidMock.reset(listener1, listener2, dataSources, providerUtils);
|
|
||||||
|
|
||||||
ContentObserver observer = observerCapture.getValue();
|
|
||||||
|
|
||||||
expect(providerUtils.getWaypointsCursor(
|
|
||||||
eq(TRACK_ID), leq(0L), eq(Constants.MAX_DISPLAYED_WAYPOINTS_POINTS)))
|
|
||||||
.andStubAnswer(new FixedSizeCursorAnswer(3));
|
|
||||||
expect(providerUtils.createWaypoint(isA(Cursor.class)))
|
|
||||||
.andReturn(wpt1)
|
|
||||||
.andReturn(wpt2)
|
|
||||||
.andReturn(wpt3);
|
|
||||||
|
|
||||||
// Now expect an update.
|
|
||||||
listener1.clearWaypoints();
|
|
||||||
listener2.clearWaypoints();
|
|
||||||
listener1.onNewWaypoint(wpt1);
|
|
||||||
listener2.onNewWaypoint(wpt1);
|
|
||||||
listener1.onNewWaypoint(wpt2);
|
|
||||||
listener2.onNewWaypoint(wpt2);
|
|
||||||
listener1.onNewWaypoint(wpt3);
|
|
||||||
listener2.onNewWaypoint(wpt3);
|
|
||||||
listener1.onNewWaypointsDone();
|
|
||||||
listener2.onNewWaypointsDone();
|
|
||||||
|
|
||||||
AndroidMock.replay(listener1, listener2, dataSources, providerUtils);
|
|
||||||
|
|
||||||
observer.onChange(false);
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources, providerUtils);
|
|
||||||
AndroidMock.reset(listener1, listener2, dataSources, providerUtils);
|
|
||||||
|
|
||||||
// Unregister one, get another update.
|
|
||||||
expect(providerUtils.getWaypointsCursor(
|
|
||||||
eq(TRACK_ID), leq(0L), eq(Constants.MAX_DISPLAYED_WAYPOINTS_POINTS)))
|
|
||||||
.andStubAnswer(new FixedSizeCursorAnswer(4));
|
|
||||||
expect(providerUtils.createWaypoint(isA(Cursor.class)))
|
|
||||||
.andReturn(wpt1)
|
|
||||||
.andReturn(wpt2)
|
|
||||||
.andReturn(wpt3)
|
|
||||||
.andReturn(wpt4);
|
|
||||||
|
|
||||||
// Now expect an update.
|
|
||||||
listener2.clearWaypoints();
|
|
||||||
listener2.onNewWaypoint(wpt1);
|
|
||||||
listener2.onNewWaypoint(wpt2);
|
|
||||||
listener2.onNewWaypoint(wpt3);
|
|
||||||
listener2.onNewWaypoint(wpt4);
|
|
||||||
listener2.onNewWaypointsDone();
|
|
||||||
|
|
||||||
AndroidMock.replay(listener1, listener2, dataSources, providerUtils);
|
|
||||||
|
|
||||||
hub.unregisterTrackDataListener(listener1);
|
|
||||||
|
|
||||||
observer.onChange(false);
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources, providerUtils);
|
|
||||||
AndroidMock.reset(listener1, listener2, dataSources, providerUtils);
|
|
||||||
|
|
||||||
// Unregister the other, expect internal unregistration
|
|
||||||
dataSources.unregisterContentObserver(observer);
|
|
||||||
|
|
||||||
AndroidMock.replay(listener1, listener2, dataSources, providerUtils);
|
|
||||||
|
|
||||||
hub.unregisterTrackDataListener(listener2);
|
|
||||||
observer.onChange(false);
|
|
||||||
|
|
||||||
AndroidMock.verify(listener1, listener2, dataSources, providerUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPointsListen() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPointsListen_resample() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLocationListen() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCompassListen() throws Exception {
|
|
||||||
Sensor compass = newSensor();
|
|
||||||
expect(dataSources.getSensor(Sensor.TYPE_ORIENTATION)).andReturn(compass);
|
|
||||||
Capture<SensorEventListener> listenerCapture = new Capture<SensorEventListener>();
|
|
||||||
dataSources.registerSensorListener(capture(listenerCapture), same(compass), anyInt());
|
|
||||||
|
|
||||||
Capture<LocationListener> locationListenerCapture = new Capture<LocationListener>();
|
|
||||||
dataSources.requestLocationUpdates(capture(locationListenerCapture));
|
|
||||||
|
|
||||||
SensorEvent event = newSensorEvent();
|
|
||||||
event.sensor = compass;
|
|
||||||
|
|
||||||
// First, get a dummy heading update.
|
|
||||||
listener1.onCurrentHeadingChanged(0.0);
|
|
||||||
|
|
||||||
// Then, get a heading update without a known location (thus can't calculate declination).
|
|
||||||
listener1.onCurrentHeadingChanged(42.0f);
|
|
||||||
|
|
||||||
// Also expect location updates which are not relevant to us.
|
|
||||||
listener1.onProviderStateChange(isA(ProviderState.class));
|
|
||||||
AndroidMock.expectLastCall().anyTimes();
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1);
|
|
||||||
|
|
||||||
hub.registerTrackDataListener(listener1,
|
|
||||||
EnumSet.of(ListenerDataType.COMPASS_UPDATES, ListenerDataType.LOCATION_UPDATES));
|
|
||||||
hub.start();
|
|
||||||
|
|
||||||
SensorEventListener sensorListener = listenerCapture.getValue();
|
|
||||||
LocationListener locationListener = locationListenerCapture.getValue();
|
|
||||||
event.values[0] = 42.0f;
|
|
||||||
sensorListener.onSensorChanged(event);
|
|
||||||
|
|
||||||
AndroidMock.verify(dataSources, providerUtils, listener1);
|
|
||||||
AndroidMock.reset(dataSources, providerUtils, listener1);
|
|
||||||
|
|
||||||
// Expect the heading update to include declination.
|
|
||||||
listener1.onCurrentHeadingChanged(52.0);
|
|
||||||
|
|
||||||
// Also expect location updates which are not relevant to us.
|
|
||||||
listener1.onProviderStateChange(isA(ProviderState.class));
|
|
||||||
AndroidMock.expectLastCall().anyTimes();
|
|
||||||
listener1.onCurrentLocationChanged(isA(Location.class));
|
|
||||||
AndroidMock.expectLastCall().anyTimes();
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1);
|
|
||||||
|
|
||||||
// Now try injecting a location update, triggering a declination update.
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(10.0);
|
|
||||||
location.setLongitude(20.0);
|
|
||||||
location.setAltitude(30.0);
|
|
||||||
declination = 10.0f;
|
|
||||||
locationListener.onLocationChanged(location);
|
|
||||||
sensorListener.onSensorChanged(event);
|
|
||||||
|
|
||||||
AndroidMock.verify(dataSources, providerUtils, listener1);
|
|
||||||
AndroidMock.reset(dataSources, providerUtils, listener1);
|
|
||||||
|
|
||||||
listener1.onCurrentHeadingChanged(52.0);
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1);
|
|
||||||
|
|
||||||
// Now try changing the known declination - it should still return the old declination, since
|
|
||||||
// updates only happen sparsely.
|
|
||||||
declination = 20.0f;
|
|
||||||
sensorListener.onSensorChanged(event);
|
|
||||||
|
|
||||||
AndroidMock.verify(dataSources, providerUtils, listener1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Sensor newSensor() throws Exception {
|
|
||||||
Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
return constructor.newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SensorEvent newSensorEvent() throws Exception {
|
|
||||||
Constructor<SensorEvent> constructor = SensorEvent.class.getDeclaredConstructor(int.class);
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
return constructor.newInstance(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDisplayPreferencesListen() throws Exception {
|
|
||||||
String metricUnitsKey = context.getString(R.string.metric_units_key);
|
|
||||||
String speedKey = context.getString(R.string.report_speed_key);
|
|
||||||
|
|
||||||
prefs.edit()
|
|
||||||
.putBoolean(metricUnitsKey, true)
|
|
||||||
.putBoolean(speedKey, true)
|
|
||||||
.commit();
|
|
||||||
|
|
||||||
Capture<OnSharedPreferenceChangeListener> listenerCapture =
|
|
||||||
new Capture<OnSharedPreferenceChangeListener>();
|
|
||||||
dataSources.registerOnSharedPreferenceChangeListener(capture(listenerCapture));
|
|
||||||
|
|
||||||
expect(listener1.onUnitsChanged(true)).andReturn(false);
|
|
||||||
expect(listener2.onUnitsChanged(true)).andReturn(false);
|
|
||||||
expect(listener1.onReportSpeedChanged(true)).andReturn(false);
|
|
||||||
expect(listener2.onReportSpeedChanged(true)).andReturn(false);
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.DISPLAY_PREFERENCES));
|
|
||||||
hub.start();
|
|
||||||
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.DISPLAY_PREFERENCES));
|
|
||||||
|
|
||||||
AndroidMock.verify(dataSources, providerUtils, listener1, listener2);
|
|
||||||
AndroidMock.reset(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
expect(listener1.onReportSpeedChanged(false)).andReturn(false);
|
|
||||||
expect(listener2.onReportSpeedChanged(false)).andReturn(false);
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
prefs.edit()
|
|
||||||
.putBoolean(speedKey, false)
|
|
||||||
.commit();
|
|
||||||
OnSharedPreferenceChangeListener listener = listenerCapture.getValue();
|
|
||||||
listener.onSharedPreferenceChanged(prefs, speedKey);
|
|
||||||
|
|
||||||
AndroidMock.verify(dataSources, providerUtils, listener1, listener2);
|
|
||||||
AndroidMock.reset(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
expect(listener1.onUnitsChanged(false)).andReturn(false);
|
|
||||||
expect(listener2.onUnitsChanged(false)).andReturn(false);
|
|
||||||
|
|
||||||
AndroidMock.replay(dataSources, providerUtils, listener1, listener2);
|
|
||||||
|
|
||||||
prefs.edit()
|
|
||||||
.putBoolean(metricUnitsKey, false)
|
|
||||||
.commit();
|
|
||||||
listener.onSharedPreferenceChanged(prefs, metricUnitsKey);
|
|
||||||
|
|
||||||
AndroidMock.verify(dataSources, providerUtils, listener1, listener2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testFullListener() {
|
|
||||||
// TODO: test loading a track, getting updates, loading another, unloading
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expectStart() {
|
|
||||||
dataSources.registerOnSharedPreferenceChangeListener(capture(preferenceListenerCapture));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,875 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.anyInt;
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.capture;
|
||||||
|
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.isA;
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.leq;
|
||||||
|
import static com.google.android.testing.mocking.AndroidMock.same;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.Constants;
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationFactory;
|
||||||
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackDataListener.ProviderState;
|
||||||
|
import com.google.android.apps.mytracks.services.TrackRecordingServiceTest.MockContext;
|
||||||
|
import com.google.android.maps.mytracks.R;
|
||||||
|
import com.google.android.testing.mocking.AndroidMock;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
|
import android.database.ContentObserver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
import android.test.RenamingDelegatingContext;
|
||||||
|
import android.test.mock.MockContentResolver;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.easymock.Capture;
|
||||||
|
import org.easymock.IAnswer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link TrackDataHub}.
|
||||||
|
*
|
||||||
|
* @author Rodrigo Damazio
|
||||||
|
*/
|
||||||
|
public class TrackDataHubTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
private static final long TRACK_ID = 42L;
|
||||||
|
private static final int TARGET_POINTS = 50;
|
||||||
|
|
||||||
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
private TrackDataHub hub;
|
||||||
|
private TrackDataListeners listeners;
|
||||||
|
private DataSourcesWrapper dataSources;
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private TrackDataListener listener1;
|
||||||
|
private TrackDataListener listener2;
|
||||||
|
private Capture<OnSharedPreferenceChangeListener> preferenceListenerCapture =
|
||||||
|
new Capture<SharedPreferences.OnSharedPreferenceChangeListener>();
|
||||||
|
private MockContext context;
|
||||||
|
private float declination;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
MockContentResolver mockContentResolver = new MockContentResolver();
|
||||||
|
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
||||||
|
getContext(), getContext(), "test.");
|
||||||
|
context = new MockContext(mockContentResolver, targetContext);
|
||||||
|
|
||||||
|
prefs = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
|
||||||
|
providerUtils = AndroidMock.createMock("providerUtils", MyTracksProviderUtils.class);
|
||||||
|
dataSources = AndroidMock.createNiceMock("dataSources", DataSourcesWrapper.class);
|
||||||
|
|
||||||
|
listeners = new TrackDataListeners();
|
||||||
|
hub = new TrackDataHub(context, listeners, prefs, providerUtils, TARGET_POINTS) {
|
||||||
|
@Override
|
||||||
|
protected DataSourcesWrapper newDataSources() {
|
||||||
|
return dataSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runInListenerThread(Runnable runnable) {
|
||||||
|
// Run everything in the same thread.
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected float getDeclinationFor(Location location, long timestamp) {
|
||||||
|
return declination;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
listener1 = AndroidMock.createStrictMock("listener1", TrackDataListener.class);
|
||||||
|
listener2 = AndroidMock.createStrictMock("listener2", TrackDataListener.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tearDown() throws Exception {
|
||||||
|
AndroidMock.reset(dataSources);
|
||||||
|
|
||||||
|
// Expect everything to be unregistered.
|
||||||
|
if (preferenceListenerCapture.hasCaptured()) {
|
||||||
|
dataSources.unregisterOnSharedPreferenceChangeListener(preferenceListenerCapture.getValue());
|
||||||
|
}
|
||||||
|
dataSources.removeLocationUpdates(isA(LocationListener.class));
|
||||||
|
dataSources.unregisterSensorListener(isA(SensorEventListener.class));
|
||||||
|
dataSources.unregisterContentObserver(isA(ContentObserver.class));
|
||||||
|
AndroidMock.expectLastCall().times(3);
|
||||||
|
|
||||||
|
AndroidMock.replay(dataSources);
|
||||||
|
|
||||||
|
hub.stop();
|
||||||
|
hub = null;
|
||||||
|
|
||||||
|
super.tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTrackListen() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
Track track = new Track();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track);
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TracksColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
// Expect the initial loading.
|
||||||
|
// Both listeners (registered before and after start) should get the same data.
|
||||||
|
listener1.onTrackUpdated(track);
|
||||||
|
listener2.onTrackUpdated(track);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.TRACK_UPDATES));
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.TRACK_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
ContentObserver observer = observerCapture.getValue();
|
||||||
|
expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track);
|
||||||
|
|
||||||
|
// Now expect an update.
|
||||||
|
listener1.onTrackUpdated(track);
|
||||||
|
listener2.onTrackUpdated(track);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister one, get another update.
|
||||||
|
expect(providerUtils.getTrack(TRACK_ID)).andStubReturn(track);
|
||||||
|
listener2.onTrackUpdated(track);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener1);
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister the other, expect internal unregistration
|
||||||
|
dataSources.unregisterContentObserver(observer);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener2);
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FixedSizeCursorAnswer implements IAnswer<Cursor> {
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
public FixedSizeCursorAnswer(int size) {
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor answer() throws Throwable {
|
||||||
|
MatrixCursor cursor = new MatrixCursor(new String[] { BaseColumns._ID });
|
||||||
|
for (long i = 1; i <= size; i++) {
|
||||||
|
cursor.addRow(new Object[] { i });
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FixedSizeLocationIterator implements LocationIterator {
|
||||||
|
private final long startId;
|
||||||
|
private final Location[] locs;
|
||||||
|
private final Set<Integer> splitIndexSet = new HashSet<Integer>();
|
||||||
|
private int currentIdx = -1;
|
||||||
|
|
||||||
|
public FixedSizeLocationIterator(long startId, int size) {
|
||||||
|
this(startId, size, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedSizeLocationIterator(long startId, int size, int... splitIndices) {
|
||||||
|
this.startId = startId;
|
||||||
|
this.locs = new Location[size];
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
Location loc = new Location("gps");
|
||||||
|
loc.setLatitude(-15.0 + i / 1000.0);
|
||||||
|
loc.setLongitude(37 + i / 1000.0);
|
||||||
|
loc.setAltitude(i);
|
||||||
|
|
||||||
|
locs[i] = loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (splitIndices != null) {
|
||||||
|
for (int splitIdx : splitIndices) {
|
||||||
|
splitIndexSet.add(splitIdx);
|
||||||
|
|
||||||
|
Location splitLoc = locs[splitIdx];
|
||||||
|
splitLoc.setLatitude(100.0);
|
||||||
|
splitLoc.setLongitude(200.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectLocationsDelivered(TrackDataListener listener) {
|
||||||
|
for (int i = 0; i < locs.length; i++) {
|
||||||
|
if (splitIndexSet.contains(i)) {
|
||||||
|
listener.onSegmentSplit();
|
||||||
|
} else {
|
||||||
|
listener.onNewTrackPoint(locs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectSampledLocationsDelivered(
|
||||||
|
TrackDataListener listener, int sampleFrequency, boolean includeSampledOut) {
|
||||||
|
for (int i = 0; i < locs.length; i++) {
|
||||||
|
if (splitIndexSet.contains(i)) {
|
||||||
|
listener.onSegmentSplit();
|
||||||
|
} else if (i % sampleFrequency == 0) {
|
||||||
|
listener.onNewTrackPoint(locs[i]);
|
||||||
|
} else if (includeSampledOut) {
|
||||||
|
listener.onSampledOutTrackPoint(locs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return currentIdx < (locs.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location next() {
|
||||||
|
currentIdx++;
|
||||||
|
return locs[currentIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLocationId() {
|
||||||
|
return startId + currentIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWaypointListen() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
|
||||||
|
Waypoint wpt1 = new Waypoint(),
|
||||||
|
wpt2 = new Waypoint(),
|
||||||
|
wpt3 = new Waypoint(),
|
||||||
|
wpt4 = new Waypoint();
|
||||||
|
Location loc = new Location("gps");
|
||||||
|
loc.setLatitude(10.0);
|
||||||
|
loc.setLongitude(8.0);
|
||||||
|
wpt1.setLocation(loc);
|
||||||
|
wpt2.setLocation(loc);
|
||||||
|
wpt3.setLocation(loc);
|
||||||
|
wpt4.setLocation(loc);
|
||||||
|
|
||||||
|
expect(providerUtils.getWaypointsCursor(
|
||||||
|
eq(TRACK_ID), leq(0L), eq(Constants.MAX_DISPLAYED_WAYPOINTS_POINTS)))
|
||||||
|
.andStubAnswer(new FixedSizeCursorAnswer(2));
|
||||||
|
expect(providerUtils.createWaypoint(isA(Cursor.class)))
|
||||||
|
.andReturn(wpt1)
|
||||||
|
.andReturn(wpt2)
|
||||||
|
.andReturn(wpt1)
|
||||||
|
.andReturn(wpt2);
|
||||||
|
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(WaypointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
// Expect the initial loading.
|
||||||
|
// Both listeners (registered before and after start) should get the same data.
|
||||||
|
listener1.clearWaypoints();
|
||||||
|
listener1.onNewWaypoint(wpt1);
|
||||||
|
listener1.onNewWaypoint(wpt2);
|
||||||
|
listener1.onNewWaypointsDone();
|
||||||
|
listener2.clearWaypoints();
|
||||||
|
listener2.onNewWaypoint(wpt1);
|
||||||
|
listener2.onNewWaypoint(wpt2);
|
||||||
|
listener2.onNewWaypointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.WAYPOINT_UPDATES));
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.WAYPOINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
ContentObserver observer = observerCapture.getValue();
|
||||||
|
|
||||||
|
expect(providerUtils.getWaypointsCursor(
|
||||||
|
eq(TRACK_ID), leq(0L), eq(Constants.MAX_DISPLAYED_WAYPOINTS_POINTS)))
|
||||||
|
.andStubAnswer(new FixedSizeCursorAnswer(3));
|
||||||
|
expect(providerUtils.createWaypoint(isA(Cursor.class)))
|
||||||
|
.andReturn(wpt1)
|
||||||
|
.andReturn(wpt2)
|
||||||
|
.andReturn(wpt3);
|
||||||
|
|
||||||
|
// Now expect an update.
|
||||||
|
listener1.clearWaypoints();
|
||||||
|
listener2.clearWaypoints();
|
||||||
|
listener1.onNewWaypoint(wpt1);
|
||||||
|
listener2.onNewWaypoint(wpt1);
|
||||||
|
listener1.onNewWaypoint(wpt2);
|
||||||
|
listener2.onNewWaypoint(wpt2);
|
||||||
|
listener1.onNewWaypoint(wpt3);
|
||||||
|
listener2.onNewWaypoint(wpt3);
|
||||||
|
listener1.onNewWaypointsDone();
|
||||||
|
listener2.onNewWaypointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister one, get another update.
|
||||||
|
expect(providerUtils.getWaypointsCursor(
|
||||||
|
eq(TRACK_ID), leq(0L), eq(Constants.MAX_DISPLAYED_WAYPOINTS_POINTS)))
|
||||||
|
.andStubAnswer(new FixedSizeCursorAnswer(4));
|
||||||
|
expect(providerUtils.createWaypoint(isA(Cursor.class)))
|
||||||
|
.andReturn(wpt1)
|
||||||
|
.andReturn(wpt2)
|
||||||
|
.andReturn(wpt3)
|
||||||
|
.andReturn(wpt4);
|
||||||
|
|
||||||
|
// Now expect an update.
|
||||||
|
listener2.clearWaypoints();
|
||||||
|
listener2.onNewWaypoint(wpt1);
|
||||||
|
listener2.onNewWaypoint(wpt2);
|
||||||
|
listener2.onNewWaypoint(wpt3);
|
||||||
|
listener2.onNewWaypoint(wpt4);
|
||||||
|
listener2.onNewWaypointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener1);
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister the other, expect internal unregistration
|
||||||
|
dataSources.unregisterContentObserver(observer);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener2);
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsListen() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(10L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Register a second listener - it will get the same points as the previous one
|
||||||
|
locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(10L);
|
||||||
|
|
||||||
|
listener2.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener2);
|
||||||
|
listener2.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Deliver more points - should go to both listeners, without clearing.
|
||||||
|
ContentObserver observer = observerCapture.getValue();
|
||||||
|
|
||||||
|
locationIterator = new FixedSizeLocationIterator(11, 10, 1);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(20L);
|
||||||
|
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
locationIterator.expectLocationsDelivered(listener2);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
listener2.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister listener1, switch tracks to ensure data is cleared/reloaded.
|
||||||
|
locationIterator = new FixedSizeLocationIterator(101, 10);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID + 1)).andReturn(110L);
|
||||||
|
|
||||||
|
listener2.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener2);
|
||||||
|
listener2.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener1);
|
||||||
|
hub.loadTrack(TRACK_ID + 1);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsListen_beforeStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsListen_reRegister() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(10L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister
|
||||||
|
ContentObserver observer = observerCapture.getValue();
|
||||||
|
dataSources.unregisterContentObserver(observer);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener1);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Register again, except only points since unregistered.
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
locationIterator = new FixedSizeLocationIterator(11, 10);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(11L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(20L);
|
||||||
|
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Deliver more points - should still be incremental.
|
||||||
|
locationIterator = new FixedSizeLocationIterator(21, 10, 1);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(21L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(30L);
|
||||||
|
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsListen_reRegisterTrackChanged() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 10, 5);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(10L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Unregister
|
||||||
|
ContentObserver observer = observerCapture.getValue();
|
||||||
|
dataSources.unregisterContentObserver(observer);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.unregisterTrackDataListener(listener1);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Register again after track changed, expect all points.
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
locationIterator = new FixedSizeLocationIterator(1, 10);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID + 1), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID + 1)).andReturn(10L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.loadTrack(TRACK_ID + 1);
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsListen_largeTrackSampling() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 200, 4, 25, 71, 120);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(200L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
listener2.clearTrackPoints();
|
||||||
|
locationIterator.expectSampledLocationsDelivered(listener1, 4, false);
|
||||||
|
locationIterator.expectSampledLocationsDelivered(listener2, 4, true);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
listener2.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.registerTrackDataListener(listener1,
|
||||||
|
EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
hub.registerTrackDataListener(listener2,
|
||||||
|
EnumSet.of(ListenerDataType.POINT_UPDATES, ListenerDataType.SAMPLED_OUT_POINT_UPDATES));
|
||||||
|
hub.start();
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsListen_resampling() {
|
||||||
|
Capture<ContentObserver> observerCapture = new Capture<ContentObserver>();
|
||||||
|
prefs.edit().putLong("recordingTrack", TRACK_ID)
|
||||||
|
.putLong("selectedTrack", TRACK_ID).commit();
|
||||||
|
|
||||||
|
expectStart();
|
||||||
|
dataSources.registerContentObserver(
|
||||||
|
eq(TrackPointsColumns.CONTENT_URI), eq(false), capture(observerCapture));
|
||||||
|
|
||||||
|
// Deliver 30 points (no sampling happens)
|
||||||
|
FixedSizeLocationIterator locationIterator = new FixedSizeLocationIterator(1, 30, 5);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(30L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
locationIterator.expectLocationsDelivered(listener1);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.POINT_UPDATES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Now deliver 30 more (incrementally sampled)
|
||||||
|
ContentObserver observer = observerCapture.getValue();
|
||||||
|
locationIterator = new FixedSizeLocationIterator(31, 30);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(31L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(60L);
|
||||||
|
|
||||||
|
locationIterator.expectSampledLocationsDelivered(listener1, 2, false);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Now another 30 (triggers resampling)
|
||||||
|
locationIterator = new FixedSizeLocationIterator(1, 90);
|
||||||
|
expect(providerUtils.getLocationIterator(
|
||||||
|
eq(TRACK_ID), eq(0L), eq(false), isA(LocationFactory.class)))
|
||||||
|
.andReturn(locationIterator);
|
||||||
|
expect(providerUtils.getLastLocationId(TRACK_ID)).andReturn(90L);
|
||||||
|
|
||||||
|
listener1.clearTrackPoints();
|
||||||
|
locationIterator.expectSampledLocationsDelivered(listener1, 2, false);
|
||||||
|
listener1.onNewTrackPointsDone();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
observer.onChange(false);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocationListen() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCompassListen() throws Exception {
|
||||||
|
AndroidMock.resetToDefault(listener1);
|
||||||
|
|
||||||
|
Sensor compass = newSensor();
|
||||||
|
expect(dataSources.getSensor(Sensor.TYPE_ORIENTATION)).andReturn(compass);
|
||||||
|
Capture<SensorEventListener> listenerCapture = new Capture<SensorEventListener>();
|
||||||
|
dataSources.registerSensorListener(capture(listenerCapture), same(compass), anyInt());
|
||||||
|
|
||||||
|
Capture<LocationListener> locationListenerCapture = new Capture<LocationListener>();
|
||||||
|
dataSources.requestLocationUpdates(capture(locationListenerCapture));
|
||||||
|
|
||||||
|
SensorEvent event = newSensorEvent();
|
||||||
|
event.sensor = compass;
|
||||||
|
|
||||||
|
// First, get a dummy heading update.
|
||||||
|
listener1.onCurrentHeadingChanged(0.0);
|
||||||
|
|
||||||
|
// Then, get a heading update without a known location (thus can't calculate declination).
|
||||||
|
listener1.onCurrentHeadingChanged(42.0f);
|
||||||
|
|
||||||
|
// Also expect location updates which are not relevant to us.
|
||||||
|
listener1.onProviderStateChange(isA(ProviderState.class));
|
||||||
|
AndroidMock.expectLastCall().anyTimes();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.registerTrackDataListener(listener1,
|
||||||
|
EnumSet.of(ListenerDataType.COMPASS_UPDATES, ListenerDataType.LOCATION_UPDATES));
|
||||||
|
hub.start();
|
||||||
|
|
||||||
|
SensorEventListener sensorListener = listenerCapture.getValue();
|
||||||
|
LocationListener locationListener = locationListenerCapture.getValue();
|
||||||
|
event.values[0] = 42.0f;
|
||||||
|
sensorListener.onSensorChanged(event);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
// Expect the heading update to include declination.
|
||||||
|
listener1.onCurrentHeadingChanged(52.0);
|
||||||
|
|
||||||
|
// Also expect location updates which are not relevant to us.
|
||||||
|
listener1.onProviderStateChange(isA(ProviderState.class));
|
||||||
|
AndroidMock.expectLastCall().anyTimes();
|
||||||
|
listener1.onCurrentLocationChanged(isA(Location.class));
|
||||||
|
AndroidMock.expectLastCall().anyTimes();
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
// Now try injecting a location update, triggering a declination update.
|
||||||
|
Location location = new Location("gps");
|
||||||
|
location.setLatitude(10.0);
|
||||||
|
location.setLongitude(20.0);
|
||||||
|
location.setAltitude(30.0);
|
||||||
|
declination = 10.0f;
|
||||||
|
locationListener.onLocationChanged(location);
|
||||||
|
sensorListener.onSensorChanged(event);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
listener1.onCurrentHeadingChanged(52.0);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
// Now try changing the known declination - it should still return the old declination, since
|
||||||
|
// updates only happen sparsely.
|
||||||
|
declination = 20.0f;
|
||||||
|
sensorListener.onSensorChanged(event);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sensor newSensor() throws Exception {
|
||||||
|
Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor();
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return constructor.newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SensorEvent newSensorEvent() throws Exception {
|
||||||
|
Constructor<SensorEvent> constructor = SensorEvent.class.getDeclaredConstructor(int.class);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return constructor.newInstance(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDisplayPreferencesListen() throws Exception {
|
||||||
|
String metricUnitsKey = context.getString(R.string.metric_units_key);
|
||||||
|
String speedKey = context.getString(R.string.report_speed_key);
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean(metricUnitsKey, true)
|
||||||
|
.putBoolean(speedKey, true)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
Capture<OnSharedPreferenceChangeListener> listenerCapture =
|
||||||
|
new Capture<OnSharedPreferenceChangeListener>();
|
||||||
|
dataSources.registerOnSharedPreferenceChangeListener(capture(listenerCapture));
|
||||||
|
|
||||||
|
expect(listener1.onUnitsChanged(true)).andReturn(false);
|
||||||
|
expect(listener2.onUnitsChanged(true)).andReturn(false);
|
||||||
|
expect(listener1.onReportSpeedChanged(true)).andReturn(false);
|
||||||
|
expect(listener2.onReportSpeedChanged(true)).andReturn(false);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
hub.registerTrackDataListener(listener1, EnumSet.of(ListenerDataType.DISPLAY_PREFERENCES));
|
||||||
|
hub.start();
|
||||||
|
hub.registerTrackDataListener(listener2, EnumSet.of(ListenerDataType.DISPLAY_PREFERENCES));
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
|
||||||
|
expect(listener1.onReportSpeedChanged(false)).andReturn(false);
|
||||||
|
expect(listener2.onReportSpeedChanged(false)).andReturn(false);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean(speedKey, false)
|
||||||
|
.commit();
|
||||||
|
OnSharedPreferenceChangeListener listener = listenerCapture.getValue();
|
||||||
|
listener.onSharedPreferenceChanged(prefs, speedKey);
|
||||||
|
|
||||||
|
AndroidMock.verify(dataSources, providerUtils, listener1, listener2);
|
||||||
|
AndroidMock.reset(dataSources, providerUtils, listener1, listener2);
|
||||||
|
|
||||||
|
expect(listener1.onUnitsChanged(false)).andReturn(false);
|
||||||
|
expect(listener2.onUnitsChanged(false)).andReturn(false);
|
||||||
|
|
||||||
|
replay();
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean(metricUnitsKey, false)
|
||||||
|
.commit();
|
||||||
|
listener.onSharedPreferenceChanged(prefs, metricUnitsKey);
|
||||||
|
|
||||||
|
verifyAndReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectStart() {
|
||||||
|
dataSources.registerOnSharedPreferenceChangeListener(capture(preferenceListenerCapture));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replay() {
|
||||||
|
AndroidMock.replay(dataSources, providerUtils, listener1, listener2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyAndReset() {
|
||||||
|
AndroidMock.verify(listener1, listener2, dataSources, providerUtils);
|
||||||
|
AndroidMock.reset(listener1, listener2, dataSources, providerUtils);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -110,9 +110,6 @@ public class DatabaseDumperTest extends TestCase {
|
|||||||
// Verify the number of rows
|
// Verify the number of rows
|
||||||
assertEquals(FAKE_DATA.length, reader.readInt());
|
assertEquals(FAKE_DATA.length, reader.readInt());
|
||||||
|
|
||||||
// Verify the row contents
|
|
||||||
byte[] blob = new byte[4];
|
|
||||||
|
|
||||||
// Row 0 -- everything populated
|
// Row 0 -- everything populated
|
||||||
assertEquals(EXPECTED_FIELD_SETS[0], reader.readLong());
|
assertEquals(EXPECTED_FIELD_SETS[0], reader.readLong());
|
||||||
assertEquals(42, reader.readInt());
|
assertEquals(42, reader.readInt());
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
package com.google.android.apps.mytracks.io.docs;
|
package com.google.android.apps.mytracks.io.docs;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
|
import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
|
||||||
|
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.test.mock.MockContext;
|
import android.test.mock.MockContext;
|
||||||
@@ -43,7 +44,7 @@ public class DocsHelperTest extends TestCase {
|
|||||||
// touch AndroidMock at all to get this failure.
|
// touch AndroidMock at all to get this failure.
|
||||||
// The bug is filed with Android Mock as
|
// The bug is filed with Android Mock as
|
||||||
// http://code.google.com/p/android-mock/issues/detail?id=3
|
// http://code.google.com/p/android-mock/issues/detail?id=3
|
||||||
private class MockGDataWrapper extends GDataWrapper {
|
private class MockGDataWrapper extends GDataWrapper<GDataServiceClient> {
|
||||||
private final boolean returnValue;
|
private final boolean returnValue;
|
||||||
|
|
||||||
MockGDataWrapper(boolean returnValue) {
|
MockGDataWrapper(boolean returnValue) {
|
||||||
@@ -51,7 +52,7 @@ public class DocsHelperTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean runQuery(QueryFunction queryFunction) {
|
public boolean runQuery(QueryFunction<GDataServiceClient> queryFunction) {
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-9
@@ -13,10 +13,7 @@
|
|||||||
* 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;
|
package com.google.android.apps.mytracks.io.file;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.io.file.MockTrackWriter;
|
|
||||||
import com.google.android.apps.mytracks.io.file.TrackWriter;
|
|
||||||
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.test.ActivityInstrumentationTestCase2;
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
@@ -29,9 +26,9 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
*
|
*
|
||||||
* @author Matthew Simmons
|
* @author Matthew Simmons
|
||||||
*/
|
*/
|
||||||
public class WriteProgressControllerTest extends ActivityInstrumentationTestCase2<MyTracks> {
|
public class WriteProgressControllerTest extends ActivityInstrumentationTestCase2<SaveActivity> {
|
||||||
public WriteProgressControllerTest() {
|
public WriteProgressControllerTest() {
|
||||||
super(MyTracks.class);
|
super(SaveActivity.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertProgress(ProgressDialog dialog, int expectedProgress,
|
private static void assertProgress(ProgressDialog dialog, int expectedProgress,
|
||||||
@@ -57,15 +54,15 @@ public class WriteProgressControllerTest extends ActivityInstrumentationTestCase
|
|||||||
};
|
};
|
||||||
|
|
||||||
WriteProgressController controller = new WriteProgressController(
|
WriteProgressController controller = new WriteProgressController(
|
||||||
getActivity(), mockWriter);
|
getActivity(), mockWriter, SaveActivity.PROGRESS_DIALOG);
|
||||||
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
|
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(TrackWriter writer) {
|
public void onComplete() {
|
||||||
controllerDoneRef.set(true);
|
controllerDoneRef.set(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogRef.set(controller.getDialog());
|
dialogRef.set(controller.createProgressDialog());
|
||||||
|
|
||||||
controller.startWrite();
|
controller.startWrite();
|
||||||
|
|
||||||
+6
-7
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io.sendtogoogle;
|
package com.google.android.apps.mytracks.io.sendtogoogle;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracks;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
@@ -33,9 +32,9 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author Matthew Simmons
|
* @author Matthew Simmons
|
||||||
*/
|
*/
|
||||||
public class ResultDialogFactoryTest extends ActivityInstrumentationTestCase2<MyTracks> {
|
public class ResultDialogFactoryTest extends ActivityInstrumentationTestCase2<SendActivity> {
|
||||||
public ResultDialogFactoryTest() {
|
public ResultDialogFactoryTest() {
|
||||||
super(MyTracks.class);
|
super(SendActivity.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SendResult> makeResults(SendResult... results) {
|
private List<SendResult> makeResults(SendResult... results) {
|
||||||
@@ -55,7 +54,7 @@ public class ResultDialogFactoryTest extends ActivityInstrumentationTestCase2<My
|
|||||||
List<SendResult> results = makeResults(new SendResult(SendType.MYMAPS, true),
|
List<SendResult> results = makeResults(new SendResult(SendType.MYMAPS, true),
|
||||||
new SendResult(SendType.DOCS, true));
|
new SendResult(SendType.DOCS, true));
|
||||||
|
|
||||||
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results, clickListener, null);
|
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results, clickListener, null, null);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
ListView listView = (ListView) dialog.findViewById(R.id.send_to_google_result_list);
|
ListView listView = (ListView) dialog.findViewById(R.id.send_to_google_result_list);
|
||||||
@@ -87,7 +86,7 @@ public class ResultDialogFactoryTest extends ActivityInstrumentationTestCase2<My
|
|||||||
new SendResult(SendType.DOCS, true));
|
new SendResult(SendType.DOCS, true));
|
||||||
|
|
||||||
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results,
|
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results,
|
||||||
clickListener, clickListener);
|
clickListener, clickListener, null);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
assertEquals(View.VISIBLE, dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility());
|
assertEquals(View.VISIBLE, dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility());
|
||||||
@@ -98,7 +97,7 @@ public class ResultDialogFactoryTest extends ActivityInstrumentationTestCase2<My
|
|||||||
List<SendResult> results = makeResults(new SendResult(SendType.MYMAPS, true),
|
List<SendResult> results = makeResults(new SendResult(SendType.MYMAPS, true),
|
||||||
new SendResult(SendType.DOCS, false));
|
new SendResult(SendType.DOCS, false));
|
||||||
|
|
||||||
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results, clickListener, null);
|
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results, clickListener, null, null);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
assertEquals(View.GONE,
|
assertEquals(View.GONE,
|
||||||
@@ -112,7 +111,7 @@ public class ResultDialogFactoryTest extends ActivityInstrumentationTestCase2<My
|
|||||||
new SendResult(SendType.DOCS, false));
|
new SendResult(SendType.DOCS, false));
|
||||||
|
|
||||||
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results,
|
AlertDialog dialog = ResultDialogFactory.makeDialog(getActivity(), results,
|
||||||
clickListener, clickListener);
|
clickListener, clickListener, null);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
||||||
assertEquals(View.VISIBLE, dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility());
|
assertEquals(View.VISIBLE, dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility());
|
||||||
|
|||||||
+39
-112
@@ -49,9 +49,9 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the MyTracks track recording service.
|
* Tests for the MyTracks track recording service.
|
||||||
*
|
*
|
||||||
* @author Bartlomiej Niechwiej
|
* @author Bartlomiej Niechwiej
|
||||||
*
|
*
|
||||||
* TODO: The original class, ServiceTestCase, has a few limitations, e.g.
|
* TODO: The original class, ServiceTestCase, has a few limitations, e.g.
|
||||||
* it's not possible to properly shutdown the service, unless tearDown()
|
* it's not possible to properly shutdown the service, unless tearDown()
|
||||||
* is called, which prevents from testing multiple scenarios in a single
|
* is called, which prevents from testing multiple scenarios in a single
|
||||||
@@ -63,7 +63,7 @@ public class TrackRecordingServiceTest
|
|||||||
private Context context;
|
private Context context;
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In order to support starting and binding to the service in the same
|
* In order to support starting and binding to the service in the same
|
||||||
* unit test, we provide a workaround, as the original class doesn't allow
|
* unit test, we provide a workaround, as the original class doesn't allow
|
||||||
@@ -72,19 +72,19 @@ public class TrackRecordingServiceTest
|
|||||||
|
|
||||||
private boolean bound;
|
private boolean bound;
|
||||||
private Intent serviceIntent;
|
private Intent serviceIntent;
|
||||||
|
|
||||||
public TrackRecordingServiceTest() {
|
public TrackRecordingServiceTest() {
|
||||||
super(TrackRecordingService.class);
|
super(TrackRecordingService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A context wrapper with the user provided {@link ContentResolver}.
|
* A context wrapper with the user provided {@link ContentResolver}.
|
||||||
*
|
*
|
||||||
* TODO: Move to test utils package.
|
* TODO: Move to test utils package.
|
||||||
*/
|
*/
|
||||||
public static class MockContext extends ContextWrapper {
|
public static class MockContext extends ContextWrapper {
|
||||||
private final ContentResolver contentResolver;
|
private final ContentResolver contentResolver;
|
||||||
|
|
||||||
public MockContext(ContentResolver contentResolver, Context base) {
|
public MockContext(ContentResolver contentResolver, Context base) {
|
||||||
super(base);
|
super(base);
|
||||||
this.contentResolver = contentResolver;
|
this.contentResolver = contentResolver;
|
||||||
@@ -108,7 +108,7 @@ public class TrackRecordingServiceTest
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IBinder bindService(Intent intent) {
|
protected IBinder bindService(Intent intent) {
|
||||||
if (getService() != null) {
|
if (getService() != null) {
|
||||||
@@ -190,7 +190,7 @@ public class TrackRecordingServiceTest
|
|||||||
startService(startIntent);
|
startService(startIntent);
|
||||||
assertNotNull(getService());
|
assertNotNull(getService());
|
||||||
|
|
||||||
// We expect to resume the previous track.
|
// We expect to resume the previous track.
|
||||||
assertTrue(getService().isRecording());
|
assertTrue(getService().isRecording());
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
assertEquals(123, service.getRecordingTrackId());
|
assertEquals(123, service.getRecordingTrackId());
|
||||||
@@ -293,7 +293,7 @@ public class TrackRecordingServiceTest
|
|||||||
assertTrue(tracks.isEmpty());
|
assertTrue(tracks.isEmpty());
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
// Test if we start in no-recording mode by default.
|
// Test if we start in no-recording mode by default.
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
assertEquals(-1, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
@@ -463,92 +463,19 @@ public class TrackRecordingServiceTest
|
|||||||
context.getString(R.string.recording_track_key), 0));
|
context.getString(R.string.recording_track_key), 0));
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
assertEquals(-1, service.getRecordingTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testIntegration_completeRecordingSession() throws Exception {
|
public void testIntegration_completeRecordingSession() throws Exception {
|
||||||
List<Track> tracks = providerUtils.getAllTracks();
|
List<Track> tracks = providerUtils.getAllTracks();
|
||||||
assertTrue(tracks.isEmpty());
|
assertTrue(tracks.isEmpty());
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testDeleteAllTracks_noRecording() throws Exception {
|
|
||||||
createDummyTrack(123, -1, false);
|
|
||||||
assertEquals(1, providerUtils.getAllTracks().size());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// Deleting all tracks should succeed.
|
|
||||||
service.deleteAllTracks();
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertTrue(providerUtils.getAllTracks().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testDeleteAllTracks_noTracks() throws Exception {
|
|
||||||
assertTrue(providerUtils.getAllTracks().isEmpty());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// Deleting all tracks should succeed.
|
|
||||||
service.deleteAllTracks();
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertTrue(providerUtils.getAllTracks().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testDeleteAllTracks_trackInProgress() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
assertEquals(1, providerUtils.getAllTracks().size());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
// Since we have a track in progress, we expect to fail.
|
|
||||||
try {
|
|
||||||
service.deleteAllTracks();
|
|
||||||
fail("Expecting IllegalStateException");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// Expected.
|
|
||||||
}
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
assertEquals(1, providerUtils.getAllTracks().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testHasRecorded_noTracks() throws Exception {
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertFalse(service.hasRecorded());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testHasRecorded_trackInProgress() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
assertEquals(1, providerUtils.getAllTracks().size());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
assertTrue(service.hasRecorded());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testHasRecorded_oldTracks() throws Exception {
|
|
||||||
createDummyTrack(123, -1, false);
|
|
||||||
assertEquals(1, providerUtils.getAllTracks().size());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertTrue(service.hasRecorded());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||||
fail("Expecting IllegalStateException");
|
fail("Expecting IllegalStateException");
|
||||||
@@ -556,17 +483,17 @@ public class TrackRecordingServiceTest
|
|||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
||||||
createDummyTrack(123, -1, true);
|
createDummyTrack(123, -1, true);
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
|
|
||||||
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
||||||
assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
assertEquals(2, service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS));
|
||||||
|
|
||||||
Waypoint wpt = providerUtils.getWaypoint(1);
|
Waypoint wpt = providerUtils.getWaypoint(1);
|
||||||
assertEquals(getContext().getString(R.string.stats_icon_url),
|
assertEquals(getContext().getString(R.string.stats_icon_url),
|
||||||
wpt.getIcon());
|
wpt.getIcon());
|
||||||
@@ -586,7 +513,7 @@ public class TrackRecordingServiceTest
|
|||||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
|
||||||
fail("Expecting IllegalStateException");
|
fail("Expecting IllegalStateException");
|
||||||
@@ -594,14 +521,14 @@ public class TrackRecordingServiceTest
|
|||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
||||||
createDummyTrack(123, -1, true);
|
createDummyTrack(123, -1, true);
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
assertTrue(service.isRecording());
|
assertTrue(service.isRecording());
|
||||||
|
|
||||||
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER));
|
assertEquals(1, service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER));
|
||||||
Waypoint wpt = providerUtils.getWaypoint(1);
|
Waypoint wpt = providerUtils.getWaypoint(1);
|
||||||
assertEquals(getContext().getString(R.string.waypoint_icon_url),
|
assertEquals(getContext().getString(R.string.waypoint_icon_url),
|
||||||
@@ -614,7 +541,7 @@ public class TrackRecordingServiceTest
|
|||||||
assertNotNull(wpt.getLocation());
|
assertNotNull(wpt.getLocation());
|
||||||
assertNull(wpt.getStatistics());
|
assertNull(wpt.getStatistics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_noAnnouncementFreq() throws Exception {
|
public void testWithProperties_noAnnouncementFreq() throws Exception {
|
||||||
functionalTest(R.string.announcement_frequency_key, (Object) null);
|
functionalTest(R.string.announcement_frequency_key, (Object) null);
|
||||||
@@ -624,7 +551,7 @@ public class TrackRecordingServiceTest
|
|||||||
public void testWithProperties_defaultAnnouncementFreq() throws Exception {
|
public void testWithProperties_defaultAnnouncementFreq() throws Exception {
|
||||||
functionalTest(R.string.announcement_frequency_key, 1);
|
functionalTest(R.string.announcement_frequency_key, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_noMaxRecordingDist() throws Exception {
|
public void testWithProperties_noMaxRecordingDist() throws Exception {
|
||||||
functionalTest(R.string.max_recording_distance_key, (Object) null);
|
functionalTest(R.string.max_recording_distance_key, (Object) null);
|
||||||
@@ -654,7 +581,7 @@ public class TrackRecordingServiceTest
|
|||||||
public void testWithProperties_defaultSplitFreqByDist() throws Exception {
|
public void testWithProperties_defaultSplitFreqByDist() throws Exception {
|
||||||
functionalTest(R.string.split_frequency_key, 5);
|
functionalTest(R.string.split_frequency_key, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_defaultSplitFreqByTime() throws Exception {
|
public void testWithProperties_defaultSplitFreqByTime() throws Exception {
|
||||||
functionalTest(R.string.split_frequency_key, -2);
|
functionalTest(R.string.split_frequency_key, -2);
|
||||||
@@ -674,7 +601,7 @@ public class TrackRecordingServiceTest
|
|||||||
public void testWithProperties_metricUnitsDisabled() throws Exception {
|
public void testWithProperties_metricUnitsDisabled() throws Exception {
|
||||||
functionalTest(R.string.metric_units_key, false);
|
functionalTest(R.string.metric_units_key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_noMinRecordingInterval() throws Exception {
|
public void testWithProperties_noMinRecordingInterval() throws Exception {
|
||||||
functionalTest(R.string.min_recording_interval_key, (Object) null);
|
functionalTest(R.string.min_recording_interval_key, (Object) null);
|
||||||
@@ -690,23 +617,23 @@ public class TrackRecordingServiceTest
|
|||||||
public void testWithProperties_noMinRequiredAccuracy() throws Exception {
|
public void testWithProperties_noMinRequiredAccuracy() throws Exception {
|
||||||
functionalTest(R.string.min_required_accuracy_key, (Object) null);
|
functionalTest(R.string.min_required_accuracy_key, (Object) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_defaultMinRequiredAccuracy() throws Exception {
|
public void testWithProperties_defaultMinRequiredAccuracy() throws Exception {
|
||||||
functionalTest(R.string.min_required_accuracy_key, 500);
|
functionalTest(R.string.min_required_accuracy_key, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_noSensorType() throws Exception {
|
public void testWithProperties_noSensorType() throws Exception {
|
||||||
functionalTest(R.string.sensor_type_key, (Object) null);
|
functionalTest(R.string.sensor_type_key, (Object) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testWithProperties_zephyrSensorType() throws Exception {
|
public void testWithProperties_zephyrSensorType() throws Exception {
|
||||||
functionalTest(R.string.sensor_type_key,
|
functionalTest(R.string.sensor_type_key,
|
||||||
context.getString(R.string.zephyr_sensor_type));
|
context.getString(R.string.zephyr_sensor_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ITrackRecordingService bindAndGetService(Intent intent) {
|
private ITrackRecordingService bindAndGetService(Intent intent) {
|
||||||
ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(
|
ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(
|
||||||
bindService(intent));
|
bindService(intent));
|
||||||
@@ -724,7 +651,7 @@ public class TrackRecordingServiceTest
|
|||||||
addTrack(dummyTrack, isRecording);
|
addTrack(dummyTrack, isRecording);
|
||||||
return dummyTrack;
|
return dummyTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
|
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
|
||||||
Editor editor = sharedPreferences.edit();
|
Editor editor = sharedPreferences.edit();
|
||||||
editor.putInt(context.getString(
|
editor.putInt(context.getString(
|
||||||
@@ -733,20 +660,20 @@ public class TrackRecordingServiceTest
|
|||||||
R.string.auto_resume_track_timeout_key), timeoutMins);
|
R.string.auto_resume_track_timeout_key), timeoutMins);
|
||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent createStartIntent() {
|
private Intent createStartIntent() {
|
||||||
Intent startIntent = new Intent();
|
Intent startIntent = new Intent();
|
||||||
startIntent.setClass(context, TrackRecordingService.class);
|
startIntent.setClass(context, TrackRecordingService.class);
|
||||||
return startIntent;
|
return startIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTrack(Track track, boolean isRecording) {
|
private void addTrack(Track track, boolean isRecording) {
|
||||||
assertTrue(track.getId() >= 0);
|
assertTrue(track.getId() >= 0);
|
||||||
providerUtils.insertTrack(track);
|
providerUtils.insertTrack(track);
|
||||||
assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
|
assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
|
||||||
setRecordingTrack(isRecording ? track.getId() : -1);
|
setRecordingTrack(isRecording ? track.getId() : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setRecordingTrack(long id) {
|
private void setRecordingTrack(long id) {
|
||||||
Editor editor = sharedPreferences.edit();
|
Editor editor = sharedPreferences.edit();
|
||||||
editor.putLong(context.getString(R.string.recording_track_key), id);
|
editor.putLong(context.getString(R.string.recording_track_key), id);
|
||||||
@@ -760,11 +687,11 @@ public class TrackRecordingServiceTest
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
final String key = context.getString(resourceId);
|
final String key = context.getString(resourceId);
|
||||||
for (Object value : values) {
|
for (Object value : values) {
|
||||||
// Remove all properties and set the property for the given key.
|
// Remove all properties and set the property for the given key.
|
||||||
Editor editor = sharedPreferences.edit();
|
Editor editor = sharedPreferences.edit();
|
||||||
editor.clear();
|
editor.clear();
|
||||||
if (value instanceof String) {
|
if (value instanceof String) {
|
||||||
editor.putString(key, (String) value);
|
editor.putString(key, (String) value);
|
||||||
} else if (value instanceof Long) {
|
} else if (value instanceof Long) {
|
||||||
editor.putLong(key, (Long) value);
|
editor.putLong(key, (Long) value);
|
||||||
} else if (value instanceof Integer) {
|
} else if (value instanceof Integer) {
|
||||||
@@ -775,15 +702,15 @@ public class TrackRecordingServiceTest
|
|||||||
// Do nothing, as clear above has already removed this property.
|
// Do nothing, as clear above has already removed this property.
|
||||||
}
|
}
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
|
||||||
fullRecordingSession();
|
fullRecordingSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fullRecordingSession() throws Exception {
|
private void fullRecordingSession() throws Exception {
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|
||||||
// Start a track.
|
// Start a track.
|
||||||
long id = service.startNewTrack();
|
long id = service.startNewTrack();
|
||||||
assertTrue(id >= 0);
|
assertTrue(id >= 0);
|
||||||
@@ -806,14 +733,14 @@ public class TrackRecordingServiceTest
|
|||||||
loc.setTime(startTime + i * 10000);
|
loc.setTime(startTime + i * 10000);
|
||||||
loc.setBearing(3.0f);
|
loc.setBearing(3.0f);
|
||||||
service.recordLocation(loc);
|
service.recordLocation(loc);
|
||||||
|
|
||||||
if (i % 10 == 0) {
|
if (i % 10 == 0) {
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_STATISTICS);
|
||||||
} else if (i % 7 == 0) {
|
} else if (i % 7 == 0) {
|
||||||
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
|
service.insertWaypoint(WaypointCreationRequest.DEFAULT_MARKER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the track. Validate if it has correct data.
|
// Stop the track. Validate if it has correct data.
|
||||||
service.endCurrentTrack();
|
service.endCurrentTrack();
|
||||||
assertFalse(service.isRecording());
|
assertFalse(service.isRecording());
|
||||||
|
|||||||
+22
-3
@@ -9,21 +9,40 @@ public class ZephyrMessageParserTest extends TestCase {
|
|||||||
ZephyrMessageParser parser = new ZephyrMessageParser();
|
ZephyrMessageParser parser = new ZephyrMessageParser();
|
||||||
|
|
||||||
public void testIsValid() {
|
public void testIsValid() {
|
||||||
byte[] buf = new byte[60];
|
byte[] smallBuf = new byte[59];
|
||||||
|
assertFalse(parser.isValid(smallBuf));
|
||||||
|
// A complete and valid Zephyr HxM packet
|
||||||
|
byte[] buf = { 2,38,55,26,0,49,101,80,0,49,98,100,42,113,120,-53,-24,-60,-123,-61,117,-69,42,-75,74,-78,51,-79,27,-83,28,-88,28,-93,29,-98,25,-103,26,-108,26,-113,59,-118,0,0,0,0,0,0,-22,3,125,1,48,0,96,4,30,0 };
|
||||||
|
// Make buffer invalid
|
||||||
|
buf[0] = buf[58] = buf[59] = 0;
|
||||||
assertFalse(parser.isValid(buf));
|
assertFalse(parser.isValid(buf));
|
||||||
buf[0] = 0x02;
|
buf[0] = 0x02;
|
||||||
assertFalse(parser.isValid(buf));
|
assertFalse(parser.isValid(buf));
|
||||||
|
buf[58] = 0x1E;
|
||||||
|
assertFalse(parser.isValid(buf));
|
||||||
buf[59] = 0x03;
|
buf[59] = 0x03;
|
||||||
assertTrue(parser.isValid(buf));
|
assertTrue(parser.isValid(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testParseBuffer() {
|
public void testParseBuffer() {
|
||||||
byte[] buf = new byte[60];
|
byte[] buf = new byte[60];
|
||||||
buf[12] = 50;
|
// Heartrate (-1 =^ 255 unsigned byte)
|
||||||
|
buf[12] = -1;
|
||||||
|
// Power
|
||||||
|
buf[11] = 51;
|
||||||
|
// Cadence (=^ 255*16 strides/min)
|
||||||
|
buf[56] = -1;
|
||||||
|
buf[57] = 15;
|
||||||
Sensor.SensorDataSet sds = parser.parseBuffer(buf);
|
Sensor.SensorDataSet sds = parser.parseBuffer(buf);
|
||||||
assertTrue(sds.hasHeartRate());
|
assertTrue(sds.hasHeartRate());
|
||||||
assertTrue(sds.getHeartRate().getState() == Sensor.SensorState.SENDING);
|
assertTrue(sds.getHeartRate().getState() == Sensor.SensorState.SENDING);
|
||||||
assertEquals(50, sds.getHeartRate().getValue());
|
assertEquals(255, sds.getHeartRate().getValue());
|
||||||
|
assertTrue(sds.hasPower());
|
||||||
|
assertTrue(sds.getPower().getState() == Sensor.SensorState.SENDING);
|
||||||
|
assertEquals(51, sds.getPower().getValue());
|
||||||
|
assertTrue(sds.hasCadence());
|
||||||
|
assertTrue(sds.getCadence().getState() == Sensor.SensorState.SENDING);
|
||||||
|
assertEquals(255, sds.getCadence().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testFindNextAlignment() {
|
public void testFindNextAlignment() {
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
* 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.services.sensors;
|
package com.google.android.apps.mytracks.services.sensors.ant;
|
||||||
|
|
||||||
import com.dsi.ant.AntMesg;
|
import com.dsi.ant.AntMesg;
|
||||||
import com.google.android.apps.mytracks.Constants;
|
import com.google.android.apps.mytracks.Constants;
|
||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
* 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.services.sensors;
|
package com.google.android.apps.mytracks.services.sensors.ant;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.services.sensors.ant.AntSensorManager;
|
import com.google.android.apps.mytracks.services.sensors.ant.AntSensorManager;
|
||||||
|
|
||||||
+7
-7
@@ -1,12 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010 Google Inc.
|
* Copyright 2010 Google Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* 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
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
* the License at
|
* the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.signalstrength;
|
package com.google.android.apps.mytracks.signalstrength;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.signalstrength.SignalStrengthConstants.*;
|
import static com.google.android.apps.mytracks.signalstrength.SignalStrengthConstants.START_SAMPLING;
|
||||||
|
import static com.google.android.apps.mytracks.signalstrength.SignalStrengthConstants.STOP_SAMPLING;
|
||||||
|
import static com.google.android.apps.mytracks.signalstrength.SignalStrengthConstants.TAG;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl;
|
|
||||||
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
|
||||||
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
import com.google.android.apps.mytracks.services.ITrackRecordingService;
|
||||||
import com.google.android.apps.mytracks.signalstrength.SignalStrengthListener.SignalStrengthCallback;
|
import com.google.android.apps.mytracks.signalstrength.SignalStrengthListener.SignalStrengthCallback;
|
||||||
@@ -206,7 +206,7 @@ public class SignalStrengthService extends Service
|
|||||||
|
|
||||||
// Tell the user we've stopped
|
// Tell the user we've stopped
|
||||||
Toast.makeText(this, R.string.stopped_sampling, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.stopped_sampling, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
// Stop
|
// Stop
|
||||||
stopSelf();
|
stopSelf();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user