mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-11-04 23:34:44 +01:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cdbeefcd16 | |||
| ffdd5f4091 | |||
| bb60525780 | |||
| 82634af649 | |||
| 3643ed9f13 | |||
| fac59ab705 | |||
| 1e343b0d12 | |||
| 34bb467b97 | |||
| b092525c10 | |||
| 256dac3a82 | |||
| dfe947f54d | |||
| ce09407816 | |||
| beee6af79d | |||
| c282562867 | |||
| 2aed9b3a33 | |||
| 210910f21e | |||
| 1d666318ae | |||
| 4df6095089 | |||
| d4edcce224 | |||
| d7c6761841 | |||
| 5733224f8d | |||
| 22fd5042bc | |||
| 44a6f2d448 | |||
| cd9c17f9b3 | |||
| dc07bcf0fa | |||
| 248b8fb4fd | |||
| 35182a1483 | |||
| 21b0a517a9 | |||
| 19a770445e | |||
| c6512f1376 | |||
| 2cb3963138 | |||
| d05c394c68 | |||
| 797de31960 | |||
| 872d2d4337 | |||
| b84da40365 | |||
| 236bcba2bd | |||
| 0347877290 | |||
| b464526d5e | |||
| 0ccd05e605 | |||
| 916e4586c5 |
@@ -3,4 +3,3 @@ syntax:glob
|
|||||||
*/bin
|
*/bin
|
||||||
*/gen/**
|
*/gen/**
|
||||||
.metadata
|
.metadata
|
||||||
MyTracksTest/annotations.log
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<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:versionCode="19"
|
||||||
android:versionCode="22"
|
android:versionName="1.0.18">
|
||||||
android:versionName="1.0.21">
|
|
||||||
<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">
|
||||||
@@ -13,6 +12,7 @@
|
|||||||
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
|
android:value="AEdPqrEAAAAIi-_QiwoRSc9_bAC9cmuNXTQyU8ajJmGtKdhskQ" />
|
||||||
|
|
||||||
<provider android:name="com.google.android.apps.mytracks.content.MyTracksProvider"
|
<provider android:name="com.google.android.apps.mytracks.content.MyTracksProvider"
|
||||||
|
android:multiprocess="true"
|
||||||
android:authorities="com.google.android.maps.mytracks" />
|
android:authorities="com.google.android.maps.mytracks" />
|
||||||
|
|
||||||
<activity android:name="com.google.android.apps.mytracks.MyTracks"
|
<activity android:name="com.google.android.apps.mytracks.MyTracks"
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
android:label="@string/welcome"/>
|
android:label="@string/welcome"/>
|
||||||
|
|
||||||
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
|
<service android:name="com.google.android.apps.mytracks.services.TrackRecordingService"
|
||||||
|
android:process=":trackrecord"
|
||||||
android:label="Track Recording Service"
|
android:label="Track Recording Service"
|
||||||
android:icon="@drawable/arrow_320"
|
android:icon="@drawable/arrow_320"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -100,22 +101,6 @@
|
|||||||
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.writely" />
|
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.writely" />
|
||||||
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
|
||||||
|
|
||||||
<!-- Permissions for misc services -->
|
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
||||||
|
|
||||||
<!-- Features required -->
|
|
||||||
<uses-feature android:name="android.hardware.location" android:required="true"/>
|
|
||||||
<uses-feature android:name="android.hardware.location.gps" android:required="true"/>
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="true"/>
|
|
||||||
|
|
||||||
<!-- Features optional -->
|
|
||||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.sensor.compass" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.telephony" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.telephony.gsm" android:required="false"/>
|
|
||||||
<uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false"/>
|
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="3"/>
|
<uses-sdk android:minSdkVersion="3"/>
|
||||||
<supports-screens
|
<supports-screens
|
||||||
android:largeScreens="true"
|
android:largeScreens="true"
|
||||||
|
|||||||
@@ -9,11 +9,9 @@
|
|||||||
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
|
<booleanAttribute key="com.android.ide.eclipse.adt.wipedata" value="false"/>
|
||||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||||
<listEntry value="/MyTracks"/>
|
<listEntry value="/MyTracks"/>
|
||||||
<listEntry value="/MyTracks/AndroidManifest.xml"/>
|
|
||||||
</listAttribute>
|
</listAttribute>
|
||||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||||
<listEntry value="4"/>
|
<listEntry value="4"/>
|
||||||
<listEntry value="1"/>
|
|
||||||
</listAttribute>
|
</listAttribute>
|
||||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ limitations under the License.
|
|||||||
|
|
||||||
<!-- Used by DeleteAllDataHandler -->
|
<!-- Used by DeleteAllDataHandler -->
|
||||||
<string name="are_you_sure_question">Jesteś pewien?</string>
|
<string name="are_you_sure_question">Jesteś pewien?</string>
|
||||||
<string name="all_data_will_be_permanently_deleted">Wszystkie trasy i znaczniki zostaną usunięte z telefonu.</string>
|
<string name="all_data_will_be_permanently_deleted">Wszystkie trasy i znaczniki zostaną usunięte z tego telefonu.</string>
|
||||||
<string name="track_will_be_permanently_deleted">Trasa i jej znaczniki zostanie całkowicie usunięta z telefonu.</string>
|
<string name="track_will_be_permanently_deleted">Trasy i ich znaczniki zostaną całkowicie usunięte z tego telefonu.</string>
|
||||||
<string name="marker_will_be_permanently_deleted">Znacznik zostanie całkowicie usunięty z telefonu.</string>
|
<string name="marker_will_be_permanently_deleted">Znacznik zostanie całkowicie usunięty z tego telefonu.</string>
|
||||||
|
|
||||||
<!-- Text and links when uploading to Google My Maps -->
|
<!-- Text and links when uploading to Google My Maps -->
|
||||||
<string name="new_map_description">Utworzono za pomocą My Tracks na Android.</string>
|
<string name="new_map_description">Utworzono za pomocą My Tracks na Android.</string>
|
||||||
|
|||||||
@@ -147,14 +147,6 @@ limitations under the License.
|
|||||||
<item>1 分钟</item>
|
<item>1 分钟</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<!-- Auto-resume track timeout -->
|
|
||||||
<string-array name="auto_resume_track_timeout_options">
|
|
||||||
<item>从不</item>
|
|
||||||
<item>5 分钟</item>
|
|
||||||
<item>10 分钟</item>
|
|
||||||
<item>15 分钟</item>
|
|
||||||
<item>总是</item>
|
|
||||||
</string-array>
|
|
||||||
<!-- Other non-preference arrays -->
|
<!-- Other non-preference arrays -->
|
||||||
<array name="activity_types">
|
<array name="activity_types">
|
||||||
<item>未知</item>
|
<item>未知</item>
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
|
<!-- DO NOT TRANSLATE -->
|
||||||
|
<string name="app_name">My Tracks</string>
|
||||||
|
|
||||||
<!-- Used by the Menu -->
|
<!-- Used by the Menu -->
|
||||||
<string name="start_recording">记录轨迹</string>
|
<string name="start_recording">记录轨迹</string>
|
||||||
@@ -32,7 +32,6 @@ limitations under the License.
|
|||||||
<string name="choose_track_message">请选择要查看的轨迹</string>
|
<string name="choose_track_message">请选择要查看的轨迹</string>
|
||||||
<string name="current_track">当前轨迹</string>
|
<string name="current_track">当前轨迹</string>
|
||||||
<string name="current_segment">上一段轨迹</string>
|
<string name="current_segment">上一段轨迹</string>
|
||||||
<string name="aggregated_stats">累积统计</string>
|
|
||||||
|
|
||||||
<!-- Error messages -->
|
<!-- Error messages -->
|
||||||
<string name="error_no_location_manager">此设备上没有位置管理器!</string>
|
<string name="error_no_location_manager">此设备上没有位置管理器!</string>
|
||||||
@@ -123,7 +122,6 @@ limitations under the License.
|
|||||||
<string name="tracklist_save_as_gpx">保存为 GPX 文件</string>
|
<string name="tracklist_save_as_gpx">保存为 GPX 文件</string>
|
||||||
<string name="tracklist_save_as_kml">保存为 KML 文件</string>
|
<string name="tracklist_save_as_kml">保存为 KML 文件</string>
|
||||||
<string name="tracklist_save_as_csv">保存为 CSV 文件</string>
|
<string name="tracklist_save_as_csv">保存为 CSV 文件</string>
|
||||||
<string name="tracklist_save_as_tcx">保存为 TCX 文件</string>
|
|
||||||
<string name="tracklist_btn_send_all_to_mymaps">发送到 Google My Maps</string>
|
<string name="tracklist_btn_send_all_to_mymaps">发送到 Google My Maps</string>
|
||||||
<string name="tracklist_btn_delete_all">删除所有轨迹</string>
|
<string name="tracklist_btn_delete_all">删除所有轨迹</string>
|
||||||
<!-- Export All -->
|
<!-- Export All -->
|
||||||
@@ -137,7 +135,6 @@ limitations under the License.
|
|||||||
<string name="tracklist_share_gpx_file">GPX 文件</string>
|
<string name="tracklist_share_gpx_file">GPX 文件</string>
|
||||||
<string name="tracklist_share_kml_file">KML 文件</string>
|
<string name="tracklist_share_kml_file">KML 文件</string>
|
||||||
<string name="tracklist_share_csv_file">CSV 文件</string>
|
<string name="tracklist_share_csv_file">CSV 文件</string>
|
||||||
<string name="tracklist_share_tcx_file">TCX 文件</string>
|
|
||||||
<string name="tracklist_clear_map">清除地图</string>
|
<string name="tracklist_clear_map">清除地图</string>
|
||||||
|
|
||||||
<!-- Waypoints list -->
|
<!-- Waypoints list -->
|
||||||
@@ -168,11 +165,10 @@ limitations under the License.
|
|||||||
<string name="settings_recording">记录</string>
|
<string name="settings_recording">记录</string>
|
||||||
<string name="settings_sharing">分享</string>
|
<string name="settings_sharing">分享</string>
|
||||||
|
|
||||||
<string name="settings_not_available_summary">你使用的 Android 版本无法支持</string>
|
|
||||||
|
|
||||||
<string name="settings_announcement_frequency">提示频度</string>
|
<string name="settings_announcement_frequency">提示频度</string>
|
||||||
<string name="settings_announcement_frequency_summary">语音提示的最小时间间隔</string>
|
<string name="settings_announcement_frequency_summary">语音提示的最小时间间隔</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="settings_min_recording_distance">最小记录间距</string>
|
<string name="settings_min_recording_distance">最小记录间距</string>
|
||||||
<string name="settings_min_recording_distance_summary">位置记录的最小间隔距离</string>
|
<string name="settings_min_recording_distance_summary">位置记录的最小间隔距离</string>
|
||||||
|
|
||||||
@@ -206,38 +202,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<string name="settings_split_frequency">切割频度</string>
|
<string name="settings_split_frequency">切割频度</string>
|
||||||
<string name="settings_split_frequency_summary">插入新切割标记所需要达到的最大时间或距离</string>
|
<string name="settings_split_frequency_summary">插入新切割标记所需要达到的最大时间或距离</string>
|
||||||
|
<string name="pace_label">步数</string>
|
||||||
<string name="settings_auto_resume_track_timeout">自动恢复追踪等待时间</string>
|
|
||||||
<string name="settings_auto_resume_track_timeout_summary">手机重启后自动继续上次轨迹记录的最大间隔时间</string>
|
|
||||||
|
|
||||||
<string name="pace_label">节奏</string>
|
|
||||||
|
|
||||||
<string name="eclair_required">只在 Android 2.0 及以上版本支持</string>
|
|
||||||
<string name="settings_sensor_type">感应器类型</string>
|
|
||||||
<string name="settings_sensor_type_summary">要连接到的感应器类型</string>
|
|
||||||
<string name="settings_sensor_id">感应器ID</string>
|
|
||||||
<string name="settings_sensor_id_summary">发送到感应器的ID.</string>
|
|
||||||
<string name="sensor_id_malformed">ID必须是一个整数值.</string>
|
|
||||||
<string name="bluetooth_sensor_title">蓝牙感应器</string>
|
|
||||||
<string name="bluetooth_sensor_summary">要连接到的蓝牙感应器</string>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<string name="settings_backup">备份 (测试版)</string>
|
|
||||||
<string name="settings_sd_backup">SD 存储卡备份</string>
|
|
||||||
<string name="settings_backup_to_sd">立即备份</string>
|
|
||||||
<string name="settings_backup_to_sd_summary">将你所有轨迹和设置的完整备份写入到 SD 存储卡</string>
|
|
||||||
<string name="settings_restore_from_sd">立即恢复</string>
|
|
||||||
<string name="settings_restore_from_sd_summary">从 SD 存储卡恢复所有轨迹和设置</string>
|
|
||||||
<string name="settings_no_backup_while_recording">在记录轨迹时无法处理备份数据.</string>
|
|
||||||
|
|
||||||
<!-- Backups -->
|
|
||||||
<string name="restore_overwrites_warning">从 SD 存储卡恢复备份数据将覆盖掉你当前的所有轨迹和设置. 确定要继续吗?</string>
|
|
||||||
<string name="select_backup_to_restore">请选择要恢复的备份文件</string>
|
|
||||||
<string name="no_backups">没有找到备份文件.</string>
|
|
||||||
<string name="backup_write_progress_message">将所有轨迹和设置写入 SD 存储卡中. 这需要一些时间...</string>
|
|
||||||
<string name="backup_import_progress_message">从 SD 存储卡读取轨迹和设置中. 这需要一些时间...</string>
|
|
||||||
|
|
||||||
<!-- MyTracksSky -->
|
<!-- MyTracksSky -->
|
||||||
<string name="mytracks_sky">GPS 卫星</string>
|
<string name="mytracks_sky">GPS 卫星</string>
|
||||||
@@ -276,7 +241,7 @@ limitations under the License.
|
|||||||
|
|
||||||
<!-- Visibility -->
|
<!-- Visibility -->
|
||||||
<string name="public_map">公开</string>
|
<string name="public_map">公开</string>
|
||||||
<string name="unlisted_map">未公布</string>
|
<string name="unlisted_map">Unlisted</string>
|
||||||
<string name="mapslist_no_maps_available">你还没有保存任何地图.</string>
|
<string name="mapslist_no_maps_available">你还没有保存任何地图.</string>
|
||||||
|
|
||||||
<!-- Statistics View -->
|
<!-- Statistics View -->
|
||||||
@@ -327,32 +292,24 @@ limitations under the License.
|
|||||||
<string name="second">秒</string>
|
<string name="second">秒</string>
|
||||||
<string name="seconds">秒</string>
|
<string name="seconds">秒</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Distance -->
|
<!-- Distance -->
|
||||||
|
<string name="meter">m</string>
|
||||||
|
<string name="kilometer">km</string>
|
||||||
<string name="kilometers_long">千米</string>
|
<string name="kilometers_long">千米</string>
|
||||||
|
<string name="feet">ft</string>
|
||||||
|
<string name="mile">mi</string>
|
||||||
<string name="miles_long">英里</string>
|
<string name="miles_long">英里</string>
|
||||||
|
|
||||||
<!-- Speed and Pace -->
|
<!-- Speed and Pace -->
|
||||||
|
<string name="kilometer_per_hour">km/h</string>
|
||||||
<string name="kilometer_per_hour_long">千米每小时</string>
|
<string name="kilometer_per_hour_long">千米每小时</string>
|
||||||
|
<string name="min_per_kilometer">min/km</string>
|
||||||
<string name="per_kilometer">每千米</string>
|
<string name="per_kilometer">每千米</string>
|
||||||
|
<string name="mile_per_hour">mi/h</string>
|
||||||
<string name="mile_per_hour_long">英里每小时</string>
|
<string name="mile_per_hour_long">英里每小时</string>
|
||||||
|
<string name="min_per_mile">min/mi</string>
|
||||||
<string name="per_mile">每英里</string>
|
<string name="per_mile">每英里</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Stats Labels -->
|
<!-- Stats Labels -->
|
||||||
<string name="moving_time_label">移动时间</string>
|
<string name="moving_time_label">移动时间</string>
|
||||||
<string name="total_time_label">总时间</string>
|
<string name="total_time_label">总时间</string>
|
||||||
@@ -363,11 +320,7 @@ limitations under the License.
|
|||||||
<string name="statistics">统计</string>
|
<string name="statistics">统计</string>
|
||||||
<string name="total_distance_label">总距离</string>
|
<string name="total_distance_label">总距离</string>
|
||||||
<string name="distance_label">距离</string>
|
<string name="distance_label">距离</string>
|
||||||
<string name="power">电源</string>
|
<string name="unknown">-</string>
|
||||||
<string name="cadence">节奏</string>
|
|
||||||
<string name="heart_rate">心率</string>
|
|
||||||
<string name="max_heart_rate">最大心率</string>
|
|
||||||
|
|
||||||
<string name="waiting">请稍候...</string>
|
<string name="waiting">请稍候...</string>
|
||||||
|
|
||||||
<!-- End User License Agreement -->
|
<!-- End User License Agreement -->
|
||||||
@@ -383,27 +336,20 @@ limitations under the License.
|
|||||||
<string name="about_email">技术支持</string>
|
<string name="about_email">技术支持</string>
|
||||||
<string name="about_support">技术支持:</string>
|
<string name="about_support">技术支持:</string>
|
||||||
|
|
||||||
|
<!-- DO NOT TRANSLATE -->
|
||||||
|
<string name="version">1.0.14</string>
|
||||||
|
<string name="about_googlegroups">mytracks@googlegroups.com</string>
|
||||||
|
<string name="about_weburl">mytracks.appspot.com</string>
|
||||||
|
<string name="about_copyright_message">Copyright 2009 Google Inc.</string>
|
||||||
|
<string name="about_dylan">Dylan Casey</string>
|
||||||
|
<string name="about_leif">Leif Hendrik Wilden</string>
|
||||||
|
<string name="about_rdamazio">Rodrigo Damazio Bovendorp</string>
|
||||||
|
<string name="about_sandor">Sandor Dornbush</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Send to Google -->
|
<!-- Send to Google -->
|
||||||
<string name="sendtogoogle_title">发送到...</string>
|
<string name="sendtogoogle_title">发送到...</string>
|
||||||
|
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
|
||||||
|
<string name="sendtogoogle_google_docs">Google Docs</string>
|
||||||
<string name="sendtogoogle_send_now">立即发送</string>
|
<string name="sendtogoogle_send_now">立即发送</string>
|
||||||
<string name="sendtogoogle_create_new_map">创建新地图</string>
|
<string name="sendtogoogle_create_new_map">创建新地图</string>
|
||||||
<string name="sendtogoogle_pick_existing_map">选择已有地图</string>
|
<string name="sendtogoogle_pick_existing_map">选择已有地图</string>
|
||||||
@@ -418,9 +364,8 @@ limitations under the License.
|
|||||||
<!-- SD card -->
|
<!-- SD card -->
|
||||||
<string name="io_no_external_storage_found">没有发现存贮设备. 请确认插入了 SD 卡并且你的手机没有将挂载 SD 卡到电脑.</string>
|
<string name="io_no_external_storage_found">没有发现存贮设备. 请确认插入了 SD 卡并且你的手机没有将挂载 SD 卡到电脑.</string>
|
||||||
<string name="io_write_failed">无法写入文件.</string>
|
<string name="io_write_failed">无法写入文件.</string>
|
||||||
<string name="io_create_dir_failed">无法在 SD 存储卡上创建目录.</string>
|
<string name="io_create_dir_failed">无法在 SD 卡上创建目录.</string>
|
||||||
<string name="io_write_finished">轨迹已导出到 SD 卡.</string>
|
<string name="io_write_finished">轨迹已导出到 SD 卡.</string>
|
||||||
<string name="io_read_finished">从 SD 存储卡读取完毕.</string>
|
|
||||||
|
|
||||||
<!-- VersionChecker -->
|
<!-- VersionChecker -->
|
||||||
<string name="version_check_title">检查更新</string>
|
<string name="version_check_title">检查更新</string>
|
||||||
@@ -458,29 +403,4 @@ limitations under the License.
|
|||||||
<string name="gsm_strength">GSM 强度 %1$d</string>
|
<string name="gsm_strength">GSM 强度 %1$d</string>
|
||||||
<string name="cdma_strength">CDMA 强度 %1$d dBm</string>
|
<string name="cdma_strength">CDMA 强度 %1$d dBm</string>
|
||||||
<string name="evdo_strength">EVDO 强度 %1$d dBm</string>
|
<string name="evdo_strength">EVDO 强度 %1$d dBm</string>
|
||||||
|
|
||||||
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
|
|
||||||
<string name="method_bluetooth">蓝牙</string>
|
|
||||||
<string name="bluetooth_options">蓝牙设置</string>
|
|
||||||
<string name="bluetooth_pairing">设备关联</string>
|
|
||||||
<string name="bluetooth_pairing_summary">打开 Android 的蓝牙设置关联设备</string>
|
|
||||||
|
|
||||||
<string name="sensor_options">感应器选项</string>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Bluetooth Sensor -->
|
|
||||||
<string name="bluetooth_connected">连接到 %s</string>
|
|
||||||
<string name="bluetooth_connecting">蓝牙感应器连接中 ...</string>
|
|
||||||
<string name="bluetooth_not_connected">未连接蓝牙感应器</string>
|
|
||||||
|
|
||||||
<!-- Sensor State -->
|
|
||||||
<string name="sensor_data_time">感应器数据时间</string>
|
|
||||||
<string name="power_meter_enabled">电源计量器已启用</string>
|
|
||||||
<string name="heart_rate_monitor_enabled">心率监视器已启用</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ limitations under the License.
|
|||||||
<string name="share_url_only_key">shareUrlOnly</string>
|
<string name="share_url_only_key">shareUrlOnly</string>
|
||||||
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
<string name="signal_sampling_frequency_key">signalSamplingFrequency</string>
|
||||||
<string name="split_frequency_key">splitFrequency</string>
|
<string name="split_frequency_key">splitFrequency</string>
|
||||||
<string name="timestamp_track_name_key">timestampTrackName</string>
|
<string name="debug_draw_bounds_key">debugDrawBounds</string>
|
||||||
|
|
||||||
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
|
<!-- Preference keys which keep transient state (not user preferences) - DO NOT TRANSLATE -->
|
||||||
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
|
<string name="auto_resume_track_current_retry_key">autoResumeTrackCurrentRetry</string>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
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.
|
|
||||||
-->
|
|
||||||
<resources>
|
|
||||||
<array name="left_icons">
|
|
||||||
<item>@drawable/left_arrow_chart_stateful</item>
|
|
||||||
<item>@drawable/left_arrow_track_stateful</item>
|
|
||||||
<item>@drawable/left_arrow_stats_stateful</item>
|
|
||||||
</array>
|
|
||||||
|
|
||||||
<array name="right_icons">
|
|
||||||
<item>@drawable/right_arrow_stats_stateful</item>
|
|
||||||
<item>@drawable/right_arrow_chart_stateful</item>
|
|
||||||
<item>@drawable/right_arrow_track_stateful</item>
|
|
||||||
</array>
|
|
||||||
</resources>
|
|
||||||
@@ -210,10 +210,6 @@ limitations under the License.
|
|||||||
<string name="settings_auto_resume_track_timeout">Auto-resume track timeout</string>
|
<string name="settings_auto_resume_track_timeout">Auto-resume track timeout</string>
|
||||||
<string name="settings_auto_resume_track_timeout_summary">Maximum time in minutes after which a previously recording track will not be resumed on phone reboot</string>
|
<string name="settings_auto_resume_track_timeout_summary">Maximum time in minutes after which a previously recording track will not be resumed on phone reboot</string>
|
||||||
|
|
||||||
<string name="settings_timestamp_track_name">Timestamp as track name</string>
|
|
||||||
<string name="settings_timestamp_track_name_summary_on">The default track name will be the date and time at the start of the track</string>
|
|
||||||
<string name="settings_timestamp_track_name_summary_off">Track names will have numbers which increment for each new track.</string>
|
|
||||||
|
|
||||||
<string name="pace_label">Pace</string>
|
<string name="pace_label">Pace</string>
|
||||||
|
|
||||||
<string name="eclair_required">Only supported in Android 2.0 or above</string>
|
<string name="eclair_required">Only supported in Android 2.0 or above</string>
|
||||||
|
|||||||
@@ -99,14 +99,6 @@ limitations under the License.
|
|||||||
android:entries="@array/auto_resume_track_timeout_options"
|
android:entries="@array/auto_resume_track_timeout_options"
|
||||||
android:entryValues="@array/auto_resume_track_timeout_values"
|
android:entryValues="@array/auto_resume_track_timeout_values"
|
||||||
android:defaultValue="10" />
|
android:defaultValue="10" />
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:key="@string/timestamp_track_name_key"
|
|
||||||
android:title="@string/settings_timestamp_track_name"
|
|
||||||
android:summaryOn="@string/settings_timestamp_track_name_summary_on"
|
|
||||||
android:summaryOff="@string/settings_timestamp_track_name_summary_off"
|
|
||||||
android:defaultValue="true" />
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
<!-- Sharing settings -->
|
<!-- Sharing settings -->
|
||||||
@@ -141,4 +133,13 @@ limitations under the License.
|
|||||||
android:persistent="false" />
|
android:persistent="false" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
|
<!-- Debug settings - uncomment this while debugging only -->
|
||||||
|
<!--
|
||||||
|
<PreferenceScreen android:title="Debug">
|
||||||
|
<CheckBoxPreference android:key="@string/debug_draw_bounds_key"
|
||||||
|
android:title="Draw bounding box"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
</PreferenceScreen>
|
||||||
|
-->
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class AggregatedStatsActivity extends Activity implements
|
|||||||
|
|
||||||
public AggregatedStatsActivity() {
|
public AggregatedStatsActivity() {
|
||||||
this.utils = new StatsUtilities(this);
|
this.utils = new StatsUtilities(this);
|
||||||
|
this.tracksProvider = MyTracksProviderUtils.Factory.get(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,8 +58,6 @@ public class AggregatedStatsActivity extends Activity implements
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
this.tracksProvider = MyTracksProviderUtils.Factory.get(this);
|
|
||||||
|
|
||||||
// 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);
|
||||||
setContentView(R.layout.stats);
|
setContentView(R.layout.stats);
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ import com.google.android.apps.mytracks.content.Track;
|
|||||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
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;
|
||||||
@@ -37,6 +35,7 @@ import android.location.Location;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -244,9 +243,7 @@ public class ChartActivity extends Activity implements
|
|||||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
providerUtils = MyTracksProviderUtils.Factory.get(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
|
||||||
int volumeStream =
|
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
|
||||||
setVolumeControlStream(volumeStream);
|
|
||||||
|
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
setContentView(R.layout.mytracks_elevation);
|
setContentView(R.layout.mytracks_elevation);
|
||||||
@@ -298,13 +295,13 @@ public class ChartActivity extends Activity implements
|
|||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
Log.d(MyTracksConstants.TAG, "ChartActivity: ContentObserver.onChange");
|
Log.d(MyTracksConstants.TAG, "ChartActivity: ContentObserver.onChange");
|
||||||
// Check for any new locations and append them to the currently
|
// Check for any new locations and append them to the currently
|
||||||
// recording track.
|
// recording track:
|
||||||
if (recordingTrackId < 0) {
|
if (!MyTracks.getInstance().isRecording()) {
|
||||||
// No track is being recorded. We should not be here.
|
// No track is being recorded. We should not be here.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedTrackId != recordingTrackId) {
|
if (selectedTrackId != recordingTrackId) {
|
||||||
// No track, or one other than the recording track is selected, don't
|
// no track, or one other than the recording track is selected, don't
|
||||||
// bother.
|
// bother.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -455,8 +452,7 @@ public class ChartActivity extends Activity implements
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case MyTracksConstants.MENU_CHART_SETTINGS:
|
case MyTracksConstants.MENU_CHART_SETTINGS:
|
||||||
MyTracks.getInstance().getDialogManager().showDialogSafely(
|
MyTracks.getInstance().showDialogSafely(MyTracks.DIALOG_CHART_SETTINGS);
|
||||||
DialogManager.DIALOG_CHART_SETTINGS);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import android.graphics.Path;
|
|||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class encapsulates meta data about one series of values for a chart.
|
* This class encasulates meta data about one series of values for a chart.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,231 +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.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager.BadTokenException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class to handle all dialog related events for My Tracks.
|
|
||||||
*
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
public static final int DIALOG_SEND_TO_GOOGLE_RESULT = 5;
|
|
||||||
public static final int DIALOG_WRITE_PROGRESS = 6;
|
|
||||||
|
|
||||||
private ProgressDialog progressDialog;
|
|
||||||
private ProgressDialog importProgressDialog;
|
|
||||||
private ProgressDialog writeProgressDialog;
|
|
||||||
private SendToGoogleDialog sendToGoogleDialog;
|
|
||||||
private AlertDialog sendToGoogleResultDialog;
|
|
||||||
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 SendToGoogleDialog(activity);
|
|
||||||
return sendToGoogleDialog;
|
|
||||||
case DIALOG_SEND_TO_GOOGLE_RESULT:
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
||||||
builder.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
builder.setTitle("Title");
|
|
||||||
builder.setMessage("Message");
|
|
||||||
builder.setPositiveButton(activity.getString(R.string.ok), null);
|
|
||||||
builder.setNeutralButton(activity.getString(R.string.share_map),
|
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
activity.shareLinkToMyMap(activity.getSendToMyMapsMapId());
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sendToGoogleResultDialog = builder.create();
|
|
||||||
return sendToGoogleResultDialog;
|
|
||||||
case DIALOG_WRITE_PROGRESS:
|
|
||||||
writeProgressDialog = new ProgressDialog(activity);
|
|
||||||
writeProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
|
|
||||||
writeProgressDialog.setTitle(
|
|
||||||
activity.getString(R.string.progress_title));
|
|
||||||
writeProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
|
||||||
writeProgressDialog.setMessage(
|
|
||||||
activity.getString(R.string.write_progress_message));
|
|
||||||
return writeProgressDialog;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
|
||||||
switch (id) {
|
|
||||||
case DIALOG_SEND_TO_GOOGLE:
|
|
||||||
activity.resetSendToGoogleStatus();
|
|
||||||
break;
|
|
||||||
case DIALOG_SEND_TO_GOOGLE_RESULT:
|
|
||||||
boolean success = activity.getSendToGoogleSuccess();
|
|
||||||
sendToGoogleResultDialog.setTitle(
|
|
||||||
success ? R.string.success : R.string.error);
|
|
||||||
sendToGoogleResultDialog.setIcon(success
|
|
||||||
? android.R.drawable.ic_dialog_info
|
|
||||||
: android.R.drawable.ic_dialog_alert);
|
|
||||||
sendToGoogleResultDialog.setMessage(activity.getMapsResultMessage());
|
|
||||||
|
|
||||||
boolean canShare = activity.getSendToMyMapsMapId() != null;
|
|
||||||
View share =
|
|
||||||
sendToGoogleResultDialog.findViewById(android.R.id.button3);
|
|
||||||
if (share != null) {
|
|
||||||
share.setVisibility(canShare ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DIALOG_CHART_SETTINGS:
|
|
||||||
Log.d(MyTracksConstants.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 SendToGoogleDialog 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 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(MyTracksConstants.TAG,
|
|
||||||
"Could not display dialog with id " + id, e);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"Could not display dialog with id " + id, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ import com.google.android.apps.mytracks.io.TrackWriterFactory;
|
|||||||
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
import com.google.android.apps.mytracks.io.TrackWriterFactory.TrackFileFormat;
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -47,7 +46,7 @@ public class ExportAllTracks {
|
|||||||
public static final int KML_OPTION_INDEX = 1;
|
public static final int KML_OPTION_INDEX = 1;
|
||||||
public static final int CSV_OPTION_INDEX = 2;
|
public static final int CSV_OPTION_INDEX = 2;
|
||||||
|
|
||||||
private final Activity activity;
|
private final Context context;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
private ProgressDialog progress;
|
private ProgressDialog progress;
|
||||||
|
|
||||||
@@ -71,11 +70,11 @@ public class ExportAllTracks {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public ExportAllTracks(Activity activity) {
|
public ExportAllTracks(Context c) {
|
||||||
this.activity = activity;
|
this.context = c;
|
||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Starting");
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Starting");
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
AlertDialog.Builder builder = new AlertDialog.Builder(c);
|
||||||
builder.setSingleChoiceItems(R.array.export_formats, 0, itemClick);
|
builder.setSingleChoiceItems(R.array.export_formats, 0, itemClick);
|
||||||
builder.setPositiveButton(R.string.ok, positiveClick);
|
builder.setPositiveButton(R.string.ok, positiveClick);
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
@@ -108,11 +107,11 @@ public class ExportAllTracks {
|
|||||||
*/
|
*/
|
||||||
private void aquireLocksAndExport() {
|
private void aquireLocksAndExport() {
|
||||||
SharedPreferences prefs =
|
SharedPreferences prefs =
|
||||||
activity.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
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(context.getString(R.string.recording_track_key), -1);
|
||||||
}
|
}
|
||||||
if (recordingTrackId != -1) {
|
if (recordingTrackId != -1) {
|
||||||
acquireWakeLock();
|
acquireWakeLock();
|
||||||
@@ -128,12 +127,12 @@ public class ExportAllTracks {
|
|||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Releasing wake lock.");
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Releasing wake lock.");
|
||||||
}
|
}
|
||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Done");
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Done");
|
||||||
Toast.makeText(activity, R.string.export_done, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, R.string.export_done, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeProgressDialog(final int trackCount) {
|
private void makeProgressDialog(final int trackCount) {
|
||||||
String exportMsg = activity.getString(R.string.tracklist_btn_export_all);
|
String exportMsg = context.getString(R.string.tracklist_btn_export_all);
|
||||||
progress = new ProgressDialog(activity);
|
progress = new ProgressDialog(context);
|
||||||
progress.setIcon(android.R.drawable.ic_dialog_info);
|
progress.setIcon(android.R.drawable.ic_dialog_info);
|
||||||
progress.setTitle(exportMsg);
|
progress.setTitle(exportMsg);
|
||||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
@@ -151,7 +150,7 @@ public class ExportAllTracks {
|
|||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
MyTracksProviderUtils providerUtils =
|
MyTracksProviderUtils providerUtils =
|
||||||
MyTracksProviderUtils.Factory.get(activity);
|
MyTracksProviderUtils.Factory.get(context);
|
||||||
cursor = providerUtils.getTracksCursor("");
|
cursor = providerUtils.getTracksCursor("");
|
||||||
if (cursor == null) {
|
if (cursor == null) {
|
||||||
return;
|
return;
|
||||||
@@ -161,7 +160,7 @@ public class ExportAllTracks {
|
|||||||
Log.i(MyTracksConstants.TAG,
|
Log.i(MyTracksConstants.TAG,
|
||||||
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
|
"ExportAllTracks: Exporting: " + cursor.getCount() + " tracks.");
|
||||||
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
int idxTrackId = cursor.getColumnIndexOrThrow(TracksColumns._ID);
|
||||||
activity.runOnUiThread(new Runnable() {
|
MyTracks.getInstance().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
makeProgressDialog(trackCount);
|
makeProgressDialog(trackCount);
|
||||||
}
|
}
|
||||||
@@ -169,7 +168,7 @@ public class ExportAllTracks {
|
|||||||
|
|
||||||
for (int i = 0; cursor.moveToNext(); i++) {
|
for (int i = 0; cursor.moveToNext(); i++) {
|
||||||
final int status = i;
|
final int status = i;
|
||||||
activity.runOnUiThread(new Runnable() {
|
MyTracks.getInstance().runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (progress == null) {
|
if (progress == null) {
|
||||||
@@ -183,13 +182,13 @@ public class ExportAllTracks {
|
|||||||
long id = cursor.getLong(idxTrackId);
|
long id = cursor.getLong(idxTrackId);
|
||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: exporting: " + id);
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: exporting: " + id);
|
||||||
TrackWriter writer =
|
TrackWriter writer =
|
||||||
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
|
TrackWriterFactory.newWriter(context, providerUtils, id, format);
|
||||||
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();
|
int error = writer.getErrorMessage();
|
||||||
Toast.makeText(activity, error, Toast.LENGTH_LONG).show();
|
Toast.makeText(context, error, Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,7 +213,7 @@ public class ExportAllTracks {
|
|||||||
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
|
Log.i(MyTracksConstants.TAG, "ExportAllTracks: Aquiring wake lock.");
|
||||||
try {
|
try {
|
||||||
PowerManager pm =
|
PowerManager pm =
|
||||||
(PowerManager) activity.getSystemService(Context.POWER_SERVICE);
|
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
if (pm == null) {
|
if (pm == null) {
|
||||||
Log.e(MyTracksConstants.TAG,
|
Log.e(MyTracksConstants.TAG,
|
||||||
"ExportAllTracks: Power manager not found!");
|
"ExportAllTracks: Power manager not found!");
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import android.view.MenuItem;
|
|||||||
*/
|
*/
|
||||||
class MenuManager {
|
class MenuManager {
|
||||||
|
|
||||||
private final MyTracks activity;
|
private MyTracks activity;
|
||||||
|
|
||||||
public MenuManager(MyTracks activity) {
|
public MenuManager(MyTracks activity) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
@@ -44,12 +44,12 @@ class MenuManager {
|
|||||||
menu.findItem(R.id.menu_list_tracks).setEnabled(hasRecorded);
|
menu.findItem(R.id.menu_list_tracks).setEnabled(hasRecorded);
|
||||||
menu.findItem(R.id.menu_list_markers)
|
menu.findItem(R.id.menu_list_markers)
|
||||||
.setEnabled(hasRecorded && hasSelectedTrack);
|
.setEnabled(hasRecorded && hasSelectedTrack);
|
||||||
menu.findItem(R.id.menu_start_recording)
|
MenuItem startRecording = menu.findItem(R.id.menu_start_recording);
|
||||||
.setEnabled(!isRecording)
|
startRecording.setEnabled(!isRecording);
|
||||||
.setVisible(!isRecording);
|
startRecording.setVisible(!isRecording);
|
||||||
menu.findItem(R.id.menu_stop_recording)
|
MenuItem stopRecording = menu.findItem(R.id.menu_stop_recording);
|
||||||
.setEnabled(isRecording)
|
stopRecording.setEnabled(isRecording);
|
||||||
.setVisible(isRecording);
|
stopRecording.setVisible(isRecording);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
@@ -69,7 +69,7 @@ class MenuManager {
|
|||||||
}
|
}
|
||||||
case R.id.menu_list_markers: {
|
case R.id.menu_list_markers: {
|
||||||
Intent startIntent = new Intent(activity, MyTracksWaypointsList.class);
|
Intent startIntent = new Intent(activity, MyTracksWaypointsList.class);
|
||||||
startIntent.putExtra("trackid", activity.getSelectedTrackId());
|
startIntent.putExtra("trackid", activity.getSelectedTrack());
|
||||||
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
|
activity.startActivityForResult(startIntent, MyTracksConstants.SHOW_WAYPOINT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ class MenuManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case MyTracksConstants.MENU_CLEAR_MAP: {
|
case MyTracksConstants.MENU_CLEAR_MAP: {
|
||||||
activity.setSelectedTrackId(-1);
|
activity.setSelectedTrack(-1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants used by the MyTracks application.
|
* Constants used by the MyTracks application.
|
||||||
*
|
*
|
||||||
@@ -38,6 +40,11 @@ public abstract class MyTracksConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String SDCARD_TOP_DIR = "MyTracks";
|
public static final String SDCARD_TOP_DIR = "MyTracks";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API level of the Android version we're being run under.
|
||||||
|
*/
|
||||||
|
public static final int ANDROID_API_LEVEL = Integer.parseInt(Build.VERSION.SDK);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* onActivityResult request codes:
|
* onActivityResult request codes:
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ import com.google.android.apps.mytracks.content.Track;
|
|||||||
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
import com.google.android.apps.mytracks.content.TrackPointsColumns;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
import com.google.android.apps.mytracks.services.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.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
import com.google.android.maps.MapActivity;
|
import com.google.android.maps.MapActivity;
|
||||||
@@ -49,16 +47,17 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SubMenu;
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
|
||||||
import android.view.View.OnCreateContextMenuListener;
|
import android.view.View.OnCreateContextMenuListener;
|
||||||
|
import android.view.Window;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
@@ -118,7 +117,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
private double variation;
|
private double variation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From the shared preferences.
|
* From the shared preferences:
|
||||||
*/
|
*/
|
||||||
private int minRequiredAccuracy =
|
private int minRequiredAccuracy =
|
||||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY;
|
||||||
@@ -135,23 +134,19 @@ public class MyTracksMap extends MapActivity
|
|||||||
private Location currentLocation;
|
private Location currentLocation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread with a looper. Post to updateTrackHandler to execute
|
* A thread with a looper. Post to updateTrackHandler to execute Runnables on
|
||||||
* {@link Runnable}s on this thread.
|
* this thread.
|
||||||
*/
|
*/
|
||||||
private HandlerThread updateTrackThread;
|
private HandlerThread updateTrackThread;
|
||||||
|
|
||||||
/**
|
/** Handler for updateTrackThread */
|
||||||
* Handler for updateTrackThread.
|
|
||||||
*/
|
|
||||||
private Handler updateTrackHandler;
|
private Handler updateTrackHandler;
|
||||||
|
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable that updates the track from the provider (looking for points
|
* A runnable that updates the track from the provider (looking for points
|
||||||
* added after "lastSeenLocationId").
|
* added after "lastSeenLocationId".
|
||||||
*/
|
*/
|
||||||
private final Runnable updateTrackRunnable = new Runnable() {
|
private final Runnable updateTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -159,7 +154,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
if (!isATrackSelected()) {
|
if (!isATrackSelected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readAllNewTrackPoints();
|
readAllNewTrackPoints();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -173,7 +167,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
if (!isATrackSelected()) {
|
if (!isATrackSelected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOverlay.clearPoints();
|
mapOverlay.clearPoints();
|
||||||
firstSeenLocationId = -1;
|
firstSeenLocationId = -1;
|
||||||
lastSeenLocationId = -1;
|
lastSeenLocationId = -1;
|
||||||
@@ -199,11 +192,14 @@ public class MyTracksMap extends MapActivity
|
|||||||
cursor = providerUtils.getWaypointsCursor(
|
cursor = providerUtils.getWaypointsCursor(
|
||||||
selectedTrackId, 0,
|
selectedTrackId, 0,
|
||||||
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
MyTracksConstants.MAX_DISPLAYED_WAYPOINTS_POINTS);
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
if (cursor != null) {
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
do {
|
do {
|
||||||
mapOverlay.addWaypoint(providerUtils.createWaypoint(cursor));
|
Waypoint wpt = providerUtils.createWaypoint(cursor);
|
||||||
|
mapOverlay.addWaypoint(wpt);
|
||||||
} while (cursor.moveToNext());
|
} while (cursor.moveToNext());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
Log.w(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -216,9 +212,9 @@ public class MyTracksMap extends MapActivity
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable intended to be posted to the {@code #updateTrackThread} after
|
* A runnable intended to be posted to the updateTrackThread after the
|
||||||
* the selected track changes. It will post to the UI thread to update
|
* selected track changes. It will post to the ui thread to update the screen
|
||||||
* the screen elements and move the map to show the selected track.
|
* elements and move the map to show the selected track.
|
||||||
*/
|
*/
|
||||||
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
private final Runnable setSelectedTrackRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -286,9 +282,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
|
|
||||||
// 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 =
|
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
|
||||||
setVolumeControlStream(volumeStream);
|
|
||||||
|
|
||||||
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
providerUtils = MyTracksProviderUtils.Factory.get(this);
|
||||||
|
|
||||||
@@ -333,6 +327,12 @@ public class MyTracksMap extends MapActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onChange(boolean selfChange) {
|
public void onChange(boolean selfChange) {
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange");
|
Log.d(MyTracksConstants.TAG, "MyTracksMap: ContentObserver.onChange");
|
||||||
|
// Check for any new locations and append them to the currently
|
||||||
|
// recording track:
|
||||||
|
if (!MyTracks.getInstance().isRecording()) {
|
||||||
|
// No track is being recorded. We should not be here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isRecordingSelected()) {
|
if (!isRecordingSelected()) {
|
||||||
// No track, or one other than the recording track is selected,
|
// No track, or one other than the recording track is selected,
|
||||||
// don't bother.
|
// don't bother.
|
||||||
@@ -357,30 +357,27 @@ public class MyTracksMap extends MapActivity
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read shared preferences and register change listener.
|
// Read shared preferences and register change listener:
|
||||||
sharedPreferences = getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
SharedPreferences preferences =
|
||||||
if (sharedPreferences != null) {
|
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
reloadSharedPreferences(sharedPreferences, null);
|
if (preferences != null) {
|
||||||
|
minRequiredAccuracy = preferences.getInt(
|
||||||
|
getString(R.string.min_required_accuracy_key),
|
||||||
|
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
||||||
|
recordingTrackId =
|
||||||
|
preferences.getLong(getString(R.string.recording_track_key), -1);
|
||||||
|
long selectedTrackId =
|
||||||
|
preferences.getLong(getString(R.string.selected_track_key), -1);
|
||||||
|
if (selectedTrackId >= 0) {
|
||||||
|
setSelectedTrack(selectedTrackId);
|
||||||
|
}
|
||||||
updateOptionsButton();
|
updateOptionsButton();
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
mapOverlay.setDrawBounds(preferences.getBoolean(
|
||||||
|
getString(R.string.debug_draw_bounds_key), false));
|
||||||
|
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onDestroy");
|
|
||||||
|
|
||||||
if (updateTrackThread != null) {
|
|
||||||
ApiFeatures.getInstance().getApiPlatformAdapter().stopHandlerThread(
|
|
||||||
updateTrackThread);
|
|
||||||
}
|
|
||||||
if (sharedPreferences != null) {
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether there's a track currently selected for display.
|
* Returns whether there's a track currently selected for display.
|
||||||
*/
|
*/
|
||||||
@@ -442,9 +439,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.onResume");
|
Log.d(MyTracksConstants.TAG, "MyTracksMap.onResume");
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
// Reload all preferences as they might have changed since last run.
|
|
||||||
reloadSharedPreferences(sharedPreferences, null);
|
|
||||||
|
|
||||||
// Make sure any updates that might have happened are propagated to the
|
// Make sure any updates that might have happened are propagated to the
|
||||||
// Map overlay:
|
// Map overlay:
|
||||||
observer.onChange(false);
|
observer.onChange(false);
|
||||||
@@ -701,14 +695,11 @@ public class MyTracksMap extends MapActivity
|
|||||||
* @param trackId a given track id
|
* @param trackId a given track id
|
||||||
*/
|
*/
|
||||||
public void setSelectedTrack(final long trackId) {
|
public void setSelectedTrack(final long trackId) {
|
||||||
Log.d(MyTracksConstants.TAG, "MyTracksMap.setSelectedTrack: "
|
|
||||||
+ "selectedTrackId = " + selectedTrackId + ", trackId = " + trackId);
|
|
||||||
|
|
||||||
if (selectedTrackId == trackId) {
|
if (selectedTrackId == trackId) {
|
||||||
// Selected track did not change, nothing to do.
|
// Selected track did not change, nothing to do.
|
||||||
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
|
mapOverlay.setTrackDrawingEnabled(isATrackSelected());
|
||||||
updateOptionsButton();
|
|
||||||
mapView.invalidate();
|
mapView.invalidate();
|
||||||
|
updateOptionsButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,7 +774,7 @@ public class MyTracksMap extends MapActivity
|
|||||||
menu.setHeaderTitle(R.string.tracklist_this_track);
|
menu.setHeaderTitle(R.string.tracklist_this_track);
|
||||||
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
|
menu.add(0, MyTracksConstants.MENU_EDIT, 0,
|
||||||
R.string.tracklist_edit_track);
|
R.string.tracklist_edit_track);
|
||||||
if (!isRecordingSelected()) {
|
if (!MyTracks.getInstance().isRecording() || !isRecordingSelected()) {
|
||||||
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
|
menu.add(0, MyTracksConstants.MENU_SEND_TO_GOOGLE, 0,
|
||||||
R.string.tracklist_send_to_google);
|
R.string.tracklist_send_to_google);
|
||||||
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
|
SubMenu share = menu.addSubMenu(0, MyTracksConstants.MENU_SHARE, 0,
|
||||||
@@ -902,14 +893,36 @@ public class MyTracksMap extends MapActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSharedPreferenceChanged(
|
public void onSharedPreferenceChanged(
|
||||||
final SharedPreferences sharedPreferences, final String key) {
|
final SharedPreferences sharedPreferences,
|
||||||
Log.d(MyTracksConstants.TAG,
|
final String key) {
|
||||||
"MyTracksMap.onSharedPreferenceChanged: " + key);
|
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
uiHandler.post(new Runnable() {
|
uiHandler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
reloadSharedPreferences(sharedPreferences, key);
|
if (key.equals(getString(R.string.min_required_accuracy_key))) {
|
||||||
|
minRequiredAccuracy = sharedPreferences.getInt(
|
||||||
|
getString(R.string.min_required_accuracy_key),
|
||||||
|
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
||||||
|
} else if (key.equals(getString(R.string.selected_track_key))) {
|
||||||
|
long selectedTrackId =
|
||||||
|
sharedPreferences.getLong(
|
||||||
|
getString(R.string.selected_track_key),
|
||||||
|
-1);
|
||||||
|
setSelectedTrack(selectedTrackId);
|
||||||
|
} else if (key.equals(getString(R.string.recording_track_key))) {
|
||||||
|
recordingTrackId =
|
||||||
|
sharedPreferences.getLong(
|
||||||
|
getString(R.string.recording_track_key),
|
||||||
|
-1);
|
||||||
|
if (isATrackSelected()) {
|
||||||
|
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
||||||
|
mapView.postInvalidate();
|
||||||
|
}
|
||||||
|
} else if (key.equals(getString(R.string.debug_draw_bounds_key))) {
|
||||||
|
mapOverlay.setDrawBounds(
|
||||||
|
sharedPreferences.getBoolean(
|
||||||
|
getString(R.string.debug_draw_bounds_key), false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -937,8 +950,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
public void onLocationChanged(Location location) {
|
public void onLocationChanged(Location location) {
|
||||||
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
if (location.getProvider().equals(MyTracksConstants.GPS_PROVIDER)) {
|
||||||
// Recalculate the variation if there was a jump in location > 1km:
|
// Recalculate the variation if there was a jump in location > 1km:
|
||||||
if (currentLocation == null ||
|
if (currentLocation == null
|
||||||
location.distanceTo(currentLocation) > 1000) {
|
|| location.distanceTo(currentLocation) > 1000) {
|
||||||
setVariation(location);
|
setVariation(location);
|
||||||
}
|
}
|
||||||
currentLocation = location;
|
currentLocation = location;
|
||||||
@@ -989,31 +1002,6 @@ public class MyTracksMap extends MapActivity
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private void reloadSharedPreferences(SharedPreferences sharedPreferences,
|
|
||||||
String key) {
|
|
||||||
if (key == null ||
|
|
||||||
key.equals(getString(R.string.min_required_accuracy_key))) {
|
|
||||||
minRequiredAccuracy = sharedPreferences.getInt(
|
|
||||||
getString(R.string.min_required_accuracy_key),
|
|
||||||
MyTracksSettings.DEFAULT_MIN_REQUIRED_ACCURACY);
|
|
||||||
}
|
|
||||||
if (key == null || key.equals(getString(R.string.recording_track_key))) {
|
|
||||||
recordingTrackId = sharedPreferences.getLong(
|
|
||||||
getString(R.string.recording_track_key), -1);
|
|
||||||
}
|
|
||||||
if (key == null || key.equals(getString(R.string.selected_track_key))) {
|
|
||||||
setSelectedTrack(sharedPreferences.getLong(
|
|
||||||
getString(R.string.selected_track_key), -1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show end marker if the track has been selected and is not recording.
|
|
||||||
// Note: This check must be *after* a call to setSelectedTrack(...) above.
|
|
||||||
if (isATrackSelected()) {
|
|
||||||
mapOverlay.setShowEndMarker(!isRecordingSelected());
|
|
||||||
mapView.postInvalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readAllNewTrackPoints() {
|
private void readAllNewTrackPoints() {
|
||||||
int numPoints = mapOverlay.getNumLocations();
|
int numPoints = mapOverlay.getNumLocations();
|
||||||
if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) {
|
if (numPoints >= MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS) {
|
||||||
@@ -1023,16 +1011,15 @@ public class MyTracksMap extends MapActivity
|
|||||||
loadSelectedTrack();
|
loadSelectedTrack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Keep a copy of selectedTrackId, because it can change asynchronously.
|
|
||||||
long currentSelectedTrackId = selectedTrackId;
|
|
||||||
long lastStoredLocationId =
|
long lastStoredLocationId =
|
||||||
providerUtils.getLastLocationId(currentSelectedTrackId);
|
providerUtils.getLastLocationId(selectedTrackId);
|
||||||
int samplingFrequency = -1;
|
int samplingFrequency = -1;
|
||||||
Location location = new Location("");
|
Location location = new Location("");
|
||||||
while (currentSelectedTrackId == selectedTrackId) {
|
for (;;) {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = providerUtils.getLocationsCursor(currentSelectedTrackId,
|
cursor = providerUtils.getLocationsCursor(selectedTrackId,
|
||||||
lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false);
|
lastSeenLocationId + 1, TRACKPOINT_BUFFER_SIZE, false);
|
||||||
if (cursor == null || !cursor.moveToFirst()) {
|
if (cursor == null || !cursor.moveToFirst()) {
|
||||||
// No (more) data
|
// No (more) data
|
||||||
@@ -1052,16 +1039,8 @@ public class MyTracksMap extends MapActivity
|
|||||||
// Now we already have at least one point, calculate the sampling
|
// Now we already have at least one point, calculate the sampling
|
||||||
// frequency
|
// frequency
|
||||||
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
|
long numTotalPoints = lastStoredLocationId - firstSeenLocationId;
|
||||||
samplingFrequency = (int) (1 + numTotalPoints
|
samplingFrequency = (int) (1 +
|
||||||
/ MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
numTotalPoints / MyTracksConstants.TARGET_DISPLAYED_TRACK_POINTS);
|
||||||
// TODO: This shouldn't happen after adding currentSelectedTrackId,
|
|
||||||
// but just to be safe until we have 100% confidence.
|
|
||||||
if (samplingFrequency <= 0) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"readAllNewTrackPoints: samplingFreq <= 0, numTotalPoints = "
|
|
||||||
+ numTotalPoints + ", trackId = " + currentSelectedTrackId);
|
|
||||||
samplingFrequency = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
providerUtils.fillLocation(cursor, location);
|
providerUtils.fillLocation(cursor, location);
|
||||||
@@ -1073,13 +1052,12 @@ public class MyTracksMap extends MapActivity
|
|||||||
if (numPoints % samplingFrequency == 0 ||
|
if (numPoints % samplingFrequency == 0 ||
|
||||||
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
|
(!isRecordingSelected() && locationId == lastStoredLocationId) ||
|
||||||
!MyTracksUtils.isValidLocation(location)) {
|
!MyTracksUtils.isValidLocation(location)) {
|
||||||
// No need to allocate a new location (we can safely reuse the existing).
|
// Only allocate a new location if it is going to be kept around.
|
||||||
mapOverlay.addLocation(location);
|
mapOverlay.addLocation(new Location(location));
|
||||||
}
|
}
|
||||||
|
|
||||||
numPoints++;
|
numPoints++;
|
||||||
} while (cursor.moveToNext() &&
|
} while (cursor.moveToNext());
|
||||||
currentSelectedTrackId == selectedTrackId);
|
|
||||||
} finally {
|
} finally {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
|
|||||||
@@ -20,24 +20,20 @@ import com.google.android.apps.mytracks.util.MyTracksUtils;
|
|||||||
import com.google.android.maps.GeoPoint;
|
import com.google.android.maps.GeoPoint;
|
||||||
import com.google.android.maps.MapView;
|
import com.google.android.maps.MapView;
|
||||||
import com.google.android.maps.Overlay;
|
import com.google.android.maps.Overlay;
|
||||||
import com.google.android.maps.Projection;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Region;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map overlay that displays a "MyLocation" arrow, an error circle, the
|
* A map overlay that displays a "MyLocation" arrow, an error circle, the
|
||||||
@@ -47,7 +43,7 @@ import java.util.concurrent.BlockingQueue;
|
|||||||
*/
|
*/
|
||||||
public class MyTracksOverlay extends Overlay {
|
public class MyTracksOverlay extends Overlay {
|
||||||
|
|
||||||
private final Drawable[] arrows;
|
private final Drawable arrow[] = new Drawable[18];
|
||||||
private final int arrowWidth, arrowHeight;
|
private final int arrowWidth, arrowHeight;
|
||||||
private final Drawable statsMarker;
|
private final Drawable statsMarker;
|
||||||
private final Drawable waypointMarker;
|
private final Drawable waypointMarker;
|
||||||
@@ -57,94 +53,68 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
private final Paint selectedTrackPaint;
|
private final Paint selectedTrackPaint;
|
||||||
private final Paint errorCirclePaint;
|
private final Paint errorCirclePaint;
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final List<Waypoint> waypoints;
|
private final ArrayList<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||||
private final List<CachedLocation> points;
|
private final ArrayList<Location> points = new ArrayList<Location>(1024);
|
||||||
private final BlockingQueue<CachedLocation> pendingPoints;
|
|
||||||
|
|
||||||
private boolean trackDrawingEnabled;
|
private boolean trackDrawingEnabled;
|
||||||
private int lastHeading = 0;
|
private int lastHeading = 0;
|
||||||
private Location myLocation;
|
private Location myLocation;
|
||||||
private boolean showEndMarker = true;
|
private boolean showEndMarker = true;
|
||||||
// TODO: Remove it completely after completing performance tests.
|
private boolean drawBounds;
|
||||||
private boolean alwaysVisible = true;
|
|
||||||
|
|
||||||
private GeoPoint lastReferencePoint;
|
|
||||||
private Rect lastViewRect;
|
|
||||||
private Path lastPath;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a pre-processed {@code Location} to speed up drawing.
|
|
||||||
* This class is more like a data object and doesn't provide accessors.
|
|
||||||
*/
|
|
||||||
private static class CachedLocation {
|
|
||||||
public final boolean valid;
|
|
||||||
public final GeoPoint geoPoint;
|
|
||||||
|
|
||||||
public CachedLocation(Location location) {
|
|
||||||
this.valid = MyTracksUtils.isValidLocation(location);
|
|
||||||
this.geoPoint = valid ? MyTracksUtils.getGeoPoint(location) : null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public MyTracksOverlay(Context context) {
|
public MyTracksOverlay(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
this.waypoints = new ArrayList<Waypoint>();
|
|
||||||
this.points = new ArrayList<CachedLocation>(1024);
|
|
||||||
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
|
|
||||||
MyTracksConstants.MAX_DISPLAYED_TRACK_POINTS, true);
|
|
||||||
|
|
||||||
// TODO: Can we use a FrameAnimation or similar here rather
|
// TODO: Can we use a FrameAnimation or similar here rather
|
||||||
// than individual resources for each arrow direction?
|
// than individual resources for each arrow direction?
|
||||||
final Resources resources = context.getResources();
|
|
||||||
arrows = new Drawable[] {
|
arrow[0] = context.getResources().getDrawable(R.drawable.arrow_0);
|
||||||
resources.getDrawable(R.drawable.arrow_0),
|
arrow[1] = context.getResources().getDrawable(R.drawable.arrow_20);
|
||||||
resources.getDrawable(R.drawable.arrow_20),
|
arrow[2] = context.getResources().getDrawable(R.drawable.arrow_40);
|
||||||
resources.getDrawable(R.drawable.arrow_40),
|
arrow[3] = context.getResources().getDrawable(R.drawable.arrow_60);
|
||||||
resources.getDrawable(R.drawable.arrow_60),
|
arrow[4] = context.getResources().getDrawable(R.drawable.arrow_80);
|
||||||
resources.getDrawable(R.drawable.arrow_80),
|
arrow[5] = context.getResources().getDrawable(R.drawable.arrow_100);
|
||||||
resources.getDrawable(R.drawable.arrow_100),
|
arrow[6] = context.getResources().getDrawable(R.drawable.arrow_120);
|
||||||
resources.getDrawable(R.drawable.arrow_120),
|
arrow[7] = context.getResources().getDrawable(R.drawable.arrow_140);
|
||||||
resources.getDrawable(R.drawable.arrow_140),
|
arrow[8] = context.getResources().getDrawable(R.drawable.arrow_160);
|
||||||
resources.getDrawable(R.drawable.arrow_160),
|
arrow[9] = context.getResources().getDrawable(R.drawable.arrow_180);
|
||||||
resources.getDrawable(R.drawable.arrow_180),
|
arrow[10] = context.getResources().getDrawable(R.drawable.arrow_200);
|
||||||
resources.getDrawable(R.drawable.arrow_200),
|
arrow[11] = context.getResources().getDrawable(R.drawable.arrow_220);
|
||||||
resources.getDrawable(R.drawable.arrow_220),
|
arrow[12] = context.getResources().getDrawable(R.drawable.arrow_240);
|
||||||
resources.getDrawable(R.drawable.arrow_240),
|
arrow[13] = context.getResources().getDrawable(R.drawable.arrow_260);
|
||||||
resources.getDrawable(R.drawable.arrow_260),
|
arrow[14] = context.getResources().getDrawable(R.drawable.arrow_280);
|
||||||
resources.getDrawable(R.drawable.arrow_280),
|
arrow[15] = context.getResources().getDrawable(R.drawable.arrow_300);
|
||||||
resources.getDrawable(R.drawable.arrow_300),
|
arrow[16] = context.getResources().getDrawable(R.drawable.arrow_320);
|
||||||
resources.getDrawable(R.drawable.arrow_320),
|
arrow[17] = context.getResources().getDrawable(R.drawable.arrow_340);
|
||||||
resources.getDrawable(R.drawable.arrow_340)
|
arrowWidth = arrow[lastHeading].getIntrinsicWidth();
|
||||||
};
|
arrowHeight = arrow[lastHeading].getIntrinsicHeight();
|
||||||
arrowWidth = arrows[lastHeading].getIntrinsicWidth();
|
for (int i = 0; i <= 17; i++) {
|
||||||
arrowHeight = arrows[lastHeading].getIntrinsicHeight();
|
arrow[i].setBounds(0, 0, arrowWidth, arrowHeight);
|
||||||
for (Drawable arrow : arrows) {
|
|
||||||
arrow.setBounds(0, 0, arrowWidth, arrowHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statsMarker = resources.getDrawable(R.drawable.ylw_pushpin);
|
statsMarker = context.getResources().getDrawable(R.drawable.ylw_pushpin);
|
||||||
markerWidth = statsMarker.getIntrinsicWidth();
|
markerWidth = statsMarker.getIntrinsicWidth();
|
||||||
markerHeight = statsMarker.getIntrinsicHeight();
|
markerHeight = statsMarker.getIntrinsicHeight();
|
||||||
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
statsMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
startMarker = resources.getDrawable(R.drawable.green_dot);
|
startMarker = context.getResources().getDrawable(R.drawable.green_dot);
|
||||||
startMarker.setBounds(0, 0, markerWidth, markerHeight);
|
startMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
endMarker = resources.getDrawable(R.drawable.red_dot);
|
endMarker = context.getResources().getDrawable(R.drawable.red_dot);
|
||||||
endMarker.setBounds(0, 0, markerWidth, markerHeight);
|
endMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
waypointMarker = resources.getDrawable(R.drawable.blue_pushpin);
|
waypointMarker =
|
||||||
|
context.getResources().getDrawable(R.drawable.blue_pushpin);
|
||||||
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
waypointMarker.setBounds(0, 0, markerWidth, markerHeight);
|
||||||
|
|
||||||
selectedTrackPaint = new Paint();
|
selectedTrackPaint = new Paint();
|
||||||
selectedTrackPaint.setColor(resources.getColor(R.color.red));
|
selectedTrackPaint.setColor(context.getResources().getColor(R.color.red));
|
||||||
selectedTrackPaint.setStrokeWidth(3);
|
selectedTrackPaint.setStrokeWidth(3);
|
||||||
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
selectedTrackPaint.setStyle(Paint.Style.STROKE);
|
||||||
selectedTrackPaint.setAntiAlias(true);
|
selectedTrackPaint.setAntiAlias(true);
|
||||||
|
|
||||||
errorCirclePaint = new Paint();
|
errorCirclePaint = new Paint();
|
||||||
errorCirclePaint.setColor(resources.getColor(R.color.blue));
|
errorCirclePaint.setColor(context.getResources().getColor(R.color.blue));
|
||||||
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
errorCirclePaint.setStyle(Paint.Style.STROKE);
|
||||||
errorCirclePaint.setStrokeWidth(3);
|
errorCirclePaint.setStrokeWidth(3);
|
||||||
errorCirclePaint.setAlpha(127);
|
errorCirclePaint.setAlpha(127);
|
||||||
@@ -154,50 +124,35 @@ public class MyTracksOverlay 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 takes ownership of this location and may change it.
|
||||||
* safe to reuse the same location while calling this method.
|
|
||||||
*
|
*
|
||||||
* @param l the location to add.
|
* @param l the location to add
|
||||||
*/
|
*/
|
||||||
public void addLocation(Location l) {
|
public void addLocation(Location l) {
|
||||||
// Queue up in the pending queue until it's merged with {@code #points}.
|
if (l != null) {
|
||||||
pendingPoints.offer(new CachedLocation(l));
|
synchronized (points) {
|
||||||
|
points.add(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addWaypoint(Waypoint wpt) {
|
public void addWaypoint(Waypoint wpt) {
|
||||||
// Note: We don't cache waypoints, because it's not worth the effort.
|
|
||||||
if (wpt != null && wpt.getLocation() != null) {
|
|
||||||
synchronized (waypoints) {
|
|
||||||
waypoints.add(wpt);
|
waypoints.add(wpt);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getNumLocations() {
|
public int getNumLocations() {
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
return points.size() + pendingPoints.size();
|
return points.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
public void clearWaypoints() {
|
||||||
int getNumWaypoints() {
|
waypoints.clear();
|
||||||
synchronized (waypoints) {
|
|
||||||
return waypoints.size();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearPoints() {
|
public void clearPoints() {
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
points.clear();
|
points.clear();
|
||||||
pendingPoints.clear();
|
|
||||||
lastPath = null;
|
|
||||||
lastViewRect = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearWaypoints() {
|
|
||||||
synchronized (waypoints) {
|
|
||||||
waypoints.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,175 +170,230 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's safe to keep projection within a single draw operation.
|
|
||||||
final Projection projection = getMapProjection(mapView);
|
|
||||||
// Get the current viewing window.
|
|
||||||
if (trackDrawingEnabled) {
|
if (trackDrawingEnabled) {
|
||||||
Rect viewRect = getMapViewRect(mapView);
|
|
||||||
|
|
||||||
// Draw the selected track:
|
// Draw the selected track:
|
||||||
drawTrack(canvas, projection, viewRect);
|
drawTrack(canvas, mapView);
|
||||||
|
|
||||||
// Draw the waypoints:
|
// Draw the waypoints:
|
||||||
drawWaypoints(canvas, projection);
|
drawWaypoints(canvas, mapView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the current location
|
// Draw the current location
|
||||||
drawMyLocation(canvas, projection);
|
drawMyLocation(canvas, mapView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
private void drawWaypoints(Canvas canvas, MapView mapView) {
|
||||||
Projection getMapProjection(MapView mapView) {
|
ArrayList<Waypoint> currentWaypoints = waypoints;
|
||||||
return mapView.getProjection();
|
for (int i = 1; i < currentWaypoints.size(); i++) {
|
||||||
|
Waypoint wpt = currentWaypoints.get(i);
|
||||||
|
if (wpt == null) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
|
||||||
Rect getMapViewRect(MapView mapView) {
|
|
||||||
int w = mapView.getLongitudeSpan();
|
|
||||||
int h = mapView.getLatitudeSpan();
|
|
||||||
int cx = mapView.getMapCenter().getLongitudeE6();
|
|
||||||
int cy = mapView.getMapCenter().getLatitudeE6();
|
|
||||||
return new Rect(cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing.
|
|
||||||
Path newPath() {
|
|
||||||
return new Path();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing.
|
|
||||||
Path getLastPath() {
|
|
||||||
return lastPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawWaypoints(Canvas canvas, Projection projection) {
|
|
||||||
synchronized (waypoints) {;
|
|
||||||
for (Waypoint wpt : waypoints) {
|
|
||||||
Location loc = wpt.getLocation();
|
Location loc = wpt.getLocation();
|
||||||
drawElement(canvas, projection, MyTracksUtils.getGeoPoint(loc),
|
if (loc == null) {
|
||||||
wpt.getType() == Waypoint.TYPE_STATISTICS ? statsMarker
|
continue;
|
||||||
: waypointMarker, -(markerWidth / 2) + 3, -markerHeight);
|
|
||||||
}
|
}
|
||||||
|
GeoPoint geoPoint = MyTracksUtils.getGeoPoint(loc);
|
||||||
|
Point pt = new Point();
|
||||||
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(pt.x - (markerWidth / 2) + 3, pt.y - (markerHeight));
|
||||||
|
if (wpt.getType() == Waypoint.TYPE_STATISTICS) {
|
||||||
|
statsMarker.draw(canvas);
|
||||||
|
} else {
|
||||||
|
waypointMarker.draw(canvas);
|
||||||
|
}
|
||||||
|
canvas.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawMyLocation(Canvas canvas, Projection projection) {
|
private void drawMyLocation(Canvas canvas, MapView mapView) {
|
||||||
// Draw the arrow icon.
|
// Draw the arrow icon:
|
||||||
if (myLocation == null) {
|
if (myLocation == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point pt = drawElement(canvas, projection,
|
GeoPoint geoPoint = new GeoPoint(
|
||||||
MyTracksUtils.getGeoPoint(myLocation), arrows[lastHeading],
|
(int) (myLocation.getLatitude() * 1E6),
|
||||||
-(arrowWidth / 2) + 3, -(arrowHeight / 2));
|
(int) (myLocation.getLongitude() * 1E6));
|
||||||
// Draw the error circle.
|
Point pt = new Point();
|
||||||
float radius = projection.metersToEquatorPixels(myLocation.getAccuracy());
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(pt.x - (arrowWidth / 2), pt.y - (arrowHeight / 2));
|
||||||
|
arrow[lastHeading].draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
// Draw the error circle:
|
||||||
|
float radius =
|
||||||
|
mapView.getProjection().metersToEquatorPixels(myLocation.getAccuracy());
|
||||||
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
|
canvas.drawCircle(pt.x, pt.y, radius, errorCirclePaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawTrack(Canvas canvas, Projection projection, Rect viewRect) {
|
private void drawTrack(Canvas canvas, MapView mapView) {
|
||||||
Path path;
|
Path path;
|
||||||
|
Point pt = new Point();
|
||||||
|
Location lastValidLocation;
|
||||||
|
int locLon = 0, locLat = 0;
|
||||||
|
GeoPoint firstGeoPoint = null;
|
||||||
|
boolean lastLocValid;
|
||||||
|
|
||||||
|
// Get the current viewing window:
|
||||||
|
int w = mapView.getLongitudeSpan();
|
||||||
|
int h = mapView.getLatitudeSpan();
|
||||||
|
int cx = mapView.getMapCenter().getLongitudeE6();
|
||||||
|
int cy = mapView.getMapCenter().getLatitudeE6();
|
||||||
|
Rect viewRect = new Rect(cx - w, cy - h, cx + w, cy + h);
|
||||||
|
|
||||||
|
// Global bounding box, including points not visible
|
||||||
|
int allMinLat, allMinLon, allMaxLat, allMaxLon;
|
||||||
|
|
||||||
synchronized (points) {
|
synchronized (points) {
|
||||||
// Merge the pending points with the list of cached locations.
|
|
||||||
final GeoPoint referencePoint = projection.fromPixels(0, 0);
|
|
||||||
int newPoints = pendingPoints.drainTo(points);
|
|
||||||
boolean newProjection = !viewRect.equals(lastViewRect) ||
|
|
||||||
!referencePoint.equals(lastReferencePoint);
|
|
||||||
if (newPoints == 0 && lastPath != null && !newProjection) {
|
|
||||||
// No need to recreate path (same points and viewing area).
|
|
||||||
path = lastPath;
|
|
||||||
} else {
|
|
||||||
int numPoints = points.size();
|
int numPoints = points.size();
|
||||||
if (numPoints < 2) {
|
if (numPoints < 2) {
|
||||||
// Not enough points to draw a path.
|
return;
|
||||||
path = null;
|
}
|
||||||
} else if (lastPath != null && !newProjection) {
|
|
||||||
// Incremental update of the path, without repositioning the view.
|
GeoPoint geoPoint;
|
||||||
path = lastPath;
|
Location loc;
|
||||||
updatePath(projection, viewRect, path, numPoints - newPoints);
|
int minLon, maxLon, minLat, maxLat;
|
||||||
} else {
|
lastValidLocation = points.get(0);
|
||||||
// The view has changed so we have to start from scratch.
|
int lastLocLon = allMinLon = allMaxLon = (int) (lastValidLocation.getLongitude() * 1E6);
|
||||||
path = newPath();
|
int lastLocLat = allMinLat = allMaxLat = (int) (lastValidLocation.getLatitude() * 1E6);
|
||||||
|
lastLocValid = MyTracksUtils.isValidLocation(lastValidLocation);
|
||||||
|
boolean lastLocVisible = false;
|
||||||
|
path = new Path();
|
||||||
|
|
||||||
|
// Loop over track points:
|
||||||
path.incReserve(numPoints);
|
path.incReserve(numPoints);
|
||||||
updatePath(projection, viewRect, path, 0);
|
for (int i = 1; i < numPoints; i++) {
|
||||||
}
|
loc = points.get(i);
|
||||||
lastPath = path;
|
|
||||||
}
|
boolean locValid = MyTracksUtils.isValidLocation(loc);
|
||||||
lastReferencePoint = referencePoint;
|
boolean locVisible = false;
|
||||||
lastViewRect = viewRect;
|
if (locValid) {
|
||||||
}
|
locLon = (int) (loc.getLongitude() * 1E6);
|
||||||
if (path != null) {
|
locLat = (int) (loc.getLatitude() * 1E6);
|
||||||
canvas.drawPath(path, selectedTrackPaint);
|
|
||||||
|
if (firstGeoPoint == null) {
|
||||||
|
// Found the starting point
|
||||||
|
firstGeoPoint = new GeoPoint(locLat, locLon);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the "End" marker.
|
// If both the current and previous locations were valid
|
||||||
if (showEndMarker) {
|
if (lastLocValid) {
|
||||||
for (int i = points.size() - 1; i >= 0; --i) {
|
lastValidLocation = loc;
|
||||||
if (points.get(i).valid) {
|
|
||||||
drawElement(canvas, projection, points.get(i).geoPoint, endMarker,
|
|
||||||
-markerWidth / 2, -markerHeight);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the "Start" marker.
|
// Get the bounding box of the segment about to be drawn
|
||||||
for (int i = 0; i < points.size(); ++i) {
|
if (locLon > lastLocLon) {
|
||||||
if (points.get(i).valid) {
|
minLon = lastLocLon;
|
||||||
drawElement(canvas, projection, points.get(i).geoPoint, startMarker,
|
maxLon = locLon;
|
||||||
-markerWidth / 2, -markerHeight);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePath(Projection projection, Rect viewRect, Path path,
|
|
||||||
int startLocationIdx) {
|
|
||||||
// Whether to start a new segment on new valid and visible point.
|
|
||||||
boolean newSegment =
|
|
||||||
startLocationIdx > 0 ? !points.get(startLocationIdx - 1).valid : true;
|
|
||||||
boolean lastVisible = !newSegment;
|
|
||||||
final Point pt = new Point();
|
|
||||||
// Loop over track points.
|
|
||||||
for (int i = startLocationIdx; i < points.size(); ++i) {
|
|
||||||
CachedLocation loc = points.get(i);
|
|
||||||
// Check if valid, if not then indicate a new segment.
|
|
||||||
if (!loc.valid) {
|
|
||||||
newSegment = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final GeoPoint geoPoint = loc.geoPoint;
|
|
||||||
// Check if this breaks the existing segment.
|
|
||||||
boolean visible = alwaysVisible || viewRect.contains(
|
|
||||||
geoPoint.getLongitudeE6(), geoPoint.getLatitudeE6());
|
|
||||||
if (!visible && !lastVisible) {
|
|
||||||
// This is a point outside view not connected to a visible one.
|
|
||||||
newSegment = true;
|
|
||||||
}
|
|
||||||
lastVisible = visible;
|
|
||||||
|
|
||||||
// Either move to beginning of a new segment or continue the old one.
|
|
||||||
projection.toPixels(geoPoint, pt);
|
|
||||||
if (newSegment) {
|
|
||||||
path.moveTo(pt.x, pt.y);
|
|
||||||
newSegment = false;
|
|
||||||
} else {
|
} else {
|
||||||
|
minLon = locLon;
|
||||||
|
maxLon = lastLocLon;
|
||||||
|
}
|
||||||
|
if (locLat > lastLocLat) {
|
||||||
|
minLat = lastLocLat;
|
||||||
|
maxLat = locLat;
|
||||||
|
} else {
|
||||||
|
minLat = locLat;
|
||||||
|
maxLat = lastLocLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawBounds) {
|
||||||
|
allMaxLat = Math.max(allMaxLat, maxLat);
|
||||||
|
allMinLat = Math.min(allMinLat, minLat);
|
||||||
|
allMaxLon = Math.max(allMaxLon, maxLon);
|
||||||
|
allMinLon = Math.min(allMinLon, minLon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if that bounding box intersects the viewable bounding box
|
||||||
|
// Assume that if it does, the location is visible
|
||||||
|
locVisible = viewRect.intersects(minLon, minLat, maxLon, maxLat);
|
||||||
|
if (locVisible) {
|
||||||
|
// If the previous point wasn't drawn, start at its position
|
||||||
|
if (!lastLocVisible) {
|
||||||
|
geoPoint = new GeoPoint(lastLocLat, lastLocLon);
|
||||||
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
|
path.moveTo(pt.x, pt.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a line to the new point
|
||||||
|
geoPoint = new GeoPoint(locLat, locLon);
|
||||||
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
path.lineTo(pt.x, pt.y);
|
path.lineTo(pt.x, pt.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lastLocLon = locLon;
|
||||||
|
lastLocLat = locLat;
|
||||||
|
lastLocValid = locValid;
|
||||||
|
lastLocVisible = locVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Visible for testing.
|
canvas.drawPath(path, selectedTrackPaint);
|
||||||
Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
|
|
||||||
Drawable element, int offsetX, int offsetY) {
|
if (drawBounds) {
|
||||||
Point pt = new Point();
|
drawBoundingBox(canvas, mapView,
|
||||||
projection.toPixels(geoPoint, pt);
|
allMinLat, allMinLon, allMaxLat, allMaxLon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the "End" marker:
|
||||||
|
if (showEndMarker && lastValidLocation != null) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(pt.x + offsetX, pt.y + offsetY);
|
GeoPoint geoPoint = MyTracksUtils.getGeoPoint(lastValidLocation);
|
||||||
element.draw(canvas);
|
mapView.getProjection().toPixels(geoPoint, pt);
|
||||||
|
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
|
||||||
|
endMarker.draw(canvas);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
return pt;
|
}
|
||||||
|
|
||||||
|
// Draw the "Start" marker:
|
||||||
|
if (firstGeoPoint != null) {
|
||||||
|
mapView.getProjection().toPixels(firstGeoPoint, pt);
|
||||||
|
canvas.save();
|
||||||
|
canvas.translate(pt.x - (markerWidth / 2), pt.y - markerHeight);
|
||||||
|
startMarker.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBoundingBox(Canvas canvas, MapView mapView, int allMinLat,
|
||||||
|
int allMinLon, int allMaxLat, int allMaxLon) {
|
||||||
|
// Transform coordinates
|
||||||
|
GeoPoint maxPoint = new GeoPoint(allMaxLat, allMaxLon);
|
||||||
|
GeoPoint minPoint = new GeoPoint(allMinLat, allMinLon);
|
||||||
|
Point minPt = new Point();
|
||||||
|
Point maxPt = new Point();
|
||||||
|
mapView.getProjection().toPixels(minPoint, minPt);
|
||||||
|
mapView.getProjection().toPixels(maxPoint, maxPt);
|
||||||
|
Rect allBounds = new Rect(minPt.x, minPt.y, maxPt.x, maxPt.y);
|
||||||
|
|
||||||
|
// Prepare a green paint
|
||||||
|
Paint boundingBoxPaint = new Paint();
|
||||||
|
boundingBoxPaint.setColor(context.getResources().getColor(R.color.green));
|
||||||
|
boundingBoxPaint.setStrokeWidth(2);
|
||||||
|
boundingBoxPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
boundingBoxPaint.setAntiAlias(true);
|
||||||
|
|
||||||
|
// Disable clipping
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRect(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY,
|
||||||
|
Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
|
||||||
|
Region.Op.REPLACE);
|
||||||
|
|
||||||
|
// Draw the bounding box
|
||||||
|
canvas.drawRect(allBounds, boundingBoxPaint);
|
||||||
|
|
||||||
|
// Re-enable clipping
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean draw(Canvas canvas, MapView mapView, boolean shadow,
|
||||||
|
long when) {
|
||||||
|
draw(canvas, mapView, shadow);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -426,7 +436,6 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
final Location tapLocation = MyTracksUtils.getLocation(p);
|
final Location tapLocation = MyTracksUtils.getLocation(p);
|
||||||
double dmin = Double.MAX_VALUE;
|
double dmin = Double.MAX_VALUE;
|
||||||
Waypoint waypoint = null;
|
Waypoint waypoint = null;
|
||||||
synchronized (waypoints) {
|
|
||||||
for (int i = 0; i < waypoints.size(); i++) {
|
for (int i = 0; i < waypoints.size(); i++) {
|
||||||
final Location waypointLocation = waypoints.get(i).getLocation();
|
final Location waypointLocation = waypoints.get(i).getLocation();
|
||||||
if (waypointLocation == null) {
|
if (waypointLocation == null) {
|
||||||
@@ -438,10 +447,9 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
waypoint = waypoints.get(i);
|
waypoint = waypoints.get(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (waypoint != null &&
|
if (waypoint != null
|
||||||
dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
&& dmin < 15000000 / Math.pow(2, mapView.getZoomLevel())) {
|
||||||
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
Intent intent = new Intent(context, MyTracksWaypointDetails.class);
|
||||||
intent.putExtra("waypointid", waypoint.getId());
|
intent.putExtra("waypointid", waypoint.getId());
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
@@ -449,4 +457,8 @@ public class MyTracksOverlay extends Overlay {
|
|||||||
}
|
}
|
||||||
return super.onTap(p, mapView);
|
return super.onTap(p, mapView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDrawBounds(boolean drawBounds) {
|
||||||
|
this.drawBounds = drawBounds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ package com.google.android.apps.mytracks;
|
|||||||
|
|
||||||
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
import com.google.android.apps.mytracks.io.backup.BackupActivityHelper;
|
||||||
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
import com.google.android.apps.mytracks.io.backup.BackupPreferencesListener;
|
||||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
import com.google.android.apps.mytracks.services.SafeStatusAnnouncerTask;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@@ -30,6 +29,8 @@ import android.preference.PreferenceActivity;
|
|||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.preference.Preference.OnPreferenceChangeListener;
|
import android.preference.Preference.OnPreferenceChangeListener;
|
||||||
import android.preference.Preference.OnPreferenceClickListener;
|
import android.preference.Preference.OnPreferenceClickListener;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An activity that let's the user see and edit the settings.
|
* An activity that let's the user see and edit the settings.
|
||||||
@@ -52,19 +53,29 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200;
|
public static final int DEFAULT_MIN_REQUIRED_ACCURACY = 200;
|
||||||
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
public static final int DEFAULT_SPLIT_FREQUENCY = 0;
|
||||||
|
|
||||||
|
private static boolean mTTSAvailable;
|
||||||
private BackupPreferencesListener backupListener;
|
private BackupPreferencesListener backupListener;
|
||||||
|
|
||||||
private SharedPreferences preferences;
|
private SharedPreferences preferences;
|
||||||
|
|
||||||
|
/* establish whether the tts class is available to us */
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
SafeStatusAnnouncerTask.checkAvailable();
|
||||||
|
mTTSAvailable = true;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.d(MyTracksConstants.TAG, "TTS not available.", t);
|
||||||
|
mTTSAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
|
|
||||||
// The volume we want to control is the Text-To-Speech volume
|
// The volume we want to control is the Text-To-Speech volume
|
||||||
ApiFeatures apiFeatures = ApiFeatures.getInstance();
|
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||||
int volumeStream =
|
|
||||||
new StatusAnnouncerFactory(apiFeatures).getVolumeStream();
|
|
||||||
setVolumeControlStream(volumeStream);
|
|
||||||
|
|
||||||
// Tell it where to read/write preferences
|
// Tell it where to read/write preferences
|
||||||
PreferenceManager preferenceManager = getPreferenceManager();
|
PreferenceManager preferenceManager = getPreferenceManager();
|
||||||
@@ -72,7 +83,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
preferenceManager.setSharedPreferencesMode(0);
|
preferenceManager.setSharedPreferencesMode(0);
|
||||||
|
|
||||||
// Set up automatic preferences backup
|
// Set up automatic preferences backup
|
||||||
backupListener = BackupPreferencesListener.create(this, apiFeatures);
|
backupListener = BackupPreferencesListener.create(this);
|
||||||
preferences = preferenceManager.getSharedPreferences();
|
preferences = preferenceManager.getSharedPreferences();
|
||||||
preferences.registerOnSharedPreferenceChangeListener(backupListener);
|
preferences.registerOnSharedPreferenceChangeListener(backupListener);
|
||||||
|
|
||||||
@@ -97,7 +108,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
updatePreferenceUnits(metricUnitsPreference.isChecked());
|
||||||
|
|
||||||
// Disable TTS announcement preference if not available
|
// Disable TTS announcement preference if not available
|
||||||
if (!apiFeatures.hasTextToSpeech()) {
|
if (!mTTSAvailable) {
|
||||||
IntegerListPreference announcementFrequency =
|
IntegerListPreference announcementFrequency =
|
||||||
(IntegerListPreference) findPreference(
|
(IntegerListPreference) findPreference(
|
||||||
getString(R.string.announcement_frequency_key));
|
getString(R.string.announcement_frequency_key));
|
||||||
@@ -158,7 +169,7 @@ public class MyTracksSettings extends PreferenceActivity {
|
|||||||
getPreferenceManager().getSharedPreferences()
|
getPreferenceManager().getSharedPreferences()
|
||||||
.unregisterOnSharedPreferenceChangeListener(backupListener);
|
.unregisterOnSharedPreferenceChangeListener(backupListener);
|
||||||
|
|
||||||
super.onDestroy();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
package com.google.android.apps.mytracks;
|
package com.google.android.apps.mytracks;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -63,7 +62,8 @@ public class NavControls {
|
|||||||
addView(icon);
|
addView(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIcon(Drawable drawable) {
|
public void setIcon(int iconId) {
|
||||||
|
Drawable drawable = getContext().getResources().getDrawable(iconId);
|
||||||
icon.setImageDrawable(drawable);
|
icon.setImageDrawable(drawable);
|
||||||
icon.setVisibility(View.VISIBLE);
|
icon.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
@@ -102,21 +102,21 @@ public class NavControls {
|
|||||||
|
|
||||||
private final TouchLayout prevImage;
|
private final TouchLayout prevImage;
|
||||||
private final TouchLayout nextImage;
|
private final TouchLayout nextImage;
|
||||||
private final TypedArray leftIcons;
|
private final int[] leftIcons;
|
||||||
private final TypedArray rightIcons;
|
private final int[] rightIcons;
|
||||||
private final Runnable touchRunnable;
|
private final Runnable touchRunnable;
|
||||||
|
|
||||||
private boolean isVisible = false;
|
private boolean isVisible = false;
|
||||||
private int currentIcons;
|
private int currentIcons;
|
||||||
|
|
||||||
public NavControls(Context context, ViewGroup container,
|
public NavControls(Context context, ViewGroup container,
|
||||||
TypedArray leftIcons, TypedArray rightIcons,
|
int[] leftIcons, int[] rightIcons,
|
||||||
Runnable touchRunnable) {
|
Runnable touchRunnable) {
|
||||||
this.leftIcons = leftIcons;
|
this.leftIcons = leftIcons;
|
||||||
this.rightIcons = rightIcons;
|
this.rightIcons = rightIcons;
|
||||||
this.touchRunnable = touchRunnable;
|
this.touchRunnable = touchRunnable;
|
||||||
|
|
||||||
if (leftIcons.length() != rightIcons.length() || leftIcons.length() < 1) {
|
if (leftIcons.length != rightIcons.length || leftIcons.length < 1) {
|
||||||
throw new IllegalArgumentException("Invalid icons specified");
|
throw new IllegalArgumentException("Invalid icons specified");
|
||||||
}
|
}
|
||||||
if (touchRunnable == null) {
|
if (touchRunnable == null) {
|
||||||
@@ -145,8 +145,8 @@ public class NavControls {
|
|||||||
container.addView(prevImage);
|
container.addView(prevImage);
|
||||||
container.addView(nextImage);
|
container.addView(nextImage);
|
||||||
|
|
||||||
prevImage.setIcon(leftIcons.getDrawable(0));
|
prevImage.setIcon(leftIcons[0]);
|
||||||
nextImage.setIcon(rightIcons.getDrawable(0));
|
nextImage.setIcon(rightIcons[0]);
|
||||||
this.currentIcons = 0;
|
this.currentIcons = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,9 +206,8 @@ public class NavControls {
|
|||||||
|
|
||||||
private void shiftIcons(boolean isLeft) {
|
private void shiftIcons(boolean isLeft) {
|
||||||
// Increment or decrement by one, with wrap around
|
// Increment or decrement by one, with wrap around
|
||||||
currentIcons = (currentIcons + leftIcons.length() +
|
currentIcons = (currentIcons + leftIcons.length + (isLeft ? -1 : 1)) % leftIcons.length;
|
||||||
(isLeft ? -1 : 1)) % leftIcons.length();
|
prevImage.setIcon(leftIcons[currentIcons]);
|
||||||
prevImage.setIcon(leftIcons.getDrawable(currentIcons));
|
nextImage.setIcon(rightIcons[currentIcons]);
|
||||||
nextImage.setIcon(rightIcons.getDrawable(currentIcons));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ package com.google.android.apps.mytracks;
|
|||||||
*/
|
*/
|
||||||
public interface ProgressIndicator {
|
public interface ProgressIndicator {
|
||||||
|
|
||||||
public void setProgressMessage(int resId);
|
public void setProgressMessage(String text);
|
||||||
public void clearProgressMessage();
|
|
||||||
|
|
||||||
public void setProgressValue(int percent);
|
public void setProgressValue(int percent);
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class SendToGoogleDialog extends Dialog {
|
|||||||
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);
|
MyTracks.getInstance().dismissDialog(MyTracks.DIALOG_SEND_TO_GOOGLE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import com.google.android.apps.mytracks.content.MyTracksProviderUtilsImpl;
|
|||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.content.TracksColumns;
|
import com.google.android.apps.mytracks.content.TracksColumns;
|
||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
import com.google.android.apps.mytracks.services.StatusAnnouncerFactory;
|
|
||||||
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;
|
||||||
@@ -35,6 +33,7 @@ import android.location.LocationManager;
|
|||||||
import android.location.LocationProvider;
|
import android.location.LocationProvider;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -52,7 +51,7 @@ import android.widget.Toast;
|
|||||||
public class StatsActivity extends Activity
|
public class StatsActivity extends Activity
|
||||||
implements OnSharedPreferenceChangeListener {
|
implements OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
private StatsUtilities utils;
|
private final StatsUtilities utils;
|
||||||
private UIUpdateThread thread;
|
private UIUpdateThread thread;
|
||||||
|
|
||||||
private ContentObserver observer;
|
private ContentObserver observer;
|
||||||
@@ -94,7 +93,7 @@ public class StatsActivity extends Activity
|
|||||||
*/
|
*/
|
||||||
private boolean showCurrentSegment = false;
|
private boolean showCurrentSegment = false;
|
||||||
|
|
||||||
private MyTracksProviderUtils providerUtils;
|
private final MyTracksProviderUtils providerUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A runnable for posting to the UI thread. Will update the total time field.
|
* A runnable for posting to the UI thread. Will update the total time field.
|
||||||
@@ -133,18 +132,18 @@ public class StatsActivity extends Activity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StatsActivity() {
|
||||||
|
utils = new StatsUtilities(this);
|
||||||
|
providerUtils = new MyTracksProviderUtilsImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
utils = new StatsUtilities(this);
|
|
||||||
providerUtils = new MyTracksProviderUtilsImpl(getContentResolver());
|
|
||||||
|
|
||||||
// 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 =
|
setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM);
|
||||||
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
|
|
||||||
setVolumeControlStream(volumeStream);
|
|
||||||
|
|
||||||
// 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);
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public interface MyTracksProviderUtils {
|
|||||||
* @return A cursor over the selected range of locations
|
* @return A cursor over the selected range of locations
|
||||||
*/
|
*/
|
||||||
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
int maxWaypoints);
|
long maxWaypoints);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a track by given unique track id.
|
* Finds a track by given unique track id.
|
||||||
@@ -206,6 +206,32 @@ public interface MyTracksProviderUtils {
|
|||||||
*/
|
*/
|
||||||
long getTrackPoints(Track track, int maxPoints);
|
long getTrackPoints(Track track, int maxPoints);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches some number of locations for the given track.
|
||||||
|
*
|
||||||
|
* This is designed to be used to stream through large tracks without loading
|
||||||
|
* all points into memory.
|
||||||
|
*
|
||||||
|
* @param track to load locations for
|
||||||
|
* @param buffer an array of locations to fill
|
||||||
|
* @deprecated use {@link #fillTrackPoints} instead
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void getTrackPoints(Track track, TrackBuffer buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches some number of locations for the given track.
|
||||||
|
*
|
||||||
|
* This is designed to be used to stream through large tracks without loading
|
||||||
|
* all points into memory.
|
||||||
|
* This method will reuse the Location objects in the buffer. If you need a
|
||||||
|
* Location object copy the object.
|
||||||
|
*
|
||||||
|
* @param track to load locations for
|
||||||
|
* @param buffer an array of locations to fill
|
||||||
|
*/
|
||||||
|
void fillTrackPoints(Track track, TrackBuffer buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a cursor over the tracks provider with a given selection.
|
* Creates a cursor over the tracks provider with a given selection.
|
||||||
* Caller gets to own the returned cursor. Don't forget to close it.
|
* Caller gets to own the returned cursor. Don't forget to close it.
|
||||||
@@ -345,7 +371,7 @@ public interface MyTracksProviderUtils {
|
|||||||
* Creates an instance of {@link MyTracksProviderUtils}.
|
* Creates an instance of {@link MyTracksProviderUtils}.
|
||||||
*/
|
*/
|
||||||
protected MyTracksProviderUtils newForContext(Context context) {
|
protected MyTracksProviderUtils newForContext(Context context) {
|
||||||
return new MyTracksProviderUtilsImpl(context.getContentResolver());
|
return new MyTracksProviderUtilsImpl(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+96
-32
@@ -17,8 +17,8 @@ package com.google.android.apps.mytracks.content;
|
|||||||
|
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -35,10 +35,10 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
||||||
|
|
||||||
private final ContentResolver contentResolver;
|
private final Context context;
|
||||||
|
|
||||||
public MyTracksProviderUtilsImpl(ContentResolver contentResolver) {
|
public MyTracksProviderUtilsImpl(Context context) {
|
||||||
this.contentResolver = contentResolver;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -448,22 +448,25 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteAllTracks() {
|
public void deleteAllTracks() {
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, null, null);
|
context.getContentResolver().delete(TracksColumns.CONTENT_URI, null, null);
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI, null, null);
|
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI, null, null);
|
null, null);
|
||||||
|
context.getContentResolver().delete(
|
||||||
|
WaypointsColumns.CONTENT_URI, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteTrack(long trackId) {
|
public void deleteTrack(long trackId) {
|
||||||
Track track = getTrack(trackId);
|
Track track = getTrack(trackId);
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
contentResolver.delete(TrackPointsColumns.CONTENT_URI,
|
context.getContentResolver().delete(TrackPointsColumns.CONTENT_URI,
|
||||||
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
|
"_id>=" + track.getStartId() + " AND _id<=" + track.getStopId(),
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
contentResolver.delete(WaypointsColumns.CONTENT_URI,
|
context.getContentResolver().delete(WaypointsColumns.CONTENT_URI,
|
||||||
WaypointsColumns.TRACKID + "=" + trackId, null);
|
WaypointsColumns.TRACKID + "=" + trackId, null);
|
||||||
contentResolver.delete(TracksColumns.CONTENT_URI, "_id=" + trackId, null);
|
context.getContentResolver().delete(
|
||||||
|
TracksColumns.CONTENT_URI, "_id=" + trackId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -488,7 +491,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
"No statistics marker after the deleted one was found.");
|
"No statistics marker after the deleted one was found.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contentResolver.delete(
|
context.getContentResolver().delete(
|
||||||
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
|
WaypointsColumns.CONTENT_URI, "_id=" + waypointId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,7 +503,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
|
final String sortOrder = WaypointsColumns._ID + " LIMIT 1";
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
selection,
|
selection,
|
||||||
@@ -522,7 +525,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public boolean updateWaypoint(Waypoint waypoint) {
|
public boolean updateWaypoint(Waypoint waypoint) {
|
||||||
try {
|
try {
|
||||||
final int rows = contentResolver.update(
|
final int rows = context.getContentResolver().update(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
createContentValues(waypoint),
|
createContentValues(waypoint),
|
||||||
"_id=" + waypoint.getId(),
|
"_id=" + waypoint.getId(),
|
||||||
@@ -543,7 +546,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
private Location findLocationBy(String select) {
|
private Location findLocationBy(String select) {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TrackPointsColumns.CONTENT_URI, null, select, null, null);
|
TrackPointsColumns.CONTENT_URI, null, select, null, null);
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
return createLocation(cursor);
|
return createLocation(cursor);
|
||||||
@@ -567,7 +570,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
private Track findTrackBy(String select) {
|
private Track findTrackBy(String select) {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, null, select, null, null);
|
TracksColumns.CONTENT_URI, null, select, null, null);
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
return createTrack(cursor);
|
return createTrack(cursor);
|
||||||
@@ -589,7 +592,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Waypoint getFirstWaypoint(long trackId) {
|
public Waypoint getFirstWaypoint(long trackId) {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
"trackid=" + trackId,
|
"trackid=" + trackId,
|
||||||
@@ -611,7 +614,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Waypoint getWaypoint(long waypointId) {
|
public Waypoint getWaypoint(long waypointId) {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
null /*projection*/,
|
null /*projection*/,
|
||||||
"_id=" + waypointId,
|
"_id=" + waypointId,
|
||||||
@@ -634,7 +637,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getLastLocationId(long trackId) {
|
public long getLastLocationId(long trackId) {
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
TrackPointsColumns.CONTENT_URI,
|
TrackPointsColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
|
"_id=(select max(_id) from trackpoints WHERE trackid=" + trackId + ")",
|
||||||
@@ -658,7 +661,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getFirstWaypointId(long trackId) {
|
public long getFirstWaypointId(long trackId) {
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
"trackid=" + trackId,
|
"trackid=" + trackId,
|
||||||
@@ -682,7 +685,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getLastWaypointId(long trackId) {
|
public long getLastWaypointId(long trackId) {
|
||||||
final String[] projection = {"_id"};
|
final String[] projection = {"_id"};
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI,
|
WaypointsColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
WaypointsColumns.TRACKID + "=" + trackId,
|
WaypointsColumns.TRACKID + "=" + trackId,
|
||||||
@@ -707,7 +710,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
public Track getLastTrack() {
|
public Track getLastTrack() {
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
|
TracksColumns.CONTENT_URI, null, "_id=(select max(_id) from tracks)",
|
||||||
null, null);
|
null, null);
|
||||||
if (cursor != null && cursor.moveToNext()) {
|
if (cursor != null && cursor.moveToNext()) {
|
||||||
@@ -726,7 +729,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public long getLastTrackId() {
|
public long getLastTrackId() {
|
||||||
String[] proj = { TracksColumns._ID };
|
String[] proj = { TracksColumns._ID };
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
|
TracksColumns.CONTENT_URI, proj, "_id=(select max(_id) from tracks)",
|
||||||
null, null);
|
null, null);
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
@@ -766,13 +769,13 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
sortOrder += " LIMIT " + maxLocations;
|
sortOrder += " LIMIT " + maxLocations;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentResolver.query(
|
return context.getContentResolver().query(
|
||||||
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
TrackPointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
public Cursor getWaypointsCursor(long trackId, long minWaypointId,
|
||||||
int maxWaypoints) {
|
long maxWaypoints) {
|
||||||
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",
|
||||||
@@ -788,7 +791,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
sortOrder += " LIMIT " + maxWaypoints;
|
sortOrder += " LIMIT " + maxWaypoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentResolver.query(
|
return context.getContentResolver().query(
|
||||||
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
WaypointsColumns.CONTENT_URI, null, selection, null, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,9 +845,70 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
return lastId;
|
return lastId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getTrackPoints(Track track, TrackBuffer buffer) {
|
||||||
|
getTrackPoints(track, buffer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fillTrackPoints(Track track, TrackBuffer buffer) {
|
||||||
|
getTrackPoints(track, buffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getTrackPoints(Track track, TrackBuffer buffer,
|
||||||
|
boolean reuseLocations) {
|
||||||
|
long startingPoint = buffer.getLastLocationRead() == 0 ? track.getStartId()
|
||||||
|
: buffer.getLastLocationRead();
|
||||||
|
buffer.reset();
|
||||||
|
Cursor cursor = getLocationsCursor(track.getId(),
|
||||||
|
startingPoint,
|
||||||
|
buffer.getSize(), false);
|
||||||
|
if (cursor == null) {
|
||||||
|
Log.w(MyTracksProvider.TAG, "Cannot get a locations cursor!");
|
||||||
|
buffer.setInvalid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (cursor.getCount() == 0) {
|
||||||
|
Log.w(MyTracksProvider.TAG, "No matching locations found.");
|
||||||
|
buffer.resetAt(startingPoint + buffer.getSize());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cursor.moveToFirst()) {
|
||||||
|
Log.w(MyTracksProvider.TAG, "Could not move to first.");
|
||||||
|
buffer.setInvalid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int idColumnIdx =
|
||||||
|
cursor.getColumnIndexOrThrow(TrackPointsColumns._ID);
|
||||||
|
do {
|
||||||
|
if (reuseLocations) {
|
||||||
|
fillLocation(cursor, buffer.add(cursor.getLong(idColumnIdx)));
|
||||||
|
} else {
|
||||||
|
Location location = createLocation(cursor);
|
||||||
|
if (location == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buffer.add(location, cursor.getLong(idColumnIdx));
|
||||||
|
}
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
|
||||||
|
if (buffer.getLocationsLoaded() == 0) {
|
||||||
|
Log.w(MyTracksProvider.TAG, "No locations read.");
|
||||||
|
buffer.resetAt(startingPoint + buffer.getSize());
|
||||||
|
}
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Log.w(MyTracksProvider.TAG, "Caught unexpected exception.", e);
|
||||||
|
} finally {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor getTracksCursor(String selection) {
|
public Cursor getTracksCursor(String selection) {
|
||||||
Cursor cursor = contentResolver.query(
|
Cursor cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI, null, selection, null, "_id");
|
TracksColumns.CONTENT_URI, null, selection, null, "_id");
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
@@ -852,14 +916,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public Uri insertTrack(Track track) {
|
public Uri insertTrack(Track track) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrack");
|
||||||
return contentResolver.insert(TracksColumns.CONTENT_URI,
|
return context.getContentResolver().insert(TracksColumns.CONTENT_URI,
|
||||||
createContentValues(track));
|
createContentValues(track));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insertTrackPoint(Location location, long trackId) {
|
public Uri insertTrackPoint(Location location, long trackId) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertTrackPoint");
|
||||||
return contentResolver.insert(TrackPointsColumns.CONTENT_URI,
|
return context.getContentResolver().insert(TrackPointsColumns.CONTENT_URI,
|
||||||
createContentValues(location, trackId));
|
createContentValues(location, trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,14 +936,14 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
values[i] = createContentValues(locations[i], trackId);
|
values[i] = createContentValues(locations[i], trackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentResolver.bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
return context.getContentResolver().bulkInsert(TrackPointsColumns.CONTENT_URI, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insertWaypoint(Waypoint waypoint) {
|
public Uri insertWaypoint(Waypoint waypoint) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.insertWaypoint");
|
||||||
waypoint.setId(-1);
|
waypoint.setId(-1);
|
||||||
return contentResolver.insert(WaypointsColumns.CONTENT_URI,
|
return context.getContentResolver().insert(WaypointsColumns.CONTENT_URI,
|
||||||
createContentValues(waypoint));
|
createContentValues(waypoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,7 +952,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
final String[] projection = { TracksColumns._ID };
|
final String[] projection = { TracksColumns._ID };
|
||||||
cursor = contentResolver.query(
|
cursor = context.getContentResolver().query(
|
||||||
TracksColumns.CONTENT_URI,
|
TracksColumns.CONTENT_URI,
|
||||||
projection,
|
projection,
|
||||||
TracksColumns._ID + "=" + id/*selection*/,
|
TracksColumns._ID + "=" + id/*selection*/,
|
||||||
@@ -908,7 +972,7 @@ public class MyTracksProviderUtilsImpl implements MyTracksProviderUtils {
|
|||||||
@Override
|
@Override
|
||||||
public void updateTrack(Track track) {
|
public void updateTrack(Track track) {
|
||||||
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
|
Log.d(MyTracksProvider.TAG, "MyTracksProviderUtilsImpl.updateTrack");
|
||||||
contentResolver.update(TracksColumns.CONTENT_URI,
|
context.getContentResolver().update(TracksColumns.CONTENT_URI,
|
||||||
createContentValues(track), "_id=" + track.getId(), null);
|
createContentValues(track), "_id=" + track.getId(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2009 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.apps.mytracks.content;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A buffer of Locations from a track.
|
||||||
|
* This class contains some additional information about the loaded points.
|
||||||
|
*
|
||||||
|
* @author Sandor Dornbush
|
||||||
|
*/
|
||||||
|
public class TrackBuffer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of location objects that are part of a track.
|
||||||
|
*/
|
||||||
|
private final Location[] buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the last location read from the track.
|
||||||
|
*/
|
||||||
|
private long lastLocationRead = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of locations loaded into the buffer.
|
||||||
|
*/
|
||||||
|
private int locationsLoaded = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a TrackBuffer with size elements.
|
||||||
|
*
|
||||||
|
* @param size The size of the buffer
|
||||||
|
*/
|
||||||
|
public TrackBuffer(int size) {
|
||||||
|
buffer = new Location[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the track to a state with no locations.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
resetAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the track at the given starting location id.
|
||||||
|
*/
|
||||||
|
public void resetAt(long lastLocation) {
|
||||||
|
lastLocationRead = lastLocation;
|
||||||
|
locationsLoaded = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInvalid() {
|
||||||
|
lastLocationRead = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of locations that can be stored in this buffer
|
||||||
|
*/
|
||||||
|
public int getSize() {
|
||||||
|
return buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param index The index of the location to fetch
|
||||||
|
* @return The location for the given index
|
||||||
|
*/
|
||||||
|
public Location get(int index) {
|
||||||
|
return buffer[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a location to the end of the buffer.
|
||||||
|
* @param location The location to add.
|
||||||
|
* @param id The id of the location to be added.
|
||||||
|
*/
|
||||||
|
public void add(Location location, long id) {
|
||||||
|
buffer[locationsLoaded++] = location;
|
||||||
|
lastLocationRead = Math.max(lastLocationRead, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a location from the end of the buffer.
|
||||||
|
* This may reuse Location objects already allocated to the buffer.
|
||||||
|
* @param locationId The of the location to be added.
|
||||||
|
* @return The location object associated with the give id.
|
||||||
|
*/
|
||||||
|
public Location add(long locationId) {
|
||||||
|
lastLocationRead = Math.max(lastLocationRead, locationId);
|
||||||
|
if (buffer[locationsLoaded] == null) {
|
||||||
|
buffer[locationsLoaded] = new Location("");
|
||||||
|
} else {
|
||||||
|
buffer[locationsLoaded].reset();
|
||||||
|
}
|
||||||
|
return buffer[locationsLoaded++];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The id of the last location loaded into the buffer
|
||||||
|
*/
|
||||||
|
public long getLastLocationRead() {
|
||||||
|
return lastLocationRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of locations loaded into the buffer.
|
||||||
|
*/
|
||||||
|
public int getLocationsLoaded() {
|
||||||
|
return locationsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the start location, i.e. the one which is the first point of a
|
||||||
|
* segment with at least two points.
|
||||||
|
*
|
||||||
|
* @return the start location
|
||||||
|
*/
|
||||||
|
public Location findStartLocation() {
|
||||||
|
int numValidLocations = 0;
|
||||||
|
for (int i = 0; i < getLocationsLoaded(); i++) {
|
||||||
|
Location location = buffer[i];
|
||||||
|
if (MyTracksUtils.isValidLocation(location)) {
|
||||||
|
numValidLocations++;
|
||||||
|
if (numValidLocations == 2) {
|
||||||
|
return buffer[i - 1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
numValidLocations = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -468,7 +468,8 @@ public class GpxImporter extends DefaultHandler {
|
|||||||
|
|
||||||
// initialize start time with time of first track point
|
// initialize start time with time of first track point
|
||||||
if (statsBuilder == null) {
|
if (statsBuilder == null) {
|
||||||
statsBuilder = new TripStatisticsBuilder(time);
|
statsBuilder = new TripStatisticsBuilder();
|
||||||
|
statsBuilder.resumeAt(time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ public class SendToDocs {
|
|||||||
androidClient = GDataClientFactory.getGDataClient(activity);
|
androidClient = GDataClientFactory.getGDataClient(activity);
|
||||||
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
SpreadsheetsClient gdataClient = new SpreadsheetsClient(androidClient,
|
||||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||||
wiseWrapper.setClient(gdataClient);
|
wiseWrapper.setClient(androidClient, gdataClient);
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"GData connection prepared: " + this.writelyAuth);
|
"GData connection prepared: " + this.writelyAuth);
|
||||||
String sheetTitle = "My Tracks";
|
String sheetTitle = "My Tracks";
|
||||||
@@ -220,7 +220,7 @@ public class SendToDocs {
|
|||||||
|
|
||||||
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
DocumentsClient docsGdataClient = new DocumentsClient(androidClient,
|
||||||
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
new XmlMapsGDataParserFactory(new AndroidXmlParserFactory()));
|
||||||
writelyWrapper.setClient(docsGdataClient);
|
writelyWrapper.setClient(androidClient, docsGdataClient);
|
||||||
|
|
||||||
// First try to find the spreadsheet:
|
// First try to find the spreadsheet:
|
||||||
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
if (!getSpreadsheetId(writelyWrapper, sheetTitle)) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.google.android.apps.mytracks.MyTracksSettings;
|
|||||||
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;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackBuffer;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
import com.google.android.apps.mytracks.stats.DoubleBuffer;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
@@ -43,392 +44,53 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.HandlerThread;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class used to transmit tracks to Google MyMaps.
|
* A helper class used to transmit tracks to Google MyMaps.
|
||||||
* A new instance should be used for each upload.
|
|
||||||
*
|
*
|
||||||
* @author Leif Hendrik Wilden
|
* @author Leif Hendrik Wilden
|
||||||
*/
|
*/
|
||||||
public class SendToMyMaps implements Runnable {
|
public class SendToMyMaps {
|
||||||
|
|
||||||
public static final String NEW_MAP_ID = "new";
|
|
||||||
private static final String END_ICON_URL =
|
private static final String END_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
"http://maps.google.com/mapfiles/ms/micons/red-dot.png";
|
||||||
private static final String START_ICON_URL =
|
private static final String START_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
"http://maps.google.com/mapfiles/ms/micons/green-dot.png";
|
||||||
private static final int MAX_POINTS_PER_UPLOAD = 2048;
|
|
||||||
|
|
||||||
private final Activity context;
|
private final Activity context;
|
||||||
private final AuthManager auth;
|
private final AuthManager auth;
|
||||||
private final long trackId;
|
private final long trackId;
|
||||||
private final ProgressIndicator progressIndicator;
|
private final ProgressIndicator progressIndicator;
|
||||||
private final OnSendCompletedListener onCompletion;
|
|
||||||
private final StringUtils stringUtils;
|
private final StringUtils stringUtils;
|
||||||
private final MyTracksProviderUtils providerUtils;
|
private final MyTracksProviderUtils providerUtils;
|
||||||
private String mapId;
|
private String mapId;
|
||||||
|
|
||||||
// GData service
|
private String statusMessage = "";
|
||||||
|
private boolean success = true;
|
||||||
|
private Runnable onCompletion;
|
||||||
|
|
||||||
|
private HandlerThread handlerThread;
|
||||||
|
private Handler handler;
|
||||||
|
|
||||||
|
private String originalDescription;
|
||||||
|
|
||||||
private MyMapsGDataWrapper wrapper;
|
private MyMapsGDataWrapper wrapper;
|
||||||
private MyMapsGDataConverter gdataConverter;
|
private MyMapsGDataConverter gdataConverter;
|
||||||
|
|
||||||
// Progress status
|
private Location last = null;
|
||||||
private int totalLocationsRead;
|
|
||||||
private int totalLocationsPrepared;
|
|
||||||
private int totalLocationsUploaded;
|
|
||||||
private int totalLocations;
|
|
||||||
private int totalSegmentsUploaded;
|
|
||||||
|
|
||||||
public interface OnSendCompletedListener {
|
private Vector<Double> distances;
|
||||||
void onSendCompleted(String mapId, boolean success, int statusMessage);
|
private Vector<Double> elevations;
|
||||||
}
|
private DoubleBuffer elevationBuffer;
|
||||||
|
|
||||||
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
|
||||||
long trackId, ProgressIndicator progressIndicator,
|
|
||||||
OnSendCompletedListener onCompletion) {
|
|
||||||
this.context = context;
|
|
||||||
this.mapId = mapId;
|
|
||||||
this.auth = auth;
|
|
||||||
this.trackId = trackId;
|
|
||||||
this.progressIndicator = progressIndicator;
|
|
||||||
this.onCompletion = onCompletion;
|
|
||||||
this.stringUtils = new StringUtils(context);
|
|
||||||
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Sending to MyMaps: trackId = " + trackId);
|
|
||||||
doUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doUpload() {
|
|
||||||
int statusMessageId = R.string.error_sending_to_mymap;
|
|
||||||
boolean success = true;
|
|
||||||
try {
|
|
||||||
gdataConverter = new MyMapsGDataConverter();
|
|
||||||
|
|
||||||
progressIndicator.setProgressValue(1);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_reading_track);
|
|
||||||
|
|
||||||
// Get the track meta-data
|
|
||||||
Track track = providerUtils.getTrack(trackId);
|
|
||||||
String originalDescription = track.getDescription();
|
|
||||||
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
|
||||||
wrapper = new MyMapsGDataWrapper(context);
|
|
||||||
wrapper.setAuthManager(auth);
|
|
||||||
wrapper.setRetryOnAuthFailure(true);
|
|
||||||
|
|
||||||
// Create a new map if necessary:
|
|
||||||
boolean isNewMap = mapId.equals(NEW_MAP_ID);
|
|
||||||
if (isNewMap) {
|
|
||||||
success = createNewMap(track, originalDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload all of the segments of the track plus start/end markers
|
|
||||||
if (success) {
|
|
||||||
success = uploadAllTrackPoints(track, originalDescription);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put waypoints.
|
|
||||||
if (success) {
|
|
||||||
success = uploadWaypoints(track);
|
|
||||||
if (!success) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: upload waypoints failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
statusMessageId = isNewMap
|
|
||||||
? R.string.status_new_mymap_has_been_created
|
|
||||||
: R.string.status_tracks_have_been_uploaded;
|
|
||||||
}
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
|
||||||
progressIndicator.setProgressValue(100);
|
|
||||||
} catch (XmlPullParserException e) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
|
||||||
} finally {
|
|
||||||
if (wrapper != null) {
|
|
||||||
wrapper.cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean finalSuccess = success;
|
|
||||||
final int finalStatusMessageId = statusMessageId;
|
|
||||||
context.runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if (onCompletion != null) {
|
|
||||||
onCompletion.onSendCompleted(
|
|
||||||
mapId, finalSuccess, finalStatusMessageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new map for the given track.
|
|
||||||
*
|
|
||||||
* @param track The track that will be uploaded to this map
|
|
||||||
* @return True on success.
|
|
||||||
*/
|
|
||||||
private boolean createNewMap(final Track track, final String description) {
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_creating_map);
|
|
||||||
return wrapper.runQuery(new QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client) throws IOException, Exception {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
|
||||||
String mapFeed = MapsClient.getMapsFeed();
|
|
||||||
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
|
||||||
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
|
||||||
metaData.setTitle(track.getName());
|
|
||||||
metaData.setDescription(description + " - "
|
|
||||||
+ track.getCategory() + " - "
|
|
||||||
+ context.getString(R.string.new_map_description));
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
boolean mapPublic = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
mapPublic = preferences.getBoolean(
|
|
||||||
context.getString(R.string.default_map_public_key), true);
|
|
||||||
}
|
|
||||||
metaData.setSearchable(mapPublic);
|
|
||||||
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
|
||||||
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
|
||||||
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
|
||||||
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
|
||||||
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadAllTrackPoints(
|
|
||||||
final Track track, String originalDescription) {
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
boolean metricUnits = true;
|
|
||||||
if (preferences != null) {
|
|
||||||
metricUnits =
|
|
||||||
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Cursor locationsCursor =
|
|
||||||
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
|
||||||
try {
|
|
||||||
if (!locationsCursor.moveToFirst()) {
|
|
||||||
Log.w(MyTracksConstants.TAG, "Unable to get any points to upload");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalLocationsRead = 0;
|
|
||||||
totalLocationsPrepared = 0;
|
|
||||||
totalLocationsUploaded = 0;
|
|
||||||
totalLocations = locationsCursor.getCount();
|
|
||||||
totalSegmentsUploaded = 0;
|
|
||||||
|
|
||||||
// Limit the number of elevation readings. Ideally we would want around 250.
|
|
||||||
int elevationSamplingFrequency =
|
|
||||||
Math.max(1, (int) (totalLocations / 250.0));
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"Using elevation sampling factor: " + elevationSamplingFrequency
|
|
||||||
+ " on " + totalLocations);
|
|
||||||
double totalDistance = 0;
|
|
||||||
|
|
||||||
Vector<Double> distances = new Vector<Double>();
|
|
||||||
Vector<Double> elevations = new Vector<Double>();
|
|
||||||
DoubleBuffer elevationBuffer =
|
|
||||||
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
|
||||||
|
|
||||||
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_reading_track);
|
|
||||||
Location lastLocation = null;
|
|
||||||
do {
|
|
||||||
if (totalLocationsRead % 100 == 0) {
|
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
Location loc = providerUtils.createLocation(locationsCursor);
|
|
||||||
locations.add(loc);
|
|
||||||
|
|
||||||
if (totalLocationsRead == 0) {
|
|
||||||
// Put a marker at the first point of the first valid segment:
|
|
||||||
uploadMarker(track, loc, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the elevation profile.
|
|
||||||
if (loc != null && MyTracksUtils.isValidLocation(loc)) {
|
|
||||||
// All points go into the smoothing buffer...
|
|
||||||
elevationBuffer.setNext(metricUnits ? loc.getAltitude()
|
|
||||||
: loc.getAltitude() * UnitConversions.M_TO_FT);
|
|
||||||
if (lastLocation != null) {
|
|
||||||
double dist = lastLocation.distanceTo(loc);
|
|
||||||
totalDistance += dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...but only a few points are really used to keep the url short.
|
|
||||||
if (totalLocationsRead % elevationSamplingFrequency == 0) {
|
|
||||||
distances.add(totalDistance);
|
|
||||||
elevations.add(elevationBuffer.getAverage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// for in the next iteration.
|
|
||||||
lastLocation = loc;
|
|
||||||
|
|
||||||
// Every now and then, upload the accumulated points
|
|
||||||
if (totalLocationsRead % MAX_POINTS_PER_UPLOAD
|
|
||||||
== MAX_POINTS_PER_UPLOAD - 1) {
|
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalLocationsRead++;
|
|
||||||
} while (locationsCursor.moveToNext());
|
|
||||||
|
|
||||||
// Do a final upload with what's left
|
|
||||||
if (!prepareAndUploadPoints(track, locations)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put an end marker at the last point of the last valid segment:
|
|
||||||
if (lastLocation != null) {
|
|
||||||
track.setDescription("<p>" + originalDescription + "</p><p>"
|
|
||||||
+ stringUtils.generateTrackDescription(
|
|
||||||
track, distances, elevations)
|
|
||||||
+ "</p>");
|
|
||||||
return uploadMarker(track, lastLocation, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
locationsCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean uploadMarker(final Track track,
|
|
||||||
final Location location,
|
|
||||||
final boolean isStart) {
|
|
||||||
boolean okay = wrapper.runQuery(new QueryFunction() {
|
|
||||||
@Override
|
|
||||||
public void query(MapsClient client)
|
|
||||||
throws AuthenticationException, IOException, Exception {
|
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
|
||||||
insertMarker(context, client, featureFeed, track, location, isStart);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return okay;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
|
||||||
* 2nd failure.
|
|
||||||
*/
|
|
||||||
private void insertMarker(Context context, MapsClient client,
|
|
||||||
String featureFeed, Track track, Location loc,
|
|
||||||
boolean isStart) throws IOException, Exception {
|
|
||||||
Entry entry = gdataConverter.getEntryForFeature(
|
|
||||||
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
|
||||||
+ entry.getTitle());
|
|
||||||
try {
|
|
||||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
|
||||||
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
|
||||||
// Retry once (often IOException is thrown on a timeout):
|
|
||||||
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: createEntry success on 2nd try!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a placemark MyMapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @param isStart true if it's the start of the track, or false for end
|
|
||||||
* @return a MyMapsFeature
|
|
||||||
*/
|
|
||||||
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
|
||||||
Track track, Location loc, boolean isStart) {
|
|
||||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
|
||||||
myMapsFeature.generateAndroidId();
|
|
||||||
myMapsFeature.setType(MyMapsFeature.MARKER);
|
|
||||||
if (isStart) {
|
|
||||||
myMapsFeature.setIconUrl(START_ICON_URL);
|
|
||||||
} else {
|
|
||||||
myMapsFeature.setIconUrl(END_ICON_URL);
|
|
||||||
}
|
|
||||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
|
||||||
String name = track.getName() + " "
|
|
||||||
+ (isStart ? context.getString(R.string.start)
|
|
||||||
: context.getString(R.string.end));
|
|
||||||
myMapsFeature.setTitle(name);
|
|
||||||
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
|
||||||
return myMapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean prepareAndUploadPoints(Track track, List<Location> locations) {
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_preparing_track);
|
|
||||||
updateProgress();
|
|
||||||
|
|
||||||
int numLocations = locations.size();
|
|
||||||
if (numLocations < 2) {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Not preparing/uploading too few points");
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare/pre-process the points
|
|
||||||
ArrayList<Track> splitTracks = prepareLocations(track, locations);
|
|
||||||
|
|
||||||
// Start uploading them
|
|
||||||
progressIndicator.setProgressMessage(
|
|
||||||
R.string.progress_message_sending_mymaps);
|
|
||||||
for (Track splitTrack : splitTracks) {
|
|
||||||
if (totalSegmentsUploaded > 1) {
|
|
||||||
splitTrack.setName(splitTrack.getName() + " "
|
|
||||||
+ String.format(
|
|
||||||
context.getString(R.string.part), totalSegmentsUploaded));
|
|
||||||
}
|
|
||||||
totalSegmentsUploaded++;
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: Prepared feature for upload w/ "
|
|
||||||
+ splitTrack.getLocations().size() + " points.");
|
|
||||||
|
|
||||||
// Transmit tracks via GData feed:
|
|
||||||
// -------------------------------
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
|
||||||
if (!uploadTrackPoints(splitTrack)) {
|
|
||||||
Log.e(MyTracksConstants.TAG, "Uploading failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locations.clear();
|
|
||||||
totalLocationsUploaded += numLocations;
|
|
||||||
updateProgress();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares a buffer of locations for transmission to google maps.
|
* Prepares a buffer of locations for transmission to google maps.
|
||||||
@@ -438,8 +100,8 @@ public class SendToMyMaps implements Runnable {
|
|||||||
* @return an array of tracks each with a sub section of the points in the
|
* @return an array of tracks each with a sub section of the points in the
|
||||||
* original buffer
|
* original buffer
|
||||||
*/
|
*/
|
||||||
private ArrayList<Track> prepareLocations(
|
private ArrayList<Track> prepareLocations(Track track,
|
||||||
Track track, Iterable<Location> locations) {
|
TrackBuffer buffer) {
|
||||||
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
ArrayList<Track> splitTracks = new ArrayList<Track>();
|
||||||
|
|
||||||
// Create segments from each full track:
|
// Create segments from each full track:
|
||||||
@@ -453,10 +115,8 @@ public class SendToMyMaps implements Runnable {
|
|||||||
segmentStats.setStartTime(trackStats.getStartTime());
|
segmentStats.setStartTime(trackStats.getStartTime());
|
||||||
segmentStats.setStopTime(trackStats.getStopTime());
|
segmentStats.setStopTime(trackStats.getStopTime());
|
||||||
boolean startNewTrackSegment = false;
|
boolean startNewTrackSegment = false;
|
||||||
for (Location loc : locations) {
|
for (int i = 0; i < buffer.getLocationsLoaded(); ++i) {
|
||||||
if (totalLocationsPrepared % 100 == 0) {
|
Location loc = buffer.get(i);
|
||||||
updateProgress();
|
|
||||||
}
|
|
||||||
if (loc.getLatitude() > 90) {
|
if (loc.getLatitude() > 90) {
|
||||||
startNewTrackSegment = true;
|
startNewTrackSegment = true;
|
||||||
}
|
}
|
||||||
@@ -481,7 +141,6 @@ public class SendToMyMaps implements Runnable {
|
|||||||
segmentStats.setStartTime(loc.getTime());
|
segmentStats.setStartTime(loc.getTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
totalLocationsPrepared++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareTrackSegment(segment, splitTracks);
|
prepareTrackSegment(segment, splitTracks);
|
||||||
@@ -524,29 +183,236 @@ public class SendToMyMaps implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
* Inserts a place mark. Second try if 1st try fails. Will throw exception on
|
||||||
|
* 2nd failure.
|
||||||
*/
|
*/
|
||||||
private boolean uploadTrackPoints(final Track track) {
|
private void insertMarker(Context context, MapsClient client,
|
||||||
|
String featureFeed, Track track, Location loc,
|
||||||
|
boolean isStart) throws IOException, Exception {
|
||||||
|
Entry entry = gdataConverter.getEntryForFeature(
|
||||||
|
buildMyMapsPlacemarkFeature(context, track, loc, isStart));
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Creating placemark "
|
||||||
|
+ entry.getTitle());
|
||||||
|
try {
|
||||||
|
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: createEntry success!");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: createEntry 1st try failed. Trying again.");
|
||||||
|
// Retry once (often IOException is thrown on a timeout):
|
||||||
|
client.createEntry(featureFeed, auth.getAuthToken(), entry);
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: createEntry success on 2nd try!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadMarker(final Track track,
|
||||||
|
final Location location,
|
||||||
|
final boolean isStart) {
|
||||||
|
boolean okay = wrapper.runQuery(new QueryFunction() {
|
||||||
|
@Override
|
||||||
|
public void query(MapsClient client)
|
||||||
|
throws AuthenticationException, IOException, Exception {
|
||||||
|
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
|
insertMarker(context, client, featureFeed, track, location, isStart);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current upload progress.
|
||||||
|
*
|
||||||
|
* @param track The track being uploaded
|
||||||
|
* @param totalLocationsRead The total number of locations already read and
|
||||||
|
* uploaded
|
||||||
|
* @param segmentSize The percent of total that each segment corresponds to
|
||||||
|
* @param progressInSegment The approximate progress in this segment
|
||||||
|
*/
|
||||||
|
private void setProgress(final Track track, int totalLocationsRead,
|
||||||
|
double segmentSize, double progressInSegment) {
|
||||||
|
long totalLocations = track.getStopId() - track.getStartId();
|
||||||
|
|
||||||
|
// The percent of the total that represents the completed part of this
|
||||||
|
// segment.
|
||||||
|
double percentSegmentInTotal = (segmentSize * progressInSegment);
|
||||||
|
|
||||||
|
int progress = (int) ((100.0 * totalLocationsRead / totalLocations)
|
||||||
|
+ percentSegmentInTotal);
|
||||||
|
progress = Math.min(99, progress);
|
||||||
|
progressIndicator.setProgressValue(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadAllTrackPoints(final Track track) {
|
||||||
|
int totalLocationsRead = 0;
|
||||||
|
// TODO: This might not actually be accurate.
|
||||||
|
long totalLocations = track.getStopId() - track.getStartId();
|
||||||
|
|
||||||
|
// Limit the number of elevation readings. Ideally we would want around 250.
|
||||||
|
int elevationSamplingFrequency =
|
||||||
|
Math.max(1, (int) (totalLocations / 250.0));
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"Using elevation sampling factor: " + elevationSamplingFrequency
|
||||||
|
+ " on " + totalLocations);
|
||||||
|
double totalDistance = 0;
|
||||||
|
Location lastLocation = null;
|
||||||
|
|
||||||
|
TrackBuffer buffer = new TrackBuffer((int) Math.min(2048, totalLocations));
|
||||||
|
int part = 1;
|
||||||
|
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
boolean metricUnits = true;
|
||||||
|
if (preferences != null) {
|
||||||
|
metricUnits =
|
||||||
|
preferences.getBoolean(context.getString(R.string.metric_units_key),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the progress monitor:
|
||||||
|
// For each track segment 33% = read, 33% = prep & 33% = upload
|
||||||
|
// Note: These numbers are very rough.
|
||||||
|
|
||||||
|
// The size of each segment in the 100% progress
|
||||||
|
double segmentSize = buffer.getSize() * 100.0 / totalLocations;
|
||||||
|
while (success && buffer.getLastLocationRead() < track.getStopId()) {
|
||||||
|
setProgress(track, totalLocationsRead, segmentSize, 0.0);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_reading_track));
|
||||||
|
|
||||||
|
providerUtils.getTrackPoints(track, buffer);
|
||||||
|
if (buffer.getLocationsLoaded() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (totalLocationsRead == 0) {
|
||||||
|
// Put a marker at the first point of the first valid segment:
|
||||||
|
uploadMarker(track, buffer.get(0), true);
|
||||||
|
}
|
||||||
|
last = buffer.get(buffer.getLocationsLoaded() - 1);
|
||||||
|
|
||||||
|
// Extract the elevation profile.
|
||||||
|
for (int i = 0; i < buffer.getLocationsLoaded(); i++) {
|
||||||
|
// Add all points to the smoothing buffer.
|
||||||
|
Location l = buffer.get(i);
|
||||||
|
if (l != null && MyTracksUtils.isValidLocation(l)) {
|
||||||
|
elevationBuffer.setNext(metricUnits ? l.getAltitude()
|
||||||
|
: l.getAltitude() * UnitConversions.M_TO_FT);
|
||||||
|
if (lastLocation != null) {
|
||||||
|
double dist = lastLocation.distanceTo(l);
|
||||||
|
totalDistance += dist;
|
||||||
|
}
|
||||||
|
lastLocation = l;
|
||||||
|
|
||||||
|
// Only add some data points to keep the url short.
|
||||||
|
if (i % elevationSamplingFrequency == 0) {
|
||||||
|
distances.add(totalDistance);
|
||||||
|
elevations.add(elevationBuffer.getAverage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setProgress(track, totalLocationsRead, segmentSize, 0.3333);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_preparing_track));
|
||||||
|
|
||||||
|
ArrayList<Track> splitTracks = prepareLocations(track, buffer);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Track t : splitTracks) {
|
||||||
|
if (part > 1) {
|
||||||
|
t.setName(t.getName() + " "
|
||||||
|
+ String.format(context.getString(R.string.part), part));
|
||||||
|
}
|
||||||
|
part++;
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: Prepared feature for upload w/ "
|
||||||
|
+ t.getLocations().size() + " points.");
|
||||||
|
|
||||||
|
//
|
||||||
|
// Transmit tracks via GData feed:
|
||||||
|
// -------------------------------
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: Uploading to map " + mapId + " w/ auth " + auth);
|
||||||
|
if (!uploadTrackPoints(track, t,
|
||||||
|
totalLocationsRead, segmentSize)) {
|
||||||
|
success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
totalLocationsRead += buffer.getLocationsLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a given list of tracks to Google MyMaps using the maps GData feed.
|
||||||
|
*
|
||||||
|
* @param track the track meta-data is used for the map name
|
||||||
|
*/
|
||||||
|
private boolean uploadTrackPoints(final Track track, final Track currentTrack,
|
||||||
|
int totalLocationsRead, double segmentSize) {
|
||||||
|
setProgress(track, totalLocationsRead, segmentSize, 0.6666);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_sending_mymaps));
|
||||||
return wrapper.runQuery(new QueryFunction() {
|
return wrapper.runQuery(new QueryFunction() {
|
||||||
@Override
|
@Override
|
||||||
public void query(MapsClient client)
|
public void query(MapsClient client)
|
||||||
throws AuthenticationException, IOException, Exception {
|
throws AuthenticationException, IOException, Exception {
|
||||||
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
String featureFeed = MapsClient.getFeaturesFeed(mapId);
|
||||||
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
Log.d(MyTracksConstants.TAG, "Feature feed url: " + featureFeed);
|
||||||
uploadTrackPoints(track, client, featureFeed);
|
uploadTrackPoints(track, currentTrack, client, featureFeed);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean uploadTrackPoints(Track splitTrack,
|
/**
|
||||||
|
* Creates a new map for the given track.
|
||||||
|
*
|
||||||
|
* @param track The track that will be uploaded to this map
|
||||||
|
* @return True on success.
|
||||||
|
*/
|
||||||
|
private boolean createNewMap(final Track track) {
|
||||||
|
progressIndicator.setProgressMessage(context
|
||||||
|
.getString(R.string.progress_message_creating_map));
|
||||||
|
return wrapper.runQuery(new QueryFunction() {
|
||||||
|
@Override
|
||||||
|
public void query(MapsClient client) throws IOException, Exception {
|
||||||
|
Log.d(MyTracksConstants.TAG, "Creating a new map.");
|
||||||
|
String mapFeed = MapsClient.getMapsFeed();
|
||||||
|
Log.d(MyTracksConstants.TAG, "Map feed is " + mapFeed);
|
||||||
|
MyMapsMapMetadata metaData = new MyMapsMapMetadata();
|
||||||
|
metaData.setTitle(track.getName());
|
||||||
|
metaData.setDescription(originalDescription + " - "
|
||||||
|
+ track.getCategory() + " - "
|
||||||
|
+ context.getString(R.string.new_map_description));
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(
|
||||||
|
MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
boolean mapPublic = true;
|
||||||
|
if (preferences != null) {
|
||||||
|
mapPublic = preferences.getBoolean(
|
||||||
|
context.getString(R.string.default_map_public_key), true);
|
||||||
|
}
|
||||||
|
metaData.setSearchable(mapPublic);
|
||||||
|
Entry entry = MyMapsGDataConverter.getMapEntryForMetadata(metaData);
|
||||||
|
Log.d(MyTracksConstants.TAG, "Title: " + entry.getTitle());
|
||||||
|
Entry map = client.createEntry(mapFeed, auth.getAuthToken(), entry);
|
||||||
|
mapId = MapsClient.getMapIdFromMapEntryId(map.getId());
|
||||||
|
Log.d(MyTracksConstants.TAG, "New map id is: " + mapId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean uploadTrackPoints(final Track track,
|
||||||
|
Track splitTrack,
|
||||||
MapsClient client,
|
MapsClient client,
|
||||||
String featureFeed)
|
String featureFeed)
|
||||||
throws IOException, Exception {
|
throws IOException, Exception {
|
||||||
Entry entry = null;
|
Entry entry = null;
|
||||||
int numLocations = splitTrack.getLocations().size();
|
if (splitTrack.getId() != track.getId()
|
||||||
if (numLocations < 2) {
|
|| splitTrack.getLocations().size() < 2) {
|
||||||
// Need at least two points for a polyline:
|
// Need at least two points for a polyline:
|
||||||
Log.w(MyTracksConstants.TAG, "Not uploading too few points");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,29 +435,6 @@ public class SendToMyMaps implements Runnable {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a MyMapsFeature from a track.
|
|
||||||
*
|
|
||||||
* @param track the track
|
|
||||||
* @return a MyMapsFeature
|
|
||||||
*/
|
|
||||||
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
|
||||||
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
|
||||||
myMapsFeature.generateAndroidId();
|
|
||||||
myMapsFeature.setType(MyMapsFeature.LINE);
|
|
||||||
if (track.getName().length() < 1) {
|
|
||||||
// Features must have a name (otherwise GData upload may fail):
|
|
||||||
myMapsFeature.setTitle("-");
|
|
||||||
} else {
|
|
||||||
myMapsFeature.setTitle(track.getName());
|
|
||||||
}
|
|
||||||
myMapsFeature.setColor(0x80FF0000);
|
|
||||||
for (Location loc : track.getLocations()) {
|
|
||||||
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
|
||||||
}
|
|
||||||
return myMapsFeature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads all of the waypoints associated with this track to map.
|
* Uploads all of the waypoints associated with this track to map.
|
||||||
*
|
*
|
||||||
@@ -651,6 +494,185 @@ public class SendToMyMaps implements Runnable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
|
||||||
|
long trackId, ProgressIndicator progressIndicator) {
|
||||||
|
this.context = context;
|
||||||
|
this.mapId = mapId;
|
||||||
|
this.auth = auth;
|
||||||
|
this.trackId = trackId;
|
||||||
|
this.progressIndicator = progressIndicator;
|
||||||
|
this.stringUtils = new StringUtils(context);
|
||||||
|
this.providerUtils = MyTracksProviderUtils.Factory.get(context);
|
||||||
|
|
||||||
|
Log.d(MyTracksConstants.TAG, "Sending to MyMap: trackId = " + trackId);
|
||||||
|
handlerThread = new HandlerThread("SendToMyMaps");
|
||||||
|
handlerThread.start();
|
||||||
|
handler = new Handler(handlerThread.getLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
handler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
doUpload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnCompletion(Runnable onCompletion) {
|
||||||
|
this.onCompletion = onCompletion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMapId() {
|
||||||
|
return mapId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatusMessage() {
|
||||||
|
return statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doUpload() {
|
||||||
|
statusMessage = context.getString(R.string.error_sending_to_mymap);
|
||||||
|
success = true;
|
||||||
|
try {
|
||||||
|
gdataConverter = new MyMapsGDataConverter();
|
||||||
|
|
||||||
|
progressIndicator.setProgressValue(1);
|
||||||
|
progressIndicator.setProgressMessage(
|
||||||
|
context.getString(R.string.progress_message_reading_track));
|
||||||
|
|
||||||
|
// Get the track meta-data
|
||||||
|
Track track = providerUtils.getTrack(trackId);
|
||||||
|
originalDescription = track.getDescription();
|
||||||
|
track.setDescription("<p>" + track.getDescription() + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
|
||||||
|
wrapper = new MyMapsGDataWrapper(context);
|
||||||
|
wrapper.setAuthManager(auth);
|
||||||
|
wrapper.setRetryOnAuthFailure(true);
|
||||||
|
|
||||||
|
distances = new Vector<Double>();
|
||||||
|
elevations = new Vector<Double>();
|
||||||
|
elevationBuffer =
|
||||||
|
new DoubleBuffer(MyTracksConstants.ELEVATION_SMOOTHING_FACTOR);
|
||||||
|
|
||||||
|
// Create a new map if necessary:
|
||||||
|
if (mapId.equals("new")) {
|
||||||
|
if (!createNewMap(track)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload all of the segments of the track.
|
||||||
|
if (success) {
|
||||||
|
if (!uploadAllTrackPoints(track)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start end end markers.
|
||||||
|
if (success) {
|
||||||
|
// Put an end marker at the last point of the last valid segment:
|
||||||
|
if (last != null) {
|
||||||
|
track.setDescription("<p>" + originalDescription + "</p><p>"
|
||||||
|
+ stringUtils.generateTrackDescription(
|
||||||
|
track, distances, elevations)
|
||||||
|
+ "</p>");
|
||||||
|
if (!uploadMarker(track, last, false)) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put waypoints.
|
||||||
|
if (success) {
|
||||||
|
if (!uploadWaypoints(track)) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"SendToMyMaps: upload waypoints failed.");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
if (mapId.equals("new")) {
|
||||||
|
statusMessage =
|
||||||
|
context.getString(R.string.status_new_mymap_has_been_created);
|
||||||
|
} else {
|
||||||
|
statusMessage =
|
||||||
|
context.getString(R.string.status_tracks_have_been_uploaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(MyTracksConstants.TAG, "SendToMyMaps: Done: " + success);
|
||||||
|
progressIndicator.setProgressValue(100);
|
||||||
|
} catch (XmlPullParserException e) {
|
||||||
|
Log.e(MyTracksConstants.TAG, "Caught an unexpected exception.", e);
|
||||||
|
} finally {
|
||||||
|
if (wrapper != null) {
|
||||||
|
wrapper.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.runOnUiThread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (onCompletion != null) {
|
||||||
|
onCompletion.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a MyMapsFeature from a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @return a MyMapsFeature
|
||||||
|
*/
|
||||||
|
private static MyMapsFeature buildMyMapsLineFeature(Track track) {
|
||||||
|
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||||
|
myMapsFeature.generateAndroidId();
|
||||||
|
myMapsFeature.setType(MyMapsFeature.LINE);
|
||||||
|
if (track.getName().length() < 1) {
|
||||||
|
// Features must have a name (otherwise GData upload may fail):
|
||||||
|
myMapsFeature.setTitle("-");
|
||||||
|
} else {
|
||||||
|
myMapsFeature.setTitle(track.getName());
|
||||||
|
}
|
||||||
|
myMapsFeature.setColor(0x80FF0000);
|
||||||
|
for (Location loc : track.getLocations()) {
|
||||||
|
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||||
|
}
|
||||||
|
return myMapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a placemark MyMapsFeature from a track.
|
||||||
|
*
|
||||||
|
* @param track the track
|
||||||
|
* @param isStart true if it's the start of the track, or false for end
|
||||||
|
* @return a MyMapsFeature
|
||||||
|
*/
|
||||||
|
private static MyMapsFeature buildMyMapsPlacemarkFeature(Context context,
|
||||||
|
Track track, Location loc, boolean isStart) {
|
||||||
|
MyMapsFeature myMapsFeature = new MyMapsFeature();
|
||||||
|
myMapsFeature.generateAndroidId();
|
||||||
|
myMapsFeature.setType(MyMapsFeature.MARKER);
|
||||||
|
if (isStart) {
|
||||||
|
myMapsFeature.setIconUrl(START_ICON_URL);
|
||||||
|
} else {
|
||||||
|
myMapsFeature.setIconUrl(END_ICON_URL);
|
||||||
|
}
|
||||||
|
myMapsFeature.addPoint(MyTracksUtils.getGeoPoint(loc));
|
||||||
|
String name = track.getName() + " "
|
||||||
|
+ (isStart ? context.getString(R.string.start)
|
||||||
|
: context.getString(R.string.end));
|
||||||
|
myMapsFeature.setTitle(name);
|
||||||
|
myMapsFeature.setDescription(isStart ? "" : track.getDescription());
|
||||||
|
return myMapsFeature;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a MyMapsFeature from a track.
|
* Builds a MyMapsFeature from a track.
|
||||||
*
|
*
|
||||||
@@ -673,17 +695,4 @@ public class SendToMyMaps implements Runnable {
|
|||||||
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
myMapsFeature.setDescription(wpt.getDescription().replaceAll("\n", "<br>"));
|
||||||
return myMapsFeature;
|
return myMapsFeature;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the current upload progress.
|
|
||||||
*/
|
|
||||||
private void updateProgress() {
|
|
||||||
// The percent of the total that represents the completed part of this
|
|
||||||
// segment.
|
|
||||||
int totalPercentage =
|
|
||||||
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
|
|
||||||
/ (totalLocations * 3);
|
|
||||||
totalPercentage = Math.min(99, totalPercentage);
|
|
||||||
progressIndicator.setProgressValue(totalPercentage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks.io;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A class to clean up old temporary files.
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class TempFileCleaner {
|
|
||||||
|
|
||||||
private long currentTimeMillis;
|
|
||||||
|
|
||||||
public static void clean() {
|
|
||||||
(new TempFileCleaner(System.currentTimeMillis())).cleanImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @VisibleForTesting
|
|
||||||
TempFileCleaner(long time) {
|
|
||||||
currentTimeMillis = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanImpl() {
|
|
||||||
if (!Environment.getExternalStorageState().equals(
|
|
||||||
Environment.MEDIA_MOUNTED)) {
|
|
||||||
return; // Can't do anything now.
|
|
||||||
}
|
|
||||||
cleanTmpDirectory("csv");
|
|
||||||
cleanTmpDirectory("gpx");
|
|
||||||
cleanTmpDirectory("kml");
|
|
||||||
cleanTmpDirectory("tcx");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanTmpDirectory(String name) {
|
|
||||||
String sep = System.getProperty("file.separator");
|
|
||||||
cleanTmpDirectory(
|
|
||||||
new File(
|
|
||||||
Environment.getExternalStorageDirectory() + sep + name + sep
|
|
||||||
+ "tmp"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// @VisibleForTesting
|
|
||||||
int cleanTmpDirectory(File dir) {
|
|
||||||
if (!dir.exists()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int count = 0;
|
|
||||||
long oldest = currentTimeMillis - 1000 * 3600;
|
|
||||||
for (File f : dir.listFiles()) {
|
|
||||||
if (f.lastModified() < oldest) {
|
|
||||||
f.delete();
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,7 @@ package com.google.android.apps.mytracks.io;
|
|||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
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.TrackBuffer;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.util.FileUtils;
|
import com.google.android.apps.mytracks.util.FileUtils;
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
@@ -253,8 +254,55 @@ public class TrackWriter {
|
|||||||
void writeDocument() {
|
void writeDocument() {
|
||||||
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
Log.d(MyTracksConstants.TAG, "Started writing track.");
|
||||||
writer.writeHeader();
|
writer.writeHeader();
|
||||||
// TODO: Fix ordering (in GPX waypoints should come first)
|
TrackBuffer buffer = new TrackBuffer(1024);
|
||||||
writeLocations();
|
Location last = null;
|
||||||
|
boolean wroteFirst = false;
|
||||||
|
boolean segmentOpen = false;
|
||||||
|
int nValidLocations = 0;
|
||||||
|
|
||||||
|
// Fetch small pieces of the track.
|
||||||
|
while (buffer.getLastLocationRead() < track.getStopId()) {
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"Reading track points starting at: " + buffer.getLastLocationRead());
|
||||||
|
providerUtils.fillTrackPoints(track, buffer);
|
||||||
|
if (!wroteFirst) {
|
||||||
|
Location first = buffer.findStartLocation();
|
||||||
|
writer.writeBeginTrack(first);
|
||||||
|
wroteFirst = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"Reading " + buffer.getLocationsLoaded()
|
||||||
|
+ " Ending at: " + buffer.getLastLocationRead());
|
||||||
|
for (int i = 0; i < buffer.getLocationsLoaded(); i++) {
|
||||||
|
Location location = buffer.get(i);
|
||||||
|
if (MyTracksUtils.isValidLocation(location)) {
|
||||||
|
nValidLocations++;
|
||||||
|
if (!segmentOpen) {
|
||||||
|
writer.writeOpenSegment();
|
||||||
|
segmentOpen = true;
|
||||||
|
}
|
||||||
|
writer.writeLocation(location);
|
||||||
|
if (nValidLocations >= 2) {
|
||||||
|
last = location;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nValidLocations = 0;
|
||||||
|
last = null;
|
||||||
|
if (segmentOpen) {
|
||||||
|
writer.writeCloseSegment();
|
||||||
|
segmentOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (segmentOpen) {
|
||||||
|
writer.writeCloseSegment();
|
||||||
|
segmentOpen = false;
|
||||||
|
}
|
||||||
|
if (wroteFirst) {
|
||||||
|
writer.writeEndTrack(last);
|
||||||
|
}
|
||||||
writeWaypoints(track.getId());
|
writeWaypoints(track.getId());
|
||||||
writer.writeFooter();
|
writer.writeFooter();
|
||||||
writer.close();
|
writer.close();
|
||||||
@@ -262,66 +310,4 @@ public class TrackWriter {
|
|||||||
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
Log.d(MyTracksConstants.TAG, "Done writing track.");
|
||||||
errorMessage = R.string.io_write_finished;
|
errorMessage = R.string.io_write_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLocations() {
|
|
||||||
boolean wroteFirst = false;
|
|
||||||
boolean segmentOpen = false;
|
|
||||||
Location lastLoc = null, loc = null;
|
|
||||||
boolean isLastValid = false;
|
|
||||||
Cursor locationsCursor =
|
|
||||||
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
|
|
||||||
|
|
||||||
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
|
|
||||||
Log.w(MyTracksConstants.TAG, "Unable to get any points to write");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (loc == null) loc = new Location("");
|
|
||||||
providerUtils.fillLocation(locationsCursor, loc);
|
|
||||||
|
|
||||||
boolean isValid = MyTracksUtils.isValidLocation(loc);
|
|
||||||
boolean validSegment = isValid && isLastValid;
|
|
||||||
if (!wroteFirst && validSegment) {
|
|
||||||
// Found the first two consecutive points which are valid
|
|
||||||
writer.writeBeginTrack(lastLoc);
|
|
||||||
wroteFirst = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validSegment) {
|
|
||||||
if (!segmentOpen) {
|
|
||||||
// Start a segment for this point
|
|
||||||
writer.writeOpenSegment();
|
|
||||||
segmentOpen = true;
|
|
||||||
|
|
||||||
// Write the previous point, which we had previously skipped
|
|
||||||
writer.writeLocation(lastLoc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the current point
|
|
||||||
writer.writeLocation(loc);
|
|
||||||
} else {
|
|
||||||
if (segmentOpen) {
|
|
||||||
writer.writeCloseSegment();
|
|
||||||
segmentOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap loc and lastLoc (so lastLoc is reused)
|
|
||||||
Location tmp = lastLoc;
|
|
||||||
lastLoc = loc;
|
|
||||||
loc = tmp;
|
|
||||||
if (loc != null) loc.reset();
|
|
||||||
|
|
||||||
isLastValid = isValid;
|
|
||||||
} while (locationsCursor.moveToNext());
|
|
||||||
|
|
||||||
if (segmentOpen) {
|
|
||||||
writer.writeCloseSegment();
|
|
||||||
segmentOpen = false;
|
|
||||||
}
|
|
||||||
if (wroteFirst) {
|
|
||||||
writer.writeEndTrack(lastLoc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public class TrackWriterFactory {
|
|||||||
* @param format the output format to write in
|
* @param format the output format to write in
|
||||||
* @return the new track writer
|
* @return the new track writer
|
||||||
*/
|
*/
|
||||||
private static TrackWriter newWriter(Context context,
|
public static TrackWriter newWriter(Context context,
|
||||||
MyTracksProviderUtils providerUtils,
|
MyTracksProviderUtils providerUtils,
|
||||||
Track track, TrackFileFormat format) {
|
Track track, TrackFileFormat format) {
|
||||||
TrackFormatWriter writer = format.newFormatWriter(context);
|
TrackFormatWriter writer = format.newFormatWriter(context);
|
||||||
|
|||||||
+3
-4
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.io.backup;
|
package com.google.android.apps.mytracks.io.backup;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
|
|
||||||
import android.app.backup.BackupManager;
|
import android.app.backup.BackupManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -66,9 +66,8 @@ public abstract class BackupPreferencesListener
|
|||||||
/**
|
/**
|
||||||
* Creates and returns a proper instance of the listener for this device.
|
* Creates and returns a proper instance of the listener for this device.
|
||||||
*/
|
*/
|
||||||
public static BackupPreferencesListener create(
|
public static BackupPreferencesListener create(Context context) {
|
||||||
Context context, ApiFeatures apiFeatures) {
|
if (MyTracksConstants.ANDROID_API_LEVEL >= 8) {
|
||||||
if (apiFeatures.hasBackup()) {
|
|
||||||
return new BackupPreferencesListenerImpl(context);
|
return new BackupPreferencesListenerImpl(context);
|
||||||
} else {
|
} else {
|
||||||
return new DummyBackupPreferencesListener();
|
return new DummyBackupPreferencesListener();
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package com.google.android.apps.mytracks.io.gdata;
|
|||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.io.AuthManager;
|
import com.google.android.apps.mytracks.io.AuthManager;
|
||||||
|
import com.google.wireless.gdata.client.GDataClient;
|
||||||
import com.google.wireless.gdata.client.GDataServiceClient;
|
import com.google.wireless.gdata.client.GDataServiceClient;
|
||||||
import com.google.wireless.gdata.client.HttpException;
|
import com.google.wireless.gdata.client.HttpException;
|
||||||
import com.google.wireless.gdata.parser.ParseException;
|
import com.google.wireless.gdata.parser.ParseException;
|
||||||
@@ -27,10 +28,6 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.FutureTask;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,63 +84,43 @@ public class GDataWrapper {
|
|||||||
// An unknown error occurred.
|
// An unknown error occurred.
|
||||||
public static final int ERROR_UNKNOWN = 100;
|
public static final int ERROR_UNKNOWN = 100;
|
||||||
|
|
||||||
private static final int AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES = 1;
|
|
||||||
private static final int AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT = 5000;
|
|
||||||
|
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
private int errorType;
|
private int errorType;
|
||||||
|
private GDataClient androidGDataClient;
|
||||||
private GDataServiceClient gdataServiceClient;
|
private GDataServiceClient gdataServiceClient;
|
||||||
private AuthManager auth;
|
private AuthManager auth;
|
||||||
private boolean retryOnAuthFailure;
|
private boolean retryOnAuthFailure;
|
||||||
|
private int retriesPending;
|
||||||
|
private boolean cleanupCalled;
|
||||||
|
|
||||||
public GDataWrapper() {
|
public GDataWrapper() {
|
||||||
errorType = ERROR_NO_ERROR;
|
errorType = ERROR_NO_ERROR;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
auth = null;
|
auth = null;
|
||||||
retryOnAuthFailure = false;
|
retryOnAuthFailure = false;
|
||||||
|
retriesPending = 0;
|
||||||
|
cleanupCalled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClient(GDataServiceClient gdataServiceClient) {
|
public void setClient(GDataClient androidGDataClient,
|
||||||
|
GDataServiceClient gdataServiceClient) {
|
||||||
|
this.androidGDataClient = androidGDataClient;
|
||||||
this.gdataServiceClient = gdataServiceClient;
|
this.gdataServiceClient = gdataServiceClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean runAuthenticatedFunction(
|
public boolean runAuthenticatedFunction(
|
||||||
final AuthenticatedFunction function) {
|
final AuthenticatedFunction function) {
|
||||||
return runCommon(function, null);
|
return runOne(function, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean runQuery(final QueryFunction query) {
|
public boolean runQuery(final QueryFunction query) {
|
||||||
return runCommon(null, query);
|
return runOne(null, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs an arbitrary piece of code.
|
* Runs an arbitrary piece of code.
|
||||||
*/
|
*/
|
||||||
private boolean runCommon(final AuthenticatedFunction function,
|
private boolean runOne(final AuthenticatedFunction function,
|
||||||
final QueryFunction query) {
|
|
||||||
for (int i = 0; i <= AUTH_TOKEN_INVALIDATE_REFRESH_NUM_RETRIES; i++) {
|
|
||||||
runOne(function, query);
|
|
||||||
if (errorType == ERROR_NO_ERROR) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
|
||||||
if (errorType == ERROR_AUTH && auth != null) {
|
|
||||||
if (!retryOnAuthFailure || !invalidateAndRefreshAuthToken()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(MyTracksConstants.TAG, "retrying function/query");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a given function or query. If one is executed, errorType and
|
|
||||||
* errorMessage will contain the result/status of the function/query.
|
|
||||||
*/
|
|
||||||
private void runOne(final AuthenticatedFunction function,
|
|
||||||
final QueryFunction query) {
|
final QueryFunction query) {
|
||||||
try {
|
try {
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
@@ -151,14 +128,11 @@ public class GDataWrapper {
|
|||||||
} else if (query != null) {
|
} else if (query != null) {
|
||||||
query.query(gdataServiceClient);
|
query.query(gdataServiceClient);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
return false;
|
||||||
"invalid invocation of runOne; one of function/query " +
|
|
||||||
"must be non-null");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errorType = ERROR_NO_ERROR;
|
errorType = ERROR_NO_ERROR;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
|
return true;
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
Log.e(MyTracksConstants.TAG, "Exception", e);
|
Log.e(MyTracksConstants.TAG, "Exception", e);
|
||||||
errorType = ERROR_AUTH;
|
errorType = ERROR_AUTH;
|
||||||
@@ -192,42 +166,32 @@ public class GDataWrapper {
|
|||||||
errorType = ERROR_CONFLICT;
|
errorType = ERROR_CONFLICT;
|
||||||
errorMessage = e.getMessage();
|
errorMessage = e.getMessage();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates and refreshes the auth token. Blocks until the refresh has
|
|
||||||
* completed or until we deem the refresh as having timed out.
|
|
||||||
*
|
|
||||||
* @return true If the invalidate/refresh succeeds, false if it fails or
|
|
||||||
* times out.
|
|
||||||
*/
|
|
||||||
private boolean invalidateAndRefreshAuthToken() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "Retrying due to auth failure");
|
|
||||||
// This FutureTask doesn't do anything -- it exists simply to be
|
|
||||||
// blocked upon using get().
|
|
||||||
FutureTask<?> whenFinishedFuture = new FutureTask<Object>(new Runnable() {
|
|
||||||
public void run() {}
|
|
||||||
}, null);
|
|
||||||
|
|
||||||
auth.invalidateAndRefresh(whenFinishedFuture);
|
|
||||||
|
|
||||||
|
Log.d(MyTracksConstants.TAG, "GData error encountered: " + errorMessage);
|
||||||
|
if (errorType == ERROR_AUTH && auth != null) {
|
||||||
|
Runnable whenFinished = null;
|
||||||
|
if (retryOnAuthFailure) {
|
||||||
|
retriesPending++;
|
||||||
|
// This is a little odd. This method cannot return in the the thread
|
||||||
|
// that called the original runQuery(). We should look at reworking this
|
||||||
|
// API.
|
||||||
|
whenFinished = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
retriesPending--;
|
||||||
try {
|
try {
|
||||||
Log.d(MyTracksConstants.TAG, "waiting for invalidate");
|
runOne(function, query);
|
||||||
whenFinishedFuture.get(AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT,
|
} catch (IllegalStateException ise) {
|
||||||
TimeUnit.MILLISECONDS);
|
// This can happen if the connection pool was shut down.
|
||||||
Log.d(MyTracksConstants.TAG, "invalidate finished");
|
Log.e(MyTracksConstants.TAG, "Failed to rerun query.", ise);
|
||||||
return true;
|
}
|
||||||
|
if (cleanupCalled && retriesPending == 0) {
|
||||||
} catch (InterruptedException e) {
|
cleanUp();
|
||||||
Log.e(MyTracksConstants.TAG, "Failed to invalidate", e);
|
}
|
||||||
} catch (ExecutionException e) {
|
}
|
||||||
Log.e(MyTracksConstants.TAG, "Failed to invalidate", e);
|
};
|
||||||
} catch (TimeoutException e) {
|
}
|
||||||
Log.e(MyTracksConstants.TAG, "Invalidate didn't complete in time", e);
|
auth.invalidateAndRefresh(whenFinished);
|
||||||
} finally {
|
|
||||||
whenFinishedFuture.cancel(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +203,17 @@ public class GDataWrapper {
|
|||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanUp must be called when done using this wrapper to close the client.
|
||||||
|
// Note that the cleanup will be delayed if auth failure retries were
|
||||||
|
// requested and there is a pending retry.
|
||||||
|
public void cleanUp() {
|
||||||
|
cleanupCalled = true;
|
||||||
|
if (retriesPending == 0) {
|
||||||
|
androidGDataClient.close();
|
||||||
|
androidGDataClient = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setAuthManager(AuthManager auth) {
|
public void setAuthManager(AuthManager auth) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +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.services;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a default track name based on the current default track name policy.
|
|
||||||
*
|
|
||||||
* @author Matthew Simmons
|
|
||||||
*/
|
|
||||||
class DefaultTrackNameFactory {
|
|
||||||
private static final String TIMESTAMP_DATE_FORMAT = "yyyy-MM-dd HH:mm";
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
DefaultTrackNameFactory(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new track name.
|
|
||||||
*
|
|
||||||
* @param trackId The ID for the current track.
|
|
||||||
* @param startTime The start time, in milliseconds since the epoch, of the
|
|
||||||
* current track.
|
|
||||||
* @return The new track name.
|
|
||||||
*/
|
|
||||||
String newTrackName(long trackId, long startTime) {
|
|
||||||
if (useTimestampTrackName()) {
|
|
||||||
SimpleDateFormat formatter = new SimpleDateFormat(TIMESTAMP_DATE_FORMAT);
|
|
||||||
return formatter.format(new Date(startTime));
|
|
||||||
} else {
|
|
||||||
return String.format(context.getString(R.string.new_track), trackId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Determines whether the preferences allow a timestamp-based track name */
|
|
||||||
protected boolean useTimestampTrackName() {
|
|
||||||
SharedPreferences prefs =
|
|
||||||
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
return prefs.getBoolean(
|
|
||||||
context.getString(R.string.timestamp_track_name_key), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -84,4 +84,13 @@ interface ITrackRecordingService {
|
|||||||
* Deletes all the stored tracks.
|
* Deletes all the stored tracks.
|
||||||
*/
|
*/
|
||||||
void deleteAllTracks();
|
void deleteAllTracks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the service that its preferences may have been changed.
|
||||||
|
* This is necessary because the service running on a separate process cannot
|
||||||
|
* listen to the changes itself.
|
||||||
|
*
|
||||||
|
* @param key the preference key which may have changed
|
||||||
|
*/
|
||||||
|
void sharedPreferenceChanged(in String key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,20 +25,20 @@ import java.util.Timer;
|
|||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will periodically announce the user's trip statistics.
|
* This class will periodically announce the user's trip statitics.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class PeriodicTaskExecuter {
|
public class PeriodicTaskExecuter {
|
||||||
|
|
||||||
private final PeriodicTask task;
|
|
||||||
private final TrackRecordingService service;
|
private final TrackRecordingService service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A timer to schedule the announcements.
|
* A timer to schedule the announcements.
|
||||||
* This is non-null if the task is in started (scheduled) state.
|
|
||||||
*/
|
*/
|
||||||
private Timer timer;
|
private Timer timer = new Timer();
|
||||||
|
|
||||||
|
private final PeriodicTask task;
|
||||||
|
|
||||||
public PeriodicTaskExecuter(PeriodicTask task,
|
public PeriodicTaskExecuter(PeriodicTask task,
|
||||||
TrackRecordingService service) {
|
TrackRecordingService service) {
|
||||||
@@ -52,19 +52,8 @@ public class PeriodicTaskExecuter {
|
|||||||
* @param interval The interval in milliseconds
|
* @param interval The interval in milliseconds
|
||||||
*/
|
*/
|
||||||
public void scheduleTask(long interval) {
|
public void scheduleTask(long interval) {
|
||||||
// TODO: Decouple service from this class once and forever.
|
|
||||||
if (!service.isRecording()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timer != null) {
|
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
timer.purge();
|
timer.purge();
|
||||||
} else {
|
|
||||||
// First start, or we were previously shut down.
|
|
||||||
task.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
timer = new Timer();
|
timer = new Timer();
|
||||||
if (interval <= 0) {
|
if (interval <= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -72,14 +61,12 @@ public class PeriodicTaskExecuter {
|
|||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long next = service.getTripStatistics().getStartTime();
|
long next = service.getTripStatistics().getStartTime();
|
||||||
if (next < now) {
|
while (next < now) next += interval;
|
||||||
next = now + interval - ((now - next) % interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
Date start = new Date(next);
|
Date start = new Date(next);
|
||||||
Log.i(MyTracksConstants.TAG,
|
Log.i(MyTracksConstants.TAG,
|
||||||
task.getClass().getSimpleName() + " scheduled to start at " + start
|
"StatusAnnouncer scheduled to start at " + start + " every "
|
||||||
+ " every " + interval + " milliseconds.");
|
+ interval + " milliseconds.");
|
||||||
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
|
timer.scheduleAtFixedRate(new PeriodicTimerTask(), start, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,15 +74,11 @@ public class PeriodicTaskExecuter {
|
|||||||
* Cleans up this object.
|
* Cleans up this object.
|
||||||
*/
|
*/
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
Log.i(MyTracksConstants.TAG,
|
|
||||||
task.getClass().getSimpleName() + " shutting down.");
|
|
||||||
if (timer != null) {
|
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
timer.purge();
|
timer.purge();
|
||||||
timer = null;
|
timer = null;
|
||||||
task.shutdown();
|
task.shutdown();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timer task to announce the trip status.
|
* The timer task to announce the trip status.
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import com.google.android.apps.mytracks.MyTracksSettings;
|
|||||||
import com.google.android.maps.mytracks.R;
|
import com.google.android.maps.mytracks.R;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,11 +27,9 @@ import android.util.Log;
|
|||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
public class PreferenceManager {
|
||||||
private TrackRecordingService service;
|
private TrackRecordingService service;
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
private final String announcementFrequencyKey;
|
private final String announcementFrequencyKey;
|
||||||
private final String autoResumeTrackCurrentRetryKey;
|
|
||||||
private final String autoResumeTrackTimeoutKey;
|
private final String autoResumeTrackTimeoutKey;
|
||||||
private final String maxRecordingDistanceKey;
|
private final String maxRecordingDistanceKey;
|
||||||
private final String metricUnitsKey;
|
private final String metricUnitsKey;
|
||||||
@@ -45,19 +42,9 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
|
|
||||||
public PreferenceManager(TrackRecordingService service) {
|
public PreferenceManager(TrackRecordingService service) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
this.sharedPreferences = service.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
if (sharedPreferences == null) {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"TrackRecordingService: Couldn't get shared preferences.");
|
|
||||||
throw new IllegalStateException("Couldn't get shared preferences");
|
|
||||||
}
|
|
||||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
|
|
||||||
|
|
||||||
announcementFrequencyKey =
|
announcementFrequencyKey =
|
||||||
service.getString(R.string.announcement_frequency_key);
|
service.getString(R.string.announcement_frequency_key);
|
||||||
autoResumeTrackCurrentRetryKey =
|
|
||||||
service.getString(R.string.auto_resume_track_current_retry_key);
|
|
||||||
autoResumeTrackTimeoutKey =
|
autoResumeTrackTimeoutKey =
|
||||||
service.getString(R.string.auto_resume_track_timeout_key);
|
service.getString(R.string.auto_resume_track_timeout_key);
|
||||||
maxRecordingDistanceKey =
|
maxRecordingDistanceKey =
|
||||||
@@ -70,15 +57,12 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
service.getString(R.string.min_recording_interval_key);
|
service.getString(R.string.min_recording_interval_key);
|
||||||
minRequiredAccuracyKey =
|
minRequiredAccuracyKey =
|
||||||
service.getString(R.string.min_required_accuracy_key);
|
service.getString(R.string.min_required_accuracy_key);
|
||||||
recordingTrackKey =
|
|
||||||
service.getString(R.string.recording_track_key);
|
|
||||||
signalSamplingFrequencyKey =
|
|
||||||
service.getString(R.string.signal_sampling_frequency_key);
|
|
||||||
splitFrequencyKey =
|
splitFrequencyKey =
|
||||||
service.getString(R.string.split_frequency_key);
|
service.getString(R.string.split_frequency_key);
|
||||||
|
signalSamplingFrequencyKey =
|
||||||
// Refresh all properties.
|
service.getString(R.string.signal_sampling_frequency_key);
|
||||||
onSharedPreferenceChanged(sharedPreferences, null);
|
recordingTrackKey =
|
||||||
|
service.getString(R.string.recording_track_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,15 +71,15 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
*
|
*
|
||||||
* @param key the key that changed (may be null to update all preferences)
|
* @param key the key that changed (may be null to update all preferences)
|
||||||
*/
|
*/
|
||||||
@Override
|
public void onSharedPreferenceChanged(String key) {
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
|
SharedPreferences sharedPreferences =
|
||||||
String key) {
|
service.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
if (service == null) {
|
if (sharedPreferences == null) {
|
||||||
Log.w(MyTracksConstants.TAG,
|
Log.w(MyTracksConstants.TAG,
|
||||||
"onSharedPreferenceChanged: a preference change (key = " + key
|
"TrackRecordingService: Couldn't get shared preferences.");
|
||||||
+ ") after a call to shutdown()");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == null || key.equals(minRecordingDistanceKey)) {
|
if (key == null || key.equals(minRecordingDistanceKey)) {
|
||||||
service.setMinRecordingDistance(
|
service.setMinRecordingDistance(
|
||||||
sharedPreferences.getInt(
|
sharedPreferences.getInt(
|
||||||
@@ -151,13 +135,8 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT));
|
MyTracksSettings.DEFAULT_AUTO_RESUME_TRACK_TIMEOUT));
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(recordingTrackKey)) {
|
if (key == null || key.equals(recordingTrackKey)) {
|
||||||
long recordingTrackId = sharedPreferences.getLong(recordingTrackKey, -1);
|
service.setRecordingTrackId(
|
||||||
// Only read the id if it is valid.
|
sharedPreferences.getLong(recordingTrackKey, -1));
|
||||||
// Setting it to -1 should only happen in
|
|
||||||
// TrackRecordingService.endCurrentTrack()
|
|
||||||
if (recordingTrackId > 0) {
|
|
||||||
service.setRecordingTrackId(recordingTrackId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (key == null || key.equals(splitFrequencyKey)) {
|
if (key == null || key.equals(splitFrequencyKey)) {
|
||||||
service.getSplitManager().setSplitFrequency(
|
service.getSplitManager().setSplitFrequency(
|
||||||
@@ -172,23 +151,4 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
|
|||||||
sharedPreferences.getBoolean(metricUnitsKey, true));
|
sharedPreferences.getBoolean(metricUnitsKey, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAutoResumeTrackCurrentRetry(int retryAttempts) {
|
|
||||||
sharedPreferences
|
|
||||||
.edit()
|
|
||||||
.putInt(autoResumeTrackCurrentRetryKey, retryAttempts)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRecordingTrack(long id) {
|
|
||||||
sharedPreferences
|
|
||||||
.edit()
|
|
||||||
.putLong(recordingTrackKey, id)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
|
||||||
service = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2009 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
* use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
* the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class will periodically announce the user's trip statistics. This class
|
||||||
|
* is a wrapper for cupcake devices.
|
||||||
|
*
|
||||||
|
* @author Sandor Dornbush
|
||||||
|
*/
|
||||||
|
public class SafeStatusAnnouncerTask implements PeriodicTask {
|
||||||
|
|
||||||
|
private StatusAnnouncerTask announcer;
|
||||||
|
|
||||||
|
/* class initialization fails when this throws an exception */
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
Class.forName(
|
||||||
|
"com.google.android.apps.mytracks.services.StatusAnnouncerTask");
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
} catch (LinkageError er) {
|
||||||
|
throw new RuntimeException(er);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* calling here forces class initialization */
|
||||||
|
public static void checkAvailable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public SafeStatusAnnouncerTask(Context context) {
|
||||||
|
announcer = new StatusAnnouncerTask(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(TrackRecordingService service) {
|
||||||
|
announcer.run(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
announcer.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,6 @@ public class SignalStrengthTask extends PhoneStateListener
|
|||||||
context = c;
|
context = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
public void start() {
|
||||||
manager =
|
manager =
|
||||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||||
@@ -177,7 +176,6 @@ public class SignalStrengthTask extends PhoneStateListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (manager != null) {
|
if (manager != null) {
|
||||||
manager.listen(this, PhoneStateListener.LISTEN_NONE);
|
manager.listen(this, PhoneStateListener.LISTEN_NONE);
|
||||||
|
|||||||
@@ -1,48 +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.services;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for producing a proper {@link SignalStrengthTask} according to the
|
|
||||||
* current API level.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class SignalStrengthTaskFactory {
|
|
||||||
private final boolean hasModernSignalStrength;
|
|
||||||
|
|
||||||
SignalStrengthTaskFactory(ApiFeatures apiFeatures) {
|
|
||||||
this.hasModernSignalStrength = apiFeatures.hasModernSignalStrength();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PeriodicTask create(Context context) {
|
|
||||||
if (hasModernSignalStrength) {
|
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"TrackRecordingService using modern signal strength api.");
|
|
||||||
return new SignalStrengthTaskModern(context);
|
|
||||||
} else {
|
|
||||||
Log.w(MyTracksConstants.TAG,
|
|
||||||
"TrackRecordingService using legacy signal strength api.");
|
|
||||||
return new SignalStrengthTask(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -73,11 +73,6 @@ public class SplitManager {
|
|||||||
* Calculates the next distance that a split should be inserted at.
|
* Calculates the next distance that a split should be inserted at.
|
||||||
*/
|
*/
|
||||||
public void calculateNextSplit() {
|
public void calculateNextSplit() {
|
||||||
// TODO: Decouple service from this class once and forever.
|
|
||||||
if (!service.isRecording()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (splitFrequency >= 0) {
|
if (splitFrequency >= 0) {
|
||||||
nextSplitDistance = Double.MAX_VALUE;
|
nextSplitDistance = Double.MAX_VALUE;
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
@@ -125,15 +120,7 @@ public class SplitManager {
|
|||||||
* @param splitFrequency The frequency in time or distance
|
* @param splitFrequency The frequency in time or distance
|
||||||
*/
|
*/
|
||||||
public void setSplitFrequency(int splitFrequency) {
|
public void setSplitFrequency(int splitFrequency) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"setSplitFrequency: splitFrequency = " + splitFrequency);
|
|
||||||
this.splitFrequency = splitFrequency;
|
this.splitFrequency = splitFrequency;
|
||||||
|
|
||||||
// TODO: Decouple service from this class once and forever.
|
|
||||||
if (!service.isRecording()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (splitFrequency < 1) {
|
if (splitFrequency < 1) {
|
||||||
if (splitExecuter != null) {
|
if (splitExecuter != null) {
|
||||||
splitExecuter.shutdown();
|
splitExecuter.shutdown();
|
||||||
|
|||||||
@@ -1,61 +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.services;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory which wraps construction and setup of text-to-speech announcements in
|
|
||||||
* an API-level-safe way.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class StatusAnnouncerFactory {
|
|
||||||
|
|
||||||
private final boolean hasTts;
|
|
||||||
|
|
||||||
public StatusAnnouncerFactory(ApiFeatures apiFeatures) {
|
|
||||||
this.hasTts = apiFeatures.hasTextToSpeech();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a periodic task which does voice announcements.
|
|
||||||
*
|
|
||||||
* @return the task, or null if announcements are not supported
|
|
||||||
*/
|
|
||||||
public PeriodicTask create(Context context) {
|
|
||||||
if (hasTts) {
|
|
||||||
return new StatusAnnouncerTask(context);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the appropriate volume stream for controlling announcement
|
|
||||||
* volume.
|
|
||||||
*/
|
|
||||||
public int getVolumeStream() {
|
|
||||||
if (hasTts) {
|
|
||||||
return StatusAnnouncerTask.getVolumeStream();
|
|
||||||
} else {
|
|
||||||
return AudioManager.USE_DEFAULT_STREAM_TYPE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,25 +26,22 @@ import com.google.android.maps.mytracks.R;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.speech.tts.TextToSpeech;
|
import android.speech.tts.TextToSpeech;
|
||||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
|
||||||
import android.telephony.PhoneStateListener;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class will periodically announce the user's trip statistics.
|
* This class will periodically announce the user's trip statitics.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class StatusAnnouncerTask implements PeriodicTask {
|
public class StatusAnnouncerTask
|
||||||
|
implements TextToSpeech.OnInitListener, PeriodicTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The rate at which announcements are spoken.
|
* The rate at which announcements are spoken.
|
||||||
*/
|
*/
|
||||||
// @VisibleForTesting
|
private static final float TTS_SPEECH_RATE = 0.9f;
|
||||||
static final float TTS_SPEECH_RATE = 0.9f;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pointer to the service context.
|
* A pointer to the service context.
|
||||||
@@ -59,83 +56,42 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
/**
|
/**
|
||||||
* The interface to the text to speech engine.
|
* The interface to the text to speech engine.
|
||||||
*/
|
*/
|
||||||
private TextToSpeech tts;
|
private TextToSpeech tts = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The response received from the TTS engine after initialization.
|
* The response recieved from the TTS engine ater initialization.
|
||||||
*/
|
*/
|
||||||
private boolean ready = false;
|
private int ttsStatus = TextToSpeech.ERROR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we're allowed to speak right now.
|
* Constructs the announcer and start the TTS engine.
|
||||||
*/
|
*/
|
||||||
private boolean speechAllowed;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener which updates {@link #speechAllowed} when the phone state changes.
|
|
||||||
*/
|
|
||||||
private final PhoneStateListener phoneListener = new PhoneStateListener() {
|
|
||||||
@Override
|
|
||||||
public void onCallStateChanged(int state, String incomingNumber) {
|
|
||||||
speechAllowed = state == TelephonyManager.CALL_STATE_IDLE;
|
|
||||||
|
|
||||||
if (!speechAllowed && tts.isSpeaking()) {
|
|
||||||
// If we're already speaking, stop it.
|
|
||||||
tts.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public StatusAnnouncerTask(Context context) {
|
public StatusAnnouncerTask(Context context) {
|
||||||
this(context, new StringUtils(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
public StatusAnnouncerTask(Context context, StringUtils stringUtils) {
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.stringUtils = stringUtils;
|
this.stringUtils = new StringUtils(context);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the TTS engine is initialized.
|
|
||||||
*/
|
|
||||||
private void onTtsInit(int status) {
|
|
||||||
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
|
|
||||||
this.ready = status == TextToSpeech.SUCCESS;
|
|
||||||
|
|
||||||
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(MyTracksConstants.TAG, "Default language not available, using English.");
|
|
||||||
speechLanguage = Locale.ENGLISH;
|
|
||||||
}
|
|
||||||
tts.setLanguage(speechLanguage);
|
|
||||||
|
|
||||||
|
tts = new TextToSpeech(context, this);
|
||||||
|
// Force the language to be the same as the string we will be speaking.
|
||||||
|
tts.setLanguage(Locale.getDefault());
|
||||||
// Slow down the speed just a bit as it is hard to hear when exercising.
|
// Slow down the speed just a bit as it is hard to hear when exercising.
|
||||||
tts.setSpeechRate(TTS_SPEECH_RATE);
|
tts.setSpeechRate(TTS_SPEECH_RATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that the tts engine is done with initialization.
|
||||||
|
*/
|
||||||
|
public void onInit(int status) {
|
||||||
|
this.ttsStatus = status;
|
||||||
|
Log.i(MyTracksConstants.TAG, "TrackRecordingService.TTS init: " + status);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* Announces the trip status.
|
* Announces the trip status.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run(TrackRecordingService service) {
|
public void run(TrackRecordingService service) {
|
||||||
if (!ready || tts == null) {
|
if (ttsStatus != TextToSpeech.SUCCESS && tts != null) {
|
||||||
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not ready.");
|
Log.e(MyTracksConstants.TAG, "StatusAnnouncer Tts not initialized.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!speechAllowed) {
|
|
||||||
Log.i(MyTracksConstants.TAG,
|
|
||||||
"Not making announcement - not allowed at this time");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,8 +110,7 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
*
|
*
|
||||||
* @return The string that will be read to the user
|
* @return The string that will be read to the user
|
||||||
*/
|
*/
|
||||||
// @VisibleForTesting
|
private String getAnnouncement(final TripStatistics stats) {
|
||||||
protected String getAnnouncement(TripStatistics stats) {
|
|
||||||
SharedPreferences preferences =
|
SharedPreferences preferences =
|
||||||
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
context.getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
boolean metricUnits = true;
|
boolean metricUnits = true;
|
||||||
@@ -217,63 +172,11 @@ public class StatusAnnouncerTask implements PeriodicTask {
|
|||||||
context.getString(speedLabel));
|
context.getString(speedLabel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
tts.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start() {
|
public void start() {
|
||||||
Log.i(MyTracksConstants.TAG, "Starting TTS");
|
|
||||||
if (tts == null) {
|
|
||||||
// We can't have this class also be the listener, otherwise it's unsafe to
|
|
||||||
// reference it in Cupcake (even if we don't instantiate it).
|
|
||||||
tts = newTextToSpeech(context, new OnInitListener() {
|
|
||||||
@Override
|
|
||||||
public void onInit(int status) {
|
|
||||||
onTtsInit(status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
speechAllowed = true;
|
|
||||||
|
|
||||||
// Register ourselves as a listener so we won't speak during a call.
|
|
||||||
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
// Stop listening to phone state.
|
|
||||||
listenToPhoneState(phoneListener, PhoneStateListener.LISTEN_NONE);
|
|
||||||
|
|
||||||
if (tts != null) {
|
|
||||||
tts.shutdown();
|
|
||||||
tts = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(MyTracksConstants.TAG, "TTS shut down");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for instantiating a {@link TextToSpeech} object, which causes
|
|
||||||
* several issues during testing.
|
|
||||||
*/
|
|
||||||
// @VisibleForTesting
|
|
||||||
protected TextToSpeech newTextToSpeech(Context ctx, OnInitListener onInitListener) {
|
|
||||||
return new TextToSpeech(ctx, onInitListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for calls to the 100%-unmockable {@link TelephonyManager#listen}.
|
|
||||||
*/
|
|
||||||
// @VisibleForTesting
|
|
||||||
protected void listenToPhoneState(PhoneStateListener listener, int events) {
|
|
||||||
TelephonyManager telephony =
|
|
||||||
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
|
||||||
if (telephony != null) {
|
|
||||||
telephony.listen(listener, events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the volume stream to use for controlling announcement volume.
|
|
||||||
*/
|
|
||||||
public static int getVolumeStream() {
|
|
||||||
return TextToSpeech.Engine.DEFAULT_STREAM;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,22 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.services;
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class manages a period task executer.
|
* This class will manage a period task executer.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
public class TaskExecuterManager {
|
public class TaskExecuterManager {
|
||||||
|
|
||||||
private int frequency;
|
int frequency;
|
||||||
private final PeriodicTask task;
|
PeriodicTask task;
|
||||||
private PeriodicTaskExecuter executer;
|
PeriodicTaskExecuter executer;
|
||||||
|
|
||||||
public TaskExecuterManager(int frequency, PeriodicTask task,
|
public TaskExecuterManager(int frequency,
|
||||||
|
PeriodicTask task,
|
||||||
TrackRecordingService service) {
|
TrackRecordingService service) {
|
||||||
this.task = task;
|
this.task = task;
|
||||||
setFrequency(frequency, service);
|
setFrequency(frequency, service);
|
||||||
@@ -49,8 +50,7 @@ public class TaskExecuterManager {
|
|||||||
*/
|
*/
|
||||||
public void setFrequency(int frequency, TrackRecordingService service) {
|
public void setFrequency(int frequency, TrackRecordingService service) {
|
||||||
this.frequency = frequency;
|
this.frequency = frequency;
|
||||||
Log.i(MyTracksConstants.TAG, "Frequency set to " + frequency
|
Log.i(MyTracksConstants.TAG, "Frequency set to: " + frequency);
|
||||||
+ " for task " + task.getClass().getSimpleName());
|
|
||||||
|
|
||||||
if (frequency == -1) {
|
if (frequency == -1) {
|
||||||
if (executer != null) {
|
if (executer != null) {
|
||||||
@@ -61,6 +61,7 @@ public class TaskExecuterManager {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (executer == null) {
|
if (executer == null) {
|
||||||
|
task.start();
|
||||||
executer = new PeriodicTaskExecuter(task, service);
|
executer = new PeriodicTaskExecuter(task, service);
|
||||||
}
|
}
|
||||||
executer.scheduleTask(frequency * 60000);
|
executer.scheduleTask(frequency * 60000);
|
||||||
@@ -68,20 +69,12 @@ public class TaskExecuterManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores the task at the current frequency.
|
* Restore the task at the current frequency.
|
||||||
*/
|
*/
|
||||||
public void restore() {
|
public void restore() {
|
||||||
if (frequency > 0) {
|
if (frequency > 0) {
|
||||||
|
task.start();
|
||||||
executer.scheduleTask(frequency * 60000);
|
executer.scheduleTask(frequency * 60000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shuts down this executer.
|
|
||||||
*/
|
|
||||||
public void shutdown() {
|
|
||||||
if (executer != null) {
|
|
||||||
executer.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+156
-256
@@ -27,8 +27,6 @@ import com.google.android.apps.mytracks.content.Waypoint;
|
|||||||
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
import com.google.android.apps.mytracks.content.WaypointsColumns;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
import com.google.android.apps.mytracks.stats.TripStatisticsBuilder;
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
import com.google.android.apps.mytracks.util.ApiPlatformAdapter;
|
|
||||||
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
import com.google.android.apps.mytracks.util.MyTracksUtils;
|
||||||
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;
|
||||||
@@ -68,7 +66,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
private static final String STATISTICS_ICON_URL =
|
private static final String STATISTICS_ICON_URL =
|
||||||
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
|
"http://maps.google.com/mapfiles/ms/micons/ylw-pushpin.png";
|
||||||
|
|
||||||
static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
private static final int MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS = 3;
|
||||||
|
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
private LocationManager locationManager;
|
private LocationManager locationManager;
|
||||||
@@ -95,20 +93,20 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
*/
|
*/
|
||||||
private MyTracksProviderUtils providerUtils;
|
private MyTracksProviderUtils providerUtils;
|
||||||
|
|
||||||
private TripStatisticsBuilder statsBuilder;
|
private TripStatisticsBuilder statsBuilder = new TripStatisticsBuilder();
|
||||||
private TripStatisticsBuilder waypointStatsBuilder;
|
private TripStatisticsBuilder waypointStatsBuilder = new TripStatisticsBuilder();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current length of the recorded track. This length is calculated from the
|
* Current length of the recorded track. This length is calculated from the
|
||||||
* recorded points (as compared to each location fix). It's used to overlay
|
* recorded points (as compared to each location fix). It's used to overlay
|
||||||
* waypoints precisely in the elevation profile chart.
|
* waypoints precisely in the elevation profile chart.
|
||||||
*/
|
*/
|
||||||
private double length;
|
private double length = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status announcer executer.
|
* Status announcer executer.
|
||||||
*/
|
*/
|
||||||
private PeriodicTaskExecuter announcementExecuter;
|
private PeriodicTaskExecuter executer;
|
||||||
private TaskExecuterManager signalManager;
|
private TaskExecuterManager signalManager;
|
||||||
private SplitManager splitManager;
|
private SplitManager splitManager;
|
||||||
|
|
||||||
@@ -118,7 +116,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* The interval in milliseconds that we have requested to be notified of gps
|
* The interval in milliseconds that we have requested to be notified of gps
|
||||||
* readings.
|
* readings.
|
||||||
*/
|
*/
|
||||||
private long currentRecordingInterval;
|
private long currentRecordingInterval = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The policy used to decide how often we should request gps updates.
|
* The policy used to decide how often we should request gps updates.
|
||||||
@@ -130,7 +128,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* Task invoked by a timer periodically to make sure the location listener is
|
* Task invoked by a timer periodically to make sure the location listener is
|
||||||
* still registered.
|
* still registered.
|
||||||
*/
|
*/
|
||||||
private TimerTask checkLocationListener = new TimerTask() {
|
private final TimerTask checkLocationListener = new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// It's always safe to assume that if isRecording() is true, it implies
|
// It's always safe to assume that if isRecording() is true, it implies
|
||||||
@@ -166,23 +164,35 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
/**
|
/**
|
||||||
* Is the service currently recording a track?
|
* Is the service currently recording a track?
|
||||||
*/
|
*/
|
||||||
private boolean isRecording;
|
private boolean isRecording = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last good location the service has received from the location listener
|
* Last good location the service has received from the location listener
|
||||||
*/
|
*/
|
||||||
private Location lastLocation;
|
private Location lastLocation = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last valid location (i.e. not a marker) that was recorded.
|
* Last valid location (i.e. not a marker) that was recorded.
|
||||||
*/
|
*/
|
||||||
private Location lastValidLocation;
|
private Location lastValidLocation = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The frequency of status announcements.
|
* The frequency of status announcements.
|
||||||
*/
|
*/
|
||||||
private int announcementFrequency = -1;
|
private int announcementFrequency = -1;
|
||||||
|
|
||||||
|
private static boolean mTTSAvailable;
|
||||||
|
|
||||||
|
/* establish whether the "new" class is available to us */
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
SafeStatusAnnouncerTask.checkAvailable();
|
||||||
|
mTTSAvailable = true;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
mTTSAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Utility functions
|
* Utility functions
|
||||||
*/
|
*/
|
||||||
@@ -295,7 +305,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* Tries to acquire a partial wake lock if not already acquired. Logs errors
|
* Tries to acquire a partial wake lock if not already acquired. Logs errors
|
||||||
* and gives up trying in case the wake lock cannot be acquired.
|
* and gives up trying in case the wake lock cannot be acquired.
|
||||||
*/
|
*/
|
||||||
private void acquireWakeLock() {
|
public void acquireWakeLock() {
|
||||||
try {
|
try {
|
||||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
if (pm == null) {
|
if (pm == null) {
|
||||||
@@ -326,22 +336,10 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases the wake lock if it's currently held.
|
|
||||||
*/
|
|
||||||
private void releaseWakeLock() {
|
|
||||||
if (wakeLock != null && wakeLock.isHeld()) {
|
|
||||||
wakeLock.release();
|
|
||||||
wakeLock = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification message and icon in the notification bar.
|
* Shows the notification message and icon in the notification bar.
|
||||||
*/
|
*/
|
||||||
public void showNotification() {
|
public void showNotification() {
|
||||||
final ApiPlatformAdapter apiPlatformAdapter =
|
|
||||||
ApiFeatures.getInstance().getApiPlatformAdapter();
|
|
||||||
if (isRecording) {
|
if (isRecording) {
|
||||||
Notification notification = new Notification(
|
Notification notification = new Notification(
|
||||||
R.drawable.arrow_320, null /* tickerText */,
|
R.drawable.arrow_320, null /* tickerText */,
|
||||||
@@ -352,10 +350,9 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
notification.setLatestEventInfo(this, getString(R.string.app_name),
|
notification.setLatestEventInfo(this, getString(R.string.app_name),
|
||||||
getString(R.string.recording_your_track), contentIntent);
|
getString(R.string.recording_your_track), contentIntent);
|
||||||
notification.flags += Notification.FLAG_NO_CLEAR;
|
notification.flags += Notification.FLAG_NO_CLEAR;
|
||||||
apiPlatformAdapter.startForeground(this, notificationManager, 1,
|
notificationManager.notify(1, notification);
|
||||||
notification);
|
|
||||||
} else {
|
} else {
|
||||||
apiPlatformAdapter.stopForeground(this, notificationManager, 1);
|
notificationManager.cancelAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,11 +401,9 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void restoreStats(Track track) {
|
private void restoreStats(Track track) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"Restoring stats of track with ID: " + track.getId());
|
|
||||||
|
|
||||||
TripStatistics stats = track.getStatistics();
|
TripStatistics stats = track.getStatistics();
|
||||||
statsBuilder = new TripStatisticsBuilder(stats.getStartTime());
|
statsBuilder = new TripStatisticsBuilder();
|
||||||
|
statsBuilder.resumeAt(stats.getStartTime());
|
||||||
setUpAnnouncer();
|
setUpAnnouncer();
|
||||||
|
|
||||||
signalManager.restore();
|
signalManager.restore();
|
||||||
@@ -419,11 +414,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
|
Waypoint waypoint = providerUtils.getFirstWaypoint(recordingTrackId);
|
||||||
if (waypoint != null) {
|
if (waypoint != null) {
|
||||||
currentWaypointId = waypoint.getId();
|
currentWaypointId = waypoint.getId();
|
||||||
waypointStatsBuilder = new TripStatisticsBuilder(
|
waypointStatsBuilder = new TripStatisticsBuilder(waypoint.getStatistics());
|
||||||
waypoint.getStatistics());
|
|
||||||
} else {
|
} else {
|
||||||
// This should never happen, but we got to do something so life goes on:
|
// This should never happen, but we got to do something so life goes on:
|
||||||
waypointStatsBuilder = new TripStatisticsBuilder(stats.getStartTime());
|
waypointStatsBuilder = new TripStatisticsBuilder();
|
||||||
|
waypointStatsBuilder.resumeAt(stats.getStartTime());
|
||||||
currentWaypointId = -1;
|
currentWaypointId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +442,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
}
|
}
|
||||||
statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
|
statsBuilder.getStatistics().setMovingTime(stats.getMovingTime());
|
||||||
statsBuilder.pauseAt(stats.getStopTime());
|
statsBuilder.pauseAt(stats.getStopTime());
|
||||||
statsBuilder.resumeAt(System.currentTimeMillis());
|
statsBuilder.resume();
|
||||||
} else {
|
} else {
|
||||||
Log.e(MyTracksConstants.TAG, "Could not get track points cursor.");
|
Log.e(MyTracksConstants.TAG, "Could not get track points cursor.");
|
||||||
}
|
}
|
||||||
@@ -615,6 +610,28 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SharedPreferencesChangeListener interface implementation. Note that
|
||||||
|
* services don't currently receive this event (Android platform limitation).
|
||||||
|
* This should be called from an activity whenever settings change.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that preferences have changed.
|
||||||
|
* Call this with key == null to update all preferences in one call.
|
||||||
|
*
|
||||||
|
* @param key the key that changed (may be null to update all preferences)
|
||||||
|
*/
|
||||||
|
public void onSharedPreferenceChanged(String key) {
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"TrackRecordingService.onSharedPreferenceChanged");
|
||||||
|
prefManager.onSharedPreferenceChanged(key);
|
||||||
|
|
||||||
|
if (isRecording) {
|
||||||
|
registerLocationListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Application lifetime events: ============================
|
* Application lifetime events: ============================
|
||||||
*/
|
*/
|
||||||
@@ -628,14 +645,21 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||||
splitManager = new SplitManager(this);
|
splitManager = new SplitManager(this);
|
||||||
|
try {
|
||||||
SignalStrengthTaskFactory strengthTaskFactory =
|
|
||||||
new SignalStrengthTaskFactory(ApiFeatures.getInstance());
|
|
||||||
signalManager =
|
signalManager =
|
||||||
new TaskExecuterManager(-1, strengthTaskFactory.create(this), this);
|
new TaskExecuterManager(-1, new SignalStrengthTaskModern(this), this);
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"TrackRecordingService using modern signal strength api.");
|
||||||
|
} catch (LinkageError e) {
|
||||||
|
Log.w(MyTracksConstants.TAG,
|
||||||
|
"TrackRecordingService could not load modern signal strength.", e);
|
||||||
|
signalManager =
|
||||||
|
new TaskExecuterManager(-1, new SignalStrengthTask(this), this);
|
||||||
|
}
|
||||||
prefManager = new PreferenceManager(this);
|
prefManager = new PreferenceManager(this);
|
||||||
|
prefManager.onSharedPreferenceChanged(null);
|
||||||
registerLocationListener();
|
registerLocationListener();
|
||||||
|
acquireWakeLock();
|
||||||
/**
|
/**
|
||||||
* After 5 min, check every minute that location listener still is
|
* After 5 min, check every minute that location listener still is
|
||||||
* registered and spit out additional debugging info to the logs:
|
* registered and spit out additional debugging info to the logs:
|
||||||
@@ -648,13 +672,6 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
if (recordingTrack != null) {
|
if (recordingTrack != null) {
|
||||||
restoreStats(recordingTrack);
|
restoreStats(recordingTrack);
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
} else {
|
|
||||||
if (recordingTrackId != -1) {
|
|
||||||
// Make sure we have consistent state in shared preferences.
|
|
||||||
Log.w(MyTracksConstants.TAG, "TrackRecordingService.onCreate: "
|
|
||||||
+ "Resetting an orphaned recording track = " + recordingTrackId);
|
|
||||||
}
|
|
||||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
|
||||||
}
|
}
|
||||||
showNotification();
|
showNotification();
|
||||||
}
|
}
|
||||||
@@ -665,72 +682,30 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* the announcements, otherwise this method is no-op.
|
* the announcements, otherwise this method is no-op.
|
||||||
*/
|
*/
|
||||||
private void setUpAnnouncer() {
|
private void setUpAnnouncer() {
|
||||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.setUpAnnouncer: "
|
if (mTTSAvailable && announcementFrequency != -1) {
|
||||||
+ announcementExecuter);
|
if (executer == null) {
|
||||||
if (announcementFrequency != -1 && recordingTrackId != -1) {
|
SafeStatusAnnouncerTask announcer = new SafeStatusAnnouncerTask(this);
|
||||||
handler.post(new Runnable() {
|
executer = new PeriodicTaskExecuter(announcer, this);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (announcementExecuter == null) {
|
|
||||||
StatusAnnouncerFactory statusAnnouncerFactory =
|
|
||||||
new StatusAnnouncerFactory(ApiFeatures.getInstance());
|
|
||||||
PeriodicTask announcer = statusAnnouncerFactory.create(
|
|
||||||
TrackRecordingService.this);
|
|
||||||
if (announcer == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Either use TaskExecuterManager everywhere, or get rid of it
|
|
||||||
announcementExecuter = new PeriodicTaskExecuter(announcer,
|
|
||||||
TrackRecordingService.this);
|
|
||||||
}
|
|
||||||
announcementExecuter.scheduleTask(announcementFrequency * 60000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void shutdownAnnouncer() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.shutdownAnnouncer: "
|
|
||||||
+ announcementExecuter);
|
|
||||||
if (announcementExecuter != null) {
|
|
||||||
try {
|
|
||||||
announcementExecuter.shutdown();
|
|
||||||
} finally {
|
|
||||||
announcementExecuter = null;
|
|
||||||
}
|
}
|
||||||
|
executer.scheduleTask(announcementFrequency * 60000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onDestroy");
|
Log.d(MyTracksConstants.TAG, "TrackRecordingService.onDestroy");
|
||||||
|
checkLocationListener.cancel();
|
||||||
|
timer.cancel();
|
||||||
|
if (wakeLock != null && wakeLock.isHeld()) {
|
||||||
|
wakeLock.release();
|
||||||
|
}
|
||||||
isRecording = false;
|
isRecording = false;
|
||||||
showNotification();
|
showNotification();
|
||||||
prefManager.shutdown();
|
|
||||||
prefManager = null;
|
|
||||||
checkLocationListener.cancel();
|
|
||||||
checkLocationListener = null;
|
|
||||||
timer.cancel();
|
|
||||||
timer.purge();
|
|
||||||
unregisterLocationListener();
|
unregisterLocationListener();
|
||||||
shutdownAnnouncer();
|
if (executer != null) {
|
||||||
signalManager.shutdown();
|
executer.shutdown();
|
||||||
signalManager = null;
|
}
|
||||||
splitManager.shutdown();
|
splitManager.shutdown();
|
||||||
splitManager = null;
|
|
||||||
|
|
||||||
// Make sure we have no indirect references to this service.
|
|
||||||
locationManager = null;
|
|
||||||
notificationManager = null;
|
|
||||||
providerUtils = null;
|
|
||||||
binder.detachFromService();
|
|
||||||
binder = null;
|
|
||||||
|
|
||||||
// This should be the last operation.
|
|
||||||
releaseWakeLock();
|
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,10 +751,8 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
// Make sure that the current track exists and is fresh enough.
|
// Make sure that the current track exists and is fresh enough.
|
||||||
if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
|
if (recordingTrack == null || !shouldResumeTrack(recordingTrack)) {
|
||||||
Log.i(MyTracksConstants.TAG,
|
Log.i(MyTracksConstants.TAG,
|
||||||
"TrackRecordingService: Not resuming, because the previous track ("
|
"TrackRecordingService: Not resuming because the previous track "
|
||||||
+ recordingTrack + ") doesn't exist or is too old");
|
+ "doesn't exist or is too old");
|
||||||
isRecording = false;
|
|
||||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
|
||||||
stopSelfResult(startId);
|
stopSelfResult(startId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -792,12 +765,16 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
SharedPreferences sharedPreferences, int retryAttempts) {
|
SharedPreferences sharedPreferences, int retryAttempts) {
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"Updating auto-resume retry attempts to: " + retryAttempts);
|
"Updating auto-resume retry attempts to: " + retryAttempts);
|
||||||
prefManager.setAutoResumeTrackCurrentRetry(retryAttempts);
|
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putInt(
|
||||||
|
getString(R.string.auto_resume_track_current_retry_key), retryAttempts);
|
||||||
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldResumeTrack(Track track) {
|
private boolean shouldResumeTrack(Track track) {
|
||||||
Log.d(MyTracksConstants.TAG, "shouldResumeTrack: autoResumeTrackTimeout = "
|
Log.d(MyTracksConstants.TAG,
|
||||||
+ autoResumeTrackTimeout);
|
"maybeResumeTrack: autoResumeTrackTimeout = " + autoResumeTrackTimeout);
|
||||||
|
|
||||||
// Check if we haven't exceeded the maximum number of retry attempts.
|
// Check if we haven't exceeded the maximum number of retry attempts.
|
||||||
SharedPreferences sharedPreferences =
|
SharedPreferences sharedPreferences =
|
||||||
@@ -805,11 +782,11 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
int retries = sharedPreferences.getInt(
|
int retries = sharedPreferences.getInt(
|
||||||
getString(R.string.auto_resume_track_current_retry_key), 0);
|
getString(R.string.auto_resume_track_current_retry_key), 0);
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"shouldResumeTrack: Attempting to auto-resume the track ("
|
"TrackRecordingService: Attempting to auto-resume the track ("
|
||||||
+ (retries + 1) + "/" + MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS + ")");
|
+ (retries + 1) + "/" + MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS + ")");
|
||||||
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
if (retries >= MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS) {
|
||||||
Log.i(MyTracksConstants.TAG,
|
Log.i(MyTracksConstants.TAG,
|
||||||
"shouldResumeTrack: Not resuming because exceeded the maximum "
|
"TrackRecordingService: Not resuming because exceeded the maximum "
|
||||||
+ "number of auto-resume retries");
|
+ "number of auto-resume retries");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -820,13 +797,9 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
// Check for special cases.
|
// Check for special cases.
|
||||||
if (autoResumeTrackTimeout == 0) {
|
if (autoResumeTrackTimeout == 0) {
|
||||||
// Never resume.
|
// Never resume.
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"shouldResumeTrack: Auto-resume disabled (never resume)");
|
|
||||||
return false;
|
return false;
|
||||||
} else if (autoResumeTrackTimeout == -1) {
|
} else if (autoResumeTrackTimeout == -1) {
|
||||||
// Always resume.
|
// Always resume.
|
||||||
Log.d(MyTracksConstants.TAG,
|
|
||||||
"shouldResumeTrack: Auto-resume forced (always resume)");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -834,8 +807,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
long lastModified =
|
long lastModified =
|
||||||
track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
|
track.getStatistics() != null ? track.getStatistics().getStopTime() : 0;
|
||||||
Log.d(MyTracksConstants.TAG,
|
Log.d(MyTracksConstants.TAG,
|
||||||
"shouldResumeTrack: lastModified = " + lastModified
|
"maybeResumeTrack: lastModified = " + lastModified);
|
||||||
+ ", autoResumeTrackTimeout: " + autoResumeTrackTimeout);
|
|
||||||
return lastModified > 0 && System.currentTimeMillis() - lastModified <=
|
return lastModified > 0 && System.currentTimeMillis() - lastModified <=
|
||||||
autoResumeTrackTimeout * 60 * 1000;
|
autoResumeTrackTimeout * 60 * 1000;
|
||||||
}
|
}
|
||||||
@@ -845,11 +817,6 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long insertWaypointMarker(Waypoint waypoint) {
|
public long insertWaypointMarker(Waypoint waypoint) {
|
||||||
if (!isRecording()) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unable to insert waypoint marker while not recording!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waypoint.getLocation() != null) {
|
if (waypoint.getLocation() != null) {
|
||||||
waypoint.setLength(length);
|
waypoint.setLength(length);
|
||||||
waypoint.setDuration(waypoint.getLocation().getTime()
|
waypoint.setDuration(waypoint.getLocation().getTime()
|
||||||
@@ -868,11 +835,6 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
* @return the unique id of the inserted marker
|
* @return the unique id of the inserted marker
|
||||||
*/
|
*/
|
||||||
public long insertStatisticsMarker(Location location) {
|
public long insertStatisticsMarker(Location location) {
|
||||||
if (!isRecording()) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unable to insert statistics marker while not recording!");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringUtils utils = new StringUtils(TrackRecordingService.this);
|
StringUtils utils = new StringUtils(TrackRecordingService.this);
|
||||||
|
|
||||||
// Create a new waypoint to save
|
// Create a new waypoint to save
|
||||||
@@ -900,69 +862,63 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
Uri uri = providerUtils.insertWaypoint(waypoint);
|
Uri uri = providerUtils.insertWaypoint(waypoint);
|
||||||
|
|
||||||
// Create a new stats keeper for the next marker
|
// Create a new stats keeper for the next marker
|
||||||
waypointStatsBuilder = new TripStatisticsBuilder(time);
|
waypointStatsBuilder = new TripStatisticsBuilder();
|
||||||
|
waypointStatsBuilder.resumeAt(time);
|
||||||
updateCurrentWaypoint();
|
updateCurrentWaypoint();
|
||||||
return Long.parseLong(uri.getLastPathSegment());
|
return Long.parseLong(uri.getLastPathSegment());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServiceBinder binder = new ServiceBinder(this);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: There is a bug in Android that leaks Binder instances. This bug is
|
* The ITrackRecordingService is defined through IDL.
|
||||||
* especially visible if we have a non-static class, as there is no way to
|
|
||||||
* nullify reference to the outer class (the service).
|
|
||||||
* A workaround is to use a static class and explicitly clear service
|
|
||||||
* and detach it from the underlying Binder. With this approach, we minimize
|
|
||||||
* the leak to 24 bytes per each service instance.
|
|
||||||
*
|
|
||||||
* For more details, see the following bug:
|
|
||||||
* http://code.google.com/p/android/issues/detail?id=6426.
|
|
||||||
*/
|
*/
|
||||||
private static class ServiceBinder extends ITrackRecordingService.Stub {
|
private final ITrackRecordingService.Stub binder =
|
||||||
private TrackRecordingService service;
|
new ITrackRecordingService.Stub() {
|
||||||
|
|
||||||
public ServiceBinder(TrackRecordingService service) {
|
|
||||||
this.service = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the reference to the outer class to minimize the leak.
|
|
||||||
*/
|
|
||||||
public void detachFromService() {
|
|
||||||
this.service = null;
|
|
||||||
attachInterface(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecording() {
|
public boolean isRecording() {
|
||||||
if (service == null) {
|
return TrackRecordingService.this.isRecording();
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
return service.isRecording();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getRecordingTrackId() {
|
public long getRecordingTrackId() {
|
||||||
if (service == null) {
|
return recordingTrackId;
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
return service.recordingTrackId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasRecorded() {
|
public boolean hasRecorded() {
|
||||||
if (service == null) {
|
return providerUtils.getLastTrackId() >= 0;
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
return service.providerUtils.getLastTrackId() >= 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long startNewTrack() {
|
public long startNewTrack() {
|
||||||
if (service == null) {
|
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
Track track = new Track();
|
||||||
}
|
TripStatistics trackStats = track.getStatistics();
|
||||||
return service.startNewTrack();
|
track.setName("new");
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
trackStats.setStartTime(startTime);
|
||||||
|
track.setStartId(-1);
|
||||||
|
Uri trackUri = providerUtils.insertTrack(track);
|
||||||
|
long trackId = Long.parseLong(trackUri.getLastPathSegment());
|
||||||
|
track.setId(trackId);
|
||||||
|
track.setName(String.format(getString(R.string.new_track), trackId));
|
||||||
|
providerUtils.updateTrack(track);
|
||||||
|
recordingTrackId = trackId;
|
||||||
|
currentWaypointId = insertStatisticsMarker(null);
|
||||||
|
isRecording = true;
|
||||||
|
isMoving = true;
|
||||||
|
statsBuilder = new TripStatisticsBuilder();
|
||||||
|
statsBuilder.resumeAt(startTime);
|
||||||
|
setUpAnnouncer();
|
||||||
|
length = 0;
|
||||||
|
showNotification();
|
||||||
|
registerLocationListener();
|
||||||
|
splitManager.restore();
|
||||||
|
signalManager.restore();
|
||||||
|
// Reset the number of auto-resume retries.
|
||||||
|
SharedPreferences sharedPreferences =
|
||||||
|
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0);
|
||||||
|
setAutoResumeTrackRetries(sharedPreferences, 0);
|
||||||
|
return trackId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -974,10 +930,7 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long insertWaypointMarker(Waypoint waypoint) {
|
public long insertWaypointMarker(Waypoint waypoint) {
|
||||||
if (service == null) {
|
return TrackRecordingService.this.insertWaypointMarker(waypoint);
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
return service.insertWaypointMarker(waypoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -989,88 +942,12 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long insertStatisticsMarker(Location location) {
|
public long insertStatisticsMarker(Location location) {
|
||||||
if (service == null) {
|
return TrackRecordingService.this.insertStatisticsMarker(location);
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
return service.insertStatisticsMarker(location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endCurrentTrack() {
|
public void endCurrentTrack() {
|
||||||
if (service == null) {
|
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
service.endCurrentTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteAllTracks() {
|
|
||||||
if (service == null) {
|
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
if (isRecording()) {
|
|
||||||
throw new IllegalStateException("Cannot delete all tracks while recording!");
|
|
||||||
}
|
|
||||||
service.providerUtils.deleteAllTracks();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void recordLocation(Location loc) {
|
|
||||||
if (service == null) {
|
|
||||||
throw new IllegalStateException("The service has been already detached!");
|
|
||||||
}
|
|
||||||
service.onLocationChanged(loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long startNewTrack() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.startNewTrack");
|
|
||||||
if (recordingTrackId != -1 || isRecording) {
|
|
||||||
throw new IllegalStateException("A track is already in progress!");
|
|
||||||
}
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
acquireWakeLock();
|
|
||||||
|
|
||||||
Track track = new Track();
|
|
||||||
TripStatistics trackStats = track.getStatistics();
|
|
||||||
trackStats.setStartTime(startTime);
|
|
||||||
track.setStartId(-1);
|
|
||||||
Uri trackUri = providerUtils.insertTrack(track);
|
|
||||||
recordingTrackId = Long.parseLong(trackUri.getLastPathSegment());
|
|
||||||
track.setId(recordingTrackId);
|
|
||||||
track.setName(new DefaultTrackNameFactory(this).newTrackName(
|
|
||||||
recordingTrackId, startTime));
|
|
||||||
isRecording = true;
|
|
||||||
isMoving = true;
|
|
||||||
|
|
||||||
providerUtils.updateTrack(track);
|
|
||||||
statsBuilder = new TripStatisticsBuilder(startTime);
|
|
||||||
waypointStatsBuilder = new TripStatisticsBuilder(startTime);
|
|
||||||
currentWaypointId = insertStatisticsMarker(null);
|
|
||||||
setUpAnnouncer();
|
|
||||||
length = 0;
|
|
||||||
showNotification();
|
|
||||||
registerLocationListener();
|
|
||||||
splitManager.restore();
|
|
||||||
signalManager.restore();
|
|
||||||
|
|
||||||
// Reset the number of auto-resume retries.
|
|
||||||
setAutoResumeTrackRetries(
|
|
||||||
getSharedPreferences(MyTracksSettings.SETTINGS_NAME, 0), 0);
|
|
||||||
// Persist the current recording track.
|
|
||||||
prefManager.setRecordingTrack(recordingTrackId);
|
|
||||||
|
|
||||||
return recordingTrackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void endCurrentTrack() {
|
|
||||||
Log.d(MyTracksConstants.TAG, "TrackRecordingService.endCurrentTrack");
|
Log.d(MyTracksConstants.TAG, "TrackRecordingService.endCurrentTrack");
|
||||||
if (recordingTrackId == -1 || !isRecording) {
|
|
||||||
throw new IllegalStateException("No recording track in progress!");
|
|
||||||
}
|
|
||||||
|
|
||||||
shutdownAnnouncer();
|
|
||||||
isRecording = false;
|
isRecording = false;
|
||||||
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
Track recordingTrack = providerUtils.getTrack(recordingTrackId);
|
||||||
if (recordingTrack != null) {
|
if (recordingTrack != null) {
|
||||||
@@ -1090,11 +967,29 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
"_id=" + recordingTrack.getId(), null);
|
"_id=" + recordingTrack.getId(), null);
|
||||||
}
|
}
|
||||||
showNotification();
|
showNotification();
|
||||||
prefManager.setRecordingTrack(recordingTrackId = -1);
|
recordingTrackId = -1;
|
||||||
releaseWakeLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TripStatistics getTripStatistics() {
|
@Override
|
||||||
|
public void deleteAllTracks() {
|
||||||
|
endCurrentTrack();
|
||||||
|
providerUtils.deleteAllTracks();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recordLocation(Location loc) {
|
||||||
|
onLocationChanged(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sharedPreferenceChanged(String key) {
|
||||||
|
Log.d(MyTracksConstants.TAG,
|
||||||
|
"TrackRecordingService.sharedPreferenceChanged: " + key);
|
||||||
|
onSharedPreferenceChanged(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TripStatistics getTripStatistics() {
|
||||||
return statsBuilder.getStatistics();
|
return statsBuilder.getStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,12 +1011,17 @@ public class TrackRecordingService extends Service implements LocationListener {
|
|||||||
|
|
||||||
public void setAnnouncementFrequency(int announcementFrequency) {
|
public void setAnnouncementFrequency(int announcementFrequency) {
|
||||||
this.announcementFrequency = announcementFrequency;
|
this.announcementFrequency = announcementFrequency;
|
||||||
|
if (mTTSAvailable) {
|
||||||
if (announcementFrequency == -1) {
|
if (announcementFrequency == -1) {
|
||||||
shutdownAnnouncer();
|
if (executer != null) {
|
||||||
|
executer.shutdown();
|
||||||
|
executer = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setUpAnnouncer();
|
setUpAnnouncer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getMaxRecordingDistance() {
|
public int getMaxRecordingDistance() {
|
||||||
return maxRecordingDistance;
|
return maxRecordingDistance;
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ public class TripStatisticsBuilder {
|
|||||||
/**
|
/**
|
||||||
* The last location that the gps reported.
|
* The last location that the gps reported.
|
||||||
*/
|
*/
|
||||||
private Location lastLocation;
|
private Location lastLocation = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last location that contributed to the stats. It is also the last
|
* The last location that contributed to the stats. It is also the last
|
||||||
* location the user was found to be moving.
|
* location the user was found to be moving.
|
||||||
*/
|
*/
|
||||||
private Location lastMovingLocation;
|
private Location lastMovingLocation = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current speed in meters/second as reported by the gps.
|
* The current speed in meters/second as reported by the gps.
|
||||||
@@ -90,13 +90,10 @@ public class TripStatisticsBuilder {
|
|||||||
private long totalLocations = 0;
|
private long totalLocations = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new trip starting at the given time.
|
* Creates a new trip starting at the current system time.
|
||||||
*
|
|
||||||
* @param startTime the start time.
|
|
||||||
*/
|
*/
|
||||||
public TripStatisticsBuilder(long startTime) {
|
public TripStatisticsBuilder() {
|
||||||
data = new TripStatistics();
|
data = new TripStatistics();
|
||||||
resumeAt(startTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,9 +103,6 @@ public class TripStatisticsBuilder {
|
|||||||
*/
|
*/
|
||||||
public TripStatisticsBuilder(TripStatistics statsData) {
|
public TripStatisticsBuilder(TripStatistics statsData) {
|
||||||
data = new TripStatistics(statsData);
|
data = new TripStatistics(statsData);
|
||||||
if (data.getStartTime() > 0) {
|
|
||||||
resumeAt(data.getStartTime());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,8 +140,8 @@ public class TripStatisticsBuilder {
|
|||||||
|
|
||||||
// Don't do anything if we didn't move since last fix:
|
// Don't do anything if we didn't move since last fix:
|
||||||
double distance = lastLocation.distanceTo(currentLocation);
|
double distance = lastLocation.distanceTo(currentLocation);
|
||||||
if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE &&
|
if (distance < MyTracksConstants.MAX_NO_MOVEMENT_DISTANCE
|
||||||
currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
|
&& currentSpeed < MyTracksConstants.MAX_NO_MOVEMENT_SPEED) {
|
||||||
lastLocation = currentLocation;
|
lastLocation = currentLocation;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -297,6 +291,13 @@ public class TripStatisticsBuilder {
|
|||||||
data.updateGradeExtremities(gradeBuffer.getAverage());
|
data.updateGradeExtremities(gradeBuffer.getAverage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the track at the current time.
|
||||||
|
*/
|
||||||
|
public void pause() {
|
||||||
|
pauseAt(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses the track at the given time.
|
* Pauses the track at the given time.
|
||||||
*
|
*
|
||||||
@@ -311,6 +312,13 @@ public class TripStatisticsBuilder {
|
|||||||
paused = true;
|
paused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the current track at the current time.
|
||||||
|
*/
|
||||||
|
public void resume() {
|
||||||
|
resumeAt(System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resumes the current track at the given time.
|
* Resumes the current track at the given time.
|
||||||
*
|
*
|
||||||
@@ -356,7 +364,8 @@ public class TripStatisticsBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TripStatistics getStatistics() {
|
public TripStatistics getStatistics() {
|
||||||
// Take a snapshot - we don't want anyone messing with our internals
|
// Take a snapshot - we do't want anyone messing with our internals
|
||||||
return new TripStatistics(data);
|
return new TripStatistics(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,119 +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.util;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for determining if newer-API features are available on the
|
|
||||||
* current device.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class ApiFeatures {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The API level of the Android version we're being run under.
|
|
||||||
*/
|
|
||||||
public static final int ANDROID_API_LEVEL = Integer.parseInt(
|
|
||||||
Build.VERSION.SDK);
|
|
||||||
|
|
||||||
private static ApiFeatures instance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The API platform adapter supported by this system.
|
|
||||||
*/
|
|
||||||
private ApiPlatformAdapter apiPlatformAdapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the singleton instance of this class.
|
|
||||||
*/
|
|
||||||
public static ApiFeatures getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new ApiFeatures();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injects a specific singleton instance, to be used for unit tests.
|
|
||||||
*/
|
|
||||||
public static void injectInstance(ApiFeatures instance) {
|
|
||||||
ApiFeatures.instance = instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow subclasses for mocking, but no direct instantiation.
|
|
||||||
*/
|
|
||||||
protected ApiFeatures() {
|
|
||||||
if (getApiLevel() >= 5) {
|
|
||||||
try {
|
|
||||||
Class<?> clazz = Class.forName(
|
|
||||||
"com.google.android.apps.mytracks.util.EclairPlatformAdapter");
|
|
||||||
apiPlatformAdapter = (ApiPlatformAdapter) clazz.newInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.i(MyTracksConstants.TAG, "ApiFeatures: Unable to instantiate Eclair"
|
|
||||||
+ " platform adapter", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (apiPlatformAdapter == null) {
|
|
||||||
Log.i(MyTracksConstants.TAG,
|
|
||||||
"ApiFeatures: Using default platform adapter");
|
|
||||||
// Cupcake adapter is always supported, so it's safe to do static linkage.
|
|
||||||
apiPlatformAdapter = new CupcakePlatformAdapter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiPlatformAdapter getApiPlatformAdapter() {
|
|
||||||
return apiPlatformAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether cloud backup (a.k.a. Froyo backup) is available.
|
|
||||||
*/
|
|
||||||
public boolean hasBackup() {
|
|
||||||
return getApiLevel() >= 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether text-to-speech is available.
|
|
||||||
*/
|
|
||||||
public boolean hasTextToSpeech() {
|
|
||||||
if (getApiLevel() < 4) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class.forName("android.speech.tts.TextToSpeech");
|
|
||||||
} catch (ClassNotFoundException ex) {
|
|
||||||
return false;
|
|
||||||
} catch (LinkageError er) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasModernSignalStrength() {
|
|
||||||
return getApiLevel() >= 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visible for testing.
|
|
||||||
protected int getApiLevel() {
|
|
||||||
return ANDROID_API_LEVEL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +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.util;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A set of methods that may be implemented in a platform specific way.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public interface ApiPlatformAdapter {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the specified service into foreground.
|
|
||||||
*
|
|
||||||
* @param service the service to be put in foreground.
|
|
||||||
* @param notificationManager the notification manager used to post the given
|
|
||||||
* notification.
|
|
||||||
* @param id the ID of the notification, unique within the application.
|
|
||||||
* @param notification the notification to post.
|
|
||||||
*/
|
|
||||||
void startForeground(Service service, NotificationManager notificationManager,
|
|
||||||
int id, Notification notification);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the given service into background.
|
|
||||||
* @param service the service to put into background.
|
|
||||||
* @param notificationManager the notification manager to user when removing
|
|
||||||
* notifications.
|
|
||||||
* @param id the ID of the notification to be remove, or -1 if the
|
|
||||||
* notification shouldn't be removed.
|
|
||||||
*/
|
|
||||||
void stopForeground(Service service, NotificationManager notificationManager,
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -32,12 +32,12 @@ public class ChartsExtendedEncoder {
|
|||||||
|
|
||||||
public static String getEncodedValue(int scaled) {
|
public static String getEncodedValue(int scaled) {
|
||||||
int index1 = scaled / CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
int index1 = scaled / CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||||
if (index1 < 0 || index1 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
int index2 = scaled % CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
||||||
|
|
||||||
|
if (index1 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||||
return MISSING_POINT_EXTENDED_ENCODING;
|
return MISSING_POINT_EXTENDED_ENCODING;
|
||||||
}
|
}
|
||||||
|
if (index2 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
||||||
int index2 = scaled % CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES;
|
|
||||||
if (index2 < 0 || index2 >= CHARTSERVER_EXTENDED_SINGLE_CHARACTER_VALUES) {
|
|
||||||
return MISSING_POINT_EXTENDED_ENCODING;
|
return MISSING_POINT_EXTENDED_ENCODING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +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.util;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Cupcake (API level 3) specific implementation of the
|
|
||||||
* {@link ApiPlatformAdapter}.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public class CupcakePlatformAdapter implements ApiPlatformAdapter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startForeground(Service service,
|
|
||||||
NotificationManager notificationManager, int id,
|
|
||||||
Notification notification) {
|
|
||||||
service.setForeground(true);
|
|
||||||
notificationManager.notify(id, notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopForeground(Service service,
|
|
||||||
NotificationManager notificationManager, int id) {
|
|
||||||
service.setForeground(false);
|
|
||||||
if (id != -1) {
|
|
||||||
notificationManager.cancel(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
|
||||||
// Do nothing, as Cupcake doesn't provide quit().
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +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.util;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.os.HandlerThread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Eclair (API level 5) specific implementation of the
|
|
||||||
* {@link ApiPlatformAdapter}.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public class EclairPlatformAdapter implements ApiPlatformAdapter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startForeground(Service service,
|
|
||||||
NotificationManager notificationManager, int id,
|
|
||||||
Notification notification) {
|
|
||||||
service.startForeground(id, notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stopForeground(Service service,
|
|
||||||
NotificationManager notificationManager, int id) {
|
|
||||||
service.stopForeground(id != -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean stopHandlerThread(HandlerThread handlerThread) {
|
|
||||||
return handlerThread.quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,7 @@ public class FileUtils {
|
|||||||
* A set of characters that are prohibited from being in file names.
|
* A set of characters that are prohibited from being in file names.
|
||||||
*/
|
*/
|
||||||
private static final Pattern PROHIBITED_CHAR_PATTERN =
|
private static final Pattern PROHIBITED_CHAR_PATTERN =
|
||||||
Pattern.compile("[^ A-Za-z0-9_.()-]+");
|
Pattern.compile("[^ A-Za-z0-9_.()]+");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a path inside the My Tracks directory in the SD card.
|
* Builds a path inside the My Tracks directory in the SD card.
|
||||||
@@ -69,6 +69,8 @@ public class FileUtils {
|
|||||||
* Normalizes the input string and make sure it is a valid fat32 file name.
|
* Normalizes the input string and make sure it is a valid fat32 file name.
|
||||||
*
|
*
|
||||||
* @param name the name to normalize
|
* @param name the name to normalize
|
||||||
|
* @param overheadSize the number of additional characters that will be added
|
||||||
|
* to the name after sanitization
|
||||||
* @return the sanitized name
|
* @return the sanitized name
|
||||||
*/
|
*/
|
||||||
String sanitizeName(String name) {
|
String sanitizeName(String name) {
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
<factorypath>
|
<factorypath>
|
||||||
<factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="PLUGIN" id="org.eclipse.jst.ws.annotations.core" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="WKSPJAR" id="/MyTracksTest/libs/AndroidMockGenerator.jar" enabled="true" runInBatchMode="false"/>
|
<factorypathentry kind="WKSPJAR" id="/MyTracksTest/libs/AndroidMockGenerator.jar" enabled="true" runInBatchMode="false"/>
|
||||||
<factorypathentry kind="VARJAR" id="ANDROID_SDK/platforms/android-8/android.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="VARJAR" id="ANDROID_SDK/add-ons/addon_google_apis_google_inc_8/libs/maps.jar" enabled="true" runInBatchMode="false"/>
|
|
||||||
<factorypathentry kind="VARJAR" id="MYTRACKS_BIN" enabled="true" runInBatchMode="false"/>
|
|
||||||
</factorypath>
|
</factorypath>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#Mon Nov 01 02:42:03 BRST 2010
|
#Thu Aug 12 19:30:48 BRT 2010
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.apt.aptEnabled=true
|
org.eclipse.jdt.apt.aptEnabled=true
|
||||||
org.eclipse.jdt.apt.genSrcDir=.apt_generated
|
org.eclipse.jdt.apt.genSrcDir=.apt_generated
|
||||||
org.eclipse.jdt.apt.processorOptions/bin_dir=%PROJECT.DIR%/bin
|
org.eclipse.jdt.apt.processorOptions/bin_dir=MyTracksTest/bin
|
||||||
org.eclipse.jdt.apt.processorOptions/logfile=%PROJECT.DIR%/annotations.log
|
org.eclipse.jdt.apt.processorOptions/logfile=MyTracksTest/annotations.log
|
||||||
org.eclipse.jdt.apt.reconcileEnabled=false
|
org.eclipse.jdt.apt.reconcileEnabled=false
|
||||||
|
|||||||
@@ -1,94 +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.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Paint.Style;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class ChartValueSeriesTest extends AndroidTestCase {
|
|
||||||
private ChartValueSeries series;
|
|
||||||
private Paint fillPaint1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
fillPaint1 = new Paint();
|
|
||||||
fillPaint1.setStyle(Style.FILL);
|
|
||||||
fillPaint1.setColor(getContext().getResources().getColor(R.color.green));
|
|
||||||
fillPaint1.setAntiAlias(true);
|
|
||||||
series = new ChartValueSeries(getContext(),
|
|
||||||
"###,###",
|
|
||||||
fillPaint1,
|
|
||||||
null,
|
|
||||||
100,
|
|
||||||
R.string.elevation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testInitialConditions() {
|
|
||||||
assertEquals(25, series.getInterval());
|
|
||||||
assertEquals(1, series.getMaxLabelLength());
|
|
||||||
assertEquals(0, series.getMin());
|
|
||||||
assertEquals(0, series.getMax());
|
|
||||||
assertEquals(0.0, series.getSpread());
|
|
||||||
assertEquals(fillPaint1, series.getPaint());
|
|
||||||
assertEquals(getContext().getString(R.string.elevation),
|
|
||||||
series.getTitle());
|
|
||||||
assertTrue(series.isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEnabled() {
|
|
||||||
series.setEnabled(false);
|
|
||||||
assertFalse(series.isEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSmallUpdates() {
|
|
||||||
series.update(0);
|
|
||||||
series.update(10);
|
|
||||||
series.updateDimension();
|
|
||||||
assertEquals(25, series.getInterval());
|
|
||||||
assertEquals(3, series.getMaxLabelLength());
|
|
||||||
assertEquals(0, series.getMin());
|
|
||||||
assertEquals(100, series.getMax());
|
|
||||||
assertEquals(100.0, series.getSpread());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBigUpdates() {
|
|
||||||
series.update(0);
|
|
||||||
series.update(901);
|
|
||||||
series.updateDimension();
|
|
||||||
assertEquals(200, series.getInterval());
|
|
||||||
assertEquals(5, series.getMaxLabelLength());
|
|
||||||
assertEquals(0, series.getMin());
|
|
||||||
assertEquals(1000, series.getMax());
|
|
||||||
assertEquals(1000.0, series.getSpread());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNotZeroBasedUpdates() {
|
|
||||||
series.update(500);
|
|
||||||
series.update(1401);
|
|
||||||
series.updateDimension();
|
|
||||||
assertEquals(200, series.getInterval());
|
|
||||||
assertEquals(5, series.getMaxLabelLength());
|
|
||||||
assertEquals(500, series.getMin());
|
|
||||||
assertEquals(1500, series.getMax());
|
|
||||||
assertEquals(1000.0, series.getSpread());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +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 android.app.AlertDialog;
|
|
||||||
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 SendToGoogleDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_onCreateSendToGoogleResult() {
|
|
||||||
Dialog d =
|
|
||||||
getActivity().onCreateDialog(
|
|
||||||
DialogManager.DIALOG_SEND_TO_GOOGLE_RESULT, null);
|
|
||||||
assertNotNull(d);
|
|
||||||
assertTrue(d instanceof AlertDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_onCreateWriteProgress() {
|
|
||||||
Dialog d =
|
|
||||||
getActivity().onCreateDialog(
|
|
||||||
DialogManager.DIALOG_WRITE_PROGRESS, null);
|
|
||||||
assertNotNull(d);
|
|
||||||
assertTrue(d instanceof ProgressDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,243 +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.content.Waypoint;
|
|
||||||
import com.google.android.maps.GeoPoint;
|
|
||||||
import com.google.android.maps.MapView;
|
|
||||||
import com.google.android.maps.Projection;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Path;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.PointF;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the MyTracks map overlay.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public class MyTracksOverlayTest extends AndroidTestCase {
|
|
||||||
private Canvas canvas;
|
|
||||||
private MockMyTracksOverlay myTracksOverlay;
|
|
||||||
private MapView mockView;
|
|
||||||
private Projection mockProjection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mock version of {@code MyTracksOverlay} that does not use
|
|
||||||
* {@class MapView}.
|
|
||||||
*/
|
|
||||||
private class MockMyTracksOverlay extends MyTracksOverlay {
|
|
||||||
public MockMyTracksOverlay(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
Projection getMapProjection(MapView mapView) {
|
|
||||||
return mockProjection;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
Rect getMapViewRect(MapView mapView) {
|
|
||||||
return new Rect(0, 0, 100, 100);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
Path newPath() {
|
|
||||||
return new MockPath();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mock class that intercepts {@code Path}'s and records calls to
|
|
||||||
* {@code #moveTo()} and {@code #lineTo()}.
|
|
||||||
*/
|
|
||||||
private static class MockPath extends Path {
|
|
||||||
/** A list of disjoined path segments. */
|
|
||||||
public final List<List<PointF>> segments = new LinkedList<List<PointF>>();
|
|
||||||
/** The total number of points in this path. */
|
|
||||||
public int totalPoints;
|
|
||||||
|
|
||||||
private List<PointF> currentSegment;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void lineTo(float x, float y) {
|
|
||||||
super.lineTo(x, y);
|
|
||||||
assertNotNull(currentSegment);
|
|
||||||
currentSegment.add(new PointF(x, y));
|
|
||||||
totalPoints++;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void moveTo(float x, float y) {
|
|
||||||
super.moveTo(x, y);
|
|
||||||
segments.add(currentSegment =
|
|
||||||
new ArrayList<PointF>(Arrays.asList(new PointF(x, y))));
|
|
||||||
totalPoints++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mock {@code Projection} that acts as the identity matrix.
|
|
||||||
*/
|
|
||||||
private static class MockProjection implements Projection {
|
|
||||||
@Override
|
|
||||||
public Point toPixels(GeoPoint in, Point out) {
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public float metersToEquatorPixels(float meters) {
|
|
||||||
return meters;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public GeoPoint fromPixels(int x, int y) {
|
|
||||||
return new GeoPoint(y, x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
canvas = new Canvas();
|
|
||||||
myTracksOverlay = new MockMyTracksOverlay(getContext());
|
|
||||||
// Enable drawing.
|
|
||||||
myTracksOverlay.setTrackDrawingEnabled(true);
|
|
||||||
mockView = null;
|
|
||||||
mockProjection = new MockProjection();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAddLocation() throws Exception {
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(10);
|
|
||||||
location.setLongitude(20);
|
|
||||||
myTracksOverlay.addLocation(location);
|
|
||||||
assertEquals(1, myTracksOverlay.getNumLocations());
|
|
||||||
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
|
||||||
|
|
||||||
location.setLatitude(20);
|
|
||||||
location.setLongitude(30);
|
|
||||||
myTracksOverlay.addLocation(location);
|
|
||||||
assertEquals(2, myTracksOverlay.getNumLocations());
|
|
||||||
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertNull(myTracksOverlay.getLastPath());
|
|
||||||
|
|
||||||
// Draw and make sure that we don't lose any point.
|
|
||||||
myTracksOverlay.draw(canvas, mockView, false);
|
|
||||||
assertEquals(2, myTracksOverlay.getNumLocations());
|
|
||||||
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertNotNull(myTracksOverlay.getLastPath());
|
|
||||||
assertEquals(2, ((MockPath)myTracksOverlay.getLastPath()).totalPoints);
|
|
||||||
|
|
||||||
myTracksOverlay.draw(canvas, mockView, true);
|
|
||||||
assertEquals(2, myTracksOverlay.getNumLocations());
|
|
||||||
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertNotNull(myTracksOverlay.getLastPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testClearPoints() throws Exception {
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(10);
|
|
||||||
location.setLongitude(20);
|
|
||||||
myTracksOverlay.addLocation(location);
|
|
||||||
assertEquals(1, myTracksOverlay.getNumLocations());
|
|
||||||
myTracksOverlay.clearPoints();
|
|
||||||
assertEquals(0, myTracksOverlay.getNumLocations());
|
|
||||||
|
|
||||||
// Same after drawing on canvas.
|
|
||||||
final int locations = 100;
|
|
||||||
for (int i = 0; i < locations; ++i) {
|
|
||||||
myTracksOverlay.addLocation(location);
|
|
||||||
}
|
|
||||||
assertEquals(locations, myTracksOverlay.getNumLocations());
|
|
||||||
myTracksOverlay.draw(canvas, mockView, false);
|
|
||||||
myTracksOverlay.draw(canvas, mockView, true);
|
|
||||||
myTracksOverlay.clearPoints();
|
|
||||||
assertEquals(0, myTracksOverlay.getNumLocations());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAddWaypoint() throws Exception {
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(10);
|
|
||||||
location.setLongitude(20);
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
waypoint.setLocation(location);
|
|
||||||
myTracksOverlay.addWaypoint(waypoint);
|
|
||||||
assertEquals(1, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertEquals(0, myTracksOverlay.getNumLocations());
|
|
||||||
assertNull(myTracksOverlay.getLastPath());
|
|
||||||
|
|
||||||
final int waypoints = 10;
|
|
||||||
for (int i = 0; i < waypoints; ++i) {
|
|
||||||
waypoint = new Waypoint();
|
|
||||||
waypoint.setLocation(location);
|
|
||||||
myTracksOverlay.addWaypoint(waypoint);
|
|
||||||
}
|
|
||||||
assertEquals(1 + waypoints, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertEquals(0, myTracksOverlay.getNumLocations());
|
|
||||||
assertNull(myTracksOverlay.getLastPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testClearWaypoints() throws Exception {
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(10);
|
|
||||||
location.setLongitude(20);
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
waypoint.setLocation(location);
|
|
||||||
myTracksOverlay.addWaypoint(waypoint);
|
|
||||||
assertEquals(1, myTracksOverlay.getNumWaypoints());
|
|
||||||
myTracksOverlay.clearWaypoints();
|
|
||||||
assertEquals(0, myTracksOverlay.getNumWaypoints());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDrawing() {
|
|
||||||
Location location = new Location("gps");
|
|
||||||
location.setLatitude(10);
|
|
||||||
for (int i = 0; i < 40; ++i) {
|
|
||||||
location.setLongitude(20 + i);
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
waypoint.setLocation(location);
|
|
||||||
myTracksOverlay.addWaypoint(waypoint);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 100; ++i) {
|
|
||||||
location = new Location("gps");
|
|
||||||
location.setLatitude(20 + i / 2);
|
|
||||||
location.setLongitude(150 - i);
|
|
||||||
myTracksOverlay.addLocation(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shadow.
|
|
||||||
myTracksOverlay.draw(canvas, mockView, true);
|
|
||||||
// We don't expect to do anything if
|
|
||||||
assertNull(myTracksOverlay.getLastPath());
|
|
||||||
assertEquals(40, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertEquals(100, myTracksOverlay.getNumLocations());
|
|
||||||
|
|
||||||
// No shadow.
|
|
||||||
myTracksOverlay.draw(canvas, mockView, false);
|
|
||||||
assertNotNull(myTracksOverlay.getLastPath());
|
|
||||||
assertTrue(myTracksOverlay.getLastPath() instanceof MockPath);
|
|
||||||
MockPath path = (MockPath) myTracksOverlay.getLastPath();
|
|
||||||
assertEquals(40, myTracksOverlay.getNumWaypoints());
|
|
||||||
assertEquals(100, myTracksOverlay.getNumLocations());
|
|
||||||
assertEquals(100, path.totalPoints);
|
|
||||||
// TODO: Check the points from the path (and the segments).
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,250 +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.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Instrumentation.ActivityMonitor;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.Editor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.test.ActivityInstrumentationTestCase2;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A unit test for {@link MyTracks} activity.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public class MyTracksTest extends ActivityInstrumentationTestCase2<MyTracks>{
|
|
||||||
public MyTracksTest() {
|
|
||||||
super(MyTracks.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
MyTracks.clearInstance();
|
|
||||||
assertNull(MyTracks.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() throws Exception {
|
|
||||||
clearSelectedAndRecordingTracks();
|
|
||||||
waitForIdle();
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testInitialization_mainAction() {
|
|
||||||
// Make sure we can start MyTracks and the activity doesn't start recording.
|
|
||||||
assertNotNull(getActivity());
|
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
|
||||||
assertFalse(getActivity().isRecording());
|
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testInitialization_viewActionWithNoData() {
|
|
||||||
// Simulate start with ACTION_VIEW intent.
|
|
||||||
Intent startIntent = new Intent();
|
|
||||||
startIntent.setAction(Intent.ACTION_VIEW);
|
|
||||||
setActivityIntent(startIntent);
|
|
||||||
|
|
||||||
assertNotNull(getActivity());
|
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
|
||||||
assertFalse(getActivity().isRecording());
|
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testInitialization_viewActionWithValidData() throws Exception {
|
|
||||||
// Simulate start with ACTION_VIEW intent.
|
|
||||||
Intent startIntent = new Intent();
|
|
||||||
startIntent.setAction(Intent.ACTION_VIEW);
|
|
||||||
Uri uri = Uri.fromFile(File.createTempFile("valid", ".gpx"));
|
|
||||||
|
|
||||||
// TODO: Add a valid GPX.
|
|
||||||
|
|
||||||
startIntent.setData(uri);
|
|
||||||
setActivityIntent(startIntent);
|
|
||||||
|
|
||||||
assertNotNull(getActivity());
|
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
|
||||||
assertFalse(getActivity().isRecording());
|
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
|
|
||||||
// TODO: Finish this test.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testInitialization_viewActionWithInvalidData() throws Exception {
|
|
||||||
// Simulate start with ACTION_VIEW intent.
|
|
||||||
Intent startIntent = new Intent();
|
|
||||||
startIntent.setAction(Intent.ACTION_VIEW);
|
|
||||||
Uri uri = Uri.fromFile(File.createTempFile("invalid", ".gpx"));
|
|
||||||
startIntent.setData(uri);
|
|
||||||
setActivityIntent(startIntent);
|
|
||||||
|
|
||||||
assertNotNull(getActivity());
|
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
|
||||||
assertFalse(getActivity().isRecording());
|
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
|
|
||||||
// TODO: Finish this test.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRecording_startAndStop() throws Exception {
|
|
||||||
// Make sure we can start MyTracks and the activity doesn't start recording.
|
|
||||||
assertNotNull(getActivity());
|
|
||||||
assertNotNull(MyTracks.getInstance());
|
|
||||||
assertNotNull(getActivity().getSharedPreferences());
|
|
||||||
|
|
||||||
// Check if not recording.
|
|
||||||
clearSelectedAndRecordingTracks();
|
|
||||||
waitForIdle();
|
|
||||||
|
|
||||||
assertFalse(getActivity().isRecording());
|
|
||||||
assertEquals(-1, getActivity().getRecordingTrackId());
|
|
||||||
long selectedTrackId = getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
|
|
||||||
// Start a new track.
|
|
||||||
getActivity().startRecording();
|
|
||||||
long recordingTrackId = awaitRecordingStatus(5000, true);
|
|
||||||
assertTrue(recordingTrackId >= 0);
|
|
||||||
|
|
||||||
// Wait until we are done and make sure that selectedTrack = recordingTrack.
|
|
||||||
waitForIdle();
|
|
||||||
assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.recording_track_key), -1));
|
|
||||||
selectedTrackId = getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
assertEquals(recordingTrackId, selectedTrackId);
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
|
|
||||||
// Watch for MyTracksDetails activity.
|
|
||||||
ActivityMonitor monitor = getInstrumentation().addMonitor(
|
|
||||||
MyTracksDetails.class.getName(), null, false);
|
|
||||||
|
|
||||||
// Now, stop the track and make sure that it is still selected, but
|
|
||||||
// no longer recording.
|
|
||||||
getActivity().stopRecording();
|
|
||||||
|
|
||||||
// Check if we got back MyTracksDetails activity.
|
|
||||||
Activity activity = getInstrumentation().waitForMonitor(monitor);
|
|
||||||
assertTrue(activity instanceof MyTracksDetails);
|
|
||||||
|
|
||||||
// TODO: Update track name and other properties and test if they were
|
|
||||||
// properly saved.
|
|
||||||
|
|
||||||
// Simulate a click on Save button.
|
|
||||||
Button save = (Button) activity.findViewById(R.id.trackdetails_save);
|
|
||||||
save.performClick();
|
|
||||||
|
|
||||||
// Check the remaining properties.
|
|
||||||
recordingTrackId = awaitRecordingStatus(5000, false);
|
|
||||||
assertEquals(-1, recordingTrackId);
|
|
||||||
assertEquals(recordingTrackId, getActivity().getRecordingTrackId());
|
|
||||||
assertEquals(recordingTrackId, getActivity().getSharedPreferences().getLong(
|
|
||||||
getActivity().getString(R.string.recording_track_key), -1));
|
|
||||||
// Make sure this is the same track as the last recording track ID.
|
|
||||||
assertEquals(selectedTrackId, getActivity().getSelectedTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits until the UI thread becomes idle.
|
|
||||||
*/
|
|
||||||
private void waitForIdle() throws InterruptedException {
|
|
||||||
// Note: We can't use getInstrumentation().waitForIdleSync() here.
|
|
||||||
final Object semaphore = new Object();
|
|
||||||
synchronized (semaphore) {
|
|
||||||
final AtomicBoolean isIdle = new AtomicBoolean();
|
|
||||||
getInstrumentation().waitForIdle(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronized (semaphore) {
|
|
||||||
isIdle.set(true);
|
|
||||||
semaphore.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
while (!isIdle.get()) {
|
|
||||||
semaphore.wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears {selected,recording}TrackId in the {@link SharedPreferences}.
|
|
||||||
*/
|
|
||||||
private void clearSelectedAndRecordingTracks() {
|
|
||||||
Editor editor = getActivity().getSharedPreferences().edit();
|
|
||||||
editor.putLong(getActivity().getString(R.string.selected_track_key), -1);
|
|
||||||
editor.putLong(getActivity().getString(R.string.recording_track_key), -1);
|
|
||||||
|
|
||||||
editor.clear();
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits until the recording state changes to the given status.
|
|
||||||
*
|
|
||||||
* @param timeout the maximum time to wait, in milliseconds.
|
|
||||||
* @param isRecording the final status to await.
|
|
||||||
* @return the recording track ID.
|
|
||||||
*/
|
|
||||||
private long awaitRecordingStatus(long timeout, boolean isRecording)
|
|
||||||
throws TimeoutException, InterruptedException {
|
|
||||||
long startTime = System.nanoTime();
|
|
||||||
while (getActivity().isRecording() != isRecording) {
|
|
||||||
if (System.nanoTime() - startTime > timeout * 1000000) {
|
|
||||||
throw new TimeoutException("Timeout while waiting for recording!");
|
|
||||||
}
|
|
||||||
Thread.sleep(20);
|
|
||||||
}
|
|
||||||
waitForIdle();
|
|
||||||
assertEquals(isRecording, getActivity().isRecording());
|
|
||||||
return getActivity().getRecordingTrackId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks.io;
|
|
||||||
|
|
||||||
import static com.google.android.testing.mocking.AndroidMock.expect;
|
|
||||||
import com.google.android.testing.mocking.AndroidMock;
|
|
||||||
import com.google.android.testing.mocking.UsesMocks;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sandor Dornbush
|
|
||||||
*/
|
|
||||||
public class TempFileCleanerTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
@UsesMocks({
|
|
||||||
File.class,
|
|
||||||
})
|
|
||||||
|
|
||||||
public void test_noDir() {
|
|
||||||
File dir = AndroidMock.createMock(File.class, "/no_file");
|
|
||||||
TempFileCleaner cleaner = new TempFileCleaner(0);
|
|
||||||
expect(dir.exists()).andStubReturn(false);
|
|
||||||
AndroidMock.replay(dir);
|
|
||||||
assertEquals(0, cleaner.cleanTmpDirectory(dir));
|
|
||||||
AndroidMock.verify(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_emptyDir() {
|
|
||||||
File dir = AndroidMock.createMock(File.class, "/no_file");
|
|
||||||
TempFileCleaner cleaner = new TempFileCleaner(0);
|
|
||||||
expect(dir.exists()).andStubReturn(true);
|
|
||||||
expect(dir.listFiles()).andStubReturn(new File[0]);
|
|
||||||
AndroidMock.replay(dir);
|
|
||||||
assertEquals(0, cleaner.cleanTmpDirectory(dir));
|
|
||||||
AndroidMock.verify(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_newFile() {
|
|
||||||
File dir = AndroidMock.createMock(File.class, "/no_file");
|
|
||||||
long now = 100000000;
|
|
||||||
TempFileCleaner cleaner = new TempFileCleaner(now);
|
|
||||||
expect(dir.exists()).andStubReturn(true);
|
|
||||||
File file = AndroidMock.createMock(File.class, "/no_file/foo");
|
|
||||||
expect(file.lastModified()).andStubReturn(now);
|
|
||||||
File[] list = { file };
|
|
||||||
expect(dir.listFiles()).andStubReturn(list);
|
|
||||||
|
|
||||||
AndroidMock.replay(dir, file);
|
|
||||||
assertEquals(0, cleaner.cleanTmpDirectory(dir));
|
|
||||||
AndroidMock.verify(dir, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void test_oldFile() {
|
|
||||||
File dir = AndroidMock.createMock(File.class, "/no_file");
|
|
||||||
long now = 100000000;
|
|
||||||
TempFileCleaner cleaner = new TempFileCleaner(now);
|
|
||||||
expect(dir.exists()).andStubReturn(true);
|
|
||||||
File file = AndroidMock.createMock(File.class, "/no_file/foo");
|
|
||||||
expect(file.lastModified()).andStubReturn(now - 3600001);
|
|
||||||
expect(file.delete()).andStubReturn(true);
|
|
||||||
File[] list = { file };
|
|
||||||
expect(dir.listFiles()).andStubReturn(list);
|
|
||||||
|
|
||||||
AndroidMock.replay(dir, file);
|
|
||||||
assertEquals(1, cleaner.cleanTmpDirectory(dir));
|
|
||||||
AndroidMock.verify(dir, file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,23 +2,21 @@
|
|||||||
|
|
||||||
package com.google.android.apps.mytracks.io;
|
package com.google.android.apps.mytracks.io;
|
||||||
|
|
||||||
import static org.easymock.EasyMock.eq;
|
|
||||||
import static org.easymock.EasyMock.expect;
|
import static org.easymock.EasyMock.expect;
|
||||||
import static org.easymock.EasyMock.isA;
|
import static org.easymock.EasyMock.isA;
|
||||||
import static org.easymock.EasyMock.leq;
|
|
||||||
import static org.easymock.EasyMock.same;
|
import static org.easymock.EasyMock.same;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksConstants;
|
import com.google.android.apps.mytracks.MyTracksConstants;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.Factory;
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
|
import com.google.android.apps.mytracks.content.TrackBuffer;
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
import com.google.android.apps.mytracks.content.Waypoint;
|
||||||
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
import com.google.android.apps.mytracks.testing.TestingProviderUtilsFactory;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.MatrixCursor;
|
import android.database.Cursor;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.provider.BaseColumns;
|
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -27,7 +25,6 @@ import java.io.OutputStream;
|
|||||||
|
|
||||||
import org.easymock.EasyMock;
|
import org.easymock.EasyMock;
|
||||||
import org.easymock.IAnswer;
|
import org.easymock.IAnswer;
|
||||||
import org.easymock.IArgumentMatcher;
|
|
||||||
import org.easymock.IMocksControl;
|
import org.easymock.IMocksControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,8 +226,6 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
expect(providerUtils.getWaypointsCursor(
|
expect(providerUtils.getWaypointsCursor(
|
||||||
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
|
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
|
||||||
.andStubReturn(null);
|
.andStubReturn(null);
|
||||||
expect(providerUtils.getLocationsCursor(
|
|
||||||
eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(null);
|
|
||||||
|
|
||||||
// Set expected mock behavior
|
// Set expected mock behavior
|
||||||
formatWriter.writeHeader();
|
formatWriter.writeHeader();
|
||||||
@@ -247,78 +242,61 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
public void testWriteDocument() {
|
public void testWriteDocument() {
|
||||||
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
|
writer = new TrackWriter(getContext(), providerUtils, track, formatWriter);
|
||||||
|
|
||||||
final Location[] locs = {
|
Location l1 = new Location("fake1");
|
||||||
new Location("fake0"),
|
Location l2 = new Location("fake2");
|
||||||
new Location("fake1"),
|
Location l3 = new Location("fake3");
|
||||||
new Location("fake2"),
|
Location l4 = new Location("fake4");
|
||||||
new Location("fake3"),
|
Location l5 = new Location("fake5");
|
||||||
new Location("fake4"),
|
Location l6 = new Location("fake6");
|
||||||
new Location("fake5"),
|
Waypoint p1 = new Waypoint();
|
||||||
};
|
Waypoint p2 = new Waypoint();
|
||||||
Waypoint[] wps = { new Waypoint(), new Waypoint(), new Waypoint() };
|
|
||||||
|
|
||||||
// Fill locations with valid values
|
addLocations(l1, l2, l3, l4, l5, l6);
|
||||||
fillLocations(locs);
|
stubBufferFill(
|
||||||
|
new Location[] { l1, l2, l3, l4 },
|
||||||
|
new Location[] { l5, l6 });
|
||||||
|
|
||||||
|
track.setStopId(6L);
|
||||||
|
|
||||||
// Make location 3 invalid
|
// Make location 3 invalid
|
||||||
locs[2].setLatitude(100);
|
l3.setLatitude(100);
|
||||||
|
|
||||||
// Set up cursors
|
|
||||||
// We use fake columns since the cursor is only read by the provider utils
|
|
||||||
final MatrixCursor locCursor =
|
|
||||||
new MatrixCursor(new String[] { BaseColumns._ID }, 6);
|
|
||||||
for (int i = 1; i <= 6; i++) {
|
|
||||||
locCursor.newRow().add(i);
|
|
||||||
}
|
|
||||||
expect(providerUtils.getLocationsCursor(
|
|
||||||
eq(TRACK_ID), leq(0L), leq(0), eq(false))).andStubReturn(locCursor);
|
|
||||||
providerUtils.fillLocation(same(locCursor), isA(Location.class));
|
|
||||||
EasyMock.expectLastCall().andStubAnswer(new IAnswer<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void answer() throws Throwable {
|
|
||||||
Location loc = (Location) EasyMock.getCurrentArguments()[1];
|
|
||||||
loc.set(locs[locCursor.getPosition()]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
MatrixCursor wpCursor =
|
|
||||||
new MatrixCursor(new String[] { BaseColumns._ID }, 3);
|
|
||||||
wpCursor.newRow().add(1);
|
|
||||||
wpCursor.newRow().add(2);
|
|
||||||
wpCursor.newRow().add(3);
|
|
||||||
expect(providerUtils.getWaypointsCursor(
|
|
||||||
eq(TRACK_ID), leq(0L),
|
|
||||||
eq(MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS)))
|
|
||||||
.andStubReturn(wpCursor);
|
|
||||||
expect(providerUtils.createWaypoint(wpCursor))
|
|
||||||
.andStubAnswer(stubCursorToArray(wpCursor, wps));
|
|
||||||
|
|
||||||
// Begin the track
|
// Begin the track
|
||||||
formatWriter.writeHeader();
|
formatWriter.writeHeader();
|
||||||
formatWriter.writeBeginTrack(locEq(locs[0]));
|
formatWriter.writeBeginTrack(l1);
|
||||||
|
|
||||||
// Write locations 1-2
|
// Write locations 1-2
|
||||||
formatWriter.writeOpenSegment();
|
formatWriter.writeOpenSegment();
|
||||||
formatWriter.writeLocation(locEq(locs[0]));
|
formatWriter.writeLocation(l1);
|
||||||
formatWriter.writeLocation(locEq(locs[1]));
|
formatWriter.writeLocation(l2);
|
||||||
formatWriter.writeCloseSegment();
|
formatWriter.writeCloseSegment();
|
||||||
|
|
||||||
// Location 3 is not written - it's invalid
|
// Location 3 is not written - it's invalid
|
||||||
|
|
||||||
// Write locations 4-6
|
// Write locations 4-6
|
||||||
formatWriter.writeOpenSegment();
|
formatWriter.writeOpenSegment();
|
||||||
formatWriter.writeLocation(locEq(locs[3]));
|
formatWriter.writeLocation(l4);
|
||||||
formatWriter.writeLocation(locEq(locs[4]));
|
formatWriter.writeLocation(l5);
|
||||||
formatWriter.writeLocation(locEq(locs[5]));
|
formatWriter.writeLocation(l6);
|
||||||
formatWriter.writeCloseSegment();
|
formatWriter.writeCloseSegment();
|
||||||
|
|
||||||
// End the track
|
// End the track
|
||||||
formatWriter.writeEndTrack(locEq(locs[5]));
|
formatWriter.writeEndTrack(l6);
|
||||||
|
|
||||||
// Expect reading/writing of the waypoints (except the first)
|
// Expect reading/writing of the waypoints
|
||||||
formatWriter.writeWaypoint(wps[1]);
|
Cursor cursor = mocksControl.createMock(Cursor.class);
|
||||||
formatWriter.writeWaypoint(wps[2]);
|
expect(providerUtils.getWaypointsCursor(
|
||||||
|
TRACK_ID, 0, MyTracksConstants.MAX_LOADED_WAYPOINTS_POINTS))
|
||||||
|
.andStubReturn(cursor);
|
||||||
|
expect(cursor.moveToFirst()).andReturn(true);
|
||||||
|
expect(cursor.moveToNext()).andReturn(true);
|
||||||
|
expect(providerUtils.createWaypoint(cursor)).andReturn(p1);
|
||||||
|
formatWriter.writeWaypoint(p1);
|
||||||
|
expect(cursor.moveToNext()).andReturn(true);
|
||||||
|
expect(providerUtils.createWaypoint(cursor)).andReturn(p2);
|
||||||
|
formatWriter.writeWaypoint(p2);
|
||||||
|
expect(cursor.moveToNext()).andReturn(false).anyTimes();
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
formatWriter.writeFooter();
|
formatWriter.writeFooter();
|
||||||
formatWriter.close();
|
formatWriter.close();
|
||||||
@@ -330,47 +308,7 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
mocksControl.verify();
|
mocksControl.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Location locEq(final Location loc) {
|
private void addLocations(Location... locs) {
|
||||||
EasyMock.reportMatcher(new IArgumentMatcher() {
|
|
||||||
@Override
|
|
||||||
public boolean matches(Object locObj2) {
|
|
||||||
if (locObj2 == null || loc == null) return loc == locObj2;
|
|
||||||
Location loc2 = (Location) locObj2;
|
|
||||||
|
|
||||||
return loc.hasAccuracy() == loc2.hasAccuracy()
|
|
||||||
&& (!loc.hasAccuracy() || loc.getAccuracy() == loc2.getAccuracy())
|
|
||||||
&& loc.hasAltitude() == loc2.hasAltitude()
|
|
||||||
&& (!loc.hasAltitude() || loc.getAltitude() == loc2.getAltitude())
|
|
||||||
&& loc.hasBearing() == loc2.hasBearing()
|
|
||||||
&& (!loc.hasBearing() || loc.getBearing() == loc2.getBearing())
|
|
||||||
&& loc.hasSpeed() == loc2.hasSpeed()
|
|
||||||
&& (!loc.hasSpeed() || loc.getSpeed() == loc2.getSpeed())
|
|
||||||
&& loc.getLatitude() == loc2.getLatitude()
|
|
||||||
&& loc.getLongitude() == loc2.getLongitude()
|
|
||||||
&& loc.getTime() == loc2.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendTo(StringBuffer buffer) {
|
|
||||||
buffer.append("locEq(");
|
|
||||||
buffer.append(loc);
|
|
||||||
buffer.append(")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> IAnswer<T> stubCursorToArray(
|
|
||||||
final MatrixCursor cursor, final T[] values) {
|
|
||||||
return new IAnswer<T>() {
|
|
||||||
@Override
|
|
||||||
public T answer() throws Throwable {
|
|
||||||
return values[cursor.getPosition()];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fillLocations(Location... locs) {
|
|
||||||
assertTrue(locs.length < 90);
|
assertTrue(locs.length < 90);
|
||||||
for (int i = 0; i < locs.length; i++) {
|
for (int i = 0; i < locs.length; i++) {
|
||||||
Location location = locs[i];
|
Location location = locs[i];
|
||||||
@@ -378,4 +316,46 @@ public class TrackWriterTest extends AndroidTestCase {
|
|||||||
location.setLongitude(i + 1);
|
location.setLongitude(i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the behaviour of filling the track buffer when a read is
|
||||||
|
* requested.
|
||||||
|
* The IDs of the locations will be their sequential number.
|
||||||
|
*
|
||||||
|
* @param feeds is a list of location arrays, each element of which
|
||||||
|
* will be fed into the track buffer on each call
|
||||||
|
*/
|
||||||
|
private void stubBufferFill(final Location[]... feeds) {
|
||||||
|
providerUtils.fillTrackPoints(same(track), isA(TrackBuffer.class));
|
||||||
|
EasyMock.expectLastCall().andStubAnswer(new IAnswer<Void>() {
|
||||||
|
private int lastId = 1;
|
||||||
|
private int reads = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void answer() throws Throwable {
|
||||||
|
// Get the buffer from the arguments
|
||||||
|
Object[] args = EasyMock.getCurrentArguments();
|
||||||
|
assertEquals(2, args.length);
|
||||||
|
TrackBuffer buffer = (TrackBuffer) args[1];
|
||||||
|
assertNotNull(buffer);
|
||||||
|
|
||||||
|
// Check that we still have data to feed to the buffer
|
||||||
|
if (reads >= feeds.length) {
|
||||||
|
fail("More buffer reads than expected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill the buffer
|
||||||
|
buffer.reset();
|
||||||
|
Location[] locations = feeds[reads];
|
||||||
|
for (int i = 0; i < locations.length; i++) {
|
||||||
|
buffer.add(locations[i], lastId + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update internal state
|
||||||
|
lastId += locations.length;
|
||||||
|
reads++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-66
@@ -1,66 +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.services;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests {@link DefaultTrackNameFactory}
|
|
||||||
*
|
|
||||||
* @author Matthew Simmons
|
|
||||||
*/
|
|
||||||
public class DefaultTrackNameFactoryTest extends AndroidTestCase {
|
|
||||||
/**
|
|
||||||
* A version of the factory which allows us to supply our own answer as to
|
|
||||||
* whether a timestamp-based track name should be used.
|
|
||||||
*/
|
|
||||||
private static class MockDefaultTrackNameFactory
|
|
||||||
extends DefaultTrackNameFactory {
|
|
||||||
private final boolean useTimestamp;
|
|
||||||
|
|
||||||
MockDefaultTrackNameFactory(Context context, boolean useTimestamp) {
|
|
||||||
super(context);
|
|
||||||
this.useTimestamp = useTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean useTimestampTrackName() {
|
|
||||||
return useTimestamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long TIMESTAMP = 1288213406000L;
|
|
||||||
|
|
||||||
public void testTimestampTrackName() {
|
|
||||||
DefaultTrackNameFactory factory =
|
|
||||||
new MockDefaultTrackNameFactory(getContext(), true);
|
|
||||||
|
|
||||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
||||||
assertEquals(formatter.format(new Date(TIMESTAMP)),
|
|
||||||
factory.newTrackName(1, TIMESTAMP));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testIncrementingTrackName() {
|
|
||||||
DefaultTrackNameFactory factory =
|
|
||||||
new MockDefaultTrackNameFactory(getContext(), false);
|
|
||||||
|
|
||||||
assertEquals("Track 1", factory.newTrackName(1, TIMESTAMP));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-72
@@ -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.services;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link SignalStrengthTaskFactoryTest}.
|
|
||||||
* These tests require Eclair+ (API level 7) to run.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class SignalStrengthTaskFactoryTest extends AndroidTestCase {
|
|
||||||
/**
|
|
||||||
* Mock version of the {@link ApiFeatures} class.
|
|
||||||
*/
|
|
||||||
private static class MockApiFeatures extends ApiFeatures {
|
|
||||||
private boolean hasModernStrength;
|
|
||||||
|
|
||||||
public void setHasModernSignalStrength(boolean hasModernStrength) {
|
|
||||||
this.hasModernStrength = hasModernStrength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasModernSignalStrength() {
|
|
||||||
return hasModernStrength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MockApiFeatures apiFeatures;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
apiFeatures = new MockApiFeatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCreate() {
|
|
||||||
apiFeatures.setHasModernSignalStrength(true);
|
|
||||||
SignalStrengthTaskFactory factory =
|
|
||||||
new SignalStrengthTaskFactory(apiFeatures);
|
|
||||||
|
|
||||||
PeriodicTask task = factory.create(getContext());
|
|
||||||
assertTrue(task.getClass().getName(),
|
|
||||||
task instanceof SignalStrengthTaskModern);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCreate_legacy() {
|
|
||||||
apiFeatures.setHasModernSignalStrength(false);
|
|
||||||
SignalStrengthTaskFactory factory =
|
|
||||||
new SignalStrengthTaskFactory(apiFeatures);
|
|
||||||
|
|
||||||
PeriodicTask task = factory.create(getContext());
|
|
||||||
assertTrue(task.getClass().getName(), task instanceof SignalStrengthTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-89
@@ -1,89 +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.services;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link StatusAnnouncerFactory}.
|
|
||||||
* These tests require Donut+ to run.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class StatusAnnouncerFactoryTest extends AndroidTestCase {
|
|
||||||
/**
|
|
||||||
* Mock version of the {@link ApiFeatures} class.
|
|
||||||
*/
|
|
||||||
private class MockApiFeatures extends ApiFeatures {
|
|
||||||
private boolean hasTts;
|
|
||||||
|
|
||||||
public void setHasTextToSpeech(boolean hasTts) {
|
|
||||||
this.hasTts = hasTts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasTextToSpeech() {
|
|
||||||
return hasTts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MockApiFeatures apiFeatures;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
apiFeatures = new MockApiFeatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCreate() {
|
|
||||||
apiFeatures.setHasTextToSpeech(true);
|
|
||||||
StatusAnnouncerFactory factory =
|
|
||||||
new StatusAnnouncerFactory(apiFeatures);
|
|
||||||
PeriodicTask task = factory.create(getContext());
|
|
||||||
assertTrue(task instanceof StatusAnnouncerTask);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCreate_notAvailable() {
|
|
||||||
apiFeatures.setHasTextToSpeech(false);
|
|
||||||
StatusAnnouncerFactory factory =
|
|
||||||
new StatusAnnouncerFactory(apiFeatures);
|
|
||||||
PeriodicTask task = factory.create(getContext());
|
|
||||||
assertNull(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetVolumeStream() {
|
|
||||||
apiFeatures.setHasTextToSpeech(true);
|
|
||||||
StatusAnnouncerFactory factory =
|
|
||||||
new StatusAnnouncerFactory(apiFeatures);
|
|
||||||
assertEquals(
|
|
||||||
TextToSpeech.Engine.DEFAULT_STREAM,
|
|
||||||
factory.getVolumeStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetVolumeStream_notAvailable() {
|
|
||||||
apiFeatures.setHasTextToSpeech(false);
|
|
||||||
StatusAnnouncerFactory factory =
|
|
||||||
new StatusAnnouncerFactory(apiFeatures);
|
|
||||||
assertEquals(
|
|
||||||
AudioManager.USE_DEFAULT_STREAM_TYPE,
|
|
||||||
factory.getVolumeStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-522
@@ -1,522 +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.services;
|
|
||||||
|
|
||||||
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.same;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
|
||||||
import com.google.android.apps.mytracks.util.StringUtils;
|
|
||||||
import com.google.android.testing.mocking.AndroidMock;
|
|
||||||
import com.google.android.testing.mocking.UsesMocks;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.speech.tts.TextToSpeech;
|
|
||||||
import android.speech.tts.TextToSpeech.OnInitListener;
|
|
||||||
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
|
|
||||||
import android.telephony.PhoneStateListener;
|
|
||||||
import android.telephony.TelephonyManager;
|
|
||||||
import android.test.AndroidTestCase;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import org.easymock.Capture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link StatusAnnouncerTask}.
|
|
||||||
* WARNING: I'm not responsible if your eyes start bleeding while reading this
|
|
||||||
* code. You have been warned. It's still better than no test, though.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class StatusAnnouncerTaskTest extends AndroidTestCase {
|
|
||||||
|
|
||||||
// Use something other than our hardcoded value
|
|
||||||
private static final Locale DEFAULT_LOCALE = Locale.KOREAN;
|
|
||||||
private static final String ANNOUNCEMENT = "I can haz cheeseburger?";
|
|
||||||
private Locale oldDefaultLocale;
|
|
||||||
|
|
||||||
private StatusAnnouncerTask task;
|
|
||||||
private StringUtils stringUtils;
|
|
||||||
private StatusAnnouncerTask mockTask;
|
|
||||||
private Capture<OnInitListener> initListenerCapture;
|
|
||||||
private Capture<PhoneStateListener> phoneListenerCapture;
|
|
||||||
private TextToSpeechDelegate ttsDelegate;
|
|
||||||
private TextToSpeechInterface tts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mockable interface that we delegate TTS calls to.
|
|
||||||
*/
|
|
||||||
interface TextToSpeechInterface {
|
|
||||||
int addEarcon(String earcon, String packagename, int resourceId);
|
|
||||||
int addEarcon(String earcon, String filename);
|
|
||||||
int addSpeech(String text, String packagename, int resourceId);
|
|
||||||
int addSpeech(String text, String filename);
|
|
||||||
boolean areDefaultsEnforced();
|
|
||||||
String getDefaultEngine();
|
|
||||||
Locale getLanguage();
|
|
||||||
int isLanguageAvailable(Locale loc);
|
|
||||||
boolean isSpeaking();
|
|
||||||
int playEarcon(String earcon, int queueMode,
|
|
||||||
HashMap<String, String> params);
|
|
||||||
int playSilence(long durationInMs, int queueMode,
|
|
||||||
HashMap<String, String> params);
|
|
||||||
int setEngineByPackageName(String enginePackageName);
|
|
||||||
int setLanguage(Locale loc);
|
|
||||||
int setOnUtteranceCompletedListener(OnUtteranceCompletedListener listener);
|
|
||||||
int setPitch(float pitch);
|
|
||||||
int setSpeechRate(float speechRate);
|
|
||||||
void shutdown();
|
|
||||||
int speak(String text, int queueMode, HashMap<String, String> params);
|
|
||||||
int stop();
|
|
||||||
int synthesizeToFile(String text, HashMap<String, String> params,
|
|
||||||
String filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclass of {@link TextToSpeech} which delegates calls to the interface
|
|
||||||
* above.
|
|
||||||
* The logic here is stupid and the author is ashamed of having to write it
|
|
||||||
* like this, but basically the issue is that TextToSpeech cannot be mocked
|
|
||||||
* without running its constructor, its constructor runs async operations
|
|
||||||
* which call other methods (and then if the methods are part of a mock we'd
|
|
||||||
* have to set a behavior, but we can't 'cause the object hasn't been fully
|
|
||||||
* built yet).
|
|
||||||
* The logic is that calls made during the constructor (when tts is not yet
|
|
||||||
* set) will go up to the original class, but after tts is set we'll forward
|
|
||||||
* them all to the mock.
|
|
||||||
*/
|
|
||||||
private class TextToSpeechDelegate
|
|
||||||
extends TextToSpeech implements TextToSpeechInterface {
|
|
||||||
public TextToSpeechDelegate(Context context, OnInitListener listener) {
|
|
||||||
super(context, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addEarcon(String earcon, String packagename, int resourceId) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addEarcon(earcon, packagename, resourceId);
|
|
||||||
}
|
|
||||||
return tts.addEarcon(earcon, packagename, resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addEarcon(String earcon, String filename) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addEarcon(earcon, filename);
|
|
||||||
}
|
|
||||||
return tts.addEarcon(earcon, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addSpeech(String text, String packagename, int resourceId) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addSpeech(text, packagename, resourceId);
|
|
||||||
}
|
|
||||||
return tts.addSpeech(text, packagename, resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int addSpeech(String text, String filename) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.addSpeech(text, filename);
|
|
||||||
}
|
|
||||||
return tts.addSpeech(text, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Locale getLanguage() {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.getLanguage();
|
|
||||||
}
|
|
||||||
return tts.getLanguage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int isLanguageAvailable(Locale loc) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.isLanguageAvailable(loc);
|
|
||||||
}
|
|
||||||
return tts.isLanguageAvailable(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSpeaking() {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.isSpeaking();
|
|
||||||
}
|
|
||||||
return tts.isSpeaking();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int playEarcon(String earcon, int queueMode,
|
|
||||||
HashMap<String, String> params) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.playEarcon(earcon, queueMode, params);
|
|
||||||
}
|
|
||||||
return tts.playEarcon(earcon, queueMode, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int playSilence(long durationInMs, int queueMode,
|
|
||||||
HashMap<String, String> params) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.playSilence(durationInMs, queueMode, params);
|
|
||||||
}
|
|
||||||
return tts.playSilence(durationInMs, queueMode, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setLanguage(Locale loc) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setLanguage(loc);
|
|
||||||
}
|
|
||||||
return tts.setLanguage(loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setOnUtteranceCompletedListener(
|
|
||||||
OnUtteranceCompletedListener listener) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setOnUtteranceCompletedListener(listener);
|
|
||||||
}
|
|
||||||
return tts.setOnUtteranceCompletedListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setPitch(float pitch) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setPitch(pitch);
|
|
||||||
}
|
|
||||||
return tts.setPitch(pitch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int setSpeechRate(float speechRate) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.setSpeechRate(speechRate);
|
|
||||||
}
|
|
||||||
return tts.setSpeechRate(speechRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
if (tts == null) {
|
|
||||||
super.shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tts.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int speak(
|
|
||||||
String text, int queueMode, HashMap<String, String> params) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.speak(text, queueMode, params);
|
|
||||||
}
|
|
||||||
return tts.speak(text, queueMode, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int stop() {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.stop();
|
|
||||||
}
|
|
||||||
return tts.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int synthesizeToFile(String text, HashMap<String, String> params,
|
|
||||||
String filename) {
|
|
||||||
if (tts == null) {
|
|
||||||
return super.synthesizeToFile(text, params, filename);
|
|
||||||
}
|
|
||||||
return tts.synthesizeToFile(text, params, filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@UsesMocks({
|
|
||||||
StatusAnnouncerTask.class,
|
|
||||||
StringUtils.class,
|
|
||||||
TrackRecordingService.class,
|
|
||||||
})
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
oldDefaultLocale = Locale.getDefault();
|
|
||||||
Locale.setDefault(DEFAULT_LOCALE);
|
|
||||||
stringUtils = AndroidMock.createMock(StringUtils.class, getContext());
|
|
||||||
|
|
||||||
// Eww, the effort required just to mock TextToSpeech is insane
|
|
||||||
final AtomicBoolean listenerCalled = new AtomicBoolean();
|
|
||||||
OnInitListener blockingListener = new OnInitListener() {
|
|
||||||
@Override
|
|
||||||
public void onInit(int status) {
|
|
||||||
synchronized (this) {
|
|
||||||
listenerCalled.set(true);
|
|
||||||
notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ttsDelegate = new TextToSpeechDelegate(getContext(), blockingListener);
|
|
||||||
|
|
||||||
// Wait for all async operations done in the constructor to finish.
|
|
||||||
synchronized (blockingListener) {
|
|
||||||
while (!listenerCalled.get()) {
|
|
||||||
// Releases the synchronized lock until we're woken up.
|
|
||||||
blockingListener.wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phew, done, now we can start forwarding calls
|
|
||||||
tts = AndroidMock.createMock(TextToSpeechInterface.class);
|
|
||||||
|
|
||||||
initListenerCapture = new Capture<OnInitListener>();
|
|
||||||
phoneListenerCapture = new Capture<PhoneStateListener>();
|
|
||||||
|
|
||||||
// Create a partial forwarding mock
|
|
||||||
mockTask = AndroidMock.createMock(StatusAnnouncerTask.class,
|
|
||||||
getContext(), stringUtils);
|
|
||||||
task = new StatusAnnouncerTask(getContext(), stringUtils) {
|
|
||||||
@Override
|
|
||||||
protected TextToSpeech newTextToSpeech(Context ctx,
|
|
||||||
OnInitListener onInitListener) {
|
|
||||||
return mockTask.newTextToSpeech(ctx, onInitListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getAnnouncement(TripStatistics stats) {
|
|
||||||
return mockTask.getAnnouncement(stats);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void listenToPhoneState(
|
|
||||||
PhoneStateListener listener, int events) {
|
|
||||||
mockTask.listenToPhoneState(listener, events);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() {
|
|
||||||
Locale.setDefault(oldDefaultLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStart() {
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
assertNotNull(ttsInitListener);
|
|
||||||
|
|
||||||
expect(tts.isLanguageAvailable(DEFAULT_LOCALE))
|
|
||||||
.andStubReturn(TextToSpeech.LANG_AVAILABLE);
|
|
||||||
expect(tts.setLanguage(DEFAULT_LOCALE))
|
|
||||||
.andReturn(TextToSpeech.LANG_AVAILABLE);
|
|
||||||
expect(tts.setSpeechRate(StatusAnnouncerTask.TTS_SPEECH_RATE))
|
|
||||||
.andReturn(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
AndroidMock.replay(tts, stringUtils);
|
|
||||||
|
|
||||||
ttsInitListener.onInit(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStart_languageNotSupported() {
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
assertNotNull(ttsInitListener);
|
|
||||||
|
|
||||||
expect(tts.isLanguageAvailable(DEFAULT_LOCALE))
|
|
||||||
.andStubReturn(TextToSpeech.LANG_NOT_SUPPORTED);
|
|
||||||
expect(tts.setLanguage(Locale.ENGLISH))
|
|
||||||
.andReturn(TextToSpeech.LANG_AVAILABLE);
|
|
||||||
expect(tts.setSpeechRate(StatusAnnouncerTask.TTS_SPEECH_RATE))
|
|
||||||
.andReturn(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
AndroidMock.replay(tts, stringUtils);
|
|
||||||
|
|
||||||
ttsInitListener.onInit(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStart_notReady() {
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
assertNotNull(ttsInitListener);
|
|
||||||
|
|
||||||
AndroidMock.replay(tts, stringUtils);
|
|
||||||
|
|
||||||
ttsInitListener.onInit(TextToSpeech.ERROR);
|
|
||||||
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testShutdown() {
|
|
||||||
// First, start
|
|
||||||
doStart();
|
|
||||||
AndroidMock.verify(mockTask);
|
|
||||||
AndroidMock.reset(mockTask);
|
|
||||||
|
|
||||||
// Then, shut down
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
mockTask.listenToPhoneState(
|
|
||||||
same(phoneListener), eq(PhoneStateListener.LISTEN_NONE));
|
|
||||||
tts.shutdown();
|
|
||||||
AndroidMock.replay(mockTask, tts, stringUtils);
|
|
||||||
task.shutdown();
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun() throws Exception {
|
|
||||||
// Expect service data calls
|
|
||||||
TripStatistics stats = new TripStatistics();
|
|
||||||
TrackRecordingService service =
|
|
||||||
AndroidMock.createMock(TrackRecordingService.class);
|
|
||||||
expect(service.getTripStatistics()).andStubReturn(stats);
|
|
||||||
|
|
||||||
// Expect announcement building call
|
|
||||||
expect(mockTask.getAnnouncement(same(stats))).andStubReturn(ANNOUNCEMENT);
|
|
||||||
|
|
||||||
// Put task in "ready" state
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Expect actual announcement call
|
|
||||||
expect(tts.speak(
|
|
||||||
eq(ANNOUNCEMENT), eq(TextToSpeech.QUEUE_FLUSH),
|
|
||||||
AndroidMock.<HashMap<String, String>>isNull()))
|
|
||||||
.andReturn(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
AndroidMock.replay(tts, stringUtils, service);
|
|
||||||
task.run(service);
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun_notReady() throws Exception {
|
|
||||||
TrackRecordingService service =
|
|
||||||
AndroidMock.createMock(TrackRecordingService.class);
|
|
||||||
|
|
||||||
// Put task in "not ready" state
|
|
||||||
startTask(TextToSpeech.ERROR);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
AndroidMock.replay(tts, stringUtils, service);
|
|
||||||
task.run(service);
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun_duringCall() throws Exception {
|
|
||||||
TrackRecordingService service =
|
|
||||||
AndroidMock.createMock(TrackRecordingService.class);
|
|
||||||
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
expect(tts.isSpeaking()).andStubReturn(false);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
AndroidMock.replay(tts, stringUtils, service);
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK, null);
|
|
||||||
task.run(service);
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun_ringWhileSpeaking() throws Exception {
|
|
||||||
TrackRecordingService service =
|
|
||||||
AndroidMock.createMock(TrackRecordingService.class);
|
|
||||||
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
expect(tts.isSpeaking()).andStubReturn(true);
|
|
||||||
expect(tts.stop()).andReturn(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
AndroidMock.replay(tts, stringUtils, service);
|
|
||||||
|
|
||||||
// Update the state to ringing - this should stop the current announcement.
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
|
||||||
|
|
||||||
// Run the announcement - this should do nothing.
|
|
||||||
task.run(service);
|
|
||||||
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun_whileRinging() throws Exception {
|
|
||||||
TrackRecordingService service =
|
|
||||||
AndroidMock.createMock(TrackRecordingService.class);
|
|
||||||
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
expect(tts.isSpeaking()).andStubReturn(false);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
AndroidMock.replay(tts, stringUtils, service);
|
|
||||||
PhoneStateListener phoneListener = phoneListenerCapture.getValue();
|
|
||||||
phoneListener.onCallStateChanged(TelephonyManager.CALL_STATE_RINGING, null);
|
|
||||||
task.run(service);
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun_noService() throws Exception {
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
AndroidMock.replay(tts, stringUtils);
|
|
||||||
task.run(null);
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRun_noStats() throws Exception {
|
|
||||||
// Expect service data calls
|
|
||||||
TrackRecordingService service =
|
|
||||||
AndroidMock.createMock(TrackRecordingService.class);
|
|
||||||
expect(service.getTripStatistics()).andStubReturn(null);
|
|
||||||
|
|
||||||
startTask(TextToSpeech.SUCCESS);
|
|
||||||
|
|
||||||
// Run the announcement
|
|
||||||
AndroidMock.replay(tts, stringUtils, service);
|
|
||||||
task.run(service);
|
|
||||||
AndroidMock.verify(mockTask, tts, stringUtils, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startTask(int state) {
|
|
||||||
AndroidMock.resetToNice(tts, stringUtils);
|
|
||||||
AndroidMock.replay(tts, stringUtils);
|
|
||||||
doStart();
|
|
||||||
OnInitListener ttsInitListener = initListenerCapture.getValue();
|
|
||||||
ttsInitListener.onInit(state);
|
|
||||||
AndroidMock.resetToDefault(tts, stringUtils);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doStart() {
|
|
||||||
mockTask.listenToPhoneState(capture(phoneListenerCapture),
|
|
||||||
eq(PhoneStateListener.LISTEN_CALL_STATE));
|
|
||||||
expect(mockTask.newTextToSpeech(
|
|
||||||
same(getContext()), capture(initListenerCapture)))
|
|
||||||
.andStubReturn(ttsDelegate);
|
|
||||||
AndroidMock.replay(mockTask);
|
|
||||||
task.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-701
@@ -15,732 +15,37 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.apps.mytracks.services;
|
package com.google.android.apps.mytracks.services;
|
||||||
|
|
||||||
import static com.google.android.apps.mytracks.MyTracksConstants.RESUME_TRACK_EXTRA_NAME;
|
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.MyTracksSettings;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProvider;
|
|
||||||
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
|
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
|
||||||
import com.google.android.apps.mytracks.content.Waypoint;
|
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
|
||||||
import com.google.android.apps.mytracks.util.ApiFeatures;
|
|
||||||
import com.google.android.maps.mytracks.R;
|
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.ContextWrapper;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.Editor;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.test.RenamingDelegatingContext;
|
|
||||||
import android.test.ServiceTestCase;
|
import android.test.ServiceTestCase;
|
||||||
import android.test.mock.MockContentResolver;
|
|
||||||
import android.test.suitebuilder.annotation.MediumTest;
|
import android.test.suitebuilder.annotation.MediumTest;
|
||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
|
|
||||||
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.
|
|
||||||
* it's not possible to properly shutdown the service, unless tearDown()
|
|
||||||
* is called, which prevents from testing multiple scenarios in a single
|
|
||||||
* test (see runFunctionTest for more details).
|
|
||||||
*/
|
*/
|
||||||
public class TrackRecordingServiceTest
|
public class TrackRecordingServiceTest
|
||||||
extends ServiceTestCase<TrackRecordingService> {
|
extends ServiceTestCase<TrackRecordingService> {
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private MyTracksProviderUtils providerUtils;
|
|
||||||
private SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
* to bind after the service has been previously started.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private boolean bound;
|
|
||||||
private Intent serviceIntent;
|
|
||||||
|
|
||||||
public TrackRecordingServiceTest() {
|
public TrackRecordingServiceTest() {
|
||||||
super(TrackRecordingService.class);
|
super(TrackRecordingService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A context wrapper with the user provided {@link ContentResolver}.
|
|
||||||
*/
|
|
||||||
private static class MockContext extends ContextWrapper {
|
|
||||||
private final ContentResolver contentResolver;
|
|
||||||
|
|
||||||
public MockContext(ContentResolver contentResolver, Context base) {
|
|
||||||
super(base);
|
|
||||||
this.contentResolver = contentResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentResolver getContentResolver() {
|
|
||||||
return contentResolver;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mock class that forces API level < 5 to make sure we can workaround a bug
|
|
||||||
* in ServiceTestCase (throwing a NPE).
|
|
||||||
* See http://code.google.com/p/android/issues/detail?id=12122 for more
|
|
||||||
* details.
|
|
||||||
*/
|
|
||||||
private static class MockApiFeatures extends ApiFeatures {
|
|
||||||
@Override
|
|
||||||
protected int getApiLevel() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IBinder bindService(Intent intent) {
|
|
||||||
if (getService() != null) {
|
|
||||||
if (bound) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Service: " + getService() + " is already bound");
|
|
||||||
}
|
|
||||||
bound = true;
|
|
||||||
serviceIntent = intent.cloneFilter();
|
|
||||||
return getService().onBind(intent);
|
|
||||||
} else {
|
|
||||||
return super.bindService(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void shutdownService() {
|
|
||||||
if (bound) {
|
|
||||||
assertNotNull(getService());
|
|
||||||
getService().onUnbind(serviceIntent);
|
|
||||||
bound = false;
|
|
||||||
}
|
|
||||||
super.shutdownService();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
ApiFeatures.injectInstance(new MockApiFeatures());
|
|
||||||
|
|
||||||
MockContentResolver mockContentResolver = new MockContentResolver();
|
|
||||||
RenamingDelegatingContext targetContext = new RenamingDelegatingContext(
|
|
||||||
getContext(), getContext(), "test.");
|
|
||||||
context = new MockContext(mockContentResolver, targetContext);
|
|
||||||
MyTracksProvider provider = new MyTracksProvider();
|
|
||||||
provider.attachInfo(context, null);
|
|
||||||
mockContentResolver.addProvider(MyTracksProviderUtils.AUTHORITY, provider);
|
|
||||||
setContext(context);
|
|
||||||
|
|
||||||
providerUtils = MyTracksProviderUtils.Factory.get(context);
|
|
||||||
|
|
||||||
sharedPreferences = context.getSharedPreferences(
|
|
||||||
MyTracksSettings.SETTINGS_NAME, 0);
|
|
||||||
// Let's use default values.
|
|
||||||
sharedPreferences.edit().clear().commit();
|
|
||||||
|
|
||||||
// Disable auto resume by default.
|
|
||||||
updateAutoResumePrefs(0, -1);
|
|
||||||
// No recording track.
|
|
||||||
Editor editor = sharedPreferences.edit();
|
|
||||||
editor.putLong(context.getString(R.string.recording_track_key), -1);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public void testStartable() {
|
public void testStartable() {
|
||||||
startService(createStartIntent());
|
Intent startIntent = new Intent();
|
||||||
|
startIntent.setClass(getContext(), TrackRecordingService.class);
|
||||||
|
startService(startIntent);
|
||||||
assertNotNull(getService());
|
assertNotNull(getService());
|
||||||
}
|
}
|
||||||
|
|
||||||
@MediumTest
|
@MediumTest
|
||||||
public void testBindable() {
|
public void testBindable() {
|
||||||
IBinder service = bindService(createStartIntent());
|
|
||||||
assertNotNull(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testResumeAfterReboot_shouldResume() throws Exception {
|
|
||||||
// Insert a dummy track and mark it as recording track.
|
|
||||||
createDummyTrack(123, System.currentTimeMillis(), true);
|
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
|
||||||
updateAutoResumePrefs(0, 10);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent();
|
|
||||||
startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
startService(startIntent);
|
|
||||||
assertNotNull(getService());
|
|
||||||
|
|
||||||
// We expect to resume the previous track.
|
|
||||||
assertTrue(getService().isRecording());
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertEquals(123, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: shutdownService() has a bug and doesn't set mServiceCreated
|
|
||||||
// to false, thus preventing from a second call to onCreate().
|
|
||||||
// Report the bug to Android team. Until then, the following tests
|
|
||||||
// and checks must be commented out.
|
|
||||||
//
|
|
||||||
// TODO: If fixed, remove "disabled" prefix from the test name.
|
|
||||||
@MediumTest
|
|
||||||
public void disabledTestResumeAfterReboot_simulateReboot() throws Exception {
|
|
||||||
updateAutoResumePrefs(0, 10);
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// Simulate recording a track.
|
|
||||||
long id = service.startNewTrack();
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
assertEquals(id, service.getRecordingTrackId());
|
|
||||||
shutdownService();
|
|
||||||
assertEquals(id, sharedPreferences.getLong(
|
|
||||||
context.getString(R.string.recording_track_key), -1));
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent();
|
|
||||||
startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
startService(startIntent);
|
|
||||||
assertNotNull(getService());
|
|
||||||
|
|
||||||
assertTrue(getService().isRecording());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testResumeAfterReboot_noRecordingTrack() throws Exception {
|
|
||||||
// Insert a dummy track and mark it as recording track.
|
|
||||||
createDummyTrack(123, System.currentTimeMillis(), false);
|
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
|
||||||
updateAutoResumePrefs(0, 10);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent();
|
|
||||||
startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
startService(startIntent);
|
|
||||||
assertNotNull(getService());
|
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because it was stopped.
|
|
||||||
assertFalse(getService().isRecording());
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testResumeAfterReboot_expiredTrack() throws Exception {
|
|
||||||
// Insert a dummy track last updated 20 min ago.
|
|
||||||
createDummyTrack(123, System.currentTimeMillis() - 20 * 60 * 1000, true);
|
|
||||||
|
|
||||||
// Clear the number of attempts and set the timeout to 10 min.
|
|
||||||
updateAutoResumePrefs(0, 10);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent();
|
|
||||||
startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
startService(startIntent);
|
|
||||||
assertNotNull(getService());
|
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because it has expired.
|
|
||||||
assertFalse(getService().isRecording());
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testResumeAfterReboot_tooManyAttempts() throws Exception {
|
|
||||||
// Insert a dummy track.
|
|
||||||
createDummyTrack(123, System.currentTimeMillis(), true);
|
|
||||||
|
|
||||||
// Set the number of attempts to max.
|
|
||||||
updateAutoResumePrefs(
|
|
||||||
TrackRecordingService.MAX_AUTO_RESUME_TRACK_RETRY_ATTEMPTS, 10);
|
|
||||||
|
|
||||||
// Start the service in "resume" mode (simulates the on-reboot action).
|
|
||||||
Intent startIntent = createStartIntent();
|
|
||||||
startIntent.putExtra(RESUME_TRACK_EXTRA_NAME, true);
|
|
||||||
startService(startIntent);
|
|
||||||
assertNotNull(getService());
|
|
||||||
|
|
||||||
// We don't expect to resume the previous track, because there were already
|
|
||||||
// too many attempts.
|
|
||||||
assertFalse(getService().isRecording());
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testRecording_noTracks() throws Exception {
|
|
||||||
List<Track> tracks = providerUtils.getAllTracks();
|
|
||||||
assertTrue(tracks.isEmpty());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
// Test if we start in no-recording mode by default.
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testRecording_oldTracks() throws Exception {
|
|
||||||
createDummyTrack(123, -1, false);
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testRecording_orphanedRecordingTrack() throws Exception {
|
|
||||||
// Just set recording track to a bogus value.
|
|
||||||
setRecordingTrack(256);
|
|
||||||
|
|
||||||
// Make sure that the service will not start recording and will clear
|
|
||||||
// the bogus track.
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testStartNewTrack_noRecording() throws Exception {
|
|
||||||
List<Track> tracks = providerUtils.getAllTracks();
|
|
||||||
assertTrue(tracks.isEmpty());
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
long id = service.startNewTrack();
|
|
||||||
assertTrue(id >= 0);
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
Track track = providerUtils.getTrack(id);
|
|
||||||
assertNotNull(track);
|
|
||||||
assertEquals(id, track.getId());
|
|
||||||
assertEquals(id, sharedPreferences.getLong(
|
|
||||||
context.getString(R.string.recording_track_key), -1));
|
|
||||||
assertEquals(id, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testStartNewTrack_alreadyRecording() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
try {
|
|
||||||
service.startNewTrack();
|
|
||||||
fail("Expecting IllegalStateException");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// Expected.
|
|
||||||
}
|
|
||||||
assertEquals(123, sharedPreferences.getLong(
|
|
||||||
context.getString(R.string.recording_track_key), 0));
|
|
||||||
assertEquals(123, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testEndCurrentTrack_alreadyRecording() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
// End the current track.
|
|
||||||
service.endCurrentTrack();
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertEquals(-1, sharedPreferences.getLong(
|
|
||||||
context.getString(R.string.recording_track_key), 0));
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testEndCurrentTrack_noRecording() throws Exception {
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// End the current track.
|
|
||||||
try {
|
|
||||||
service.endCurrentTrack();
|
|
||||||
fail("Expecting IllegalStateException");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// Expected.
|
|
||||||
}
|
|
||||||
assertEquals(-1, sharedPreferences.getLong(
|
|
||||||
context.getString(R.string.recording_track_key), 0));
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testIntegration_completeRecordingSession() throws Exception {
|
|
||||||
List<Track> tracks = providerUtils.getAllTracks();
|
|
||||||
assertTrue(tracks.isEmpty());
|
|
||||||
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
|
|
||||||
public void testInsertStatisticsMarker_noRecordingTrack() throws Exception {
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
try {
|
|
||||||
service.insertStatisticsMarker(loc);
|
|
||||||
fail("Expecting IllegalStateException");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// Expected.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testInsertStatisticsMarker_validLocation() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
assertEquals(1, service.insertStatisticsMarker(loc));
|
|
||||||
assertEquals(2, service.insertStatisticsMarker(loc));
|
|
||||||
|
|
||||||
// TODO: Add more checks.
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testInsertWaypointMarker_noRecordingTrack() throws Exception {
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
waypoint.setId(1);
|
|
||||||
waypoint.setLocation(loc);
|
|
||||||
try {
|
|
||||||
service.insertWaypointMarker(waypoint);
|
|
||||||
fail("Expecting IllegalStateException");
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// Expected.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testInsertWaypointMarker_invalidWaypoint() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
assertEquals(-1, service.insertWaypointMarker(waypoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testInsertWaypointMarker_validWaypoint() throws Exception {
|
|
||||||
createDummyTrack(123, -1, true);
|
|
||||||
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
waypoint.setId(1);
|
|
||||||
waypoint.setLocation(loc);
|
|
||||||
assertEquals(1, service.insertWaypointMarker(waypoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noAnnouncementFreq() throws Exception {
|
|
||||||
functionalTest(R.string.announcement_frequency_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultAnnouncementFreq() throws Exception {
|
|
||||||
functionalTest(R.string.announcement_frequency_key, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noMaxRecordingDist() throws Exception {
|
|
||||||
functionalTest(R.string.max_recording_distance_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultMaxRecordingDist() throws Exception {
|
|
||||||
functionalTest(R.string.max_recording_distance_key, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noMinRecordingDist() throws Exception {
|
|
||||||
functionalTest(R.string.min_recording_distance_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultMinRecordingDist() throws Exception {
|
|
||||||
functionalTest(R.string.min_recording_distance_key, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noSignalSamplingFreq() throws Exception {
|
|
||||||
functionalTest(R.string.signal_sampling_frequency_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultSignalSamplingFreq() throws Exception {
|
|
||||||
functionalTest(R.string.signal_sampling_frequency_key, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noSplitFreq() throws Exception {
|
|
||||||
functionalTest(R.string.split_frequency_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultSplitFreqByDist() throws Exception {
|
|
||||||
functionalTest(R.string.split_frequency_key, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultSplitFreqByTime() throws Exception {
|
|
||||||
functionalTest(R.string.split_frequency_key, -2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noMetricUnits() throws Exception {
|
|
||||||
functionalTest(R.string.metric_units_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_metricUnitsEnabled() throws Exception {
|
|
||||||
functionalTest(R.string.metric_units_key, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_metricUnitsDisabled() throws Exception {
|
|
||||||
functionalTest(R.string.metric_units_key, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noMinRecordingInterval() throws Exception {
|
|
||||||
functionalTest(R.string.min_recording_interval_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultMinRecordingInterval()
|
|
||||||
throws Exception {
|
|
||||||
functionalTest(R.string.min_recording_interval_key, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_noMinRequiredAccuracy() throws Exception {
|
|
||||||
functionalTest(R.string.min_required_accuracy_key, (Object) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@MediumTest
|
|
||||||
public void testWithProperties_defaultMinRequiredAccuracy() throws Exception {
|
|
||||||
functionalTest(R.string.min_required_accuracy_key, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ITrackRecordingService bindAndGetService(Intent intent) {
|
|
||||||
ITrackRecordingService service = ITrackRecordingService.Stub.asInterface(
|
|
||||||
bindService(intent));
|
|
||||||
assertNotNull(service);
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Track createDummyTrack(long id, long stopTime, boolean isRecording) {
|
|
||||||
Track dummyTrack = new Track();
|
|
||||||
dummyTrack.setId(id);
|
|
||||||
dummyTrack.setName("Dummy Track");
|
|
||||||
TripStatistics tripStatistics = new TripStatistics();
|
|
||||||
tripStatistics.setStopTime(stopTime);
|
|
||||||
dummyTrack.setStatistics(tripStatistics);
|
|
||||||
addTrack(dummyTrack, isRecording);
|
|
||||||
return dummyTrack;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateAutoResumePrefs(int attempts, int timeoutMins) {
|
|
||||||
Editor editor = sharedPreferences.edit();
|
|
||||||
editor.putInt(context.getString(
|
|
||||||
R.string.auto_resume_track_current_retry_key), attempts);
|
|
||||||
editor.putInt(context.getString(
|
|
||||||
R.string.auto_resume_track_timeout_key), timeoutMins);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Intent createStartIntent() {
|
|
||||||
Intent startIntent = new Intent();
|
Intent startIntent = new Intent();
|
||||||
startIntent.setClass(context, TrackRecordingService.class);
|
startIntent.setClass(getContext(), TrackRecordingService.class);
|
||||||
return startIntent;
|
IBinder service = bindService(startIntent);
|
||||||
}
|
assertNotNull(service);
|
||||||
|
|
||||||
private void addTrack(Track track, boolean isRecording) {
|
|
||||||
assertTrue(track.getId() >= 0);
|
|
||||||
providerUtils.insertTrack(track);
|
|
||||||
assertEquals(track.getId(), providerUtils.getTrack(track.getId()).getId());
|
|
||||||
setRecordingTrack(isRecording ? track.getId() : -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setRecordingTrack(long id) {
|
|
||||||
Editor editor = sharedPreferences.edit();
|
|
||||||
editor.putLong(context.getString(R.string.recording_track_key), id);
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We support multiple values for readability, however this test's
|
|
||||||
// base class doesn't properly shutdown the service, so it's not possible
|
|
||||||
// to pass more than 1 value at a time.
|
|
||||||
private void functionalTest(int resourceId, Object ...values)
|
|
||||||
throws Exception {
|
|
||||||
final String key = context.getString(resourceId);
|
|
||||||
for (Object value : values) {
|
|
||||||
// Remove all properties and set the property for the given key.
|
|
||||||
Editor editor = sharedPreferences.edit();
|
|
||||||
editor.clear();
|
|
||||||
if (value instanceof String) {
|
|
||||||
editor.putString(key, (String) value);
|
|
||||||
} else if (value instanceof Long) {
|
|
||||||
editor.putLong(key, (Long) value);
|
|
||||||
} else if (value instanceof Integer) {
|
|
||||||
editor.putInt(key, (Integer) value);
|
|
||||||
} else if (value instanceof Boolean) {
|
|
||||||
editor.putBoolean(key, (Boolean) value);
|
|
||||||
} else if (value == null) {
|
|
||||||
// Do nothing, as clear above has already removed this property.
|
|
||||||
}
|
|
||||||
editor.commit();
|
|
||||||
|
|
||||||
fullRecordingSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fullRecordingSession() throws Exception {
|
|
||||||
ITrackRecordingService service = bindAndGetService(createStartIntent());
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
|
|
||||||
// Start a track.
|
|
||||||
long id = service.startNewTrack();
|
|
||||||
assertTrue(id >= 0);
|
|
||||||
assertTrue(service.isRecording());
|
|
||||||
Track track = providerUtils.getTrack(id);
|
|
||||||
assertNotNull(track);
|
|
||||||
assertEquals(id, track.getId());
|
|
||||||
assertEquals(id, sharedPreferences.getLong(
|
|
||||||
context.getString(R.string.recording_track_key), -1));
|
|
||||||
assertEquals(id, service.getRecordingTrackId());
|
|
||||||
|
|
||||||
// Insert a few points, markers and statistics.
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
Location loc = new Location("gps");
|
|
||||||
loc.setLongitude(35.0f + i / 10.0f);
|
|
||||||
loc.setLatitude(45.0f - i / 5.0f);
|
|
||||||
loc.setAccuracy(5);
|
|
||||||
loc.setSpeed(10);
|
|
||||||
loc.setTime(startTime + i * 10000);
|
|
||||||
loc.setBearing(3.0f);
|
|
||||||
service.recordLocation(loc);
|
|
||||||
|
|
||||||
if (i % 10 == 0) {
|
|
||||||
service.insertStatisticsMarker(loc);
|
|
||||||
} else if (i % 7 == 0) {
|
|
||||||
Waypoint waypoint = new Waypoint();
|
|
||||||
waypoint.setLocation(loc);
|
|
||||||
service.insertWaypointMarker(waypoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the track. Validate if it has correct data.
|
|
||||||
service.endCurrentTrack();
|
|
||||||
assertFalse(service.isRecording());
|
|
||||||
assertEquals(-1, service.getRecordingTrackId());
|
|
||||||
track = providerUtils.getTrack(id);
|
|
||||||
assertNotNull(track);
|
|
||||||
assertEquals(id, track.getId());
|
|
||||||
TripStatistics tripStatistics = track.getStatistics();
|
|
||||||
assertNotNull(tripStatistics);
|
|
||||||
assertTrue(tripStatistics.getStartTime() > 0);
|
|
||||||
assertTrue(tripStatistics.getStopTime() >= tripStatistics.getStartTime());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -20,11 +20,13 @@ public class TripStatisticsBuilderTest extends TestCase {
|
|||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
builder = new TripStatisticsBuilder(System.currentTimeMillis());
|
builder = new TripStatisticsBuilder();
|
||||||
|
builder.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAddLocationSimple() throws Exception {
|
public void testAddLocationSimple() throws Exception {
|
||||||
builder = new TripStatisticsBuilder(1000);
|
builder = new TripStatisticsBuilder();
|
||||||
|
builder.resumeAt(1000);
|
||||||
TripStatistics stats = builder.getStatistics();
|
TripStatistics stats = builder.getStatistics();
|
||||||
|
|
||||||
assertEquals(0.0, builder.getSmoothedElevation());
|
assertEquals(0.0, builder.getSmoothedElevation());
|
||||||
@@ -82,7 +84,8 @@ public class TripStatisticsBuilderTest extends TestCase {
|
|||||||
*/
|
*/
|
||||||
public void testElevationSimple() throws Exception {
|
public void testElevationSimple() throws Exception {
|
||||||
for (double elevation = 0; elevation < 1000; elevation += 10) {
|
for (double elevation = 0; elevation < 1000; elevation += 10) {
|
||||||
builder = new TripStatisticsBuilder(System.currentTimeMillis());
|
builder = new TripStatisticsBuilder();
|
||||||
|
builder.resume();
|
||||||
for (int j = 0; j < 100; j++) {
|
for (int j = 0; j < 100; j++) {
|
||||||
assertEquals(0.0, builder.updateElevation(elevation));
|
assertEquals(0.0, builder.updateElevation(elevation));
|
||||||
assertEquals(elevation, builder.getSmoothedElevation());
|
assertEquals(elevation, builder.getSmoothedElevation());
|
||||||
|
|||||||
@@ -1,82 +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.util;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the API feature detection code in {@link ApiFeatures}.
|
|
||||||
* This test requires Froyo+ to run.
|
|
||||||
*
|
|
||||||
* @author Rodrigo Damazio
|
|
||||||
*/
|
|
||||||
public class ApiFeaturesTest extends TestCase {
|
|
||||||
|
|
||||||
private TestableApiFeatures features;
|
|
||||||
|
|
||||||
private class TestableApiFeatures extends ApiFeatures {
|
|
||||||
private int apiLevel;
|
|
||||||
|
|
||||||
public void setApiLevel(int apiLevel) {
|
|
||||||
this.apiLevel = apiLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getApiLevel() {
|
|
||||||
return apiLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
|
|
||||||
features = new TestableApiFeatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testHasBackup() {
|
|
||||||
for (int i = 3; i <= 7; i++) {
|
|
||||||
features.setApiLevel(i);
|
|
||||||
assertFalse(features.hasBackup());
|
|
||||||
}
|
|
||||||
features.setApiLevel(8);
|
|
||||||
assertTrue(features.hasBackup());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testHasTextToSpeech() {
|
|
||||||
features.setApiLevel(3);
|
|
||||||
assertFalse(features.hasTextToSpeech());
|
|
||||||
for (int i = 4; i <= 8; i++) {
|
|
||||||
features.setApiLevel(i);
|
|
||||||
assertTrue(features.hasTextToSpeech());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testHasModernSignalStrength() {
|
|
||||||
for (int i = 3; i <= 6; i++) {
|
|
||||||
features.setApiLevel(i);
|
|
||||||
assertFalse(features.hasModernSignalStrength());
|
|
||||||
}
|
|
||||||
features.setApiLevel(7);
|
|
||||||
assertTrue(features.hasModernSignalStrength());
|
|
||||||
features.setApiLevel(8);
|
|
||||||
assertTrue(features.hasModernSignalStrength());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetApiPlatformAdapter() {
|
|
||||||
assertNotNull(features.getApiPlatformAdapter());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,14 @@
|
|||||||
/*
|
// Copyright 2009 Google Inc. All Rights Reserved.
|
||||||
* Copyright 2009 Google Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
||||||
* use this file except in compliance with the License. You may obtain a copy of
|
|
||||||
* the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations under
|
|
||||||
* the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.apps.mytracks.util;
|
package com.google.android.apps.mytracks.util;
|
||||||
|
|
||||||
import com.google.android.apps.mytracks.content.Track;
|
import com.google.android.apps.mytracks.content.Track;
|
||||||
import com.google.android.apps.mytracks.stats.TripStatistics;
|
import com.google.android.apps.mytracks.stats.TripStatistics;
|
||||||
|
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the Chart URL generator.
|
* Tests for the Chart URL generator.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,47 +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.util;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A unit test for {@link ChartsExtendedEncoder}.
|
|
||||||
*
|
|
||||||
* @author Bartlomiej Niechwiej
|
|
||||||
*/
|
|
||||||
public class ChartsExtendedEncoderTest extends TestCase {
|
|
||||||
|
|
||||||
public void testGetEncodedValue_validArguments() {
|
|
||||||
// Valid arguments.
|
|
||||||
assertEquals("AK", ChartsExtendedEncoder.getEncodedValue(10));
|
|
||||||
assertEquals("JO", ChartsExtendedEncoder.getEncodedValue(590));
|
|
||||||
assertEquals("AA", ChartsExtendedEncoder.getEncodedValue(0));
|
|
||||||
// 64^2 = 4096.
|
|
||||||
assertEquals("..", ChartsExtendedEncoder.getEncodedValue(4095));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetEncodedValue_invalidArguments() {
|
|
||||||
// Invalid arguments.
|
|
||||||
assertEquals("__", ChartsExtendedEncoder.getEncodedValue(4096));
|
|
||||||
assertEquals("__", ChartsExtendedEncoder.getEncodedValue(1234564096));
|
|
||||||
assertEquals("__", ChartsExtendedEncoder.getEncodedValue(-10));
|
|
||||||
assertEquals("__", ChartsExtendedEncoder.getEncodedValue(-12324435));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGetSeparator() {
|
|
||||||
assertEquals(",", ChartsExtendedEncoder.getSeparator());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,8 +31,8 @@ import junit.framework.TestCase;
|
|||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
public class FileUtilsTest extends TestCase {
|
public class FileUtilsTest extends TestCase {
|
||||||
private static final String ORIGINAL_NAME = "Swim\10ming-^across: the/ pacific (ocean).";
|
private static final String ORIGINAL_NAME = "Swim\10ming ^across: the/ pacific (ocean).";
|
||||||
private static final String SANITIZED_NAME = "Swimming-across the pacific (ocean).";
|
private static final String SANITIZED_NAME = "Swimming across the pacific (ocean).";
|
||||||
|
|
||||||
private FileUtils fileUtils;
|
private FileUtils fileUtils;
|
||||||
private Set<String> existingFiles;
|
private Set<String> existingFiles;
|
||||||
|
|||||||
Reference in New Issue
Block a user