Compare commits

..

129 Commits

Author SHA1 Message Date
Rodrigo Damazio c7da9a45ee Releasing v1.1.9. 2011-08-23 22:37:48 -03:00
Rodrigo Damazio aa733fbfb1 Merging to head. 2011-08-19 13:19:47 -03:00
Rodrigo Damazio d2b8f65612 Added tag v1.1.8 for changeset d4623f208a4a 2011-08-19 13:19:22 -03:00
Rodrigo Damazio cf6ac06f73 Fixing the display of waypoints. 2011-08-19 01:40:15 -03:00
Rodrigo Damazio 496ad9c4e2 Fixing menu items.
The complicated bit that was missing was the delete action, which is now its own activity with proper intent handling.
2011-08-19 01:33:46 -03:00
Rodrigo Damazio 657ec17559 Fixing usage of Charset in java.lang.String, which is only supported in API level 9. 2011-08-18 15:33:39 -03:00
Sandor Dornbush 62c9af5cba Release v 1.1.8 2011-08-17 09:34:56 -07:00
Sandor Dornbush a4f16a1f30 Pick up new Chinese translations 2011-08-13 08:25:45 -07:00
Sandor Dornbush 1b595ee7fe Pick up new Chinese translations 2011-08-13 08:24:12 -07:00
Sandor Dornbush fa556e4e33 Merged with head. 2011-08-11 09:28:10 -07:00
Sandor Dornbush 391d718909 Switch widget icons.
Fix errors in finish translation.
2011-08-11 09:06:32 -07:00
Rodrigo Damazio 2ce14e443d Merging to head. 2011-08-08 16:08:38 -03:00
Rodrigo Damazio cdf0a3a3e7 Fixing description/icon in waypoint creation request. 2011-08-07 03:48:59 -03:00
Rodrigo Damazio f00f9a5277 Added APN logging to the signal strength sampler.
(not tested on CDMA)
2011-08-07 03:47:03 -03:00
Ville Muurinen 7112642e89 Merging to head. 2011-08-04 20:50:50 +03:00
Ville Muurinen 3714c21f97 Changed and improved translation based on comments 2011-08-04 20:45:42 +03:00
Rodrigo Damazio 24c49b7f2f Merge with head. 2011-08-01 20:22:31 -03:00
Rodrigo Damazio b16429f3f8 Differentiating between GPS provider unavailable and disabled - unavailable, in some implementations (such as Samsung's) just means it doesn't have a fix.
(Fixes issue 505)
2011-08-01 15:31:42 -03:00
Rodrigo Damazio 27734495f4 Fixing race condition made evident by G1's slowness :) 2011-08-01 14:47:08 -03:00
Rodrigo Damazio 097b338d19 Fixing cupcake uploading by not referencing the Account object. 2011-08-01 14:46:51 -03:00
Sandor Dornbush cbb8450435 Merge with head. 2011-07-28 15:36:01 -07:00
Sandor Dornbush 3b7e0a91bd Tighten up the border. 2011-07-27 21:52:12 -07:00
Rodrigo Damazio d9a1e56282 Review suggestions 2011-07-25 21:33:58 -03:00
Rodrigo Damazio b63e74369c Attempt at working around Samsung bug which crashes the account manager service - the auth token call now synchronously calls 2011-07-22 21:04:15 -03:00
Rodrigo Damazio 4d0ba06cba Getting rid of backported accounts API since it's only used for API levels where we have native accounts. 2011-07-22 21:12:21 -03:00
Rodrigo Damazio c4369dc237 Bundling coding style into the projects themselves.
With this there's no need to manually import the xml files.
Downside is that if we decide to change something, we need import it in all projects (e.g. change in one, save as a template, then select that template on others).
2011-07-21 03:12:14 -03:00
Rodrigo Damazio 99dd212496 Updating main app to Honeycomb MR2.
(downgrading signal strength app to Gingerbread since it doesn't need anything more than that)
2011-07-20 14:27:06 -03:00
Rodrigo Damazio 6e41033ed6 Addressing review suggestions 2011-07-15 13:20:20 -03:00
Rodrigo Damazio 90a5cb7aae Enabling some sensible warnings which are not on by default in Eclipse. 2011-07-15 13:19:14 -03:00
Rodrigo Damazio bccfbaa03b Style and warning fixes. 2011-07-12 20:42:25 -03:00
Rodrigo Damazio e32ff7116d Simplyfing the authentication dance by handling both success and failure together, and both of them outside onActivityResult. 2011-07-12 20:19:59 -03:00
Rodrigo Damazio 00214843cb Improve state handling, specially in cases where SendActivity gets destroyed. 2011-07-12 20:11:47 -03:00
Sandor Dornbush dd4054c4fc Address comments. 2011-07-11 22:50:06 -07:00
Sandor Dornbush 0b40b92340 Clean up code + add pace. 2011-07-11 22:30:49 -07:00
Rodrigo Damazio 60a3c41003 Merge with head. 2011-07-11 18:01:39 -03:00
Sandor Dornbush f501c60de1 Address comments. 2011-07-11 11:32:54 -07:00
Sandor Dornbush b985a935b0 Address comments. 2011-07-07 22:25:41 -07:00
Rodrigo Damazio a2f1790db2 Merge with head. 2011-07-07 18:25:36 -03:00
Sandor Dornbush 5784ab63cc Final widget fixes. 2011-07-05 22:11:05 -07:00
Rodrigo Damazio fd857c2586 Fixing race condition in chart UI update thread. 2011-07-05 23:15:47 -03:00
Sandor Dornbush 29df8762e2 Merge with head. 2011-07-05 16:33:45 -07:00
Sandor Dornbush 32e13c3495 Fix polar tests and sensor strings. 2011-07-05 15:20:04 -07:00
Sandor Dornbush e6151b3b71 Fix polar tests and sensor strings. 2011-07-05 15:19:21 -07:00
Sandor Dornbush 6823b08651 Merge with head. 2011-07-03 15:47:20 -07:00
Sandor Dornbush b588bb02f4 Fix formatting. 2011-07-03 10:02:16 -07:00
Sandor Dornbush faf7b6efa2 Fix widget and address comments. 2011-07-03 09:58:04 -07:00
Rodrigo Damazio 03492da2cd Fixing time-based chart drawing by ensuring data is reloaded and fixing a race condition. 2011-07-01 03:56:33 -03:00
Rodrigo Damazio 8abfc2ada0 Removing old comment 2011-07-01 01:56:34 -03:00
Rodrigo Damazio a262a2ef71 Adressing review suggestion. 2011-07-01 01:11:00 -03:00
Ville Muurinen 435a38f1d1 Removed unnecessary files 2011-06-30 22:47:00 +03:00
Ville Muurinen 91d7db7c01 Added Finnish translation 2011-06-30 19:38:34 +03:00
Rodrigo Damazio e298c88728 "I see dead threads".
Removing handler threads which were left to die, forever alone with nothing to execute.
2011-06-30 04:03:30 -03:00
Rodrigo Damazio d58e7a58d0 Making the target account be selected only once. 2011-06-30 03:44:25 -03:00
Rodrigo Damazio e530412ef0 Adding state persistence for low-memory situations and handling the share action. 2011-06-30 03:20:08 -03:00
Rodrigo Damazio 1d5e2dc4d5 Addressing TODO: Making SendActivity a state machine. 2011-06-30 02:26:01 -03:00
Rodrigo Damazio d0b5faf266 Fixing a couple of race conditions. 2011-06-30 02:24:34 -03:00
Rodrigo Damazio 76e8a39276 Allowing SendActivity to receive sub-activity results (onActivityResult calls). 2011-06-30 02:23:48 -03:00
Rodrigo Damazio 66fe8e694f Addressing review suggestions. 2011-06-29 18:07:00 -03:00
Rodrigo Damazio abd8a230b4 Moving back to managed dialogs to make them still work cross-activity. 2011-06-28 05:58:54 -03:00
Rodrigo Damazio 565662a8aa Minor style fix in authentication classes 2011-06-28 02:38:12 -03:00
Rodrigo Damazio 0f00e71b5e Merging to head. 2011-06-27 05:40:36 -03:00
Rodrigo Damazio 44fea0117d Fixing the closing of send to google dialogs. 2011-06-27 05:20:54 -03:00
Rodrigo Damazio b9b39a24f7 Merging to head 2011-06-27 05:15:32 -03:00
Rodrigo Damazio 358fb87683 Addressing review suggestions 2011-06-24 16:20:29 -03:00
Rodrigo Damazio f0ef73d150 Merging to head. 2011-06-22 17:17:31 -03:00
Rodrigo Damazio 1d2297a5fc Fixing some nits from static analysis. 2011-06-22 17:16:40 -03:00
Rodrigo Damazio 862736bba4 Changing service connection to be a non-singleton, and properly handle its state. 2011-06-22 16:35:40 -03:00
Sandor Dornbush e30dac7cc2 Allow units not in metric. 2011-06-21 22:24:14 -07:00
Sandor Dornbush f97d5fa232 Clean up widget. 2011-06-21 22:00:40 -07:00
Rodrigo Damazio e909616aff Minor reorg in the service - just reordering methods. 2011-06-22 01:59:34 -03:00
Sandor Dornbush be219bbee6 Merged with 1.1.7 2011-06-21 21:19:01 -07:00
Sandor Dornbush 17c1c81b4e Try to clean up widget. 2011-06-21 21:17:03 -07:00
Sandor Dornbush bf0a16c707 Added tag v1.1.7 for changeset a9c635c8e7b4 2011-06-21 21:05:26 -07:00
Sandor Dornbush 86a54feb41 1.1.7 2011-06-21 21:02:57 -07:00
Rodrigo Damazio 2c923470f8 Removing service calls which were just delegates to the provider utils.
These weren't used anywhere, and this change makes a more clear separation between dealing with track data and dealing with the recording service.
2011-06-17 14:04:56 -03:00
Sandor Dornbush 5d26250b45 Revert overzealous argument checking. 2011-06-15 21:48:28 -07:00
Sandor Dornbush 6d90e0bf51 Minor fixes. 2011-06-14 21:43:06 -07:00
Rodrigo Damazio 51df795a8c Fixing leftovers from the merge 2011-06-14 20:48:00 -03:00
Rodrigo Damazio 9ee053e4f9 Merge to head (non-trivial merge) 2011-06-14 20:42:57 -03:00
Sandor Dornbush 4044185ab3 Set product version. 2011-06-14 10:32:37 -07:00
Sandor Dornbush f4fe825162 Fixed analytics. 2011-06-14 10:09:14 -07:00
Sandor Dornbush cb785b89f0 Add simple analytics. 2011-06-13 21:15:37 -07:00
JohnG 8289128399 String array sensor_type_options fix
Reverted .classpath.  (Oops)
Moved move the old strings from arrays.xml to strings.xml.
Created sensor_type_options string for "None" for language support.
2011-06-13 23:09:20 -04:00
Sandor Dornbush e9146f5fc1 Merge. 2011-06-12 22:34:18 -07:00
Sandor Dornbush bc7de458d3 Cap at 254. 2011-06-12 22:28:48 -07:00
Rodrigo Damazio 4aa44331f0 Adding new activity types, contributed by Marius F. 2011-06-10 20:00:47 -03:00
Rodrigo Damazio eada19f516 Addressing review suggestions and fixing text-to-speech initialization. 2011-06-10 19:52:18 -03:00
Sandor Dornbush 63677b1451 Merged widget. 2011-06-09 20:57:48 -07:00
Sandor Dornbush 5789d96a3d Cap cadence in TCX files. 2011-06-09 20:32:24 -07:00
Rodrigo Damazio 0afdeb9bcc Fixing a few less common issues from the Market console. 2011-06-09 22:22:57 -03:00
Rodrigo Damazio 79dd8f4e49 Fixing some of the top issues from the Market console:
- Not trying to show dialogs if activity is going away
- Properly handling when the user doesn't select an account
- Handling unwritable database (due to memory full?)
- Handling NPE from fusion tables upload
- Not trying to touch dialogs when we're about to restart the whole app
2011-06-09 22:07:25 -03:00
Rodrigo Damazio 93f204ba22 Uniformly handling id == 0 (it's not valid, but everywhere we only assume < 0 is invalid, so do the same here) 2011-06-09 19:45:08 -03:00
Rodrigo Damazio 98ad96115a Properly handling invalid track/point/waypoint IDs.
This makes the provider utils implementation return null for those cases, and fixes all callers which didn't check for that.
2011-06-09 19:12:51 -03:00
Rodrigo Damazio ae4ce12c17 Added tag v1.1.6 for changeset 43a7a27cd4f0 2011-06-03 19:23:24 -03:00
JohnG 9f44b1695d Merge error fix.
String polar_sensor_type was left out of Sensor Type array.
2011-06-02 20:58:27 -04:00
JohnG 94344deff3 Change sensor_type_options array to support different languages.
Moved sensor_type_options from arrays.xml to arrays_do_not_translate.xml.
Added strings to strings.xml.
2011-06-01 23:20:00 -04:00
Rodrigo Damazio a41994cb41 Review suggestions 2011-06-01 17:34:44 -03:00
Rodrigo Damazio eee6a713fa Clean-up of issues found by static analysis. 2011-06-01 05:23:25 -03:00
Rodrigo Damazio aed28fe503 Cosmetic and unused method changes. 2011-06-01 04:58:34 -03:00
Rodrigo Damazio 7bc72e683b Merging to head. 2011-06-01 02:45:41 -03:00
Rodrigo Damazio 31e386de44 Optimization: getting rid of some objects when the hub is stopped. 2011-06-01 02:02:49 -03:00
Rodrigo Damazio 31737efd87 And the moment we've been all waiting for....MyTracks is no longer a singleton. 2011-06-01 01:44:07 -03:00
Rodrigo Damazio be096b9657 Removing no-longer-necessary methods from the platform adapters. 2011-06-01 01:40:12 -03:00
Rodrigo Damazio 3e773ef94b Making TrackDataHub an application singleton (using the application context to ensure we don't leak MyTracks). 2011-06-01 01:39:51 -03:00
Rodrigo Damazio 111adc917b Getting rid of insertWaypoint singleton dependency. 2011-05-31 03:16:14 -03:00
Rodrigo Damazio 6a1b2861f3 Getting rid of unused DialogManager calls. 2011-05-31 03:10:25 -03:00
Rodrigo Damazio ed3ff01c22 Freeing MyTracks from isRecording() calls. 2011-05-31 03:09:08 -03:00
Rodrigo Damazio 73e4af98cc Cutting service binding and preferences dependencies to the MyTracks singleton. 2011-05-31 02:35:05 -03:00
Rodrigo Damazio 9f3099ef2b Addressing review suggestions 2011-05-26 09:58:29 -03:00
Rodrigo Damazio 412eed51cd Removing DialogManager test which is no longer needed. 2011-05-25 21:26:07 -03:00
Rodrigo Damazio bd20d1af02 Addressing review suggestions 2011-05-25 21:25:46 -03:00
Rodrigo Damazio a15932cc7f Passing track IDs as URIs instead of extras. 2011-05-25 20:13:49 -03:00
Rodrigo Damazio 8942c31a5c Making the "Show track" button after an import clear the stack. 2011-05-25 19:21:56 -03:00
Rodrigo Damazio 90fe6921e2 Moving track importing out of MyTracks class. 2011-05-25 19:19:21 -03:00
Rodrigo Damazio e8a872d351 Improving intent handling so each activity receives its data type. 2011-05-25 19:17:34 -03:00
Rodrigo Damazio eeaa50ddcb Removing singleton usage: moving all send and save logic out of the MyTracks class.
This is a non-trivial step: I decentralized dialog creation (since it made no sense to have a single place creating a lot of completely unrelated dialogs), made invisible activities just for sending/saving, and changed menu handling a bit, all to decentralize those functions from MyTracks.

There's still a TODO to handle GET_LOGIN more nicely instead of returning from the depths of auth management into the current activity.
2011-05-25 04:50:21 -03:00
Rodrigo Damazio 16b0d4eb24 Removing singleton usage: chart settings dialog 2011-05-25 02:13:38 -03:00
JohnG d238a9e9dd Polar HRM cleanup, take III
Code review of May 20 comments addressed.
2011-05-21 09:01:41 -04:00
JohnG fb993a7060 Polar HRM parser tests. 2011-05-20 00:38:39 -04:00
JohnG 6b375614ca Fixes for Polar HRM code review of May 1.
Addressed all comments.  Cleaned up Polar parser mostly.
Added decaying heart rate algo for now.

Tried to reach out to joni to work on merging projects.  No response yet.
2011-05-19 23:00:22 -04:00
JohnG b1605ac44e Polar parser 2011-05-01 15:15:13 -04:00
JohnG 90afc1e221 Polar parser 2011-05-01 15:14:41 -04:00
JohnG adeba81340 Polar parser 2011-05-01 15:13:55 -04:00
JohnG 8c10b7ba04 Polar parser 2011-05-01 15:13:34 -04:00
JohnG 8aae77ff70 Polar parser 2011-05-01 15:13:06 -04:00
JohnG 7eeacc6268 Polar parser 2011-05-01 15:12:33 -04:00
JohnG 06e02781d2 Polar parser 2011-05-01 15:11:53 -04:00
saxman 7020b78faa Removed mytracks prefix from files and variables.
Moved colors to colors.xml.
Code clean-up and more documentation.
2011-04-25 09:00:39 -07:00
saxman f1746b41a2 Added widget for displaying track stats and starting/stopping tracks.
Added track update broadcast to TrackRecordingService.
Added start and end track actions to TrackRecordingService.
2011-04-23 23:59:47 -07:00
144 changed files with 7296 additions and 3714 deletions
+1 -1
View File
@@ -3,8 +3,8 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
<classpathentry kind="lib" path="libs/google-common.jar"/>
<classpathentry kind="lib" path="libs/accounts.jar"/>
<classpathentry kind="lib" path="libs/google-api-client-1.2.2-alpha.jar"/>
<classpathentry kind="lib" path="libs/libGoogleAnalytics.jar"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry exported="true" kind="lib" path="/MyTracksLib/libs/protobuf-java-2.3.0-lite.jar"/>
+1
View File
@@ -3,6 +3,7 @@
<name>MyTracks</name>
<comment></comment>
<projects>
<project>MyTracksLib</project>
</projects>
<buildSpec>
<buildCommand>
+338 -1
View File
@@ -1,4 +1,4 @@
#Wed Sep 29 18:44:16 BRT 2010
#Thu Jul 21 03:01:59 BRT 2011
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
@@ -7,6 +7,343 @@ org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=disabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=48
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=48
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=true
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=true
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=2
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
@@ -0,0 +1,61 @@
#Thu Jul 21 03:04:13 BRT 2011
eclipse.preferences.version=1
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
formatter_profile=_MyTracks
formatter_settings_version=12
org.eclipse.jdt.ui.ignorelowercasenames=true
org.eclipse.jdt.ui.importorder=com;android;java;javax;org;
org.eclipse.jdt.ui.ondemandthreshold=99
org.eclipse.jdt.ui.staticondemandthreshold=99
sp_cleanup.add_default_serial_version_id=true
sp_cleanup.add_generated_serial_version_id=false
sp_cleanup.add_missing_annotations=true
sp_cleanup.add_missing_deprecated_annotations=true
sp_cleanup.add_missing_methods=false
sp_cleanup.add_missing_nls_tags=false
sp_cleanup.add_missing_override_annotations=true
sp_cleanup.add_missing_override_annotations_interface_methods=true
sp_cleanup.add_serial_version_id=false
sp_cleanup.always_use_blocks=true
sp_cleanup.always_use_parentheses_in_expressions=false
sp_cleanup.always_use_this_for_non_static_field_access=false
sp_cleanup.always_use_this_for_non_static_method_access=false
sp_cleanup.convert_to_enhanced_for_loop=false
sp_cleanup.correct_indentation=false
sp_cleanup.format_source_code=false
sp_cleanup.format_source_code_changes_only=false
sp_cleanup.make_local_variable_final=false
sp_cleanup.make_parameters_final=false
sp_cleanup.make_private_fields_final=true
sp_cleanup.make_type_abstract_if_missing_method=false
sp_cleanup.make_variable_declarations_final=true
sp_cleanup.never_use_blocks=false
sp_cleanup.never_use_parentheses_in_expressions=true
sp_cleanup.on_save_use_additional_actions=false
sp_cleanup.organize_imports=true
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
sp_cleanup.remove_private_constructors=true
sp_cleanup.remove_trailing_whitespaces=false
sp_cleanup.remove_trailing_whitespaces_all=true
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
sp_cleanup.remove_unnecessary_casts=true
sp_cleanup.remove_unnecessary_nls_tags=false
sp_cleanup.remove_unused_imports=false
sp_cleanup.remove_unused_local_variables=false
sp_cleanup.remove_unused_private_fields=true
sp_cleanup.remove_unused_private_members=false
sp_cleanup.remove_unused_private_methods=true
sp_cleanup.remove_unused_private_types=true
sp_cleanup.sort_members=false
sp_cleanup.sort_members_all=false
sp_cleanup.use_blocks=false
sp_cleanup.use_blocks_only_for_return_and_throw=false
sp_cleanup.use_parentheses_in_expressions=false
sp_cleanup.use_this_for_non_static_field_access=false
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
sp_cleanup.use_this_for_non_static_method_access=false
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+129 -41
View File
@@ -17,8 +17,8 @@ limitations under the License.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.maps.mytracks"
android:installLocation="auto"
android:versionCode="29"
android:versionName="1.1.6">
android:versionCode="32"
android:versionName="1.1.9">
<application android:icon="@drawable/arrow_icon"
android:label="@string/app_name"
android:backupAgent="com.google.android.apps.mytracks.io.backup.MyTracksBackupAgent">
@@ -44,6 +44,120 @@ limitations under the License.
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.MapActivity"
android:label="MyTracks"
android:exported="false">
<intent-filter>
<category android:name="android.intent.category.TAB" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.MyMapsList"
android:label="@string/map_list_title"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
android:exported="false" />
<!-- TODO: VIEW and PICK for track cursor dir -->
<activity android:name="com.google.android.apps.mytracks.TrackList"
android:label="Recorded Tracks" />
<!-- TODO: VIEW and PICK for waypoint cursor dir -->
<activity android:name="com.google.android.apps.mytracks.WaypointsList"
android:label="Recorded Waypoints" />
<activity android:name="com.google.android.apps.mytracks.TrackDetails"
android:label="@string/trackdetails_title">
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.DeleteTrack"
android:label="@string/deletetrack_title"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.DELETE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.WaypointDetails"
android:label="@string/waypointdetails_title">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.waypoint" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.SettingsActivity"
android:label="@string/mytracks_settings" >
<intent-filter>
<category android:name="android.intent.category.PREFERENCE" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.AggregatedStatsActivity"
android:label="@string/aggregated_stats" />
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
android:label="@string/statistics"
android:exported="false">
<intent-filter>
<category android:name="android.intent.category.TAB" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.SensorStateActivity"
android:label="@string/sensor_state" />
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
android:label="@string/elevation_label"
android:exported="false">
<intent-filter>
<category android:name="android.intent.category.TAB" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.io.sendtogoogle.SendActivity"
android:label="@string/sendtogoogle_title"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.io.file.SaveActivity"
android:label="@string/tracklist_write_to_sd"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.SAVE" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.track" />
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.io.file.ImportActivity"
android:label="@string/import_title"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.ATTACH_DATA" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:mimeType="application/gpx+xml" />
<data android:mimeType="application/gpx" />
@@ -52,44 +166,6 @@ limitations under the License.
</intent-filter>
</activity>
<activity android:name="com.google.android.apps.mytracks.MapActivity"
android:label="MyTracks"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.MyMapsList"
android:label="@string/map_list_title"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|fontScale"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.TrackList"
android:label="Recorded Tracks" />
<activity android:name="com.google.android.apps.mytracks.WaypointsList"
android:label="Recorded Waypoints" />
<activity android:name="com.google.android.apps.mytracks.TrackDetails"
android:label="@string/trackdetails_title" />
<activity android:name="com.google.android.apps.mytracks.WaypointDetails"
android:label="@string/waypointdetails_title" />
<activity android:name="com.google.android.apps.mytracks.SettingsActivity"
android:label="@string/mytracks_settings" />
<activity android:name="com.google.android.apps.mytracks.AggregatedStatsActivity"
android:label="@string/aggregated_stats" />
<activity android:name="com.google.android.apps.mytracks.StatsActivity"
android:label="@string/statistics"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.SensorStateActivity"
android:label="@string/sensor_state" />
<activity android:name="com.google.android.apps.mytracks.ChartActivity"
android:label="@string/elevation_label"
android:exported="false" />
<activity android:name="com.google.android.apps.mytracks.WelcomeActivity"
android:label="@string/welcome"
android:exported="false" />
@@ -104,7 +180,18 @@ limitations under the License.
<receiver android:name="com.google.android.apps.mytracks.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</intent-filter>
</receiver>
<receiver android:name="com.google.android.apps.mytracks.widgets.TrackWidgetProvider">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.google.android.apps.mytracks.TRACK_STARTED" />
<action android:name="com.google.android.apps.mytracks.TRACK_STOPPED" />
<action android:name="com.google.android.apps.mytracks.TRACK_UPDATED" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/track_widget_info" />
</receiver>
</application>
@@ -114,6 +201,7 @@ limitations under the License.
<!-- Permissions for recording tracks and showing maps -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" />
+1 -1
View File
@@ -10,7 +10,7 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=Google Inc.:Google APIs:11
target=Google Inc.:Google APIs:13
android.library.reference.1=../MyTracksLib/
# ProGuard is turned off while we investigate the removal of code that
-3
View File
@@ -5,9 +5,6 @@ licenses or the JAR's authors.
References are provided to the libraries:
accounts.jar (Android accounts library):
http://code.google.com/p/libs-for-android/wiki/AccountsLibrary
google-api-client-*.jar (GData client):
http://code.google.com/p/google-api-java-client/
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

+90
View File
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="10px"
android:paddingRight="10px"
android:paddingTop="5px"
android:paddingBottom="5px">
<LinearLayout
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:background="@drawable/appwidget_background">
<ImageButton
android:id="@+id/appwidget_button"
android:layout_height="fill_parent"
android:layout_width="wrap_content"
android:background="@android:color/transparent"
android:paddingLeft="10px"
android:paddingRight="10px"
android:src="@drawable/appwidget_button_disabled" />
<LinearLayout
android:id="@+id/appwidget_track_statistics"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_weight="1"
android:clickable="true"
android:orientation="vertical"
android:paddingLeft="15px">
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<TextView
style="@style/WidgetText.Register"
android:id="@+id/appwidget_distance_text" />
<TextView
style="@style/WidgetText"
android:text="@string/total_distance_label" />
</LinearLayout>
<ImageView
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="@color/appwidget_hr" />
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:paddingBottom="2px">
<TextView
style="@style/WidgetText.Register"
android:id="@+id/appwidget_time_text" />
<TextView
style="@style/WidgetText"
android:text="@string/moving_time_label" />
</LinearLayout>
<ImageView
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="@color/appwidget_hr" />
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<TextView
style="@style/WidgetText.Register"
android:id="@+id/appwidget_speed_text" />
<TextView
style="@style/WidgetText"
android:text="@string/speed" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
-7
View File
@@ -284,11 +284,4 @@ limitations under the License.
<item>punt de referència</item>
</array>
<array name="sensor_type_options">
<item>Cap</item>
<item>ANT+ Sensors</item>
<item>SRM ANT+ Bridge</item>
<item>Zephyr Bluetooth</item>
</array>
</resources>
+2
View File
@@ -409,4 +409,6 @@ limitations under the License.
<string name="searching">S\'està cercant</string>
<string name="sending">S\'està enviant</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">Cap</string>
</resources>
-7
View File
@@ -143,11 +143,4 @@ limitations under the License.
<item>camino</item>
</array>
<array name="sensor_type_options">
<item>Ninguna</item>
<item>ANT+ Sensores</item>
<item>SRM ANT+ Puente</item>
<item>Zephyr Bluetooth</item>
</array>
</resources>
+4
View File
@@ -128,4 +128,8 @@ limitations under the License.
$6 : Speed units (mph) : Pace units (per mile)
-->
<!-- Sensor Type Options -->
<string name="no_sensor_option">Ninguna</string>
<string name="ant_sensor_option">ANT+ Sensores</string>
<string name="ant_bridge_sensor_option">SRM ANT+ Puente</string>
</resources>
+271
View File
@@ -0,0 +1,271 @@
<?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>
<!-- Minimum recording distance -->
<string-array name="min_recording_distance_options">
<item>1 m</item>
<item>2 m</item>
<item>3 m</item>
<item>4 m</item>
<item>5 m (suositeltu)</item>
<item>10 m</item>
<item>100 m</item>
</string-array>
<string-array name="min_recording_distance_options_ft">
<item>3 ft</item>
<item>6 ft</item>
<item>10 ft</item>
<item>13 ft</item>
<item>16 ft (suositeltu)</item>
<item>33 ft</item>
<item>328 ft</item>
</string-array>
<!-- Maximum recording distance -->
<string-array name="max_recording_distance_options">
<item>50 m</item>
<item>100 m</item>
<item>200 m (suositeltu)</item>
<item>500 m</item>
<item>1000 m</item>
<item>5000 m</item>
</string-array>
<string-array name="max_recording_distance_options_ft">
<item>164 ft</item>
<item>328 ft</item>
<item>656 ft (suositeltu)</item>
<item>1640 ft</item>
<item>0.6 mailia</item>
<item>3.1 mailia</item>
</string-array>
<!-- Minimum recording interval -->
<string-array name="min_recording_interval_options">
<item>virransäästö (mukautuva)</item>
<item>paras tarkkuus (mukautuva)</item>
<item>nopein (suositeltu)</item>
<item>2 s</item>
<item>3 s</item>
<item>4 s</item>
<item>5 s</item>
<item>10 s</item>
<item>20 s</item>
<item>30 s</item>
<item>1 min</item>
<item>2 min</item>
<item>5 min</item>
<item>10 min</item>
<item>15 min</item>
<item>30 min</item>
</string-array>
<!-- Minimum required accuracy -->
<string-array name="min_required_accuracy_options">
<item>10 m (erinomainen GPS-signaali)</item>
<item>20 m</item>
<item>50 m</item>
<item>100 m</item>
<item>200 m (suositeltu)</item>
<item>500 m</item>
<item>1000 m</item>
<item>2000 m</item>
<item>5000 m (heikko GPS-signaali)</item>
</string-array>
<string-array name="min_required_accuracy_options_ft">
<item>33 ft (erinomainen GPS-signaali)</item>
<item>66 ft</item>
<item>165 ft</item>
<item>328 ft</item>
<item>656 ft (suositeltu)</item>
<item>1640 ft</item>
<item>0.6 mailia</item>
<item>1.2 mailia</item>
<item>3.1 mailia (heikko GPS-signaali)</item>
</string-array>
<!-- Task frequency -->
<string-array name="task_frequency_options">
<item>Pois päältä</item>
<item>100 km</item>
<item>50 km</item>
<item>25 km</item>
<item>10 km</item>
<item>5 km</item>
<item>1 km</item>
<item>60 min</item>
<item>30 min</item>
<item>15 min</item>
<item>10 min</item>
<item>5 min</item>
<item>2 min</item>
<item>1 min</item>
</string-array>
<string-array name="task_frequency_options_ft">
<item>Pois päältä</item>
<item>100 mi</item>
<item>50 mi</item>
<item>25 mi</item>
<item>10 mi</item>
<item>5 mi</item>
<item>1 mi</item>
<item>60 min</item>
<item>30 min</item>
<item>15 min</item>
<item>10 min</item>
<item>5 min</item>
<item>2 min</item>
<item>1 min</item>
</string-array>
<!-- Auto-resume track timeout -->
<string-array name="auto_resume_track_timeout_options">
<item>Ei koskaan</item>
<item>5 min</item>
<item>10 min</item>
<item>15 min</item>
<item>Aina</item>
</string-array>
<!-- Other non-preference arrays -->
<array name="activity_types">
<item>tuntematon</item>
<item>sekoitus</item>
<item>muu</item>
<item>juoksu</item>
<item>katujuoksu</item>
<item>ratajuoksu</item>
<item>maastojuoksu</item>
<item>kävely</item>
<item>hölkkä</item>
<item>patikointi</item>
<item>patikointi metsässä</item>
<item>suokävely</item>
<item>kiipeily</item>
<item>pyöräily</item>
<item>maantiepyöräily</item>
<item>maastopyöräily</item>
<item>ratapyöräily</item>
<item>luistelu</item>
<item>rullaluistelu</item>
<item>rullalautailu</item>
<item>rullahiihto (classic)</item>
<item>rullahiihto (skate)</item>
<item>maastohiihto (classic)</item>
<item>maastohiihto (skate)</item>
<item>lumikävely</item>
<item>hiihto</item>
<item>lumilautailu</item>
<item>kelkkailu</item>
<item>jääpurjehdus</item>
<item>purjehdus</item>
<item>melonta</item>
<item>soutu</item>
<item>moottoriveneily</item>
<item>lautta</item>
<item>RC-veneily</item>
<item>surffaus</item>
<item>purjelautailu</item>
<item>vesihiito</item>
<item>wake boarding</item>
<item>lentokone</item>
<item>katamaraani</item>
<item>liikennelentokone</item>
<item>helikopteri</item>
<item>kuumailmapallo</item>
<item>ilmalaiva</item>
<item>RC-lennokki</item>
<item>RC-helikopteri</item>
<item>laskuvarjohyppy</item>
<item>liitovarjo</item>
<item>ratsastus</item>
<item>autolla ajo</item>
<item>bussimatka</item>
<item>juna</item>
<item>moottoripyörä</item>
<item>maastopyörä</item>
<item>ATV</item>
<item>maapurjehdus</item>
<item>frisbee</item>
<item>ultimate frisbee</item>
</array>
<array name="waypoint_types">
<item>baari</item>
<item>bussipysäkki</item>
<item>cafe</item>
<item>golfrata</item>
<item>grilli</item>
<item>hotelli</item>
<item>huoltoasema</item>
<item>information</item>
<item>juomavesi</item>
<item>kahvila</item>
<item>kalastus</item>
<item>kampaamo</item>
<item>kauppa</item>
<item>kirkko</item>
<item>kohtaamispaikka</item>
<item>kuoppa</item>
<item>lautta</item>
<item>marketti</item>
<item>nuotio</item>
<item>nähtävyys</item>
<item>putoavia kiviä</item>
<item>paloasema</item>
<item>postilaatikko</item>
<item>ooppera</item>
<item>ostokset</item>
<item>pankkiautomaatti</item>
<item>pankki</item>
<item>poliisiasema</item>
<item>pysäköinti</item>
<item>posti</item>
<item>puhelin</item>
<item>puu</item>
<item>rakennus</item>
<item>ravintola</item>
<item>reittipiste</item>
<item>retkeilyreitti</item>
<item>risteys</item>
<item>ruokakauppa</item>
<item>satama</item>
<item>talo</item>
<item>tasoristeys</item>
<item>teatteri</item>
<item>tulentekopaikka</item>
<item>tunneli</item>
<item>turisti-info</item>
<item>uima-allas</item>
<item>vesiputous</item>
<item>WC</item>
</array>
</resources>
+510
View File
@@ -0,0 +1,510 @@
<?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>
<!-- Used by the Menu -->
<string name="start_recording">Aloita reitti</string>
<string name="stop_recording">Lopeta</string>
<string name="switch_to_sat">Satelliitti</string>
<string name="switch_to_map">Kartta</string>
<string name="mylocation">Oma sijainti</string>
<string name="list_tracks">Reitit</string>
<string name="list_markers">Merkinnät</string>
<string name="delete_all_tracks">Poista kaikki reitit</string>
<string name="settings">Asetukset</string>
<string name="elevation">Korkeus</string>
<string name="choose_track_message">Valitse näytettävä reitti</string>
<string name="current_track">Valittu reitti</string>
<string name="current_segment">Nykyinen segmentti</string>
<string name="sensor_state">Sensorien tila</string>
<string name="aggregated_stats">Yhteenlasketut tilastot</string>
<!-- Error messages -->
<string name="error_no_location_manager">Paikkatietoja ei löytynyt!</string>
<string name="error_no_gps_location_provider">GPS-palvelua ei löytynyt.</string>
<string name="error_sending_to_mymap">Virhe lähetettäessä reittiä Google My Maps-palveluun.</string>
<string name="error_sending_to_fusiontables">Virhe lähetettäessä reittiä Google Fusion Tables-palveluun.</string>
<string name="error_sending_to_docs">Virhe lähetettäessä reittiä Google Docs-palveluun..</string>
<string name="error_please_try_again_later">Yritä myöhemmin uudelleen.</string>
<string name="error_unable_to_start_recording">Tallennusta ei voitu aloittaa. Yritä uudelleen.</string>
<string name="error_unable_to_stop_recording">Tallennusta ei voitu pysäyttää. Yritä uudelleen.</string>
<string name="error_unable_to_read_file">Tiedostovirhe.</string>
<string name="error_invalid_gpx_format">Virheellinen GPX-formaatti.</string>
<string name="error_generic">Tapahtui virhe. Yritä uudelleen.</string>
<string name="error_track_does_not_exist">Reittiä ei ole olemassa.</string>
<string name="error_operation_cancelled">Tapahtuma keskeytettiin.</string>
<string name="error_out_of_memory">Liian suuri tiedosto (maksimi 500 KB). Keskeytetään.</string>
<string name="error_unable_to_insert_marker">Merkintää ei voitu lisätä. Paikkatietoa ei saatavilla.</string>
<!-- Status messages -->
<string name="wait_for_fix">Odotetaan parempaa GPS-yhteyttä. Varmista että taivas on näkyvissä.</string>
<string name="status_now_recording">Tallennetaan uutta reittiä.</string>
<string name="status_stopped_recording">Reitin tallentaminen lopetettiin.</string>
<string name="status_new_mymap_has_been_created">Uusi Google My Map on luotu osoitteeseen maps.google.com.</string>
<string name="status_new_fusiontable_has_been_created">Uusi Google Fusion taulukko on luotu osoitteeseen google.com/fusiontables.</string>
<string name="status_tracks_have_been_uploaded">Reittisi on lähetetty osoitteeseen maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">Reittisi on luotu taulukoksi osoitteeseen docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">Reittisi on lähetetty osoitteeseen docs.google.com.</string>
<string name="status_mymap_info">Reitin näkyminen saattaa kestää muutaman minuutin.</string>
<string name="status_enable_gps">Avaa tästä Sijainti ja suojaus -asetukset ja valitse \'Käytä GPS-satelliitteja\'. Palaa ohjelmaan painamalla Takaisin-näppäintä.</string>
<string name="status_approximate_location">Huomio: Näkyvä sijainti perustuu kännykkämastoista ja WLAN-verkoista arvioituun paikkatietoon.</string>
<string name="status_no_location">Paikkatietoa ei löydy. Aktivoi lisää paikannusmenetelmiä (katso Asetukset) ja varmista että taivas on näkyvissä. Yritä myöhemmin uudelleen.</string>
<string name="status_waypoint_inserted">Reittimerkintä lisätty.</string>
<string name="status_statistics_inserted">Tilastomerkintä lisätty.</string>
<string name="status_track_upload_status">Lähetyksen tila:</string>
<!-- Progress dialog -->
<string name="progress_title">Odota...</string>
<string name="progress_message">Lähetetään reittiä Google:n palvelimelle.</string>
<string name="progress_message_reading_track">Luetaan reittiä...</string>
<string name="progress_message_preparing_track">Valmistellaan reittiä...</string>
<string name="progress_message_authenticating_fusiontables">Todennetaan Google Fusion Tables -tiliä...</string>
<string name="progress_message_authenticating_mymaps">Todennetaan Google Maps -tiliä...</string>
<string name="progress_message_authenticating_docs">Todennetaan Google Docs -tiliä...</string>
<string name="progress_message_sending_docs">Lähetetään reittiä Google Docsiin...</string>
<string name="progress_message_sending_mymaps">Lähetetään reittiä Google Mapsiin...</string>
<string name="progress_message_creating_map">Luodaan uutta My Mapsia...</string>
<string name="progress_message_sending_fusiontables">Lähetetään reittiä Fusion Tablesiin...</string>
<string name="progress_message_creating_fusiontable">Luodaan uutta Fusion tablea...</string>
<string name="import_progress_message">Tuodaan uutta GPS-reittiä. Odota.</string>
<string name="write_progress_message">Kirjoitetaan tiedostoa SD-kortille. Odota.</string>
<string name="progress_message_updating_track">Päivitetään reitin tilastoja...</string>
<!-- Used by DeleteAllDataHandler -->
<string name="are_you_sure_question">Oletko varma?</string>
<string name="all_data_will_be_permanently_deleted">Kaikki reitit ja merkinnät poistetaan puhelimesta.</string>
<string name="track_will_be_permanently_deleted">Reitti ja se merkinnät poistetaan pysyvästi puhelimesta.</string>
<string name="marker_will_be_permanently_deleted">Merkintä poistetaan puhelimesta.</string>
<!-- Text and links when uploading to Google Fusion Tables -->
<string name="new_map_description">Created by My Tracks on Android.</string>
<string name="fusiontable_link">Created by &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; on Android.</string>
<!-- Text and links when uploading to Google My Maps -->
<string name="my_map_link">Created by &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; on Android.</string>
<string name="new_map_title">Uusi kartta</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">Pisteet</string>
<string name="recorded_date">Tallennettu</string>
<string name="category">Liikunnan muoto</string>
<string name="waypoint_category">Merkinnän tyyppi</string>
<string name="part">(Osa %d)</string>
<!-- Used by the track recording service -->
<string name="recording_your_track">Tallennetaan reittiä...</string>
<string name="new_track">Reitti %d</string>
<!-- Common -->
<string name="no">Ei</string>
<string name="yes">Kyllä</string>
<string name="ok">OK</string>
<string name="error">Virhe</string>
<string name="success">Valmis</string>
<string name="waypoint">Reittipiste</string>
<string name="license">Lisenssi</string>
<string name="cancel">Peruuta</string>
<string name="close">Sulje</string>
<string name="edit">Muokkaa</string>
<string name="save">Tallenna</string>
<!-- Placemark labels for start and end of track -->
<string name="start">(Lähtö)</string>
<string name="end">(Maali)</string>
<!-- TrackList -->
<string name="tracklist_show_track">Näytä</string>
<string name="tracklist_send_to_mymaps">Lähetä Google My Mapsiin</string>
<string name="tracklist_send_to_new_mymap">Lähetä New Google My Mapiin</string>
<string name="tracklist_send_to_fusiontables">Lähetä Google Fusion Tablesiin</string>
<string name="tracklist_send_to_new_fusiontable">Lähetä New Google Fusion Tablesiin</string>
<string name="tracklist_send_to_docs">Lähetä Google Docsiin</string>
<string name="tracklist_send_to_google">Lähetä Googleen...</string>
<string name="tracklist_delete_track">Poista puhelimesta...</string>
<string name="tracklist_edit_track">Muokkaa tietoja...</string>
<string name="tracklist_write_to_sd">Kirjoita SD-kortille...</string>
<string name="tracklist_save_as_gpx">Tallenna GPX-tiedosto</string>
<string name="tracklist_save_as_kml">Tallenna KML-tiedosto</string>
<string name="tracklist_save_as_csv">Tallenna CSV-tiedosto</string>
<string name="tracklist_save_as_tcx">Tallenna TCX-tiedosto</string>
<string name="tracklist_btn_send_all_to_fusiontables">Lähetä Google Fusion Tablesiin</string>
<string name="tracklist_btn_send_all_to_mymaps">Lähetä Google My Mapsiin</string>
<string name="tracklist_btn_delete_all">Poista kaikki</string>
<!-- Export All -->
<string name="tracklist_btn_export_all">Vie kaikki</string>
<string name="export_done">Vienti tehty</string>
<string name="tracklist_pick_one">Valitse reitti. Pitkällä painalluksella lisää vaihtoehtoja.</string>
<string name="tracklist_all_tracks">Kaikki reitit:</string>
<string name="tracklist_this_track">Tämä reitti</string>
<string name="tracklist_share_track">Jaa...</string>
<string name="tracklist_share_link">Linkki karttaan</string>
<string name="tracklist_share_gpx_file">GPX-tiedosto</string>
<string name="tracklist_share_kml_file">KML-tiedosto</string>
<string name="tracklist_share_csv_file">CSV-tiedosto</string>
<string name="tracklist_share_tcx_file">TCX-tiedosto</string>
<string name="tracklist_clear_map">Tyhjennä kartta</string>
<!-- Import All -->
<string name="tracklist_btn_import_all">Tuo kaikki</string>
<!-- Format:
$1 : Successful imports
$2 : Attempted imports
$3 : MyTracks gpx path
-->
<string name="import_multi_done">Tuotu %1$d GPX-tiedostoa %2$d:sta lähteestä %3$s</string>
<string name="import_multi_empty">Lähteestä %s ei löytynyt GPX-tiedostoja. SD-kortti saattaa olla lukukelvoton tai hakemistossa ei ole GPX-tiedostoja.</string>
<string name="import_error">Virhe tuotaessa tiedostoa %s</string>
<string name="import_title">Tuo My Tracksiin</string>
<string name="import_success">%1$d reittiä tuotu.</string>
<string name="import_show_track">Näytä reitti</string>
<!-- Waypoints list -->
<string name="waypointslist_btn_delete_all">Poista kaikki merkinnät</string>
<string name="waypointslist_this_waypoint">Tämä merkintä</string>
<string name="waypointslist_delete_waypoint">Poista</string>
<string name="waypointslist_edit_waypoint">Muokkaa tietoja</string>
<string name="waypointslist_show_waypoint">Näytä kartalla</string>
<string name="waypointslist_btn_insert_waypoint">Lisää reittipiste</string>
<string name="waypointslist_btn_insert_statistics">Lisää tilastot</string>
<string name="waypointslist_pick_one">Valitse merkintä. Pitkä painallus tuo esiin lisää vaihtoehtoja.</string>
<!-- MyTracks Details -->
<string name="trackdetails_title">Reitin tiedot</string>
<string name="trackdetails_name">Reitin nimi</string>
<string name="trackdetails_description">Reitin kuvaus</string>
<string name="trackdetails_category">Liikunnan muoto (esim. hölkkä, pyöräily, juoksu...)</string>
<!-- MyTracks Waypoint Details -->
<string name="waypointdetails_title">Merkinnän tiedot</string>
<string name="waypointdetails_name">Merkinnän nimi</string>
<string name="waypointdetails_description">Merkinnän kuvaus</string>
<string name="waypointdetails_category">Merkinnän tyyppi (esim. rakennus, kuoppa, nähtävyys...)</string>
<!-- MyTracks Settings -->
<string name="mytracks_settings">My Tracks Asetukset</string>
<string name="settings_display">Näyttö</string>
<string name="settings_recording">Tallennus</string>
<string name="settings_sharing">Jakaminen</string>
<string name="settings_not_available_summary">Ei tuettu tässä Android-versiossa</string>
<string name="settings_not_while_recording">Ei saatavilla reittiä tallennettaessa.</string>
<string name="settings_announcement_frequency">Äänimerkki</string>
<string name="settings_announcement_frequency_summary">Äänimerkkien aikaväli</string>
<string name="settings_min_recording_distance">Lyhyin piste-etäisyys</string>
<string name="settings_min_recording_distance_summary">Lyhyin sallittu reitintallennuksessa käytetty etäisyys kahden pisteen välillä</string>
<string name="settings_max_recording_distance">Pisin piste-etäisyys</string>
<string name="settings_max_recording_distance_summary">Jos edeltävä piste on valittua etäisyyttä pidemmällä reitti jaetaan</string>
<string name="settings_min_recording_interval">Lyhyin aika</string>
<string name="settings_min_recording_interval_summary">Lyhyin aikaintervalli kahden tallennetun pisteen välillä</string>
<string name="settings_min_required_accuracy">Sallittu tarkkuus</string>
<string name="settings_min_required_accuracy_summary">Reitin tallennus pysäytetään, jos tarkkuus on tätä pienempi</string>
<string name="settings_metric_units">Käytä metrijärjestelmää</string>
<string name="settings_metric_units_summary_on">Kaikki etäisyydet näytetään metrijärjestelmässä</string>
<string name="settings_metric_units_summary_off">Kaikki etäisyydet näytetään U.S yksiköitä käyttäen</string>
<string name="settings_map_public">Aseta kartat julkisiksi</string>
<string name="settings_map_public_summary_on">Lähetetyt kartat näkyvät kaikille osoitteessa maps.google.com</string>
<string name="settings_map_public_summary_off">Lähetettyjä karttoja ei näytetä julkisesti osoitteessa maps.google.com</string>
<string name="settings_report_speed_or_pace">Näytä nopeus</string>
<string name="settings_report_speed_or_pace_summary_on">Nopeus näytetään</string>
<string name="settings_report_speed_or_pace_summary_off">Tahti näytetään</string>
<string name="settings_share_url_only">Jaa vain URL</string>
<string name="settings_share_url_only_summary_on">Ainoastaan kartan URL jaetaan</string>
<string name="settings_share_url_only_summary_off">Myös reitin sisältö ja tiedot jaetaan</string>
<string name="settings_split_frequency">Reitin jaotus</string>
<string name="settings_split_frequency_summary">Etäisyys tai aika jolloin reitti jaetaan osiin</string>
<string name="settings_auto_resume_track_timeout">Reitin jatkaminen</string>
<string name="settings_auto_resume_track_timeout_summary">Aika jonka sisällä tallennettavaa reittiä jatketaan, jos puhelin käynnitetään uudelleen.</string>
<string name="settings_timestamp_track_name">Aikamerkintä nimenä</string>
<string name="settings_timestamp_track_name_summary_on">Oletusnimenä reitille käytetään aloituspäivämäärää ja aikaa</string>
<string name="settings_timestamp_track_name_summary_off">Oletusnimenä käytetään kasvavaa numerointia.</string>
<string name="settings_reset">Palauta oletusasetukset</string>
<string name="settings_reset_summary">Palauta kaikki oletusasetukset</string>
<string name="settings_reset_dialog_message">Kaikki asetuksesi poistetaan ja palautetaan oletusarvoisiksi. Tallennetut reitit säilytetään. Oletko varma?</string>
<string name="settings_reset_done">Oletusasetukset palautettu.</string>
<string name="pace_label">Tahti</string>
<string name="eclair_required">Tuettuna vain Android 2.0 tai uudemmassa</string>
<string name="settings_sensor_type">Sensorin tyyppi</string>
<string name="settings_sensor_type_summary">Sensorin tyyppi johon yhdistetään</string>
<string name="settings_heart_rate_ant_sensor_id">Resetoi ANT sykemittarin paritus</string>
<string name="settings_power_ant_sensor_id">Resetoi ANT tehomittarin paritus</string>
<string name="settings_srm_bridge_ant_sensor_id">Resetoi ANT SRM sillan paritus</string>
<string name="settings_ant_not_paired">ANT sensori ei paritettu</string>
<string name="settings_ant_paired">ANT sensori paritettu: %d</string>
<string name="bluetooth_sensor_title">Bluetooth sensori</string>
<string name="bluetooth_sensor_summary">Bluetooth-sensori johon yhdistetään</string>
<string name="settings_modular_stats_display_title">Modular Stats Display</string>
<!-- Backups -->
<string name="settings_backup">Varmuuskopioi (BETA)</string>
<string name="settings_sd_backup">Varmuuskopioi SD-kortille</string>
<string name="settings_backup_to_sd">Varmuuskopioi</string>
<string name="settings_backup_to_sd_summary">Tallenna täydellinen kopio reiteistä ja asetuksista SD-kortille</string>
<string name="settings_restore_from_sd">Palauta</string>
<string name="settings_restore_from_sd_summary">Palauta kaikki reitit ja asetukset SD-kortilta</string>
<string name="restore_overwrites_warning">Palautus SD-kortilta ylikirjoittaa kaikki nykyiset reitit ja asetukset. Jatketaanko?</string>
<string name="select_backup_to_restore">Valitse palautettava varmuuskopio</string>
<string name="no_backups">Varmuuskopioita ei löytynyt.</string>
<string name="backup_write_progress_message">Luodaan varmuuskopiota. Odota...</string>
<string name="backup_import_progress_message">Luetaan varmuuskopiota SD-kortilta. Odota...</string>
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS-satelliitit</string>
<!-- My Maps -->
<string name="select_map_message">Valitse kartta tililtäsi:</string>
<string name="create_new_map">Luo uusi kartta...</string>
<string name="map_list_title">Lähetä Google My Mapsiin</string>
<string name="maps_list_loading">Ladataan karttoja...</string>
<string name="maps_list_failed">Karttoja ei voitu ladata!</string>
<string name="open_map">Valitse tämä kartta</string>
<string name="edit_map_details">Muokkaa kartan tietoja</string>
<string name="share_track">Jaa reitti</string>
<string name="delete_map">Poista kartta tililtä</string>
<string name="map_title">Kartan otsikko:</string>
<string name="map_description">Kartan kuvaus:</string>
<string name="map_searchable_edit">Näytä kartta ja sen sisältö hakutuloksissa</string>
<string name="map_not_searchable_edit">Älä näytä karttaa tai sen sisältöä hakutuloksissa. Tiedot näkyvät suoralla URL-linkillä</string>
<string name="map_searchable">Tämä kartta ja sen sisältö näkyy hakutuloksissa</string>
<string name="map_not_searchable">Kartta tai sen sisältö ei näy hakutuloksissa. Tiedot näkyvät muille kuitenkin suoralla URL-linkillä</string>
<!-- Sharing -->
<string name="share_map">Jaa kartta</string>
<string name="share_map_subject">Haluaisin jakaa kartan kanssasi</string>
<string name="share_map_body_format">Saattaisit olla kiinnostunut tästä reitistä:
%s</string>
<string name="send_track_subject">Haluaisin jakaa reitin kanssasi</string>
<string name="send_track_body_format">Saattaisit olla kiinnostunut tästä reitistä.
Käytä Google Earthia tai jotain muuta karttaohjelmistoa kartan katsomiseen.</string>
<!-- Visibility -->
<string name="public_map">Julkinen</string>
<string name="unlisted_map">Ei listattu</string>
<string name="mapslist_no_maps_available">Ei karttoja saatavilla.</string>
<!-- Statistics View -->
<!-- Average Moving -->
<string name="average_moving_speed_label">Keskinopeus (liikuttaessa)</string>
<string name="average_moving_pace_label">Tahti (liikuttaessa)</string>
<!-- Average -->
<string name="average_speed_label">Keskinopeus</string>
<string name="average_pace_label">Tahti</string>
<string name="elevation_chart_name">Korkeuskaavio</string>
<string name="elevation_gain_label">Nousu</string>
<string name="elevation_label">Korkeus</string>
<string name="latitude_label">Leveysaste</string>
<string name="longitude_label">Pituuspiiri</string>
<!-- Min -->
<string name="min_elevation_label">Min Korkeus</string>
<string name="min_grade_label">Min Kaltevuus</string>
<string name="min_pace_label">Min Tahti</string>
<!-- Max -->
<string name="max_elevation_label">Max Korkeus</string>
<string name="max_grade_label">Max Kaltevuus</string>
<string name="max_speed_label">Max Nopeus</string>
<string name="loading_track">Ladataan reittiä...</string>
<!-- Chart Settings -->
<string name="chart_settings">Kaavion asetukset</string>
<!-- X Axis -->
<string name="x_axis">X-akseli</string>
<string name="by_distance">Etäisyyden mukaan</string>
<string name="by_time">Ajan mukaan</string>
<!-- Y Axis -->
<string name="y_axis">Y-akseli</string>
<!-- Units -->
<!-- Time -->
<string name="hour">tunti</string>
<string name="hours">tuntia</string>
<string name="hr">h</string>
<string name="min">min</string>
<string name="minute">minuutti</string>
<string name="minutes">minuuttia</string>
<string name="second">sekunti</string>
<string name="seconds">sekuntia</string>
<string name="milliseconds">ms</string>
<!-- Distance -->
<string name="meter">m</string>
<string name="kilometer">km</string>
<string name="kilometers_long">kilometriä</string>
<string name="feet">ft</string>
<string name="mile">mi</string>
<string name="miles_long">mailia</string>
<!-- Speed and Pace -->
<string name="kilometer_per_hour">km/h</string>
<string name="kilometer_per_hour_long">kilometriä tunnissa</string>
<string name="min_per_kilometer">min/km</string>
<string name="per_kilometer">per kilometri</string>
<string name="mile_per_hour">mi/h</string>
<string name="mile_per_hour_long">mailia tunnissa</string>
<string name="min_per_mile">min/mi</string>
<string name="per_mile">per maili</string>
<!-- Sensors -->
<string name="watts">W</string>
<string name="revolutions_per_minute">rpm</string>
<string name="bpm">bpm</string>
<!-- Stats Labels -->
<string name="moving_time_label">Liikkumisaika</string>
<string name="total_time_label">Kokonaisaika</string>
<string name="time_label">Aika</string>
<string name="sd_unavail">Muistikortti ei ole käytettävissä</string>
<string name="speed">Nopeus</string>
<string name="start_trip">Sinun pitää ensin aloittaa matka.</string>
<string name="statistics">Tilastot</string>
<string name="total_distance_label">Kokonaismatka</string>
<string name="distance_label">Matka</string>
<string name="power">Teho</string>
<string name="cadence">Kadenssi</string>
<string name="heart_rate">Syke</string>
<string name="max_heart_rate">Maksimisyke</string>
<string name="unknown">-</string>
<string name="waiting">Odotetaan...</string>
<!-- End User License Agreement -->
<string name="eula_title">End User License Agreement</string>
<string name="accept">Hyväksy</string>
<string name="decline">Hylkää</string>
<!-- About -->
<string name="about">Tietoja</string>
<string name="help">Apua</string>
<string name="about_team">Tiimi:</string>
<string name="about_version">Version:</string>
<string name="about_support">Tuki:</string>
<string name="about_privacy">Yksityisyys:</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">Lähetä...</string>
<string name="sendtogoogle_google_fusiontables">Google Fusion Tables</string>
<string name="sendtogoogle_google_mymaps">Google My Maps</string>
<string name="sendtogoogle_google_docs">Google Docs</string>
<string name="sendtogoogle_send_now">Lähetä</string>
<string name="sendtogoogle_create_new_map">Luo uusi kartta</string>
<string name="sendtogoogle_pick_existing_map">Valitse kartta</string>
<string name="sendtogoogle_send_stats">Lähetä reitin statistiikka</string>
<string name="sendtogoogle_send_stats_and_points">Lähetä reitin statistiikka ja pisteet</string>
<!-- Getting the account to upload to. -->
<string name="no_account_found_title">Tiliä ei löytynyt</string>
<string name="no_account_found">Tiliä johon reitin voisi lähettää ei löytynyt.</string>
<string name="choose_account_title">Valitse tili</string>
<!-- SD card -->
<string name="io_no_external_storage_found">Ulkoista tallennusmediaa ei löytynyt. Varmista että SD-kortti on kytketty eikä puhelinta ole liitetty tietokoneeseen.</string>
<string name="io_write_failed">Kirjoitusvirhe.</string>
<string name="io_read_failed">Lukuvirhe.</string>
<string name="io_create_dir_failed">Hakemistoa ei voitu luoda SD-kortille.</string>
<string name="io_write_finished">SD-kortille kirjoitus valmis.</string>
<string name="io_read_finished">SD-kortin luku valmis.</string>
<!-- VersionChecker -->
<string name="version_check_title">Tarkista päivitykset</string>
<string name="version_check_upgrade_button">Päivitä</string>
<string name="version_check_continue_button">Ohita päivitys</string>
<string name="upgrade_required">Käytössäsi oleva versio ohjelmasta %1$s saattaa lakata toimimasta eikä ole enää tuettu. Päivitä jatkaaksesi %1$s käyttöä.</string>
<string name="upgrade_recommended">Käytössäsi oleva versio ohjelmasta %1$s saattaa lakata toimimasta. Voit päivittää sen nyt tai jatkaa käyttöä omalla riskillä. Tietyt ominaisuudet, kuten synkronointi serverin kanssa, ei välttämättä toimi ennen päivitystä.</string>
<string name="upgrade_available">%1$s uusi versio saatavilla. Voit jatkaa nykyisen version käyttöä tai päivitää uuteen versioon.</string>
<!-- Welcome -->
<string name="welcome">Tervetuloa</string>
<string name="welcome_read_later">Ok</string>
<string name="welcome_about">Tietoa My Tracksista</string>
<!-- Announcement -->
<string name="announce_no_distance">My Tracks ei etäisyyttä</string>
<!-- Format:
$1 : Distance label
$2 : Distance value (5)
$3 : Distance units (mi, km)
$4 : Time description (10 minutes 5 seconds)
$5 : Speed value (5.6) : Pace value (5 minutes 6 seconds)
$6 : Speed units (mph) : Pace units (per mile)
-->
<string name="announce_template">My Tracks %1$s %2$.2f %3$s ajassa %4$s nopeudella %5$s %6$s</string>
<!-- Check Units -->
<string name="check_units_title">Huomautus mittayksiköistä</string>
<string name="check_units_message">Oletuksena käytössä on metrijärjestelmä. Haluatko muuttaa asetuksen?</string>
<!-- Fake preference keys - never saved, but show options in the preferences screen -->
<string name="method_bluetooth">Bluetooth</string>
<string name="bluetooth_options">Bluetooth asetukset</string>
<string name="bluetooth_pairing">Laiteparit</string>
<string name="bluetooth_pairing_summary">Avaa Androidin bluetooth-asetukset luodaksesi laitepareja</string>
<string name="sensor_bluetooth_options_key">bluetoothSensorsEnabled</string>
<string name="ant_options">ANT asetukset</string>
<string name="sensor_options">Sensorien asetukset</string>
<!-- Bluetooth Sensor -->
<string name="bluetooth_connected">Yhdistetty %s</string>
<string name="bluetooth_connecting">Yhdistetään bluetooth sensoria ...</string>
<string name="bluetooth_not_connected">Bluetooth sensoria ei ole yhdistetty</string>
<!-- Sensor State -->
<string name="sensor_data_time">Sensorin data-aika</string>
<string name="power_meter_enabled">Tehomittari käytössä</string>
<string name="heart_rate_monitor_enabled">Sykemittari käytössä</string>
<string name="no_sensor_manager">Ei sensorihallintaa</string>
<string name="enabled">Käytössä</string>
<string name="connecting">Yhdistetään</string>
<string name="connected">Yhdistetty</string>
<string name="disconnected">Katkaistu</string>
<string name="no_sensor_service">Ei sensoripalvelua</string>
<string name="searching">Etsitään</string>
<string name="sending">Lähetetään</string>
<string name="no_sensor_option">Ei sensoria</string>
<string name="ant_sensor_option">ANT+ -sensori</string>
<string name="ant_bridge_sensor_option">SRM ANT+ -silta</string>
<string name="polar_bt_sensor_option">Polar bluetooth</string>
<string name="zephyr_bt_sensor_option">Zephyr bluetooth</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">vastaanota huomautuksia reitin tallentamisesta</string>
<string name="read_tracks_permission_label">lue tallennettua reittidataa</string>
<string name="write_tracks_permission_label">kirjoita tallennettua reittidataa, aloita/pysäytä reitin tallennus</string>
<string name="notification_permission_description">Huomauta kun reitin tallennus aloitetaan tai pysäytetään..</string>
<string name="read_tracks_permission_description">Mahdollista reittidatan lukeminen, kuten reittipisteet, merkinnät jne.</string>
<string name="write_tracks_permission_description">Mahdollista reittidatan kirjoittaminen, kuten tallennuksen aloittaminen, merkintöjen lisääminen jne.</string>
</resources>
-7
View File
@@ -291,11 +291,4 @@ limitations under the License.
<item>point de passage</item>
</array>
<array name="sensor_type_options">
<item>Aucun</item>
<item>Capteur ANT+</item>
<item>Pont SRM ANT+</item>
<item>Bluetooth Zephyr</item>
</array>
</resources>
+7
View File
@@ -397,6 +397,13 @@ limitations under the License.
<string name="searching">Recherche</string>
<string name="sending">Envoi</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">Aucun</string>
<string name="ant_sensor_option">Capteur ANT+</string>
<string name="ant_bridge_sensor_option">Pont SRM ANT+</string>
<string name="polar_bt_sensor_option">Bluetooth Polar</string>
<string name="zephyr_bt_sensor_option">Bluetooth Zephyr</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">notifications lors de l\'enregistrement de parcours</string>
<string name="read_tracks_permission_label">lecture des parcours enregistrés</string>
-7
View File
@@ -272,11 +272,4 @@ limitations under the License.
<item>ウェイポイント</item>
</array>
<array name="sensor_type_options">
<item>なし</item>
<item>ANT+ センサー</item>
<item>SRM ANT+ ブリッジ</item>
<item>Zephyr Bluetooth</item>
</array>
</resources>
+7 -2
View File
@@ -159,8 +159,8 @@ limitations under the License.
$2 : Attempted imports
$3 : MyTracks gpx path
-->
<string name="import_done">%3$s からGPXファイル %2$d件のうち %1$d件をインポートしました</string>
<string name="import_empty">%s ではGPXファイルがありません.SDカードを読み込めないかディレクトリにGPXファイルが無いかも知れません.</string>
<string name="import_multi_done">%3$s からGPXファイル %2$d件のうち %1$d件をインポートしました</string>
<string name="import_multi_empty">%s ではGPXファイルがありません.SDカードを読み込めないかディレクトリにGPXファイルが無いかも知れません.</string>
<string name="import_error">ファイル %s のインポートエラー</string>
<!-- Waypoints list -->
@@ -463,6 +463,11 @@ limitations under the License.
<string name="searching">検索中</string>
<string name="sending">送信中</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">なし</string>
<string name="ant_sensor_option">ANT+ センサー</string>
<string name="ant_bridge_sensor_option">SRM ANT+ ブリッジ</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">トラック記録通知受信</string>
<string name="read_tracks_permission_label">トラック読み込み</string>
-7
View File
@@ -272,11 +272,4 @@ limitations under the License.
<item>путевая точка</item>
</array>
<array name="sensor_type_options">
<item>Нет</item>
<item>ANT+ Sensors</item>
<item>SRM ANT+ Bridge</item>
<item>Zephyr Bluetooth</item>
</array>
</resources>
+7 -2
View File
@@ -162,8 +162,8 @@ limitations under the License.
$2 : Attempted imports
$3 : MyTracks gpx path
-->
<string name="import_done">Импортировано %1$d из %2$d GPX файлов с %3$s</string>
<string name="import_empty">GPX файлы не найденны в %s. Возможно не читается SD-карта или каталог не содержит GPX файлы.</string>
<string name="import_multi_done">Импортировано %1$d из %2$d GPX файлов с %3$s</string>
<string name="import_multi_empty">GPX файлы не найденны в %s. Возможно не читается SD-карта или каталог не содержит GPX файлы.</string>
<string name="import_error">Ошибка импорта файла %s</string>
<!-- Waypoints list -->
@@ -490,6 +490,11 @@ limitations under the License.
<string name="searching">Поиск</string>
<string name="sending">Отправка</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">Нет</string>
<string name="ant_sensor_option">ANT+ Датчик</string>
<string name="ant_bridge_sensor_option">SRM ANT+ Bridge</string>
<!-- Manifest description (not seen inside the app) -->
<string name="notification_permission_label">получать уведомления о записи трека</string>
<string name="read_tracks_permission_label">чтение записанных данных трека</string>
+5
View File
@@ -170,6 +170,11 @@ limitations under the License.
<item>轮滑</item>
<item>滑板</item>
<item>滚轮滑雪 (传统)</item>
<item>滚轮滑雪 (滑板)</item>
<item>越野滑雪 (传统)</item>
<item>越野滑雪 (滑板)</item>
<item>雪攀</item>
<item>滑雪</item>
<item>雪板</item>
+96 -14
View File
@@ -29,19 +29,23 @@ limitations under the License.
<string name="choose_track_message">请选择要查看的轨迹</string>
<string name="current_track">当前轨迹</string>
<string name="current_segment">上一段轨迹</string>
<string name="sensor_state">感应器状态</string>
<string name="aggregated_stats">累积统计</string>
<!-- Error messages -->
<string name="error_no_location_manager">此设备上没有位置管理器!</string>
<string name="error_no_gps_location_provider">此设备上没有 GPS 定位装置.</string>
<string name="error_sending_to_fusiontables">发送你的轨迹到 Google Fusion Tables 时发生错误. 请稍后重试.</string>
<string name="error_sending_to_docs">发送你的轨迹到 Google Docs 时发生错误. 请稍后重试.</string>
<string name="error_sending_to_mymap">发送轨迹到 Google My Map 时发生错误.</string>
<string name="error_sending_to_fusiontables">发送轨迹到 Google Fusion Tables 时发生错误. 请稍后重试.</string>
<string name="error_sending_to_docs">发送轨迹到 Google Docs 时发生错误. 请稍后重试.</string>
<string name="error_please_try_again_later">请稍后再试.</string>
<string name="error_unable_to_start_recording">无法开始记录. 请重试.</string>
<string name="error_unable_to_stop_recording">无法停止记录. 请重试.</string>
<string name="error_unable_to_read_file">无法读取文件.</string>
<string name="error_invalid_gpx_format">文件不是有效的 GPX 格式.</string>
<string name="error_generic">发生错误. 请重试.</string>
<string name="error_track_does_not_exist">轨迹不存在.</string>
<string name="error_operation_cancelled">操作已被取消.</string>
<string name="error_out_of_memory">文件太大 (必须小于 500 KB). 中止操作.</string>
<string name="error_unable_to_insert_marker">无法插入标记. 定位尚未完成.</string>
@@ -49,15 +53,18 @@ limitations under the License.
<string name="wait_for_fix">等待更准确的 GPS 定位. 请确定可以看见天空.</string>
<string name="status_now_recording">现在开始记录新的轨迹.</string>
<string name="status_stopped_recording">停止记录你的轨迹.</string>
<string name="status_new_mymap_has_been_created">已在 maps.google.com 创建了新的 Google My Map.</string>
<string name="status_new_fusiontable_has_been_created">已在 google.com/fusiontables 创建新 Google Fusion Table.</string>
<string name="status_tracks_have_been_uploaded">你的轨迹已经发送到 maps.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_new_doc">你的轨迹已经作为新的电子表格发送到 docs.google.com.</string>
<string name="status_tracks_have_been_uploaded_to_docs">你的轨迹已经发送到 docs.google.com.</string>
<string name="status_mymap_info">可能需要等待几分钟才能看见新的轨迹.</string>
<string name="status_enable_gps">请点击这里进入\'安全和位置设置\'并勾选上\'启用 GPS 卫星\'. 然后用后退键返回.</string>
<string name="status_approximate_location">注意: 当前显示的位置是根据无线信号和无线网络计算的近似位置.</string>
<string name="status_no_location">目前无法定位. 请启用更多的位置来源 (在\'安全和位置设置\'中) 并确认天空是可见的. 再重试.</string>
<string name="status_waypoint_inserted">插入了一个路点标记.</string>
<string name="status_statistics_inserted">插入了一个统计标记.</string>
<string name="status_track_upload_status">上传状态:</string>
<!-- Progress dialog -->
<string name="progress_title">请稍候...</string>
@@ -69,6 +76,9 @@ limitations under the License.
<string name="progress_message_sending_docs">发送轨迹到 Google Docs 中...</string>
<string name="progress_message_sending_fusiontables">发送轨迹到 Google Fusion Tables 中...</string>
<string name="progress_message_creating_fusiontable">创建新的 Fusion Table 中...</string>
<string name="progress_message_creating_map">创建新的 My Map...</string>
<string name="progress_message_sending_fusiontables">发送轨迹到 Fusion Tables...</string>
<string name="progress_message_creating_fusiontable">创建新的 Fusion table...</string>
<string name="import_progress_message">导入 GPS 轨迹中. 请稍候.</string>
<string name="write_progress_message">写入文件到 SD 卡中. 请稍候.</string>
<string name="progress_message_updating_track">更新轨迹统计信息中...</string>
@@ -79,12 +89,16 @@ limitations under the License.
<string name="track_will_be_permanently_deleted">选定轨迹和它相关的标记都将被从本机彻底删除.</string>
<string name="marker_will_be_permanently_deleted">选定标记将被从本机删除.</string>
<!-- Text and links when uploading to Google Fusion Tables -->
<string name="new_map_description">由 Android 平台的 My Tracks 创建.</string>
<string name="fusiontable_link">由 Android 平台的 &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; 创建.</string>
<!-- Text and links when uploading to Google My Maps -->
<string name="new_map_description">由 Android 上的 My Tracks 生成.</string>
<string name="fusiontable_link">由 Android 上的 &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; 生成.</string>
<string name="fusiontable_link">由 Android 上的 &lt;a href=\'http://mytracks.appspot.com\'&gt;My Tracks&lt;/a&gt; 创建.</string>
<string name="new_map_title">新地图</string>
<!-- Formatting of track and marker descriptions -->
<string name="points">点数</string>
<string name="points">路点</string>
<string name="recorded_date">记录日期</string>
<string name="category">活动类型</string>
<string name="waypoint_category">标记类型</string>
@@ -101,6 +115,7 @@ limitations under the License.
<string name="error">错误</string>
<string name="success">成功</string>
<string name="waypoint">路点</string>
<string name="license">授权</string>
<string name="cancel">取消</string>
<string name="close">关闭</string>
<string name="edit">编辑</string>
@@ -112,8 +127,10 @@ limitations under the License.
<!-- TrackList -->
<string name="tracklist_show_track">显示</string>
<string name="tracklist_send_to_mymaps">发送到 Google My Maps</string>
<string name="tracklist_send_to_new_mymap">发送到新建的 Google My Map</string>
<string name="tracklist_send_to_fusiontables">发送到 Google Fusion Table</string>
<string name="tracklist_send_to_new_fusiontable">发送到新建 Google Fusion Table</string>
<string name="tracklist_send_to_new_fusiontable">发送到新建的 Google Fusion Table</string>
<string name="tracklist_send_to_docs">发送到 Google Docs</string>
<string name="tracklist_send_to_google">发送到 Google...</string>
<string name="tracklist_delete_track">从手机上删除...</string>
@@ -124,6 +141,7 @@ limitations under the License.
<string name="tracklist_save_as_csv">保存为 CSV 文件</string>
<string name="tracklist_save_as_tcx">保存为 TCX 文件</string>
<string name="tracklist_btn_send_all_to_fusiontables">发送到 Google Fusion Tables</string>
<string name="tracklist_btn_send_all_to_mymaps">发送到 Google My Maps</string>
<string name="tracklist_btn_delete_all">删除所有轨迹</string>
<!-- Export All -->
<string name="tracklist_btn_export_all">导出所有轨迹</string>
@@ -132,11 +150,22 @@ limitations under the License.
<string name="tracklist_all_tracks">全部轨迹:</string>
<string name="tracklist_this_track">此轨迹</string>
<string name="tracklist_share_track">与朋友分享...</string>
<string name="tracklist_share_link">访问地图的超链接</string>
<string name="tracklist_share_gpx_file">GPX 文件</string>
<string name="tracklist_share_kml_file">KML 文件</string>
<string name="tracklist_share_csv_file">CSV 文件</string>
<string name="tracklist_share_tcx_file">TCX 文件</string>
<string name="tracklist_clear_map">清除地图</string>
<!-- Import All -->
<string name="tracklist_btn_import_all">全部导入</string>
<!-- Format:
$1 : Successful imports
$2 : Attempted imports
$3 : MyTracks gpx path
-->
<string name="import_done">已从 %3$s 导入 %1$d 个 GPX 文件, 共 %2$d 个.</string>
<string name="import_empty">在 %s 中未找到 GPX 文件. SD 存储卡不可用或者目录中没有 GPX 文件.</string>
<string name="import_error">导入文件 %s 出错.</string>
<!-- Waypoints list -->
<string name="waypointslist_btn_delete_all">删除所有标记</string>
@@ -166,10 +195,11 @@ limitations under the License.
<string name="settings_recording">记录</string>
<string name="settings_sharing">分享</string>
<string name="settings_not_available_summary">你使用的 Android 版本无法支持</string>
<string name="settings_not_available_summary">不支持当前使用的 Android 版本</string>
<string name="settings_not_while_recording">在记录轨迹时不可用</string>
<string name="settings_announcement_frequency">提示频度</string>
<string name="settings_announcement_frequency_summary">语音提示的最小时间间隔</string>
<string name="settings_announcement_frequency">通知频度</string>
<string name="settings_announcement_frequency_summary">语音通知的最小时间间隔</string>
<string name="settings_min_recording_distance">最小记录间距</string>
<string name="settings_min_recording_distance_summary">位置记录的最小间隔距离</string>
@@ -205,14 +235,30 @@ limitations under the License.
<string name="settings_auto_resume_track_timeout">自动恢复追踪等待时间</string>
<string name="settings_auto_resume_track_timeout_summary">手机重启后自动继续上次轨迹记录的最大间隔时间</string>
<string name="settings_timestamp_track_name">使用时间作为轨迹名称</string>
<string name="settings_timestamp_track_name_summary_on">使用轨迹记录的开始时间作为默认名称</string>
<string name="settings_timestamp_track_name_summary_off">轨迹使用自动递增的数字进行命名.</string>
<string name="settings_reset">恢复默认值</string>
<string name="settings_reset_summary">将所有设置恢复到默认值</string>
<string name="settings_reset_dialog_message">你的所有设置都将恢复到默认值. 已保存的轨迹不会丢失. 确定要继续吗?</string>
<string name="settings_reset_done">所有设置已恢复到默认值.</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_heart_rate_ant_sensor_id">清除 ANT 心率计的设备关联</string>
<string name="settings_power_ant_sensor_id">清除 ANT 能量感应器的设备关联</string>
<string name="settings_srm_bridge_ant_sensor_id">清除 ANT SRM 桥接感应器的设备关联</string>
<string name="settings_ant_not_paired">ANT 感应器未关联</string>
<string name="settings_ant_paired">ANT 感应器已关联到: %d</string>
<string name="bluetooth_sensor_title">蓝牙感应器</string>
<string name="bluetooth_sensor_summary">要连接到的蓝牙感应器</string>
<string name="bluetooth_sensor_summary">想要连接的蓝牙感应器</string>
<string name="settings_modular_stats_display_title">模块化状态显示</string>
<!-- Backups -->
<string name="settings_backup">备份 (测试版)</string>
<string name="settings_sd_backup">SD 存储卡备份</string>
<string name="settings_backup_to_sd">立即备份</string>
@@ -220,7 +266,6 @@ limitations under the License.
<string name="settings_restore_from_sd">立即恢复</string>
<string name="settings_restore_from_sd_summary">从 SD 存储卡恢复所有轨迹和设置</string>
<!-- Backups -->
<string name="restore_overwrites_warning">从 SD 存储卡恢复备份数据将覆盖掉你当前的所有轨迹和设置. 确定要继续吗?</string>
<string name="select_backup_to_restore">请选择要恢复的备份文件</string>
<string name="no_backups">没有找到备份文件.</string>
@@ -230,7 +275,25 @@ limitations under the License.
<!-- MyTracksSky -->
<string name="mytracks_sky">GPS 卫星</string>
<!-- My Maps -->
<string name="select_map_message">从你的帐号中选择一个地图:</string>
<string name="create_new_map">创建新地图...</string>
<string name="map_list_title">发送到 Google My Maps</string>
<string name="maps_list_loading">载入地图中...</string>
<string name="maps_list_failed">现在无法载入你的地图!</string>
<string name="open_map">选择此地图</string>
<string name="edit_map_details">编辑地图详细信息</string>
<string name="share_track">分享轨迹</string>
<string name="delete_map">从你的帐号删除地图</string>
<string name="map_title">地图标题:</string>
<string name="map_description">地图描述:</string>
<string name="map_searchable_edit">让此地图和它的内容出现在搜索结果中</string>
<string name="map_not_searchable_edit">不要让这个地图的的内容出现在搜索结果中, 但其它用户依然可以通过直接 URL 访问它</string>
<string name="map_searchable">此地图和它的内容将在搜索结果中出现</string>
<string name="map_not_searchable">此地图和它的内容不会在搜索结果中出现, 但其它用户依然可以通过直接 URL 访问它</string>
<!-- Sharing -->
<string name="share_map">与朋友分享地图</string>
<string name="share_map_subject">我想与你分享一张地图</string>
<string name="share_map_body_format">我想你可能会对这个地图感兴趣:
@@ -239,7 +302,6 @@ limitations under the License.
<string name="send_track_body_format">我想你可能会对这条轨迹感兴趣.
请用 Google Earth 或其它地图软件查看此轨迹.</string>
<string name="share_track">与朋友分享轨迹</string>
<!-- Visibility -->
<string name="public_map">公开</string>
@@ -331,10 +393,13 @@ limitations under the License.
<string name="about_team">开发团队:</string>
<string name="about_version">版本:</string>
<string name="about_support">技术支持:</string>
<string name="about_privacy">隐私策略:</string>
<!-- Send to Google -->
<string name="sendtogoogle_title">发送到...</string>
<string name="sendtogoogle_send_now">立即发送</string>
<string name="sendtogoogle_create_new_map">创建新地图</string>
<string name="sendtogoogle_pick_existing_map">选择已有地图</string>
<string name="sendtogoogle_send_stats">发送轨迹统计信息</string>
<string name="sendtogoogle_send_stats_and_points">发送统计信息和路点</string>
@@ -344,10 +409,11 @@ limitations under the License.
<string name="choose_account_title">选择帐户</string>
<!-- 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_create_dir_failed">无法在 SD 存储卡上创建目录.</string>
<string name="io_write_finished">轨迹已导出到 SD 卡.</string>
<string name="io_create_dir_failed">无法在 SD 存储卡上创建目录card.</string>
<string name="io_write_finished">轨迹已导出到 SD 存储卡.</string>
<string name="io_read_finished">从 SD 存储卡读取完毕.</string>
<!-- VersionChecker -->
@@ -395,4 +461,20 @@ limitations under the License.
<string name="sensor_data_time">感应器数据时间</string>
<string name="power_meter_enabled">电源计量器已启用</string>
<string name="heart_rate_monitor_enabled">心率监视器已启用</string>
<string name="no_sensor_manager">没有感应器管理器</string>
<string name="enabled">已启用</string>
<string name="connecting">连接中</string>
<string name="connected">已连接</string>
<string name="disconnected">未连接</string>
<string name="no_sensor_service">没有感应器服务</string>
<string name="searching">搜索中</string>
<string name="sending">发送中</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">无</string>
<string name="ant_sensor_option">ANT+ 感应器</string>
<string name="ant_bridge_sensor_option">SRM ANT+ 转接器</string>
<string name="polar_bt_sensor_option">Polar Bluetooth</string>
<string name="zephyr_bt_sensor_option">Zephyr 蓝牙设备</string>
</resources>
+5 -7
View File
@@ -170,6 +170,11 @@ limitations under the License.
<item>inline skating</item>
<item>skate boarding</item>
<item>roller skiing (classic)</item>
<item>roller skiing (skate)</item>
<item>cross-country skiing (classic)</item>
<item>cross-country skiing (skate)</item>
<item>snow shoeing</item>
<item>skiing</item>
<item>snow boarding</item>
@@ -272,11 +277,4 @@ limitations under the License.
<item>way point</item>
</array>
<array name="sensor_type_options">
<item>None</item>
<item>ANT+ Sensors</item>
<item>SRM ANT+ Bridge</item>
<item>Zephyr Bluetooth</item>
</array>
</resources>
@@ -38,16 +38,6 @@ limitations under the License.
<item>100</item>
</string-array>
<string-array name="min_recording_distance_values">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>10</item>
<item>100</item>
</string-array>
<string-array name="max_recording_distance_values">
<item>50</item>
<item>100</item>
@@ -108,10 +98,19 @@ limitations under the License.
<item>TCX</item>
</array>
<array name="sensor_type_options">
<item>@string/no_sensor_option</item>
<item>@string/ant_sensor_option</item>
<item>@string/ant_bridge_sensor_option</item>
<item>@string/polar_bt_sensor_option</item>
<item>@string/zephyr_bt_sensor_option</item>
</array>
<array name="sensor_type_values">
<item>@string/none</item>
<item>@string/ant_sensor_type</item>
<item>@string/srm_ant_bridge_sensor_type</item>
<item>@string/polar_sensor_type</item>
<item>@string/zephyr_sensor_type</item>
</array>
+4
View File
@@ -47,4 +47,8 @@ limitations under the License.
<!-- Purple and a transparent purple -->
<color name="power_border">#FF401465</color>
<color name="power_fill">#40401465</color>
<!-- AppWidget colors -->
<color name="appwidget_text">#EDEDED</color>
<color name="appwidget_hr">#99999999</color>
</resources>
+7
View File
@@ -33,10 +33,17 @@ limitations under the License.
<string name="none">NONE</string>
<string name="ant_sensor_type">ANT</string>
<string name="srm_ant_bridge_sensor_type">SRM_ANT_BRIDGE</string>
<string name="polar_sensor_type">POLAR</string>
<string name="zephyr_sensor_type">ZEPHYR</string>
<!-- Send to Google URLs -->
<string name="sendtogoogle_google_fusiontables_url">google.com/fusiontables</string>
<string name="sendtogoogle_google_mymaps_url">maps.google.com</string>
<string name="sendtogoogle_google_docs_url">docs.google.com</string>
<!-- ID for Google Analytics -->
<string name="google_analytics_id">UA-7222692-2</string>
<!-- Intent actions -->
<string name="save_intent_action">android.intent.action.SAVE</string>
</resources>
+15 -3
View File
@@ -83,7 +83,8 @@ limitations under the License.
<string name="write_progress_message">Writing file to SD card. This may take a while.</string>
<string name="progress_message_updating_track">Updating track statistics...</string>
<!-- Used by DeleteAllDataHandler -->
<!-- Used by deletion actions -->
<string name="deletetrack_title">Delete track</string>
<string name="are_you_sure_question">Are you sure?</string>
<string name="all_data_will_be_permanently_deleted">All tracks and markers will be deleted from this phone.</string>
<string name="track_will_be_permanently_deleted">The track and its markers will be permanently deleted from this phone.</string>
@@ -163,9 +164,12 @@ limitations under the License.
$2 : Attempted imports
$3 : MyTracks gpx path
-->
<string name="import_done">Imported %1$d of %2$d GPX files from %3$s</string>
<string name="import_empty">No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.</string>
<string name="import_multi_done">Imported %1$d of %2$d GPX files from %3$s</string>
<string name="import_multi_empty">No GPX files found in %s. SD card may be unreadable or directory may not contain GPX files.</string>
<string name="import_error">Import error for file %s</string>
<string name="import_title">Import into My Tracks</string>
<string name="import_success">Successfully imported %1$d track(s) from file.</string>
<string name="import_show_track">Show track</string>
<!-- Waypoints list -->
<string name="waypointslist_btn_delete_all">Delete all markers</string>
@@ -498,4 +502,12 @@ limitations under the License.
<string name="notification_permission_description">Be notified when a track starts or stops being recorded.</string>
<string name="read_tracks_permission_description">Be able to read track data, such as points and waypoints.</string>
<string name="write_tracks_permission_description">Be able to write track data, such as starting recording or adding waypoints.</string>
<!-- Sensor Type Options -->
<string name="no_sensor_option">NONE</string>
<string name="ant_sensor_option">ANT+ Sensors</string>
<string name="ant_bridge_sensor_option">SRM ANT+ Bridge</string>
<string name="polar_bt_sensor_option">Polar Bluetooth</string>
<string name="zephyr_bt_sensor_option">Zephyr Bluetooth</string>
</resources>
+11
View File
@@ -74,4 +74,15 @@ limitations under the License.
<item name="android:layout_height">3dip</item>
<item name="android:layout_width">fill_parent</item>
</style>
<!-- The style for the track widget. -->
<style name="WidgetText" parent="@android:Widget.TextView">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:textColor">@color/appwidget_text</item>
<item name="android:textSize">14sp</item>
</style>
<style name="WidgetText.Register">
<item name="android:width">160px</item>
</style>
</resources>
+1 -1
View File
@@ -42,7 +42,7 @@ limitations under the License.
android:summary="@string/settings_announcement_frequency_summary"
android:entries="@array/task_frequency_options"
android:entryValues="@array/task_frequency_values"
android:defaultValue="-1" />
android:defaultValue="0" />
<com.google.android.apps.mytracks.IntegerListPreference
android:key="@string/split_frequency_key"
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/track_widget"
android:minHeight="72dp"
android:minWidth="294dp"
android:updatePeriodMillis="0" />
+4 -4
View File
@@ -61,10 +61,10 @@ public class AntInterface {
private static Object INSTANCE_LOCK = new Object();
/** The context to use. */
private static Context sContext = null;
private Context sContext = null;
/** Listens to changes to service connection status. */
private static ServiceListener sServiceListener;
private ServiceListener sServiceListener;
/** Is the ANT Radio Proxy Service connected. */
private static boolean sServiceConnected = false;
@@ -210,7 +210,7 @@ public class AntInterface {
/**
* Class for interacting with the ANT interface.
*/
private static ServiceConnection sIAntConnection = new ServiceConnection() {
private final ServiceConnection sIAntConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName pClassName, IBinder pService) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
@@ -1223,7 +1223,7 @@ public class AntInterface {
*/
public boolean requestForceClaimInterface(String appName) throws AntInterfaceException
{
if((null == appName) || ("" == appName))
if((null == appName) || ("".equals(appName)))
{
throw new IllegalArgumentException();
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,10 +15,10 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.accounts.Account;
import com.google.android.accounts.AccountManager;
import com.google.android.maps.mytracks.R;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
@@ -29,7 +29,7 @@ import android.util.Log;
* @author Sandor Dornbush
*/
public class AccountChooser {
/**
* The last selected account.
*/
@@ -44,7 +44,7 @@ public class AccountChooser {
* Handle the account being selected.
* @param account The selected account or null if none could be found
*/
public void handleAccountSelected(Account account);
public void onAccountSelected(Account account);
}
/**
@@ -52,7 +52,7 @@ public class AccountChooser {
* If no account is found the user will be alerted.
* If only one account is found that will be used.
* If multiple accounts are found the user will be allowed to choose.
*
*
* @param activity The parent activity
* @param handler The handler to be notified when an account has been selected
*/
@@ -65,16 +65,16 @@ public class AccountChooser {
return;
}
if (accounts.length == 1) {
handler.handleAccountSelected(accounts[0]);
handler.onAccountSelected(accounts[0]);
return;
}
// TODO This should be read out of a preference.
if (selectedAccount != null) {
handler.handleAccountSelected(selectedAccount);
handler.onAccountSelected(selectedAccount);
return;
}
// Let the user choose.
Log.e(Constants.TAG, "Multiple matching accounts found.");
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@@ -83,14 +83,20 @@ public class AccountChooser {
builder.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
selectedAccount = accounts[selectedAccountIndex];
handler.handleAccountSelected(selectedAccount);
dialog.dismiss();
if (selectedAccountIndex >= 0) {
selectedAccount = accounts[selectedAccountIndex];
}
handler.onAccountSelected(selectedAccount);
}
});
builder.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
handler.handleAccountSelected(null);
dialog.dismiss();
handler.onAccountSelected(null);
}
});
String[] choices = new String[accounts.length];
@@ -107,6 +113,10 @@ public class AccountChooser {
builder.show();
}
public void setChosenAccount(String accountName, String accountType) {
selectedAccount = new Account(accountName, accountType);
}
/**
* Puts up a dialog alerting the user that no suitable account was found.
*/
@@ -120,7 +130,8 @@ public class AccountChooser {
builder.setNegativeButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
handler.handleAccountSelected(null);
dialog.dismiss();
handler.onAccountSelected(null);
}
});
builder.show();
@@ -19,9 +19,9 @@ import java.util.List;
/**
* Activity for viewing the combined statistics for all the recorded tracks.
*
*
* Other features to add - menu items to change setings.
*
*
* @author Fergus Nelson
*/
public class AggregatedStatsActivity extends Activity implements
@@ -43,7 +43,7 @@ public class AggregatedStatsActivity extends Activity implements
Log.d(Constants.TAG, "StatsActivity: onSharedPreferences changed "
+ key);
if (key != null) {
if (key.equals(R.string.metric_units_key)) {
if (key.equals(getString(R.string.metric_units_key))) {
metricUnits = sharedPreferences.getBoolean(
getString(R.string.metric_units_key), true);
utils.setMetricUnits(metricUnits);
@@ -86,8 +86,8 @@ public class AggregatedStatsActivity extends Activity implements
}
/**
* 1. Reads tracks from the db
* 2. Merges the trip stats from the tracks
* 1. Reads tracks from the db
* 2. Merges the trip stats from the tracks
* 3. Updates the view
*/
private void loadAggregatedStats() {
@@ -35,6 +35,9 @@ import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
@@ -58,6 +61,7 @@ import java.util.EnumSet;
*/
public class ChartActivity extends Activity implements TrackDataListener {
private static final int CHART_SETTINGS_DIALOG = 1;
private final DoubleBuffer elevationBuffer =
new DoubleBuffer(Constants.ELEVATION_SMOOTHING_FACTOR);
private final DoubleBuffer speedBuffer =
@@ -73,7 +77,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
private double trackMaxSpeed;
// Modes of operation
private Mode mode = Mode.BY_DISTANCE;
private boolean metricUnits;
private boolean reportSpeed;
@@ -93,10 +96,16 @@ public class ChartActivity extends Activity implements TrackDataListener {
private final Runnable updateChart = new Runnable() {
@Override
public void run() {
// Get a local reference in case it's set to null concurrently with this.
TrackDataHub localDataHub = dataHub;
if (localDataHub == null || isFinishing()) {
return;
}
busyPane.setVisibility(View.GONE);
zoomControls.setIsZoomInEnabled(chartView.canZoomIn());
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
chartView.setShowPointer(dataHub.isRecordingSelected());
chartView.setShowPointer(localDataHub.isRecordingSelected());
chartView.invalidate();
}
};
@@ -105,8 +114,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
protected void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "ChartActivity.onCreate");
super.onCreate(savedInstanceState);
MyTracks.getInstance().setChartActivity(this);
dataHub = MyTracks.getInstance().getDataHub();
// The volume we want to control is the Text-To-Speech volume
int volumeStream =
@@ -117,7 +124,6 @@ public class ChartActivity extends Activity implements TrackDataListener {
setContentView(R.layout.mytracks_charts);
ViewGroup layout = (ViewGroup) findViewById(R.id.elevation_chart);
chartView = new ChartView(this);
chartView.setMode(this.mode);
LayoutParams params =
new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
layout.addView(chartView, params);
@@ -142,8 +148,10 @@ public class ChartActivity extends Activity implements TrackDataListener {
protected void onResume() {
super.onResume();
dataHub = TrackDataHub.getStartedInstance();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.POINT_UPDATES,
ListenerDataType.SAMPLED_OUT_POINT_UPDATES,
ListenerDataType.WAYPOINT_UPDATES,
@@ -153,6 +161,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
@Override
protected void onPause() {
dataHub.unregisterTrackDataListener(this);
dataHub = null;
super.onPause();
}
@@ -169,27 +178,13 @@ public class ChartActivity extends Activity implements TrackDataListener {
zoomControls.setIsZoomOutEnabled(chartView.canZoomOut());
}
public void setMode(Mode newMode) {
if (this.mode != newMode) {
this.mode = newMode;
chartView.setMode(this.mode);
private void setMode(Mode newMode) {
if (chartView.getMode() != newMode) {
chartView.setMode(newMode);
dataHub.reloadDataForListener(this);
}
}
public Mode getMode() {
return mode;
}
public void setSeriesEnabled(int index, boolean enabled) {
chartView.getChartValueSeries(index).setEnabled(enabled);
runOnUiThread(updateChart);
}
public boolean isSeriesEnabled(int index) {
return chartView.getChartValueSeries(index).isEnabled();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -204,13 +199,52 @@ public class ChartActivity extends Activity implements TrackDataListener {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case Constants.MENU_CHART_SETTINGS:
MyTracks.getInstance().getDialogManager().showDialogSafely(
DialogManager.DIALOG_CHART_SETTINGS);
showDialog(CHART_SETTINGS_DIALOG);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id == CHART_SETTINGS_DIALOG) {
final ChartSettingsDialog settingsDialog = new ChartSettingsDialog(this);
settingsDialog.setOnClickListener(new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int which) {
if (which != DialogInterface.BUTTON_POSITIVE) {
return;
}
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chartView.setChartValueSeriesEnabled(i, settingsDialog.isSeriesEnabled(i));
}
setMode(settingsDialog.getMode());
chartView.postInvalidate();
}
});
return settingsDialog;
}
return super.onCreateDialog(id);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
if (id == CHART_SETTINGS_DIALOG) {
prepareSettingsDialog((ChartSettingsDialog) dialog);
}
}
private void prepareSettingsDialog(ChartSettingsDialog settingsDialog) {
settingsDialog.setMode(chartView.getMode());
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
settingsDialog.setSeriesEnabled(i, chartView.isChartValueSeriesEnabled(i));
}
}
/**
* Given a location, creates a new data point for the chart. A data point is
* an array double[3 or 6], where:
@@ -257,6 +291,7 @@ public class ChartActivity extends Activity implements TrackDataListener {
}
// TODO: Account for segment splits?
Mode mode = chartView.getMode();
switch (mode) {
case BY_DISTANCE:
timeOrDistance = profileLength / 1000.0;
@@ -424,10 +459,11 @@ public class ChartActivity extends Activity implements TrackDataListener {
this.metricUnits = metric;
chartView.setMetricUnits(metric);
return true; // Reload data
}
@SuppressWarnings("hiding")
@Override
public boolean onReportSpeedChanged(boolean reportSpeed) {
boolean changed = reportSpeed != this.reportSpeed;
@@ -1,12 +1,12 @@
/*
* 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
@@ -36,6 +36,7 @@ import android.widget.RadioGroup;
public class ChartSettingsDialog extends Dialog {
private RadioButton distance;
private CheckBox[] series;
private OnClickListener clickListener;
public ChartSettingsDialog(Context context) {
super(context);
@@ -44,23 +45,31 @@ public class ChartSettingsDialog extends Dialog {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.chart_settings);
Button cancel = (Button) findViewById(R.id.chart_settings_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(ChartSettingsDialog.this, BUTTON_NEGATIVE);
}
dismiss();
}
});
Button ok = (Button) findViewById(R.id.chart_settings_ok);
ok.setOnClickListener(new View.OnClickListener() {
Button okButton = (Button) findViewById(R.id.chart_settings_ok);
okButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
handleOk();
if (clickListener != null) {
clickListener.onClick(ChartSettingsDialog.this, BUTTON_POSITIVE);
}
dismiss();
}
});
distance = (RadioButton) findViewById(R.id.chart_settings_by_distance);
series = new CheckBox[ChartView.NUM_SERIES];
series[ChartView.ELEVATION_SERIES] =
(CheckBox) findViewById(R.id.chart_settings_elevation);
@@ -74,29 +83,30 @@ public class ChartSettingsDialog extends Dialog {
(CheckBox) findViewById(R.id.chart_settings_heart_rate);
}
public void setup(ChartActivity chart) {
if (chart == null) {
return;
}
public void setMode(Mode mode) {
RadioGroup rd = (RadioGroup) findViewById(R.id.chart_settings_x);
rd.check(chart.getMode() == Mode.BY_DISTANCE
rd.check(mode == Mode.BY_DISTANCE
? R.id.chart_settings_by_distance
: R.id.chart_settings_by_time);
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
series[i].setChecked(chart.isSeriesEnabled(i));
}
}
private void handleOk() {
ChartActivity chart = MyTracks.getInstance().getChartActivity();
chart.setMode(distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME);
public void setSeriesEnabled(int seriesIdx, boolean enabled) {
series[seriesIdx].setChecked(enabled);
}
// TODO: check that something is visible.
for (int i = 0; i < ChartView.NUM_SERIES; i++) {
chart.setSeriesEnabled(i, series[i].isChecked());
}
public Mode getMode() {
if (distance == null) return Mode.BY_DISTANCE;
dismiss();
return distance.isChecked() ? Mode.BY_DISTANCE : Mode.BY_TIME;
}
public boolean isSeriesEnabled(int seriesIdx) {
if (series == null) return true;
return series[seriesIdx].isChecked();
}
public void setOnClickListener(OnClickListener clickListener) {
this.clickListener = clickListener;
}
}
@@ -49,6 +49,7 @@ import java.util.ArrayList;
* @author Leif Hendrik Wilden
*/
public class ChartView extends View {
private static final int MIN_ZOOM_LEVEL = 1;
/*
* Scrolling logic:
@@ -62,7 +63,6 @@ public class ChartView extends View {
* Zoom logic:
*/
private int zoomLevel = 1;
private final int minZoomLevel = 1;
private int maxZoomLevel = 10;
private static final int MAX_INTERVALS = 5;
@@ -199,7 +199,7 @@ public class ChartView extends View {
updateDimensions();
}
public void setUpChartValueSeries(Context context) {
private void setUpChartValueSeries(Context context) {
series = new ChartValueSeries[NUM_SERIES];
// Create the value series.
@@ -333,7 +333,7 @@ public class ChartView extends View {
* @return true if the chart can be zoomed out
*/
public boolean canZoomOut() {
return zoomLevel > minZoomLevel;
return zoomLevel > MIN_ZOOM_LEVEL;
}
/**
@@ -401,6 +401,7 @@ public class ChartView extends View {
/**
* Sets the display mode (by distance, by time).
* It is expected that after the mode change, data will be reloaded.
*/
public void setMode(Mode mode) {
this.mode = mode;
@@ -593,7 +594,7 @@ public class ChartView extends View {
final float x = getWaypointX(waypoint);
c.drawLine(x, h - bottomBorder, x, topBorder, gridPaint);
c.translate(x - markerWidth / 2, markerHeight);
c.translate(x - (float) markerWidth / 2.0f, (float) markerHeight);
if (waypoints.get(i).getType() == Waypoint.TYPE_STATISTICS) {
statsMarker.draw(c);
} else {
@@ -865,14 +866,24 @@ public class ChartView extends View {
}
/**
* Gets one of the chart value series.
* The index should be one of the following values:
* ELEVATION_SERIES or SPEED_SERIES
* Returns whether a given time series is enabled for drawing.
*
* @param index of the value series
* @return The chart series at the index
* @param index the time series, one of {@link #ELEVATION_SERIES},
* {@link #SPEED_SERIES}, {@link #POWER_SERIES}, etc.
* @return true if drawn, false otherwise
*/
public ChartValueSeries getChartValueSeries(int index) {
return series[index];
public boolean isChartValueSeriesEnabled(int index) {
return series[index].isEnabled();
}
/**
* Sets whether a given time series will be enabled for drawing.
*
* @param index the time series, one of {@link #ELEVATION_SERIES},
* {@link #SPEED_SERIES}, {@link #POWER_SERIES}, etc.
* @return true to be drawn, false otherwise
*/
public void setChartValueSeriesEnabled(int index, boolean enabled) {
series[index].setEnabled(enabled);
}
}
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -26,7 +26,7 @@ public abstract class Constants {
* Should be used by all log statements
*/
public static final String TAG = "MyTracks";
/**
* Name of the top-level directory inside the SD card where our files will
* be read from/written to.
@@ -39,32 +39,21 @@ public abstract class Constants {
public static final int GET_LOGIN = 0;
public static final int GET_MAP = 1;
public static final int CREATE_MAP = 2;
public static final int SHOW_TRACK = 3;
public static final int ADD_LIST = 4;
public static final int FEATURE_DETAILS = 5;
public static final int START_RECORDING = 6;
public static final int STOP_RECORDING = 7;
public static final int AUTHENTICATE_TO_MY_MAPS = 8;
public static final int AUTHENTICATE_TO_FUSION_TABLES = 9;
public static final int AUTHENTICATE_TO_DOCLIST = 10;
public static final int AUTHENTICATE_TO_TRIX = 11;
public static final int DELETE_TRACK = 13;
public static final int SEND_TO_GOOGLE_DIALOG = 14;
public static final int SHARE_LINK = 15;
public static final int SHARE_GPX_FILE = 16;
public static final int SHARE_KML_FILE = 17;
public static final int SHARE_CSV_FILE = 18;
public static final int SHARE_TCX_FILE = 19;
public static final int EDIT_DETAILS = 20;
public static final int SAVE_GPX_FILE = 21;
public static final int SAVE_KML_FILE = 22;
public static final int SAVE_CSV_FILE = 23;
public static final int SAVE_TCX_FILE = 24;
public static final int CLEAR_MAP = 25;
public static final int SHOW_WAYPOINT = 26;
public static final int EDIT_WAYPOINT = 27;
public static final int WELCOME = 28;
public static final int SHOW_TRACK = 2;
public static final int AUTHENTICATE_TO_MY_MAPS = 3;
public static final int AUTHENTICATE_TO_FUSION_TABLES = 4;
public static final int AUTHENTICATE_TO_DOCLIST = 5;
public static final int AUTHENTICATE_TO_TRIX = 6;
public static final int SHARE_GPX_FILE = 7;
public static final int SHARE_KML_FILE = 8;
public static final int SHARE_CSV_FILE = 9;
public static final int SHARE_TCX_FILE = 10;
public static final int SAVE_GPX_FILE = 11;
public static final int SAVE_KML_FILE = 12;
public static final int SAVE_CSV_FILE = 13;
public static final int SAVE_TCX_FILE = 14;
public static final int SHOW_WAYPOINT = 15;
public static final int WELCOME = 16;
/*
* Menu ids:
@@ -179,7 +168,7 @@ public abstract class Constants {
* The type of account that we can use for gdata uploads.
*/
public static final String ACCOUNT_TYPE = "com.google";
/**
* The name of extra intent property to indicate whether we want to resume
* a previously recorded track.
@@ -189,14 +178,6 @@ public abstract class Constants {
public static int getActionFromMenuId(int menuId) {
switch (menuId) {
case Constants.MENU_SEND_TO_GOOGLE:
return Constants.SEND_TO_GOOGLE_DIALOG;
case Constants.MENU_EDIT:
return Constants.EDIT_DETAILS;
case Constants.MENU_DELETE:
return Constants.DELETE_TRACK;
case Constants.MENU_SHARE_LINK:
return Constants.SHARE_LINK;
case Constants.MENU_SHARE_KML_FILE:
return Constants.SHARE_KML_FILE;
case Constants.MENU_SHARE_GPX_FILE:
@@ -213,8 +194,6 @@ public abstract class Constants {
return Constants.SAVE_CSV_FILE;
case Constants.MENU_SAVE_TCX_FILE:
return Constants.SAVE_TCX_FILE;
case Constants.MENU_CLEAR_MAP:
return Constants.CLEAR_MAP;
default:
return -1;
}
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -56,8 +56,8 @@ public class DeleteAllTracks extends Handler {
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(context.getString(R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
Log.w(Constants.TAG, "deleting all!");
MyTracksProviderUtils.Factory.get(context).deleteAllTracks();
SharedPreferences prefs =
@@ -74,8 +74,8 @@ public class DeleteAllTracks extends Handler {
});
builder.setNegativeButton(context.getString(R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
dialog = builder.create();
@@ -0,0 +1,114 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* Activity used to delete a track.
*
* @author Rodrigo Damazio
*/
public class DeleteTrack extends Activity
implements DialogInterface.OnClickListener, OnCancelListener {
private static final int CONFIRM_DIALOG = 1;
private MyTracksProviderUtils providerUtils;
private long deleteTrackId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if (!Intent.ACTION_DELETE.equals(action) ||
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
Log.e(TAG, "Got bad delete intent: " + intent);
finish();
}
deleteTrackId = ContentUris.parseId(data);
showDialog(CONFIRM_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
if (id != CONFIRM_DIALOG) {
Log.e(TAG, "Unknown dialog " + id);
return null;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.track_will_be_permanently_deleted));
builder.setTitle(getString(R.string.are_you_sure_question));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setPositiveButton(getString(R.string.yes), this);
builder.setNegativeButton(getString(R.string.no), this);
builder.setOnCancelListener(this);
return builder.create();
}
@Override
public void onClick(DialogInterface dialogInterface, int which) {
dialogInterface.dismiss();
if (which == DialogInterface.BUTTON_POSITIVE) {
deleteTrack();
}
finish();
}
@Override
public void onCancel(DialogInterface dialog) {
onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
private void deleteTrack() {
providerUtils.deleteTrack(deleteTrackId);
// If the track we just deleted was selected, unselect it.
String selectedKey = getString(R.string.selected_track_key);
SharedPreferences preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
if (preferences.getLong(selectedKey, -1) == deleteTrackId) {
Editor editor = preferences.edit().putLong(selectedKey, -1);
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(editor);
}
}
}
@@ -17,14 +17,8 @@ package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.io.sendtogoogle.SendDialog;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager.BadTokenException;
@@ -35,145 +29,16 @@ import android.view.WindowManager.BadTokenException;
*/
public class DialogManager {
public static final int DIALOG_CHART_SETTINGS = 1;
public static final int DIALOG_IMPORT_PROGRESS = 2;
public static final int DIALOG_PROGRESS = 3;
public static final int DIALOG_SEND_TO_GOOGLE = 4;
private ProgressDialog progressDialog;
private ProgressDialog importProgressDialog;
private SendDialog sendToGoogleDialog;
private ChartSettingsDialog chartSettingsDialog;
private MyTracks activity;
public DialogManager(MyTracks activity) {
this.activity = activity;
}
protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
case DIALOG_CHART_SETTINGS:
chartSettingsDialog = new ChartSettingsDialog(activity);
return chartSettingsDialog;
case DIALOG_IMPORT_PROGRESS:
importProgressDialog = new ProgressDialog(activity);
importProgressDialog.setIcon(android.R.drawable.ic_dialog_info);
importProgressDialog.setTitle(
activity.getString(R.string.progress_title));
importProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
importProgressDialog.setMessage(
activity.getString(R.string.import_progress_message));
return importProgressDialog;
case DIALOG_PROGRESS:
progressDialog = new ProgressDialog(activity);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(activity.getString(R.string.progress_title));
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("");
progressDialog.setMax(100);
progressDialog.setProgress(10);
return progressDialog;
case DIALOG_SEND_TO_GOOGLE:
sendToGoogleDialog = new SendDialog(activity);
return sendToGoogleDialog;
}
return null;
}
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_SEND_TO_GOOGLE:
activity.resetSendToGoogleStatus();
break;
case DIALOG_CHART_SETTINGS:
Log.d(TAG, "MyTracks.onPrepare chart dialog");
chartSettingsDialog.setup(activity.getChartActivity());
break;
}
}
public void setProgressMessage(final String message) {
activity.runOnUiThread(new Runnable() {
public void run() {
synchronized (this) {
if (progressDialog != null) {
progressDialog.setMessage(message);
}
}
}
});
}
public void setProgressValue(final int percent) {
activity.runOnUiThread(new Runnable() {
public void run() {
synchronized (this) {
if (progressDialog != null) {
progressDialog.setProgress(percent);
}
}
}
});
}
/**
* @return the sendToGoogleDialog
*/
public SendDialog getSendToGoogleDialog() {
return sendToGoogleDialog;
}
/**
* Shows a dialog with the given message.
* Does it on the UI thread.
*
* @param success if true, displays an info icon/title, otherwise an error
* icon/title
* @param message resource string id
*/
public void showMessageDialog(final int message, final boolean success) {
activity.runOnUiThread(new Runnable() {
public void run() {
AlertDialog dialog = null;
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(activity.getString(message));
builder.setNegativeButton(activity.getString(R.string.ok), null);
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
android.R.drawable.ic_dialog_alert);
builder.setTitle(success ? R.string.success : R.string.error);
dialog = builder.create();
dialog.show();
}
});
}
/**
* Just like showDialog, but will catch a {@link BadTokenException} that
* sometimes (very rarely) gets thrown. This might happen if the user hits
* the "back" button immediately after sending tracks to google.
*
* @param id the dialog id
*/
public void showDialogSafely(final int id) {
activity.runOnUiThread(new Runnable() {
public void run() {
try {
activity.showDialog(id);
} catch (BadTokenException e) {
Log.w(TAG, "Could not display dialog with id " + id, e);
} catch (IllegalStateException e) {
Log.w(TAG, "Could not display dialog with id " + id, e);
}
}
});
}
/**
* The equivalent of {@link #showDialogSafely(int)}, but for a specific
* dialog instance.
*/
public static void showDialogSafely(Activity activity, final Dialog dialog) {
if (activity.isFinishing()) {
Log.w(TAG, "Activity finishing - not showing dialog");
return;
}
activity.runOnUiThread(new Runnable() {
public void run() {
try {
@@ -187,26 +52,16 @@ public class DialogManager {
});
}
/**
* Dismisses the progress dialog if it is showing. Executed on the UI thread.
*/
public void dismissDialogSafely(final int id) {
activity.runOnUiThread(new Runnable() {
public void run() {
try {
activity.dismissDialog(id);
} catch (IllegalArgumentException e) {
// This will be thrown if this dialog was not shown before.
}
}
});
}
/**
* The equivalent of {@link #dismissDialogSafely(int)}, but for a specific
* dialog instance.
*/
public static void dismissDialogSafely(Activity activity, final Dialog dialog) {
if (activity.isFinishing()) {
Log.w(TAG, "Activity finishing - not dismissing dialog");
return;
}
activity.runOnUiThread(new Runnable() {
public void run() {
try {
@@ -217,4 +72,6 @@ public class DialogManager {
}
});
}
private DialogManager() {}
}
@@ -29,8 +29,6 @@ import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
@@ -91,12 +89,7 @@ public class ExportAllTracks {
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
HandlerThread handlerThread;
Handler handler;
handlerThread = new HandlerThread("SendToMyMaps");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(runner);
new Thread(runner, "SendToMyMaps").start();
}
};
@@ -117,7 +110,7 @@ public class ExportAllTracks {
long recordingTrackId = -1;
if (prefs != null) {
recordingTrackId =
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
prefs.getLong(activity.getString(R.string.recording_track_key), -1);
}
if (recordingTrackId != -1) {
wakeLock = SystemUtils.acquireWakeLock(activity, wakeLock);
@@ -189,6 +182,11 @@ public class ExportAllTracks {
Log.i(Constants.TAG, "ExportAllTracks: exporting: " + id);
TrackWriter writer =
TrackWriterFactory.newWriter(activity, providerUtils, id, format);
if (writer == null) {
showToast(R.string.error_track_does_not_exist, Toast.LENGTH_LONG);
return;
}
writer.writeTrack();
if (!writer.wasSuccess()) {
@@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -25,8 +25,6 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.widget.Toast;
@@ -45,7 +43,7 @@ import org.xml.sax.SAXException;
/**
* A class that will import all GPX tracks in /sdcard/MyTracks/gpx/
*
*
* @author David Piggott
*/
public class ImportAllTracks {
@@ -63,12 +61,8 @@ public class ImportAllTracks {
Log.i(Constants.TAG, "ImportAllTracks: Starting");
fileUtils = new FileUtils();
gpxPath = fileUtils.buildExternalDirectoryPath("gpx");
HandlerThread handlerThread;
Handler handler;
handlerThread = new HandlerThread("ImportAllTracks");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(runner);
new Thread(runner).start();
}
private final Runnable runner = new Runnable() {
@@ -104,9 +98,9 @@ public class ImportAllTracks {
Log.i(Constants.TAG, "ImportAllTracks: Done");
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
if (gpxFileCount == 0) {
builder.setMessage(activity.getString(R.string.import_empty, gpxPath + "/"));
builder.setMessage(activity.getString(R.string.import_multi_empty, gpxPath + "/"));
} else {
builder.setMessage(activity.getString(R.string.import_done, importSuccessCount, gpxFileCount,
builder.setMessage(activity.getString(R.string.import_multi_done, importSuccessCount, gpxFileCount,
gpxPath + "/"));
}
builder.setPositiveButton(R.string.ok, null);
@@ -22,7 +22,10 @@ import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.stats.TripStatistics;
import com.google.android.apps.mytracks.util.ApiFeatures;
@@ -33,8 +36,10 @@ import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.mytracks.R;
import android.content.ContentUris;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@@ -122,7 +127,7 @@ public class MapActivity extends com.google.android.maps.MapActivity
// ------------------------
@Override
public void onCreate(Bundle bundle) {
protected void onCreate(Bundle bundle) {
Log.d(TAG, "MapActivity.onCreate");
super.onCreate(bundle);
@@ -131,8 +136,6 @@ public class MapActivity extends com.google.android.maps.MapActivity
new StatusAnnouncerFactory(ApiFeatures.getInstance()).getVolumeStream();
setVolumeControlStream(volumeStream);
dataHub = MyTracks.getInstance().getDataHub();
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -179,9 +182,10 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
protected void onResume() {
Log.d(TAG, "MapActivity.onStart");
Log.d(TAG, "MapActivity.onResume");
super.onResume();
dataHub = TrackDataHub.getStartedInstance();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.POINT_UPDATES,
@@ -202,9 +206,10 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
protected void onPause() {
Log.d(TAG, "MapActivity.onStop");
Log.d(TAG, "MapActivity.onPause");
dataHub.unregisterTrackDataListener(this);
dataHub = null;
super.onPause();
}
@@ -392,13 +397,44 @@ public class MapActivity extends com.google.android.maps.MapActivity
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (!super.onMenuItemSelected(featureId, item)) {
MyTracks.getInstance().onActivityResult(
Constants.getActionFromMenuId(item.getItemId()), RESULT_OK,
new Intent());
return true;
switch (item.getItemId()) {
case Constants.MENU_SEND_TO_GOOGLE:
SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), false);
return true;
case Constants.MENU_SHARE_LINK:
SendActivity.sendToGoogle(this, dataHub.getSelectedTrackId(), true);
return true;
case Constants.MENU_SAVE_GPX_FILE:
case Constants.MENU_SAVE_KML_FILE:
case Constants.MENU_SAVE_CSV_FILE:
case Constants.MENU_SAVE_TCX_FILE:
case Constants.MENU_SHARE_GPX_FILE:
case Constants.MENU_SHARE_KML_FILE:
case Constants.MENU_SHARE_CSV_FILE:
case Constants.MENU_SHARE_TCX_FILE:
SaveActivity.handleExportTrackAction(this, dataHub.getSelectedTrackId(),
Constants.getActionFromMenuId(item.getItemId()));
return true;
case Constants.MENU_EDIT: {
Intent intent = new Intent(this, TrackDetails.class);
// TODO: Pass in a content URI
intent.putExtra("trackid", dataHub.getSelectedTrackId());
startActivity(intent);
return true;
}
case Constants.MENU_DELETE: {
Uri uri = ContentUris.withAppendedId(
TracksColumns.CONTENT_URI, dataHub.getSelectedTrackId());
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
startActivity(intent);
return true;
}
case Constants.MENU_CLEAR_MAP:
dataHub.unloadCurrentTrack();
return true;
default:
return super.onMenuItemSelected(featureId, item);
}
return false;
}
@Override
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.util.LocationUtils;
import com.google.android.maps.GeoPoint;
@@ -33,6 +35,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -71,7 +74,7 @@ public class MapOverlay extends Overlay {
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.
@@ -93,13 +96,13 @@ public class MapOverlay extends Overlay {
*/
public CachedLocation(Location location) {
this.valid = LocationUtils.isValidLocation(location);
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
this.geoPoint = valid ? LocationUtils.getGeoPoint(location) : null;
}
};
public MapOverlay(Context context) {
this.context = context;
this.waypoints = new ArrayList<Waypoint>();
this.points = new ArrayList<CachedLocation>(1024);
this.pendingPoints = new ArrayBlockingQueue<CachedLocation>(
@@ -164,7 +167,7 @@ public class MapOverlay extends Overlay {
/**
* Add a location to the map overlay.
*
*
* NOTE: This method doesn't take ownership of the given location, so it is
* safe to reuse the same location while calling this method.
*
@@ -172,14 +175,18 @@ public class MapOverlay extends Overlay {
*/
public void addLocation(Location l) {
// Queue up in the pending queue until it's merged with {@code #points}.
pendingPoints.offer(new CachedLocation(l));
if (!pendingPoints.offer(new CachedLocation(l))) {
Log.e(TAG, "Unable to add pending points");
}
}
/**
* Adds a segment split to the map overlay.
*/
public void addSegmentSplit() {
pendingPoints.offer(new CachedLocation());
if (!pendingPoints.offer(new CachedLocation())) {
Log.e(TAG, "Unable to add pending points");
}
}
public void addWaypoint(Waypoint wpt) {
@@ -203,7 +210,7 @@ public class MapOverlay extends Overlay {
return waypoints.size();
}
}
public void clearPoints() {
synchronized (points) {
points.clear();
@@ -235,10 +242,15 @@ public class MapOverlay extends Overlay {
// It's safe to keep projection within a single draw operation.
final Projection projection = getMapProjection(mapView);
if (projection == null) {
Log.w(TAG, "No projection, unable to draw");
return;
}
// Get the current viewing window.
if (trackDrawingEnabled) {
Rect viewRect = getMapViewRect(mapView);
// Draw the selected track:
drawTrack(canvas, projection, viewRect);
@@ -249,12 +261,12 @@ public class MapOverlay extends Overlay {
// Draw the current location
drawMyLocation(canvas, projection);
}
// Visible for testing.
Projection getMapProjection(MapView mapView) {
return mapView.getProjection();
}
// Visible for testing.
Rect getMapViewRect(MapView mapView) {
int w = mapView.getLongitudeSpan();
@@ -263,12 +275,12 @@ public class MapOverlay extends Overlay {
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;
@@ -304,9 +316,9 @@ public class MapOverlay extends Overlay {
synchronized (points) {
// Merge the pending points with the list of cached locations.
final GeoPoint referencePoint = projection.fromPixels(0, 0);
int newPoints = pendingPoints.drainTo(points);
int newPoints = pendingPoints.drainTo(points);
boolean newProjection = !viewRect.equals(lastViewRect) ||
!referencePoint.equals(lastReferencePoint);
!referencePoint.equals(lastReferencePoint);
if (newPoints == 0 && lastPath != null && !newProjection) {
// No need to recreate path (same points and viewing area).
path = lastPath;
@@ -318,12 +330,12 @@ public class MapOverlay extends Overlay {
} else if (lastPath != null && !newProjection) {
// Incremental update of the path, without repositioning the view.
path = lastPath;
updatePath(projection, viewRect, path, numPoints - newPoints);
updatePath(projection, viewRect, path, numPoints - newPoints);
} else {
// The view has changed so we have to start from scratch.
path = newPath();
path.incReserve(numPoints);
updatePath(projection, viewRect, path, 0);
updatePath(projection, viewRect, path, 0);
}
lastPath = path;
}
@@ -344,7 +356,7 @@ public class MapOverlay extends Overlay {
}
}
}
// Draw the "Start" marker.
for (int i = 0; i < points.size(); ++i) {
if (points.get(i).valid) {
@@ -354,7 +366,7 @@ public class MapOverlay extends Overlay {
}
}
}
private void updatePath(Projection projection, Rect viewRect, Path path,
int startLocationIdx) {
// Whether to start a new segment on new valid and visible point.
@@ -370,7 +382,7 @@ public class MapOverlay extends Overlay {
newSegment = true;
continue;
}
final GeoPoint geoPoint = loc.geoPoint;
// Check if this breaks the existing segment.
boolean visible = alwaysVisible || viewRect.contains(
@@ -380,7 +392,7 @@ public class MapOverlay extends Overlay {
newSegment = true;
}
lastVisible = visible;
// Either move to beginning of a new segment or continue the old one.
projection.toPixels(geoPoint, pt);
if (newSegment) {
@@ -391,7 +403,7 @@ public class MapOverlay extends Overlay {
}
}
}
// Visible for testing.
Point drawElement(Canvas canvas, Projection projection, GeoPoint geoPoint,
Drawable element, int offsetX, int offsetY) {
@@ -1,12 +1,12 @@
/*
* 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
@@ -24,7 +24,7 @@ import android.view.MenuItem;
/**
* Manage the application menus.
*
*
* @author Sandor Dornbush
*/
class MenuManager {
@@ -34,7 +34,7 @@ class MenuManager {
public MenuManager(MyTracks activity) {
this.activity = activity;
}
public boolean onCreateOptionsMenu(Menu menu) {
activity.getMenuInflater().inflate(R.menu.main, menu);
return true;
@@ -52,7 +52,7 @@ class MenuManager {
.setEnabled(isRecording)
.setVisible(isRecording);
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_start_recording: {
@@ -86,10 +86,6 @@ class MenuManager {
case R.id.menu_help: {
return startActivity(WelcomeActivity.class);
}
case Constants.MENU_CLEAR_MAP: {
activity.clearSelectedTrack();
return true;
}
}
return false;
}
@@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,8 +15,10 @@
*/
package com.google.android.apps.mytracks;
import com.google.android.accounts.Account;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
@@ -25,11 +27,8 @@ import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.util.Log;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
@@ -42,29 +41,15 @@ import android.widget.TextView;
* @author Rodrigo Damazio
*/
public class MyMapsList extends Activity implements MapsFacade.MapsListCallback {
private static final int MENU_OPEN = 0;
private static final int MENU_SHARE = 2;
private static final int GET_LOGIN = 1;
public static final String EXTRA_ACCOUNT_NAME = "accountName";
public static final String EXTRA_ACCOUNT_TYPE = "accountType";
private MapsFacade mapsClient;
private AuthManager auth;
private MyMapsListAdapter listAdapter;
private int contextPosition;
private final OnCreateContextMenuListener contextMenuListener =
new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
menu.add(0, MENU_OPEN, 0, R.string.open_map);
menu.add(0, MENU_SHARE, 0, R.string.share_map);
}
};
private final OnItemClickListener clickListener =
new OnItemClickListener() {
@Override
@@ -91,7 +76,6 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
ListView list = (ListView) findViewById(R.id.maplist);
list.setOnItemClickListener(clickListener);
list.setOnCreateContextMenuListener(contextMenuListener);
list.setAdapter(listAdapter);
startLogin();
@@ -101,28 +85,33 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
// Starts in the UI thread.
// TODO fix this for non-froyo devices.
if (AuthManagerFactory.useModernAuthManager()) {
MyTracks.getInstance().getAccountChooser().chooseAccount(
MyMapsList.this,
new AccountChooser.AccountHandler() {
@Override
public void handleAccountSelected(Account account) {
// Account selection happens in the UI thread.
if (account != null) {
// The user did not quit and there was a valid google
// account.
doLogin(account);
}
}
});
Intent intent = getIntent();
String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
if (accountName == null || accountType == null) {
Log.e(TAG, "Didn't receive account name or type");
setResult(RESULT_CANCELED);
finish();
return;
}
doLogin(auth.getAccountObject(accountName, accountType));
} else {
doLogin(null);
}
}
private void doLogin(final Account account) {
private void doLogin(final Object account) {
// Starts in the UI thread.
auth.doLogin(new Runnable() {
public void run() {
auth.doLogin(new AuthCallback() {
@Override
public void onAuthResult(boolean success) {
if (!success) {
setResult(RESULT_CANCELED);
finish();
return;
}
// Runs in UI thread.
mapsClient = new MapsFacade(MyMapsList.this, auth);
@@ -183,27 +172,4 @@ public class MyMapsList extends Activity implements MapsFacade.MapsListCallback
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch (item.getItemId()) {
case MENU_OPEN:
clickListener.onItemClick(null, null, contextPosition, 0);
return true;
case MENU_SHARE:
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getText(R.string.share_map_subject));
String[] listItem = (String[]) listAdapter.getMapListingArray(contextPosition);
shareIntent.putExtra(Intent.EXTRA_TEXT, String.format(
getText(R.string.share_map_body_format).toString(),
listItem[1],
MapsFacade.buildMapUrl(listItem[0])));
startActivity(Intent.createChooser(shareIntent,
getText(R.string.share_map).toString()));
return true;
}
return false;
}
}
File diff suppressed because it is too large Load Diff
@@ -16,8 +16,10 @@
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.Sensor;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.services.sensors.SensorUtils;
import com.google.android.maps.mytracks.R;
import com.google.protobuf.InvalidProtocolBufferException;
@@ -57,6 +59,11 @@ public class SensorStateActivity extends Activity {
}
};
/**
* Connection to the recording service.
*/
private TrackRecordingServiceConnection serviceConnection;
/**
* A task which will update the U/I.
*/
@@ -79,35 +86,45 @@ public class SensorStateActivity extends Activity {
setContentView(R.layout.sensor_state);
// TODO: Allow reading from sensors even if service is not running.
serviceConnection = new TrackRecordingServiceConnection(this, stateUpdater);
serviceConnection.bindIfRunning();
updateState();
}
@Override
protected void onResume() {
super.onResume();
serviceConnection.bindIfRunning();
timer = new Timer();
timer.schedule(new RefreshTask(), REFRESH_PERIOD_MS, REFRESH_PERIOD_MS);
}
@Override
protected void onPause() {
super.onPause();
timer.cancel();
timer.purge();
timer = null;
super.onPause();
}
@Override
protected void onDestroy() {
serviceConnection.unbind();
super.onDestroy();
}
protected void updateState() {
MyTracks mt = MyTracks.getInstance();
ITrackRecordingService service =
mt == null ? null : mt.getTrackRecordingService();
ITrackRecordingService service = serviceConnection.getServiceIfBound();
if (service == null) {
Log.d(Constants.TAG, "Could not get track recording service.");
updateSensorState(Sensor.SensorState.NONE);
updateSensorData(null);
return;
}
Sensor.SensorDataSet sds = null;
try {
byte[] buff = service.getSensorData();
@@ -153,8 +170,8 @@ public class SensorStateActivity extends Activity {
if (sds.hasPower() && sds.getPower().hasValue()
&& sds.getPower().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.power_state_register,
Integer.toString(sds.getPower().getValue()));
utils.setText(R.id.power_state_register,
Integer.toString(sds.getPower().getValue()));
} else {
utils.setText(R.id.power_state_register,
SensorUtils.getStateAsString(
@@ -180,14 +197,14 @@ public class SensorStateActivity extends Activity {
if (sds.hasHeartRate() && sds.getHeartRate().hasValue()
&& sds.getHeartRate().getState() == Sensor.SensorState.SENDING) {
utils.setText(R.id.heart_rate_register,
Integer.toString(sds.getHeartRate().getValue()));
Integer.toString(sds.getHeartRate().getValue()));
} else {
utils.setText(R.id.heart_rate_register,
SensorUtils.getStateAsString(
sds.hasHeartRate()
? sds.getHeartRate().getState()
: Sensor.SensorState.NONE,
this));
utils.setText(R.id.heart_rate_register,
SensorUtils.getStateAsString(
sds.hasHeartRate()
? sds.getHeartRate().getState()
: Sensor.SensorState.NONE,
this));
}
}
}
@@ -232,7 +232,8 @@ public class SettingsActivity extends PreferenceActivity {
private void updateSensorSettings(String sensorType) {
boolean usesBluetooth =
getString(R.string.zephyr_sensor_type).equals(sensorType);
getString(R.string.zephyr_sensor_type).equals(sensorType)
|| getString(R.string.polar_sensor_type).equals(sensorType);
findPreference(
getString(R.string.bluetooth_sensor_key)).setEnabled(usesBluetooth);
findPreference(
@@ -339,7 +340,7 @@ public class SettingsActivity extends PreferenceActivity {
.setPositiveButton(android.R.string.ok,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int button) {
public void onClick(DialogInterface dialogInterface, int button) {
onResetPreferencesConfirmed();
}
})
@@ -22,11 +22,13 @@ import com.google.android.apps.mytracks.content.TrackDataHub;
import com.google.android.apps.mytracks.content.TrackDataHub.ListenerDataType;
import com.google.android.apps.mytracks.content.TrackDataListener;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.tasks.StatusAnnouncerFactory;
import com.google.android.apps.mytracks.util.ApiFeatures;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.util.DisplayMetrics;
@@ -46,6 +48,18 @@ import java.util.EnumSet;
* @author Rodrigo Damazio
*/
public class StatsActivity extends Activity implements TrackDataListener {
/**
* A runnable for posting to the UI thread. Will update the total time field.
*/
private final Runnable updateResults = new Runnable() {
public void run() {
if (dataHub != null && dataHub.isRecordingSelected()) {
utils.setTime(R.id.total_time_register,
System.currentTimeMillis() - startTime);
}
}
};
private StatsUtilities utils;
private UIUpdateThread thread;
@@ -61,18 +75,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
private boolean showCurrentSegment = false;
private TrackDataHub dataHub;
/**
* A runnable for posting to the UI thread. Will update the total time field.
*/
private final Runnable updateResults = new Runnable() {
public void run() {
if (dataHub.isRecordingSelected()) {
utils.setTime(R.id.total_time_register,
System.currentTimeMillis() - startTime);
}
}
};
private SharedPreferences preferences;
/**
* A thread that updates the total time field every second.
@@ -87,7 +90,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
public void run() {
Log.i(TAG, "Started UI update thread");
while (MyTracks.getInstance().isRecording()) {
while (ServiceUtils.isRecording(StatsActivity.this, null, preferences)) {
runOnUiThread(updateResults);
try {
Thread.sleep(1000L);
@@ -102,10 +105,10 @@ public class StatsActivity extends Activity implements TrackDataListener {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataHub = MyTracks.getInstance().getDataHub();
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
utils = new StatsUtilities(this);
// The volume we want to control is the Text-To-Speech volume
@@ -132,18 +135,20 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
protected void onResume() {
super.onResume();
dataHub = TrackDataHub.getStartedInstance();
dataHub.registerTrackDataListener(this, EnumSet.of(
ListenerDataType.SELECTED_TRACK_CHANGED,
ListenerDataType.TRACK_UPDATES,
ListenerDataType.LOCATION_UPDATES,
ListenerDataType.DISPLAY_PREFERENCES));
super.onResume();
}
@Override
protected void onPause() {
dataHub.unregisterTrackDataListener(this);
dataHub = null;
if (thread != null) {
thread.interrupt();
@@ -171,7 +176,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
utils.setReportSpeed(displaySpeed);
updateLabels();
return true; // Reload data
}
@@ -260,7 +265,8 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
public void onCurrentLocationChanged(final Location loc) {
if (dataHub.isRecordingSelected()) {
TrackDataHub localDataHub = dataHub;
if (localDataHub != null && localDataHub.isRecordingSelected()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -296,6 +302,8 @@ public class StatsActivity extends Activity implements TrackDataListener {
@Override
public void onTrackUpdated(final Track track) {
TrackDataHub localDataHub = dataHub;
final boolean recordingSelected = localDataHub != null && localDataHub.isRecordingSelected();
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -305,7 +313,7 @@ public class StatsActivity extends Activity implements TrackDataListener {
}
startTime = track.getStatistics().getStartTime();
if (!dataHub.isRecordingSelected()) {
if (!recordingSelected) {
utils.setTime(R.id.total_time_register,
track.getStatistics().getTotalTime());
showUnknownLocation();
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,17 +15,26 @@
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.SaveActivity;
import com.google.android.apps.mytracks.io.sendtogoogle.SendActivity;
import com.google.android.apps.mytracks.services.ServiceUtils;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.apps.mytracks.util.UnitConversions;
import com.google.android.maps.mytracks.R;
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
@@ -76,8 +85,7 @@ public class TrackList extends ListActivity
R.string.tracklist_show_track);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.tracklist_edit_track);
if (!MyTracks.getInstance().isRecording()
|| trackId != recordingTrackId) {
if (!isRecording() || trackId != recordingTrackId) {
menu.add(0, Constants.MENU_SEND_TO_GOOGLE, 0,
R.string.tracklist_send_to_google);
SubMenu share = menu.addSubMenu(0, Constants.MENU_SHARE, 0,
@@ -109,6 +117,16 @@ public class TrackList extends ListActivity
}
};
private final Runnable serviceBindingChanged = new Runnable() {
@Override
public void run() {
updateButtonsEnabled();
}
};
private TrackRecordingServiceConnection serviceConnection;
private SharedPreferences preferences;
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
@@ -138,32 +156,48 @@ public class TrackList extends ListActivity
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (!super.onMenuItemSelected(featureId, item)) {
switch (item.getItemId()) {
case Constants.MENU_SHOW: {
onListItemClick(null, null, 0, trackId);
return true;
}
case Constants.MENU_EDIT: {
Intent intent = new Intent(this, TrackDetails.class);
intent.putExtra("trackid", trackId);
startActivity(intent);
return true;
}
case Constants.MENU_SHARE:
case Constants.MENU_WRITE_TO_SD_CARD:
return false;
default: {
Intent result = new Intent();
result.putExtra("trackid", trackId);
setResult(
Constants.getActionFromMenuId(item.getItemId()), result);
finish();
return true;
}
switch (item.getItemId()) {
case Constants.MENU_SHOW: {
onListItemClick(null, null, 0, trackId);
return true;
}
case Constants.MENU_EDIT: {
Intent intent = new Intent(this, TrackDetails.class);
intent.putExtra("trackid", trackId);
startActivity(intent);
return true;
}
case Constants.MENU_SHARE:
case Constants.MENU_WRITE_TO_SD_CARD:
return false;
case Constants.MENU_SEND_TO_GOOGLE:
SendActivity.sendToGoogle(this, trackId, false);
return true;
case Constants.MENU_SHARE_LINK:
SendActivity.sendToGoogle(this, trackId, true);
return true;
case Constants.MENU_SAVE_GPX_FILE:
case Constants.MENU_SAVE_KML_FILE:
case Constants.MENU_SAVE_CSV_FILE:
case Constants.MENU_SAVE_TCX_FILE:
case Constants.MENU_SHARE_GPX_FILE:
case Constants.MENU_SHARE_KML_FILE:
case Constants.MENU_SHARE_CSV_FILE:
case Constants.MENU_SHARE_TCX_FILE:
SaveActivity.handleExportTrackAction(this, trackId,
Constants.getActionFromMenuId(item.getItemId()));
return true;
case Constants.MENU_DELETE: {
Intent intent = new Intent(Intent.ACTION_DELETE);
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
startActivity(intent);
return true;
}
default:
Log.w(TAG, "Unknown menu item: " + item.getItemId() + "(" + item.getTitle() + ")");
return super.onMenuItemSelected(featureId, item);
}
return false;
}
@Override
@@ -197,17 +231,19 @@ public class TrackList extends ListActivity
listView = getListView();
listView.setOnCreateContextMenuListener(contextMenuListener);
preferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
serviceConnection = new TrackRecordingServiceConnection(this, serviceBindingChanged);
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
boolean notRecording = !MyTracks.getInstance().isRecording();
deleteAll.setOnClickListener(this);
deleteAll.setEnabled(notRecording);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
exportAll.setOnClickListener(this);
exportAll.setEnabled(notRecording);
updateButtonsEnabled();
findViewById(R.id.tracklist_btn_import_all).setOnClickListener(this);
SharedPreferences preferences =
getSharedPreferences(Constants.SETTINGS_NAME, 0);
preferences.registerOnSharedPreferenceChangeListener(this);
metricUnits =
preferences.getBoolean(getString(R.string.metric_units_key), true);
@@ -220,6 +256,29 @@ public class TrackList extends ListActivity
setListAdapter();
}
@Override
protected void onStart() {
super.onStart();
serviceConnection.bindIfRunning();
}
@Override
protected void onDestroy() {
serviceConnection.unbind();
super.onDestroy();
}
private void updateButtonsEnabled() {
View deleteAll = findViewById(R.id.tracklist_btn_delete_all);
View exportAll = findViewById(R.id.tracklist_btn_export_all);
boolean notRecording = !isRecording();
deleteAll.setEnabled(notRecording);
exportAll.setEnabled(notRecording);
}
private void setListAdapter() {
// Get a cursor with all tracks
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
@@ -283,4 +342,8 @@ public class TrackList extends ListActivity
});
setListAdapter(adapter);
}
private boolean isRecording() {
return ServiceUtils.isRecording(TrackList.this, serviceConnection.getServiceIfBound(), preferences);
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,10 +15,14 @@
*/
package com.google.android.apps.mytracks;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Waypoint;
import com.google.android.apps.mytracks.content.WaypointCreationRequest;
import com.google.android.apps.mytracks.content.WaypointsColumns;
import com.google.android.apps.mytracks.services.ITrackRecordingService;
import com.google.android.apps.mytracks.services.TrackRecordingServiceConnection;
import com.google.android.apps.mytracks.util.StringUtils;
import com.google.android.maps.mytracks.R;
@@ -43,6 +47,7 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
/**
* Activity which shows the list of waypoints in a track.
@@ -54,12 +59,13 @@ public class WaypointsList extends ListActivity
private int contextPosition = -1;
private long trackId = -1;
private long waypointId = -1;
private long selectedWaypointId = -1;
private ListView listView = null;
private Button insertWaypointButton = null;
private Button insertStatisticsButton = null;
private long recordingTrackId = -1;
private MyTracksProviderUtils providerUtils;
private TrackRecordingServiceConnection serviceConnection;
private Cursor waypointsCursor = null;
@@ -71,27 +77,26 @@ public class WaypointsList extends ListActivity
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo) menuInfo;
contextPosition = info.position;
waypointId = WaypointsList.this.listView.getAdapter()
selectedWaypointId = WaypointsList.this.listView.getAdapter()
.getItemId(contextPosition);
int type = providerUtils.getWaypoint(info.id).getType();
menu.add(0, Constants.MENU_SHOW, 0,
R.string.waypointslist_show_waypoint);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.waypointslist_edit_waypoint);
menu.add(0, Constants.MENU_DELETE, 0,
R.string.waypointslist_delete_waypoint).setEnabled(
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
Waypoint waypoint = providerUtils.getWaypoint(info.id);
if (waypoint != null) {
int type = waypoint.getType();
menu.add(0, Constants.MENU_SHOW, 0,
R.string.waypointslist_show_waypoint);
menu.add(0, Constants.MENU_EDIT, 0,
R.string.waypointslist_edit_waypoint);
menu.add(0, Constants.MENU_DELETE, 0,
R.string.waypointslist_delete_waypoint).setEnabled(
recordingTrackId < 0 || type == Waypoint.TYPE_WAYPOINT ||
info.id != providerUtils.getLastWaypointId(recordingTrackId));
}
}
};
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent result = new Intent();
result.putExtra("trackid", trackId);
result.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, id);
setResult(Constants.EDIT_WAYPOINT, result);
finish();
editWaypoint(id);
}
@Override
@@ -99,29 +104,38 @@ public class WaypointsList extends ListActivity
if (!super.onMenuItemSelected(featureId, item)) {
switch (item.getItemId()) {
case Constants.MENU_SHOW: {
onListItemClick(null, null, 0, waypointId);
Intent result = new Intent();
result.putExtra("trackid", trackId);
result.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, selectedWaypointId);
setResult(RESULT_OK, result);
finish();
return true;
}
case Constants.MENU_EDIT: {
Intent intent = new Intent(this, WaypointDetails.class);
intent.putExtra("trackid", trackId);
intent.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, waypointId);
startActivity(intent);
editWaypoint(selectedWaypointId);
return true;
}
case Constants.MENU_DELETE: {
deleteWaypoint(waypointId);
deleteWaypoint(selectedWaypointId);
}
}
}
return false;
}
private void editWaypoint(long waypointId) {
Intent intent = new Intent(this, WaypointDetails.class);
intent.putExtra("trackid", trackId);
intent.putExtra(WaypointDetails.WAYPOINT_ID_EXTRA, waypointId);
startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
serviceConnection = new TrackRecordingServiceConnection(this, null);
// We don't need a window title bar:
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -159,7 +173,7 @@ public class WaypointsList extends ListActivity
trackId = -1;
}
final long firstWaypointId = providerUtils.getFirstWaypointId(trackId);
final long firstWaypointId = providerUtils.getFirstWaypointId(trackId);
waypointsCursor = getContentResolver().query(
WaypointsColumns.CONTENT_URI, null,
WaypointsColumns.TRACKID + "=" + trackId + " AND "
@@ -168,6 +182,20 @@ public class WaypointsList extends ListActivity
setListAdapter();
}
@Override
protected void onResume() {
super.onResume();
serviceConnection.bindIfRunning();
}
@Override
protected void onDestroy() {
serviceConnection.unbind();
super.onDestroy();
}
@Override
public void onClick(View v) {
WaypointCreationRequest request;
@@ -181,17 +209,10 @@ public class WaypointsList extends ListActivity
default:
return;
}
long id;
try {
id = MyTracks.getInstance().insertWaypoint(request);
} catch (RemoteException e) {
Log.e(Constants.TAG, "Cannot insert marker.", e);
return;
} catch (IllegalStateException e) {
Log.e(Constants.TAG, "Cannot insert marker.", e);
return;
}
long id = insertWaypoint(request);
if (id < 0) {
Toast.makeText(this, R.string.error_unable_to_insert_marker,
Toast.LENGTH_LONG).show();
Log.e(Constants.TAG, "Failed to insert marker.");
return;
}
@@ -200,6 +221,28 @@ public class WaypointsList extends ListActivity
startActivity(intent);
}
private long insertWaypoint(WaypointCreationRequest request) {
try {
ITrackRecordingService trackRecordingService = serviceConnection.getServiceIfBound();
if (trackRecordingService != null) {
long waypointId = trackRecordingService.insertWaypoint(request);
if (waypointId >= 0) {
Toast.makeText(this, R.string.status_statistics_inserted,
Toast.LENGTH_LONG).show();
return waypointId;
}
} else {
Log.e(TAG, "Not connected to service, not inserting waypoint");
}
} catch (RemoteException e) {
Log.e(Constants.TAG, "Cannot insert marker.", e);
} catch (IllegalStateException e) {
Log.e(Constants.TAG, "Cannot insert marker.", e);
}
return -1;
}
private void setListAdapter() {
// Get a cursor with all tracks
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
@@ -209,7 +252,7 @@ public class WaypointsList extends ListActivity
new String[] { WaypointsColumns.NAME, WaypointsColumns.TIME,
WaypointsColumns.CATEGORY, WaypointsColumns.TYPE },
new int[] { R.id.waypointslist_item_name,
R.id.waypointslist_item_time,
R.id.waypointslist_item_time,
R.id.waypointslist_item_category,
R.id.waypointslist_item_icon });
@@ -219,7 +262,7 @@ public class WaypointsList extends ListActivity
waypointsCursor.getColumnIndexOrThrow(WaypointsColumns.TYPE);
adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
@Override
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
if (columnIndex == timeIdx) {
long time = cursor.getLong(timeIdx);
TextView textView = (TextView) view;
@@ -258,8 +301,8 @@ public class WaypointsList extends ListActivity
builder.setPositiveButton(getString(R.string.yes),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
providerUtils.deleteWaypoint(waypointId,
new StringUtils(WaypointsList.this));
}
@@ -267,8 +310,8 @@ public class WaypointsList extends ListActivity
builder.setNegativeButton(getString(R.string.no),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.dismiss();
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
dialog = builder.create();
@@ -24,7 +24,7 @@ import java.util.Set;
/**
* External data source manager, which converts system-level events into My Tracks data events.
*
*
* @author Rodrigo Damazio
*/
class DataSourceManager {
@@ -36,6 +36,7 @@ class DataSourceManager {
void notifyPointsUpdated();
void notifyPreferenceChanged(String key);
void notifyLocationProviderEnabled(boolean enabled);
void notifyLocationProviderAvailable(boolean available);
void notifyLocationChanged(Location loc);
void notifyHeadingChanged(float heading);
}
@@ -93,7 +94,7 @@ class DataSourceManager {
public void onStatusChanged(String provider, int status, Bundle extras) {
if (!LocationManager.GPS_PROVIDER.equals(provider)) return;
listener.notifyLocationProviderEnabled(status == LocationProvider.AVAILABLE);
listener.notifyLocationProviderAvailable(status == LocationProvider.AVAILABLE);
}
@Override
@@ -168,7 +169,7 @@ class DataSourceManager {
neededListeners.remove(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
neededListeners.add(ListenerDataType.POINT_UPDATES);
}
Log.d(TAG, "Updating internal listeners to types " + neededListeners);
// Unnecessary = registered - needed
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -154,12 +154,12 @@ public class MyTracksProvider extends ContentProvider {
// Sensor data.
if (oldVersion <= 17) {
Log.w(TAG, "Upgrade DB: Adding sensor column.");
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
db.execSQL("ALTER TABLE " + TRACKPOINTS_TABLE
+ " ADD " + TrackPointsColumns.SENSOR + " BLOB");
}
if (oldVersion <= 18) {
Log.w(TAG, "Upgrade DB: Adding tableid column.");
db.execSQL("ALTER TABLE " + TRACKS_TABLE
db.execSQL("ALTER TABLE " + TRACKS_TABLE
+ " ADD " + TracksColumns.TABLEID + " STRING");
}
}
@@ -186,7 +186,11 @@ public class MyTracksProvider extends ContentProvider {
@Override
public boolean onCreate() {
DatabaseHelper dbHelper = new DatabaseHelper(getContext());
db = dbHelper.getWritableDatabase();
try {
db = dbHelper.getWritableDatabase();
} catch (SQLiteException e) {
Log.e(TAG, "Unable to open database for writing", e);
}
return db != null;
}
@@ -383,7 +387,7 @@ public class MyTracksProvider extends ContentProvider {
if (ApiFeatures.getInstance().canReuseSQLiteQueryBuilder()) {
Log.i(Constants.TAG,
"Build query: " + qb.buildQuery(projection, selection, selectionArgs,
"Build query: " + qb.buildQuery(projection, selection, selectionArgs,
null, null, sortOrder, null));
}
Cursor c = qb.query(db, projection, selection, selectionArgs, null, null,
@@ -17,11 +17,12 @@ package com.google.android.apps.mytracks.content;
import static com.google.android.apps.mytracks.Constants.DEFAULT_MIN_REQUIRED_ACCURACY;
import static com.google.android.apps.mytracks.Constants.MAX_DISPLAYED_WAYPOINTS_POINTS;
import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS;
import static com.google.android.apps.mytracks.Constants.MAX_LOCATION_AGE_MS;
import static com.google.android.apps.mytracks.Constants.MAX_NETWORK_AGE_MS;
import static com.google.android.apps.mytracks.Constants.TAG;
import static com.google.android.apps.mytracks.Constants.TARGET_DISPLAYED_TRACK_POINTS;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.DataSourceManager.DataSourceListener;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.DoubleBufferedLocationFactory;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils.LocationIterator;
@@ -40,7 +41,6 @@ import android.location.LocationManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.Toast;
import java.util.Collections;
import java.util.EnumSet;
@@ -129,6 +129,12 @@ public class TrackDataHub {
TrackDataHub.this.notifyFixType();
}
@Override
public void notifyLocationProviderAvailable(boolean available) {
hasFix = available;
TrackDataHub.this.notifyFixType();
}
@Override
public void notifyLocationChanged(Location loc) {
TrackDataHub.this.notifyLocationChanged(loc,
@@ -150,24 +156,21 @@ public class TrackDataHub {
// Get content notifications on the main thread, send listener callbacks in another.
// This ensures listener calls are serialized.
private final HandlerThread listenerHandlerThread;
private final Handler listenerHandler;
private HandlerThread listenerHandlerThread;
private Handler listenerHandler;
/** Manager for external listeners (those from activities). */
private final TrackDataListeners listeners;
private final TrackDataListeners dataListeners;
/** Wrapper for interacting with system data managers. */
private final DataSourcesWrapper dataSources;
private DataSourcesWrapper dataSources;
/** Manager for system data listener registrations. */
private final DataSourceManager dataSourceManager;
private DataSourceManager dataSourceManager;
/** Condensed listener for system data listener events. */
private final DataSourceListener dataSourceListener = new HubDataSourceListener();
/** Whether we've been started. */
private boolean started;
// Cached preference values
private int minRequiredAccuracy;
private boolean useMetricUnits;
@@ -192,28 +195,34 @@ public class TrackDataHub {
private int lastSamplingFrequency;
private DoubleBufferedLocationFactory locationFactory;
private static TrackDataHub startedInstance;
/**
* Default constructor.
* Builds a new {@link TrackDataHub} instance.
*/
public TrackDataHub(Context ctx, SharedPreferences preferences,
MyTracksProviderUtils providerUtils) {
this(ctx, new DataSourcesWrapperImpl(ctx, preferences), new TrackDataListeners(),
preferences, providerUtils, TARGET_DISPLAYED_TRACK_POINTS);
public synchronized static TrackDataHub newInstance(Context context) {
// Ensure our singleton is never bound to an activity, to avoid memory leaks.
context = context.getApplicationContext();
SharedPreferences preferences = context.getSharedPreferences(Constants.SETTINGS_NAME, 0);
MyTracksProviderUtils providerUtils = MyTracksProviderUtils.Factory.get(context);
return new TrackDataHub(context,
new TrackDataListeners(),
preferences, providerUtils,
TARGET_DISPLAYED_TRACK_POINTS);
}
/**
* Injection constructor.
*/
// @VisibleForTesting
TrackDataHub(Context ctx, DataSourcesWrapper dataSources, TrackDataListeners listeners,
SharedPreferences preferences, MyTracksProviderUtils providerUtils, int targetNumPoints) {
TrackDataHub(Context ctx, TrackDataListeners listeners, SharedPreferences preferences,
MyTracksProviderUtils providerUtils, int targetNumPoints) {
this.context = ctx;
this.listeners = listeners;
this.dataListeners = listeners;
this.preferences = preferences;
this.providerUtils = providerUtils;
this.dataSources = dataSources;
this.targetNumPoints = targetNumPoints;
this.dataSourceManager = new DataSourceManager(dataSourceListener, dataSources);
this.locationFactory = new DoubleBufferedLocationFactory();
SELECTED_TRACK_KEY = context.getString(R.string.selected_track_key);
@@ -222,10 +231,6 @@ public class TrackDataHub {
METRIC_UNITS_KEY = context.getString(R.string.metric_units_key);
SPEED_REPORTING_KEY = context.getString(R.string.report_speed_key);
listenerHandlerThread = new HandlerThread("trackDataContentThread");
listenerHandlerThread.start();
listenerHandler = new Handler(listenerHandlerThread.getLooper());
resetState();
}
@@ -235,11 +240,17 @@ public class TrackDataHub {
*/
public void start() {
Log.i(TAG, "TrackDataHub.start");
if (started) {
if (startedInstance != null) {
Log.w(TAG, "Already started, ignoring");
return;
}
started = true;
startedInstance = this;
listenerHandlerThread = new HandlerThread("trackDataContentThread");
listenerHandlerThread.start();
listenerHandler = new Handler(listenerHandlerThread.getLooper());
dataSources = newDataSources();
dataSourceManager = new DataSourceManager(dataSourceListener, dataSources);
// This may or may not register internal listeners, depending on whether
// we already had external listeners.
@@ -250,12 +261,23 @@ public class TrackDataHub {
loadDataForAllListeners();
}
private void loadSharedPreferences() {
selectedTrackId = preferences.getLong(SELECTED_TRACK_KEY, -1);
useMetricUnits = preferences.getBoolean(METRIC_UNITS_KEY, true);
reportSpeed = preferences.getBoolean(SPEED_REPORTING_KEY, true);
minRequiredAccuracy = preferences.getInt(MIN_REQUIRED_ACCURACY_KEY,
DEFAULT_MIN_REQUIRED_ACCURACY);
// @VisibleForTesting
protected DataSourcesWrapper newDataSources() {
return new DataSourcesWrapperImpl(context, preferences);
}
/**
* If there's an instance for which {@link start} has been called, returns it.
*
* @return the started instance
* @throws IllegalStateException if there isn't a started instance
*/
public static TrackDataHub getStartedInstance() {
if (startedInstance == null) {
throw new IllegalStateException("Data hub not started");
}
return startedInstance;
}
/**
@@ -264,25 +286,42 @@ public class TrackDataHub {
*/
public void stop() {
Log.i(TAG, "TrackDataHub.stop");
if (!started) {
if (!isStarted()) {
Log.w(TAG, "Not started, ignoring");
return;
}
// Unregister internal listeners even if there are external listeners registered.
dataSourceManager.unregisterAllListeners();
listenerHandlerThread.getLooper().quit();
started = false;
startedInstance = null;
dataSources = null;
dataSourceManager = null;
listenerHandlerThread = null;
listenerHandler = null;
}
/** Permanently invalidates and throws away all resources used by this class. */
public void destroy() {
if (started) {
throw new IllegalStateException("Can only destroy the data hub after it's been stopped");
private boolean isStarted() {
return startedInstance != null;
}
@Override
protected void finalize() throws Throwable {
if (isStarted() || listenerHandlerThread.isAlive()) {
Log.e(TAG, "Forgot to stop() TrackDataHub");
}
ApiFeatures.getInstance().getApiPlatformAdapter()
.stopHandlerThread(listenerHandlerThread);
super.finalize();
}
private void loadSharedPreferences() {
selectedTrackId = preferences.getLong(SELECTED_TRACK_KEY, -1);
useMetricUnits = preferences.getBoolean(METRIC_UNITS_KEY, true);
reportSpeed = preferences.getBoolean(SPEED_REPORTING_KEY, true);
minRequiredAccuracy = preferences.getInt(MIN_REQUIRED_ACCURACY_KEY,
DEFAULT_MIN_REQUIRED_ACCURACY);
}
/** Updates known magnetic declination if needed. */
@@ -325,6 +364,10 @@ public class TrackDataHub {
* is not available or doesn't have a fix.
*/
public void forceUpdateLocation() {
if (!isStarted()) {
Log.w(TAG, "Not started, not forcing location update");
return;
}
Log.i(TAG, "Forcing location update");
Location loc = dataSources.getLastKnownLocation();
@@ -336,7 +379,7 @@ public class TrackDataHub {
/** Returns the ID of the currently-selected track. */
public long getSelectedTrackId() {
if (!started) {
if (!isStarted()) {
loadSharedPreferences();
}
return selectedTrackId;
@@ -347,17 +390,9 @@ public class TrackDataHub {
return getSelectedTrackId() > 0;
}
/** Returns whether we're currently recording a track. */
public boolean isRecording() {
if (!started) {
loadSharedPreferences();
}
return preferences.getLong(RECORDING_TRACK_KEY, -1) > 0;
}
/** Returns whether the selected track is still being recorded. */
public boolean isRecordingSelected() {
if (!started) {
if (!isStarted()) {
loadSharedPreferences();
}
long recordingTrackId = preferences.getLong(RECORDING_TRACK_KEY, -1);
@@ -377,10 +412,10 @@ public class TrackDataHub {
return;
}
// Save the selection to memory and flash.
// Save the selection to memory and flush.
selectedTrackId = trackId;
ApiFeatures.getInstance().getApiPlatformAdapter().applyPreferenceChanges(
preferences.edit().putLong(SELECTED_TRACK_KEY, trackId));
selectedTrackId = trackId;
// Force it to reload data from the beginning.
Log.d(TAG, "Loading track");
@@ -408,26 +443,27 @@ public class TrackDataHub {
public void registerTrackDataListener(
TrackDataListener listener, EnumSet<ListenerDataType> dataTypes) {
synchronized (listeners) {
ListenerRegistration registration = listeners.registerTrackDataListener(listener, dataTypes);
synchronized (dataListeners) {
ListenerRegistration registration =
dataListeners.registerTrackDataListener(listener, dataTypes);
// Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things.
if (!started) return;
if (!isStarted()) return;
reloadDataForListener(registration);
loadNewDataForListener(registration);
dataSourceManager.updateAllListeners(getNeededListenerTypes());
}
}
public void unregisterTrackDataListener(TrackDataListener listener) {
synchronized (listeners) {
listeners.unregisterTrackDataListener(listener);
synchronized (dataListeners) {
dataListeners.unregisterTrackDataListener(listener);
// Don't load any data or start internal listeners if start() hasn't been
// called. When it is called, we'll do both things.
if (!started) return;
if (!isStarted()) return;
dataSourceManager.updateAllListeners(getNeededListenerTypes());
}
@@ -438,30 +474,32 @@ public class TrackDataHub {
*/
public void reloadDataForListener(TrackDataListener listener) {
ListenerRegistration registration;
synchronized (listeners) {
registration = listeners.getRegistration(listener);
reloadDataForListener(registration);
synchronized (dataListeners) {
registration = dataListeners.getRegistration(listener);
registration.resetState();
loadNewDataForListener(registration);
}
}
/**
* Reloads all track data received so far into the specified listeners.
*
* Assumes it's called from a block that synchronizes on {@link #listeners}.
* Assumes it's called from a block that synchronizes on {@link #dataListeners}.
*/
private void reloadDataForListener(final ListenerRegistration registration) {
if (!started) {
private void loadNewDataForListener(final ListenerRegistration registration) {
if (!isStarted()) {
Log.w(TAG, "Not started, not reloading");
return;
}
if (registration == null) {
Log.w(TAG, "Not reloading for null registration");
return;
}
// If a listener happens to be added after this method but before the Runnable below is
// executed, it will have triggered a separate call to load data only up to the point this
// listener got to. This is ensured by being synchronized on listeners.
final boolean isOnlyListener = (listeners.getNumListeners() == 1);
final boolean isOnlyListener = (dataListeners.getNumListeners() == 1);
runInListenerThread(new Runnable() {
@SuppressWarnings("unchecked")
@@ -542,12 +580,12 @@ public class TrackDataHub {
* Reloads all track data received so far into the specified listeners.
*/
private void loadDataForAllListeners() {
if (!started) {
if (!isStarted()) {
Log.w(TAG, "Not started, not reloading");
return;
}
synchronized (listeners) {
if (!listeners.hasListeners()) {
synchronized (dataListeners) {
if (!dataListeners.hasListeners()) {
Log.d(TAG, "No listeners, not reloading");
return;
}
@@ -604,12 +642,15 @@ public class TrackDataHub {
} else if (SPEED_REPORTING_KEY.equals(key)) {
reportSpeed = preferences.getBoolean(SPEED_REPORTING_KEY, true);
notifySpeedReportingChanged();
} else if (SELECTED_TRACK_KEY.equals(key)) {
long trackId = preferences.getLong(SELECTED_TRACK_KEY, -1);
loadTrack(trackId);
}
}
/** Called when the speed/pace reporting preference changes. */
private void notifySpeedReportingChanged() {
if (!started) return;
if (!isStarted()) return;
runInListenerThread(new Runnable() {
@Override
@@ -620,8 +661,8 @@ public class TrackDataHub {
for (TrackDataListener listener : displayListeners) {
// TODO: Do the reloading just once for all interested listeners
if (listener.onReportSpeedChanged(reportSpeed)) {
synchronized (listeners) {
reloadDataForListener(listeners.getRegistration(listener));
synchronized (dataListeners) {
reloadDataForListener(listener);
}
}
}
@@ -631,7 +672,7 @@ public class TrackDataHub {
/** Called when the metric units setting changes. */
private void notifyUnitsChanged() {
if (!started) return;
if (!isStarted()) return;
runInListenerThread(new Runnable() {
@Override
@@ -640,8 +681,8 @@ public class TrackDataHub {
for (TrackDataListener listener : displayListeners) {
if (listener.onUnitsChanged(useMetricUnits)) {
synchronized (listeners) {
reloadDataForListener(listeners.getRegistration(listener));
synchronized (dataListeners) {
reloadDataForListener(listener);
}
}
}
@@ -708,7 +749,7 @@ public class TrackDataHub {
if (isGpsLocation) {
// We consider a good fix to be a recent one with reasonable accuracy.
hasFix = !isLocationOld(location, now, MAX_LOCATION_AGE_MS);
hasGoodFix = (location != null && location.getAccuracy() <= minRequiredAccuracy);
hasGoodFix = (location.getAccuracy() <= minRequiredAccuracy);
} else {
if (!isLocationOld(lastSeenLocation, now, MAX_LOCATION_AGE_MS)) {
// This is a network location, but we have a recent/valid GPS location, just ignore this.
@@ -791,10 +832,6 @@ public class TrackDataHub {
public void run() {
for (TrackDataListener listener : listeners) {
listener.onSelectedTrackChanged(track, isRecordingSelected());
if (track != null) {
listener.onTrackUpdated(track);
}
}
}
});
@@ -934,7 +971,7 @@ public class TrackDataHub {
Log.i(TAG, "Resampling point set after " + numLoadedPoints + " points.");
resetState();
synchronized (listeners) {
synchronized (dataListeners) {
sampledListeners = getListenersFor(ListenerDataType.POINT_UPDATES);
sampledOutListeners = getListenersFor(ListenerDataType.SAMPLED_OUT_POINT_UPDATES);
}
@@ -994,6 +1031,7 @@ public class TrackDataHub {
// no matter how many points we get in the newest batch, we'll never exceed
// MAX_DISPLAYED_TRACK_POINTS = 2 * TARGET_DISPLAYED_TRACK_POINTS before resampling.
long numTotalPoints = lastStoredLocationId - localFirstSeenLocationId;
numTotalPoints = Math.max(0L, numTotalPoints);
pointSamplingFrequency = (int) (1 + numTotalPoints / targetNumPoints);
}
@@ -1018,7 +1056,7 @@ public class TrackDataHub {
listener.onNewTrackPointsDone();
// Update the listener state
ListenerRegistration registration = listeners.getRegistration(listener);
ListenerRegistration registration = dataListeners.getRegistration(listener);
if (registration != null) {
registration.lastTrackId = currentSelectedTrackId;
registration.lastPointId = localLastSeenLocationId;
@@ -1031,7 +1069,7 @@ public class TrackDataHub {
private void notifyNewPoint(Location location,
long locationId,
long lastStoredLocationId,
int numLoadedPoints,
int loadedPoints,
int pointSamplingFrequency,
Set<TrackDataListener> sampledListeners,
Set<TrackDataListener> sampledOutListeners) {
@@ -1050,7 +1088,7 @@ public class TrackDataHub {
// - Is the last point and we are not recording this track.
boolean recordingSelected = isRecordingSelected();
boolean includeInSample =
(numLoadedPoints % pointSamplingFrequency == 0 ||
(loadedPoints % pointSamplingFrequency == 0 ||
(!recordingSelected && locationId == lastStoredLocationId));
if (!includeInSample) {
@@ -1068,17 +1106,23 @@ public class TrackDataHub {
// @VisibleForTesting
protected void runInListenerThread(Runnable runnable) {
if (listenerHandler == null) {
// Use a Throwable to ensure the stack trace is logged.
Log.e(TAG, "Tried to use listener thread before start()", new Throwable());
return;
}
listenerHandler.post(runnable);
}
private Set<TrackDataListener> getListenersFor(ListenerDataType type) {
synchronized (listeners) {
return listeners.getListenersFor(type);
synchronized (dataListeners) {
return dataListeners.getListenersFor(type);
}
}
private EnumSet<ListenerDataType> getNeededListenerTypes() {
EnumSet<ListenerDataType> neededTypes = listeners.getAllRegisteredTypes();
EnumSet<ListenerDataType> neededTypes = dataListeners.getAllRegisteredTypes();
// We always want preference updates.
neededTypes.add(ListenerDataType.DISPLAY_PREFERENCES);
@@ -1,12 +1,12 @@
/*
* Copyright 2011 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -57,6 +57,13 @@ class TrackDataListeners {
return types.contains(type);
}
public void resetState() {
lastTrackId = 0L;
lastPointId = 0L;
lastSamplingFrequency = 0;
numLoadedPoints = 0;
}
@Override
public String toString() {
return "ListenerRegistration [listener=" + listener + ", types=" + types
@@ -1,12 +1,12 @@
/*
* 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
@@ -20,21 +20,32 @@ import android.content.Intent;
/**
* This interface describes a class that will fetch and maintain a Google
* authentication token.
*
*
* @author Sandor Dornbush
*/
public interface AuthManager {
/**
* Callback for authentication token retrieval operations.
*/
public interface AuthCallback {
/**
* Indicates that we're done fetching an auth token.
*
* @param success if true, indicates we have the requested auth token available
* to be retrieved using {@link AuthManager#getAuthToken}
*/
void onAuthResult(boolean success);
}
/**
* Initializes the login process. The user should be asked to login if they
* haven't already. The {@link Runnable} provided will be executed when the
* auth token is successfully fetched.
* haven't already. The {@link AuthCallback} provided will be executed when the
* auth token fetching is done (successfully or not).
*
* @param whenFinished A {@link Runnable} to execute when the auth token
* has been successfully fetched and is available via
* {@link #getAuthToken()}
* @param whenFinished A {@link AuthCallback} to execute when the auth token
* fetching is done
*/
public abstract void doLogin(Runnable whenFinished, Object o);
void doLogin(AuthCallback whenFinished, Object o);
/**
* The {@link android.app.Activity} owner of this class should call this
@@ -49,11 +60,8 @@ public interface AuthManager {
* {@link android.app.Activity#onActivityResult} function
* @param results The data passed in to the {@link android.app.Activity}'s
* {@link android.app.Activity#onActivityResult} function
* @return True if the auth token was fetched or we aren't done fetching
* the auth token, or False if there was an error or the request was
* canceled
*/
public abstract boolean authResult(int resultCode, Intent results);
void authResult(int resultCode, Intent results);
/**
* Returns the current auth token. Response may be null if no valid auth
@@ -62,7 +70,7 @@ public interface AuthManager {
* @return The current auth token or null if no auth token has been
* fetched
*/
public abstract String getAuthToken();
String getAuthToken();
/**
* Invalidates the existing auth token and request a new one. The
@@ -72,6 +80,14 @@ public interface AuthManager {
* @param whenFinished A {@link Runnable} to execute when a new auth token
* is successfully fetched
*/
public abstract void invalidateAndRefresh(Runnable whenFinished);
void invalidateAndRefresh(AuthCallback whenFinished);
/**
* Returns an object that represents the given account, if possible.
*
* @param accountName the name of the account
* @param accountType the type of the account
* @return the account object
*/
Object getAccountObject(String accountName, String accountType);
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,7 +15,7 @@
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.Constants;
import static com.google.android.apps.mytracks.Constants.TAG;
import android.app.Activity;
import android.os.Build;
@@ -24,7 +24,7 @@ import android.util.Log;
/**
* A factory for getting the platform specific AuthManager.
*
*
* @author Sandor Dornbush
*/
public class AuthManagerFactory {
@@ -46,12 +46,10 @@ public class AuthManagerFactory {
public static AuthManager getAuthManager(Activity activity, int code,
Bundle extras, boolean requireGoogle, String service) {
if (useModernAuthManager()) {
Log.i(Constants.TAG,
"Creating modern auth manager: " + service);
return new ModernAuthManager(activity, code, extras, requireGoogle, service);
Log.i(TAG, "Creating modern auth manager: " + service);
return new ModernAuthManager(activity, service);
} else {
Log.i(Constants.TAG,
"Creating legacy auth manager: " + service);
Log.i(TAG, "Creating legacy auth manager: " + service);
return new AuthManagerOld(activity, code, extras, requireGoogle, service);
}
}
@@ -22,9 +22,6 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import java.util.Iterator;
import java.util.Vector;
/**
* AuthManager keeps track of the current auth token for a user. The advantage
* over just passing around a String is that this class can renew the auth
@@ -47,18 +44,12 @@ public class AuthManagerOld implements AuthManager {
/** The name of the service to authorize for. */
private final String service;
/** A list of handlers to call when a new auth token is fetched. */
private final Vector<Runnable> newTokenListeners = new Vector<Runnable>();
/** The handler to call when a new auth token is fetched. */
private AuthCallback authCallback;
/** The most recently fetched auth token or null if none is available. */
private String authToken;
/**
* The number of handlers at the beginning of the above list that shouldn't
* be removed after they are called.
*/
private int stickyNewTokenListenerCount;
/**
* AuthManager requires many of the same parameters as
* {@link GoogleLoginServiceHelper#getCredentials(Activity, int, Bundle,
@@ -88,15 +79,9 @@ public class AuthManagerOld implements AuthManager {
this.service = service;
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#doLogin(java.lang.Runnable)
*/
public void doLogin(Runnable whenFinished, Object o) {
synchronized (newTokenListeners) {
if (whenFinished != null) {
newTokenListeners.add(whenFinished);
}
}
@Override
public void doLogin(AuthCallback callback, Object o) {
authCallback = callback;
activity.runOnUiThread(new LoginRunnable());
}
@@ -111,50 +96,37 @@ public class AuthManagerOld implements AuthManager {
}
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#authResult(int, android.content.Intent)
*/
public boolean authResult(int resultCode, Intent results) {
if (resultCode == Activity.RESULT_OK) {
authToken = results.getStringExtra(
GoogleLoginServiceConstants.AUTHTOKEN_KEY);
if (authToken == null) {
GoogleLoginServiceHelper.getCredentials(
activity, code, extras, requireGoogle, service, false);
return true;
} else {
// Notify all active listeners that we have a new auth token.
synchronized (newTokenListeners) {
Iterator<Runnable> iter = newTokenListeners.iterator();
while (iter.hasNext()) {
iter.next().run();
}
iter = null;
// Remove anything not in the sticky part of the list.
newTokenListeners.setSize(stickyNewTokenListenerCount);
}
return true;
}
@Override
public void authResult(int resultCode, Intent results) {
if (resultCode != Activity.RESULT_OK) {
runAuthCallback(false);
return;
}
authToken = results.getStringExtra(GoogleLoginServiceConstants.AUTHTOKEN_KEY);
if (authToken == null) {
// Retry, without prompting the user.
GoogleLoginServiceHelper.getCredentials(
activity, code, extras, requireGoogle, service, false);
} else {
// Notify all active listeners that we have a new auth token.
runAuthCallback(true);
}
return false;
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#getAuthToken()
*/
private void runAuthCallback(boolean success) {
authCallback.onAuthResult(success);
authCallback = null;
}
@Override
public String getAuthToken() {
return authToken;
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#invalidateAndRefresh(java.lang.Runnable)
*/
public void invalidateAndRefresh(Runnable whenFinished) {
synchronized (newTokenListeners) {
if (whenFinished != null) {
newTokenListeners.add(whenFinished);
}
}
@Override
public void invalidateAndRefresh(AuthCallback callback) {
authCallback = callback;
activity.runOnUiThread(new Runnable() {
public void run() {
GoogleLoginServiceHelper.invalidateAuthToken(activity, code, authToken);
@@ -162,37 +134,8 @@ public class AuthManagerOld implements AuthManager {
});
}
/**
* Adds a {@link Runnable} to be executed every time the auth token is
* updated. The {@link Runnable} will not be removed until manually removed
* with {@link #removeStickyNewTokenListener(Runnable)}.
*
* @param listener The {@link Runnable} to execute every time a new auth
* token is fetched
*/
public void addStickyNewTokenListener(Runnable listener) {
synchronized (newTokenListeners) {
newTokenListeners.add(0, listener);
stickyNewTokenListenerCount++;
}
}
/**
* Stops executing the given {@link Runnable} every time the auth token is
* updated. This {@link Runnable} must have been added with
* {@link #addStickyNewTokenListener(Runnable)} above. If the
* {@link Runnable} was added more than once, only the first occurrence
* will be removed.
*
* @param listener The {@link Runnable} to stop executing every time a new
* auth token is fetched
*/
public void removeStickyNewTokenListener(Runnable listener) {
synchronized (newTokenListeners) {
if (stickyNewTokenListenerCount > 0
&& newTokenListeners.remove(listener)) {
stickyNewTokenListenerCount--;
}
}
@Override
public Object getAccountObject(String accountName, String accountType) {
throw new UnsupportedOperationException("Legacy auth manager knows nothing about accounts");
}
}
@@ -15,16 +15,17 @@
*/
package com.google.android.apps.mytracks.io;
import com.google.android.accounts.Account;
import com.google.android.accounts.AccountManager;
import com.google.android.accounts.AccountManagerCallback;
import com.google.android.accounts.AccountManagerFuture;
import com.google.android.accounts.AuthenticatorException;
import com.google.android.accounts.OperationCanceledException;
import com.google.android.apps.mytracks.AccountChooser;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.Constants;
import static com.google.android.apps.mytracks.Constants.ACCOUNT_TYPE;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.AccountChooser;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -50,7 +51,9 @@ public class ModernAuthManager implements AuthManager {
private final AccountManager accountManager;
private Runnable whenFinished;
private AuthCallback authCallback;
private Account lastAccount;
/**
* AuthManager requires many of the same parameters as
@@ -64,16 +67,9 @@ public class ModernAuthManager implements AuthManager {
* {@link Activity#onActivityResult} that calls
* {@link #authResult(int, Intent)} when {@literal code} is the request
* code
* @param code The request code to pass to
* {@link Activity#onActivityResult} when
* {@link #authResult(int, Intent)} should be called
* @param extras A {@link Bundle} of extras for
* {@link com.google.android.googlelogindist.GoogleLoginServiceHelper}
* @param requireGoogle True if the account must be a Google account
* @param service The name of the service to authenticate as
*/
public ModernAuthManager(Activity activity, int code, Bundle extras,
boolean requireGoogle, String service) {
public ModernAuthManager(Activity activity, String service) {
this.activity = activity;
this.service = service;
this.accountManager = AccountManager.get(activity);
@@ -88,47 +84,45 @@ public class ModernAuthManager implements AuthManager {
* has been successfully fetched and is available via
* {@link #getAuthToken()}
*/
public void doLogin(final Runnable runnable, Object o) {
this.whenFinished = runnable;
public void doLogin(AuthCallback runnable, Object o) {
this.authCallback = runnable;
if (!(o instanceof Account)) {
throw new IllegalArgumentException("FroyoAuthManager requires an account.");
throw new IllegalArgumentException("ModernAuthManager requires an account.");
}
Account account = (Account) o;
accountManager.getAuthToken(account, service, true,
doLogin(account);
}
private void doLogin(Account account) {
// Keep the account in case we need to retry.
this.lastAccount = account;
// NOTE: Many Samsung phones have a crashing bug in
// AccountManager#getAuthToken(Account, String, boolean, AccountManagerCallback<Bundle>)
// so we use the other version of the method.
// More details here:
// http://forum.xda-developers.com/showthread.php?p=15155487
// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/accounts/AccountManagerService.java
accountManager.getAuthToken(account, service, null, activity,
new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
// AccountManager needs user to grant permission
if (result.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = (Intent) result.get(AccountManager.KEY_INTENT);
clearNewTaskFlag(intent);
activity.startActivityForResult(intent, Constants.GET_LOGIN);
return;
}
authToken = result.getString(
authToken = future.getResult().getString(
AccountManager.KEY_AUTHTOKEN);
Log.e(Constants.TAG, "Got auth token.");
runWhenFinished();
Log.i(TAG, "Got auth token");
} catch (OperationCanceledException e) {
Log.e(Constants.TAG, "Operation Canceled", e);
Log.e(TAG, "Auth token operation Canceled", e);
} catch (IOException e) {
Log.e(Constants.TAG, "IOException", e);
Log.e(TAG, "Auth token IO exception", e);
} catch (AuthenticatorException e) {
Log.e(Constants.TAG, "Authentication Failed", e);
Log.e(TAG, "Authentication Failed", e);
}
runAuthCallback();
}
}, null /* handler */);
}
private static void clearNewTaskFlag(Intent intent) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
/**
* The {@link Activity} passed into the constructor should call this
* function when it gets {@link Activity#onActivityResult} with the request
@@ -145,16 +139,22 @@ public class ModernAuthManager implements AuthManager {
* the auth token, or False if there was an error or the request was
* canceled
*/
public boolean authResult(int resultCode, Intent results) {
if (results != null) {
authToken = results.getStringExtra(
AccountManager.KEY_AUTHTOKEN);
Log.w(Constants.TAG, "authResult: " + authToken);
public void authResult(int resultCode, Intent results) {
boolean retry = false;
if (results == null) {
Log.e(TAG, "No auth token!!");
} else {
Log.e(Constants.TAG, "No auth result results!!");
authToken = results.getStringExtra(AccountManager.KEY_AUTHTOKEN);
retry = results.getBooleanExtra("retry", false);
}
runWhenFinished();
return authToken != null;
if (authToken == null && retry) {
Log.i(TAG, "Retrying to get auth result");
doLogin(lastAccount);
return;
}
runAuthCallback();
}
/**
@@ -176,21 +176,23 @@ public class ModernAuthManager implements AuthManager {
* @param runnable A {@link Runnable} to execute when a new auth token
* is successfully fetched
*/
public void invalidateAndRefresh(final Runnable runnable) {
this.whenFinished = runnable;
public void invalidateAndRefresh(final AuthCallback callback) {
this.authCallback = callback;
activity.runOnUiThread(new Runnable() {
public void run() {
accountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
authToken);
MyTracks.getInstance().getAccountChooser().chooseAccount(activity,
accountManager.invalidateAuthToken(ACCOUNT_TYPE, authToken);
authToken = null;
AccountChooser accountChooser = new AccountChooser();
accountChooser.chooseAccount(activity,
new AccountChooser.AccountHandler() {
@Override
public void handleAccountSelected(Account account) {
public void onAccountSelected(Account account) {
if (account != null) {
doLogin(whenFinished, account);
doLogin(account);
} else {
runWhenFinished();
runAuthCallback();
}
}
});
@@ -198,14 +200,22 @@ public class ModernAuthManager implements AuthManager {
});
}
private void runWhenFinished() {
if (whenFinished != null) {
private void runAuthCallback() {
lastAccount = null;
if (authCallback != null) {
(new Thread() {
@Override
public void run() {
whenFinished.run();
authCallback.onAuthResult(authToken != null);
authCallback = null;
}
}).start();
}
}
@Override
public Object getAccountObject(String accountName, String accountType) {
return new Account(accountName, accountType);
}
}
@@ -15,8 +15,8 @@
*/
package com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.ProgressIndicator;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.io.docs.DocsHelper;
@@ -25,15 +25,13 @@ import com.google.android.apps.mytracks.io.gdata.GDataWrapper;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.android.maps.mytracks.R;
import com.google.wireless.gdata.client.GDataClient;
import com.google.wireless.gdata.client.GDataServiceClient;
import com.google.wireless.gdata.docs.DocumentsClient;
import com.google.wireless.gdata.docs.SpreadsheetsClient;
import com.google.wireless.gdata.docs.XmlDocsGDataParserFactory;
import com.google.wireless.gdata.client.GDataServiceClient;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import java.io.IOException;
@@ -46,17 +44,15 @@ import java.io.IOException;
public class SendToDocs {
/** The GData service name for Google Spreadsheets (aka Trix) */
public static final String GDATA_SERVICE_NAME_TRIX = "wise";
/** The GData service name for the Google Docs Document List */
public static final String GDATA_SERVICE_NAME_DOCLIST = "writely";
private final Activity activity;
private final AuthManager trixAuth;
private final AuthManager docListAuth;
private final long trackId;
private final ProgressIndicator progressIndicator;
private final boolean metricUnits;
private final HandlerThread handlerThread;
private final Handler handler;
private boolean createdNewSpreadSheet = false;
@@ -64,12 +60,13 @@ public class SendToDocs {
private String statusMessage = "";
private Runnable onCompletion = null;
public SendToDocs(Activity activity, AuthManager trixAuth,
AuthManager docListAuth, long trackId) {
AuthManager docListAuth, ProgressIndicator progressIndicator) {
this.activity = activity;
this.trixAuth = trixAuth;
this.docListAuth = docListAuth;
this.trackId = trackId;
this.progressIndicator = progressIndicator;
SharedPreferences preferences = activity.getSharedPreferences(
Constants.SETTINGS_NAME, 0);
@@ -80,24 +77,20 @@ public class SendToDocs {
} else {
metricUnits = true;
}
}
public void sendToDocs(final long trackId) {
Log.d(Constants.TAG,
"Sending to Google Docs: trackId = " + trackId);
handlerThread = new HandlerThread("SendToGoogleDocs");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
public void run() {
handler.post(new Runnable() {
new Thread("SendToGoogleDocs") {
@Override
public void run() {
doUpload();
doUpload(trackId);
}
});
}.start();
}
private void doUpload() {
private void doUpload(long trackId) {
// TODO
statusMessage = activity.getString(R.string.error_sending_to_fusiontables);
success = false;
@@ -162,7 +155,7 @@ public class SendToDocs {
GDataWrapper<GDataServiceClient> docListWrapper = new GDataWrapper<GDataServiceClient>();
docListWrapper.setAuthManager(docListAuth);
docListWrapper.setRetryOnAuthFailure(true);
GDataWrapper<GDataServiceClient> trixWrapper = new GDataWrapper<GDataServiceClient>();
trixWrapper.setAuthManager(trixAuth);
trixWrapper.setRetryOnAuthFailure(true);
@@ -190,7 +183,7 @@ public class SendToDocs {
// First try to find the spreadsheet:
String spreadsheetId = null;
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(Constants.TAG, "Spreadsheet lookup failed.", e);
@@ -198,7 +191,7 @@ public class SendToDocs {
}
if (spreadsheetId == null) {
MyTracks.getInstance().setProgressValue(65);
progressIndicator.setProgressValue(65);
// Waiting a few seconds and trying again. Maybe the server just had a
// hickup (unfortunately that happens quite a lot...).
try {
@@ -206,18 +199,18 @@ public class SendToDocs {
} catch (InterruptedException e) {
Log.e(Constants.TAG, "Sleep interrupted", e);
}
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(Constants.TAG, "2nd spreadsheet lookup failed.", e);
return false;
}
}
// We were unable to find an existing spreadsheet, so create a new one.
MyTracks.getInstance().setProgressValue(70);
progressIndicator.setProgressValue(70);
if (spreadsheetId == null) {
Log.i(Constants.TAG, "Creating new spreadsheet: " + sheetTitle);
@@ -229,11 +222,11 @@ public class SendToDocs {
+ sheetTitle, e);
return false;
}
MyTracks.getInstance().setProgressValue(80);
progressIndicator.setProgressValue(80);
createdNewSpreadSheet = true;
if (spreadsheetId == null) {
MyTracks.getInstance().setProgressValue(85);
progressIndicator.setProgressValue(85);
// The previous creation might have succeeded even though GData
// reported an error. Seems to be a know bug,
// see http://code.google.com/p/gdata-issues/issues/detail?id=929
@@ -247,7 +240,7 @@ public class SendToDocs {
}
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(Constants.TAG, "Failed create-failed lookup", e);
@@ -255,16 +248,16 @@ public class SendToDocs {
}
if (spreadsheetId == null) {
MyTracks.getInstance().setProgressValue(87);
progressIndicator.setProgressValue(87);
// Re-try
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Log.e(Constants.TAG, "Sleep interrupted", e);
}
try {
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
spreadsheetId = docsHelper.requestSpreadsheetId(docListWrapper,
sheetTitle);
} catch (IOException e) {
Log.i(Constants.TAG, "Failed create-failed relookup", e);
@@ -290,9 +283,9 @@ public class SendToDocs {
return false;
}
MyTracks.getInstance().setProgressValue(90);
progressIndicator.setProgressValue(90);
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
docsHelper.addTrackRow(activity, trixAuth, spreadsheetId, worksheetId,
track, metricUnits);
Log.i(Constants.TAG, "Done uploading to docs.");
} catch (IOException e) {
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.io;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.ProgressIndicator;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
@@ -156,6 +158,11 @@ public class SendToFusionTables implements Runnable {
}
public static String getMapVisualizationUrl(Track track) {
if (track == null || track.getStatistics() == null || track.getTableId() == null) {
Log.w(TAG, "Unable to get track URL");
return null;
}
// TODO(leifhendrik): Determine correct bounding box and zoom level that will show the entire track.
TripStatistics stats = track.getStatistics();
double latE6 = stats.getBottom() + (stats.getTop() - stats.getBottom()) / 2;
@@ -179,12 +186,17 @@ public class SendToFusionTables implements Runnable {
// Get the track meta-data
Track track = providerUtils.getTrack(trackId);
if (track == null) {
Log.w(Constants.TAG, "Cannot get track.");
return;
}
String originalDescription = track.getDescription();
// Create a new table:
progressIndicator.setProgressValue(PROGRESS_FUSION_TABLE_CREATE);
progressIndicator.setProgressMessage(R.string.progress_message_creating_fusiontable);
if (!createNewTable(track) || !makeTableUnlisted(tableId)) {
if (!createNewTable(track) || !makeTableUnlisted()) {
return;
}
@@ -207,7 +219,6 @@ public class SendToFusionTables implements Runnable {
Log.d(Constants.TAG, "SendToFusionTables: Done: " + success);
progressIndicator.setProgressValue(PROGRESS_COMPLETE);
} finally {
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
context.runOnUiThread(new Runnable() {
@@ -234,7 +245,7 @@ public class SendToFusionTables implements Runnable {
return runUpdate(query);
}
private boolean makeTableUnlisted(String tableId) {
private boolean makeTableUnlisted() {
Log.d(Constants.TAG, "Setting visibility to unlisted.");
String query = "UPDATE TABLE " + tableId + " SET VISIBILITY = UNLISTED";
return runUpdate(query);
@@ -304,7 +315,7 @@ public class SendToFusionTables implements Runnable {
Cursor locationsCursor = providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
try {
if (!locationsCursor.moveToFirst()) {
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
Log.w(Constants.TAG, "Unable to get any points to upload");
return false;
}
@@ -391,7 +402,9 @@ public class SendToFusionTables implements Runnable {
return true;
} finally {
locationsCursor.close();
if (locationsCursor != null) {
locationsCursor.close();
}
}
}
@@ -420,10 +433,10 @@ public class SendToFusionTables implements Runnable {
* @return the kml.
*/
private String getKmlPoint(Location location) {
StringBuilder builder = new StringBuilder("<Point><coordinates>");
appendCoordinate(location, builder);
builder.append("</coordinates></Point>");
return builder.toString();
StringBuilder builder = new StringBuilder("<Point><coordinates>");
appendCoordinate(location, builder);
builder.append("</coordinates></Point>");
return builder.toString();
}
/**
@@ -599,7 +612,7 @@ public class SendToFusionTables implements Runnable {
// I am leaving the number of waypoints very high which should not be a
// problem because we don't try to load them into objects all at the
// same time.
boolean success = true;
boolean success = true;
Cursor c = null;
try {
c = providerUtils.getWaypointsCursor(
@@ -613,9 +626,9 @@ public class SendToFusionTables implements Runnable {
Waypoint wpt = providerUtils.createWaypoint(c);
Log.d(Constants.TAG, "SendToFusionTables: Creating waypoint.");
success = createNewPoint(wpt.getName(), wpt.getDescription(), wpt.getLocation(),
MARKER_TYPE_WAYPOINT);
MARKER_TYPE_WAYPOINT);
if (!success) {
break;
break;
}
}
}
@@ -637,7 +650,7 @@ public class SendToFusionTables implements Runnable {
// to fit the completion percentage range alloted to track data upload.
double totalPercentage =
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
/ (totalLocations * 3);
/ (totalLocations * 3.0);
double scaledPercentage = totalPercentage
* (PROGRESS_UPLOAD_DATA_MAX - PROGRESS_UPLOAD_DATA_MIN) + PROGRESS_UPLOAD_DATA_MIN;
@@ -681,8 +694,8 @@ public class SendToFusionTables implements Runnable {
boolean success = response.isSuccessStatusCode;
if (success) {
byte[] result = new byte[1024];
response.getContent().read(result);
String s = Strings.fromBytesUtf8(result);
int read = response.getContent().read(result);
String s = new String(result, 0, read, "UTF8");
String[] lines = s.split(Strings.LINE_SEPARATOR);
if (lines[0].equals("tableid")) {
tableId = lines[1];
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -76,7 +76,7 @@ public class SendToMyMaps implements Runnable {
public interface OnSendCompletedListener {
void onSendCompleted(String mapId, boolean success, int statusMessage);
}
public SendToMyMaps(Activity context, String mapId, AuthManager auth,
long trackId, ProgressIndicator progressIndicator,
OnSendCompletedListener onCompletion) {
@@ -100,18 +100,22 @@ public class SendToMyMaps implements Runnable {
int statusMessageId = R.string.error_sending_to_mymap;
boolean success = true;
try {
progressIndicator.setProgressValue(1);
progressIndicator.setProgressMessage(
R.string.progress_message_reading_track);
// Get the track meta-data
Track track = providerUtils.getTrack(trackId);
if (track == null) {
Log.w(Constants.TAG, "Cannot get track.");
return;
}
String originalDescription = track.getDescription();
track.setDescription("<p>" + track.getDescription() + "</p><p>"
+ stringUtils.generateTrackDescription(track, null, null) + "</p>");
mapsClient = new MapsFacade(context, auth);
// Create a new map if necessary:
boolean isNewMap = mapId.equals(NEW_MAP_ID);
if (isNewMap) {
@@ -166,7 +170,7 @@ public class SendToMyMaps implements Runnable {
Log.w(TAG, "SendToMyMaps: upload waypoints failed.");
}
}
if (success) {
statusMessageId = isNewMap
? R.string.status_new_mymap_has_been_created
@@ -178,7 +182,7 @@ public class SendToMyMaps implements Runnable {
if (mapsClient != null) {
mapsClient.cleanUp();
}
final boolean finalSuccess = success;
final int finalStatusMessageId = statusMessageId;
context.runOnUiThread(new Runnable() {
@@ -206,17 +210,17 @@ public class SendToMyMaps implements Runnable {
Cursor locationsCursor =
providerUtils.getLocationsCursor(track.getId(), 0, -1, false);
try {
if (!locationsCursor.moveToFirst()) {
if (locationsCursor == null || !locationsCursor.moveToFirst()) {
Log.w(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));
@@ -224,12 +228,12 @@ public class SendToMyMaps implements Runnable {
"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(Constants.ELEVATION_SMOOTHING_FACTOR);
List<Location> locations = new ArrayList<Location>(MAX_POINTS_PER_UPLOAD);
progressIndicator.setProgressMessage(
R.string.progress_message_reading_track);
@@ -238,15 +242,15 @@ public class SendToMyMaps implements Runnable {
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:
mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(), loc, true);
}
// Add to the elevation profile.
if (loc != null && LocationUtils.isValidLocation(loc)) {
// All points go into the smoothing buffer...
@@ -256,19 +260,19 @@ public class SendToMyMaps implements Runnable {
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) {
@@ -276,28 +280,30 @@ public class SendToMyMaps implements Runnable {
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 mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
return mapsClient.uploadMarker(mapId, track.getName(), track.getDescription(),
lastLocation, false);
}
return true;
} finally {
locationsCursor.close();
if (locationsCursor != null) {
locationsCursor.close();
}
}
}
@@ -306,17 +312,17 @@ public class SendToMyMaps implements Runnable {
progressIndicator.setProgressMessage(
R.string.progress_message_preparing_track);
updateProgress();
int numLocations = locations.size();
if (numLocations < 2) {
Log.d(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);
@@ -330,7 +336,7 @@ public class SendToMyMaps implements Runnable {
Log.d(TAG,
"SendToMyMaps: Prepared feature for upload w/ "
+ splitTrack.getLocations().size() + " points.");
// Transmit tracks via GData feed:
// -------------------------------
Log.d(TAG,
@@ -340,7 +346,7 @@ public class SendToMyMaps implements Runnable {
return false;
}
}
locations.clear();
totalLocationsUploaded += numLocations;
updateProgress();
@@ -358,7 +364,7 @@ public class SendToMyMaps implements Runnable {
private ArrayList<Track> prepareLocations(
Track track, Iterable<Location> locations) {
ArrayList<Track> splitTracks = new ArrayList<Track>();
// Create segments from each full track:
Track segment = new Track();
TripStatistics segmentStats = segment.getStatistics();
@@ -377,11 +383,11 @@ public class SendToMyMaps implements Runnable {
if (loc.getLatitude() > 90) {
startNewTrackSegment = true;
}
if (startNewTrackSegment) {
// Close up the last segment.
prepareTrackSegment(segment, splitTracks);
Log.d(TAG,
"MyTracksSendToMyMaps: Starting new track segment...");
startNewTrackSegment = false;
@@ -391,7 +397,7 @@ public class SendToMyMaps implements Runnable {
segment.setDescription(/* track.getDescription() */ "");
segment.setCategory(track.getCategory());
}
if (loc.getLatitude() <= 90) {
segment.addLocation(loc);
if (segmentStats.getStartTime() < 0) {
@@ -400,9 +406,9 @@ public class SendToMyMaps implements Runnable {
}
totalLocationsPrepared++;
}
prepareTrackSegment(segment, splitTracks);
return splitTracks;
}
@@ -425,13 +431,13 @@ public class SendToMyMaps implements Runnable {
&& segment.getLocations().size() > 0) {
segmentStats.setStopTime(segment.getLocations().size() - 1);
}
/*
* Decimate to 2 meter precision. Mapshop doesn't like too many
* points:
*/
LocationUtils.decimate(segment, 2.0);
/* It the track still has > 500 points, split it in pieces: */
if (segment.getLocations().size() > 500) {
splitTracks.addAll(LocationUtils.split(segment, 500));
@@ -447,7 +453,7 @@ public class SendToMyMaps implements Runnable {
// The percent of the total that represents the completed part of this
// segment.
int totalPercentage =
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded)
(totalLocationsRead + totalLocationsPrepared + totalLocationsUploaded) * 100
/ (totalLocations * 3);
totalPercentage = Math.min(99, totalPercentage);
progressIndicator.setProgressValue(totalPercentage);
@@ -185,7 +185,7 @@ public class BackupActivityHelper {
*/
private void restoreFromDateAsync(final Date date) {
// Show a progress dialog
final ProgressDialog progressDialog = ProgressDialog.show(
ProgressDialog.show(
activity,
activity.getString(R.string.progress_title),
activity.getString(R.string.backup_import_progress_message),
@@ -202,8 +202,6 @@ public class BackupActivityHelper {
Log.e(Constants.TAG, "Failed to restore backup", e);
showToast(R.string.io_read_failed);
} finally {
dismissDialog(progressDialog);
// Data may have been restored, "reboot" the app to catch it
restartApplication();
}
@@ -80,12 +80,13 @@ class DatabaseDumper {
*
* @param cursor the cursor to get columns from
* @param numRows the number of rows that will be later written
* @param writer the output to write to
* @throws IOException if there are errors while writing
*/
public void writeHeaders(Cursor cursor, int numRows, DataOutputStream writer)
throws IOException {
initializeCachedValues(cursor);
writeQueryMetadata(cursor, numRows, writer);
writeQueryMetadata(numRows, writer);
}
/**
@@ -93,6 +94,7 @@ class DatabaseDumper {
* This must be called after {@link #writeHeaders}.
*
* @param cursor the cursor to write data from
* @param writer the output to write to
* @throws IOException if there are any errors while writing
*/
public void writeOneRow(Cursor cursor, DataOutputStream writer)
@@ -146,12 +148,12 @@ class DatabaseDumper {
/**
* Writes metadata about the query to be dumped.
*
* @param cursor the cursor that the data will come from
* @param numRows the number of rows that will be dumped
* @param writer the output to write to
* @throws IOException if there are any errors while writing
*/
private void writeQueryMetadata(
Cursor cursor, int numRows, DataOutputStream writer) throws IOException {
int numRows, DataOutputStream writer) throws IOException {
// Write column data
writer.writeInt(columnNames.length);
for (int i = 0; i < columnNames.length; i++) {
@@ -171,6 +173,7 @@ class DatabaseDumper {
* @param columnIdx the column index to read from
* @param columnTypeId the type of the column to be read
* @param cursor the cursor to read from
* @param writer the output to write to
* @throws IOException if there are any errors while writing
*/
private void writeCell(
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.io.backup;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.TrackPointsColumns;
import com.google.android.apps.mytracks.content.TracksColumns;
@@ -205,7 +207,9 @@ class ExternalFileBackup {
} catch (IOException e) {
// We tried to delete the partially created file, but do nothing
// if that also fails.
outputFile.delete();
if (!outputFile.delete()) {
Log.w(TAG, "Failed to delete file " + outputFile.getAbsolutePath());
}
throw e;
} finally {
@@ -69,22 +69,22 @@ public class DocsHelper {
private static final String OPENDOCUMENT_SPREADSHEET_MIME_TYPE =
"application/x-vnd.oasis.opendocument.spreadsheet";
private static final String ATOM_FEED_MIME_TYPE = "application/atom+xml";
/**
* Creates a new MyTracks spreadsheet with the given name.
*
*
* @param context The context associated with this request.
* @param docListWrapper The GData handle for the Document List service.
* @param name The name for the newly-created spreadsheet.
* @return The spreadsheet ID, if one is created. {@code null} will be
* returned if a GData error didn't occur, but no spreadsheet ID was
* returned.
* returned.
*/
public String createSpreadsheet(final Context context,
public String createSpreadsheet(final Context context,
final GDataWrapper<GDataServiceClient> docListWrapper, final String name) throws IOException {
final AtomicReference<String> idSaver = new AtomicReference<String>();
boolean result = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
boolean success = docListWrapper.runQuery(new QueryFunction<GDataServiceClient>() {
@Override
public void query(GDataServiceClient client) throws IOException,
GDataWrapper.AuthenticationException {
@@ -95,7 +95,7 @@ public class DocsHelper {
OPENDOCUMENT_SPREADSHEET_MIME_TYPE);
conn.addRequestProperty("Slug", name);
conn.addRequestProperty("Authorization",
"GoogleLogin auth=" +
"GoogleLogin auth=" +
docListWrapper.getAuthManager().getAuthToken());
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
@@ -141,24 +141,24 @@ public class DocsHelper {
if (idStringStart == -1) {
return;
}
String id = result.substring(
idStringStart + DOCS_SPREADSHEET_URL.length(), idTagCloseIndex);
Log.i(Constants.TAG, "Created new spreadsheet: " + id);
idSaver.set(id);
}});
if (!result) {
throw newIOException(docListWrapper,
if (!success) {
throw newIOException(docListWrapper,
"Failed to create new spreadsheet.");
}
return idSaver.get();
}
/**
* Retrieve the ID of a spreadsheet with the given name.
*
*
* @param docListWrapper The GData handle for the Document List service.
* @param title The name of the spreadsheet whose ID is to be retrieved.
* @return The spreadsheet ID, if it can be retrieved. {@code null} will
@@ -177,7 +177,7 @@ public class DocsHelper {
GDataParser listParser;
try {
listParser = client.getParserForFeed(Entry.class,
DOCS_MY_SPREADSHEETS_FEED_URL,
DOCS_MY_SPREADSHEETS_FEED_URL,
docListWrapper.getAuthManager().getAuthToken());
listParser.init();
@@ -201,22 +201,22 @@ public class DocsHelper {
});
if (!result) {
throw newIOException(docListWrapper,
throw newIOException(docListWrapper,
"Failed to retrieve spreadsheet list.");
}
return idSaver.get();
}
/**
* Retrieve the ID of the first worksheet in the named spreadsheet.
*
*
* @param trixWrapper The GData handle for the spreadsheet service.
* @param spreadsheetId The GData ID for the given spreadsheet.
* @return The worksheet ID, if it can be retrieved. {@code null} will be
* returned if the GData request returns without error, but without an
* ID.
* @throws IOException If an error occurs during the GData request.
* @throws IOException If an error occurs during the GData request.
*/
public String getWorksheetId(final GDataWrapper<GDataServiceClient> trixWrapper,
final String spreadsheetId) throws IOException {
@@ -250,17 +250,17 @@ public class DocsHelper {
}
}
});
if (!result) {
throw newIOException(trixWrapper, "Failed to retrieve worksheet ID.");
}
return idSaver.get();
}
/**
* Add a row to a worksheet containing the stats for a given track.
*
*
* @param context The context associated with this request.
* @param trixAuth The GData authorization for the spreadsheet service.
* @param spreadsheetId The spreadsheet to be modified.
@@ -270,17 +270,17 @@ public class DocsHelper {
* imperial units will be used.
* @throws IOException If an error occurs while updating the worksheet.
*/
public void addTrackRow(Context context, AuthManager trixAuth,
String spreadsheetId, String worksheetId, Track track,
public void addTrackRow(Context context, AuthManager trixAuth,
String spreadsheetId, String worksheetId, Track track,
boolean metricUnits) throws IOException {
String worksheetUri = String.format(DOCS_SPREADSHEET_URL_FORMAT,
spreadsheetId, worksheetId);
TripStatistics stats = track.getStatistics();
String distanceUnit = context.getString(metricUnits ?
String distanceUnit = context.getString(metricUnits ?
R.string.kilometer : R.string.mile);
String speedUnit = context.getString(metricUnits ?
String speedUnit = context.getString(metricUnits ?
R.string.kilometer_per_hour : R.string.mile_per_hour);
String elevationUnit = context.getString(metricUnits ?
R.string.meter : R.string.feet);
@@ -310,7 +310,7 @@ public class DocsHelper {
tagBuilder.append("map", String.format("%s?msa=0&msid=%s",
Constants.MAPSHOP_BASE_URL, track.getMapId()));
}
String postText = new StringBuilder()
.append("<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gsx='http://schemas.google.com/spreadsheets/"
@@ -323,7 +323,7 @@ public class DocsHelper {
"Inserting at: " + spreadsheetId + " => " + worksheetUri);
Log.i(Constants.TAG, postText);
writeRowData(trixAuth, worksheetUri, postText);
Log.i(Constants.TAG, "Post finished.");
@@ -331,13 +331,13 @@ public class DocsHelper {
/**
* Writes spreadsheet row data to the indicated worksheet.
*
*
* @param trixAuth The GData authorization for the spreadsheet service.
* @param worksheetUri The URI of the worksheet to be altered.
* @param postText The XML tags describing the change to be made.
* @throws IOException Thrown if an error occurs during the write.
*/
protected void writeRowData(AuthManager trixAuth, String worksheetUri,
protected void writeRowData(AuthManager trixAuth, String worksheetUri,
String postText) throws IOException {
// No need for a wrapper because we know that the authorization was good
// enough to get this far.
@@ -364,9 +364,9 @@ public class DocsHelper {
rd.close();
}
private static IOException newIOException(GDataWrapper<GDataServiceClient> wrapper,
private static IOException newIOException(GDataWrapper<GDataServiceClient> wrapper,
String message) {
return new IOException(String.format("%s: %d: %s", message,
return new IOException(String.format("%s: %d: %s", message,
wrapper.getErrorType(), wrapper.getErrorMessage()));
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -51,7 +51,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
SHORT_FORMAT.setMaximumFractionDigits(4);
SHORT_FORMAT.setMaximumFractionDigits(4);
TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@@ -65,6 +65,7 @@ public class CsvTrackWriter implements TrackFormatWriter {
return TrackFileFormat.CSV.getExtension();
}
@SuppressWarnings("hiding")
@Override
public void prepare(Track track, OutputStream out) {
this.track = track;
@@ -67,6 +67,7 @@ public class GpxTrackWriter implements TrackFormatWriter {
+ "\" lon=\"" + coordinateFormatter.format(l.getLongitude()) + "\"";
}
@SuppressWarnings("hiding")
@Override
public void prepare(Track track, OutputStream out) {
this.track = track;
@@ -0,0 +1,216 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
/**
* Helper activity which imports tracks from a file.
*
* @author Rodrigo Damazio
*/
public class ImportActivity extends Activity {
private static final int PROGRESS_DIALOG = 1;
private static final int SUCCESS_DIALOG = 2;
private static final int FAILURE_DIALOG = 3;
private MyTracksProviderUtils providerUtils;
private ProgressDialog progressDialog;
private int resultMessage;
private long importedTrackIds[];
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
}
@Override
public void onStart() {
super.onStart();
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if (!(Intent.ACTION_VIEW.equals(action) || Intent.ACTION_ATTACH_DATA.equals(action))
|| !UriUtils.isFileUri(data)) {
Log.e(TAG, "Received an intent with unsupported action or data: " + intent);
finish();
return;
}
Log.i(TAG, "Importing GPX file at " + data);
startTrackImport(data.getPath());
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(this);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.progress_title);
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setMessage(getString(R.string.import_progress_message));
return progressDialog;
case SUCCESS_DIALOG:
final Builder dialogBuilder = new AlertDialog.Builder(this);
dialogBuilder.setCancelable(true);
dialogBuilder.setPositiveButton(android.R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
finish();
}
});
dialogBuilder.setNeutralButton(R.string.import_show_track, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
showImportedTrack();
finish();
}
});
return dialogBuilder.create();
case FAILURE_DIALOG:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setNeutralButton(R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
finish();
}
});
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setTitle(R.string.error);
return builder.create();
}
return null;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
switch (id) {
case SUCCESS_DIALOG:
AlertDialog successDialog = (AlertDialog) dialog;
successDialog.setMessage(getString(R.string.import_success, importedTrackIds.length));
break;
case FAILURE_DIALOG:
AlertDialog failureDialog = (AlertDialog) dialog;
failureDialog.setMessage(getString(resultMessage));
break;
}
}
protected void showImportedTrack() {
long lastTrackId = importedTrackIds[importedTrackIds.length - 1];
Uri trackUri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, lastTrackId);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(trackUri, TracksColumns.CONTENT_ITEMTYPE);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
private void startTrackImport(final String fileName) {
showDialog(PROGRESS_DIALOG);
Thread t = new Thread() {
@Override
public void run() {
resultMessage = R.string.success;
importedTrackIds = null;
try {
try {
InputStream is = new FileInputStream(fileName);
importedTrackIds = GpxImporter.importGPXFile(is, providerUtils);
} catch (SAXException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_generic;
} catch (ParserConfigurationException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_generic;
} catch (IOException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_unable_to_read_file;
} catch (NullPointerException e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_invalid_gpx_format;
} catch (OutOfMemoryError e) {
Log.e(TAG, "Caught an unexpected exception.", e);
resultMessage = R.string.error_out_of_memory;
}
boolean success = (importedTrackIds != null && importedTrackIds.length > 0);
showImportResult(success);
} finally {
runOnUiThread(new Runnable() {
public void run() {
dismissDialog(PROGRESS_DIALOG);
}
});
}
}
};
t.start();
}
private void showImportResult(boolean success) {
final int dialogToShow = success ? SUCCESS_DIALOG : FAILURE_DIALOG;
// Show a dialog telling the user about the import, and asking if he wishes
// to open the track right away.
runOnUiThread(new Runnable() {
@Override
public void run() {
showDialog(dialogToShow);
}
});
}
}
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -52,6 +52,7 @@ public class KmlTrackWriter implements TrackFormatWriter {
this.stringUtils = stringUtils;
}
@SuppressWarnings("hiding")
@Override
public void prepare(Track track, OutputStream out) {
this.track = track;
@@ -0,0 +1,236 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.file.TrackWriterFactory.TrackFileFormat;
import com.google.android.apps.mytracks.util.FileUtils;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import java.io.File;
/**
* Activity for saving a track to a file (and optionally sending that file).
*
* @author Rodrigo Damazio
*/
public class SaveActivity extends Activity {
public static final String EXTRA_SHARE_FILE = "share_file";
public static final String EXTRA_FILE_FORMAT = "file_format";
private static final int RESULT_DIALOG = 1;
/* VisibleForTesting */
static final int PROGRESS_DIALOG = 2;
private MyTracksProviderUtils providerUtils;
private long trackId;
private TrackWriter writer;
private boolean shareFile;
private TrackFileFormat format;
private WriteProgressController controller;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
Uri data = intent.getData();
if (!getString(R.string.save_intent_action).equals(action) ||
!TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
Log.e(TAG, "Got bad save intent: " + intent);
finish();
return;
}
trackId = ContentUris.parseId(data);
int formatIdx = intent.getIntExtra(EXTRA_FILE_FORMAT, -1);
format = TrackFileFormat.values()[formatIdx];
shareFile = intent.getBooleanExtra(EXTRA_SHARE_FILE, false);
writer = TrackWriterFactory.newWriter(this, providerUtils, trackId, format);
if (writer == null) {
Log.e(TAG, "Unable to build writer");
finish();
return;
}
if (shareFile) {
// If the file is for sending, save it to a temporary location instead.
FileUtils fileUtils = new FileUtils();
String extension = format.getExtension();
String dirName = fileUtils.buildExternalDirectoryPath(extension, "tmp");
File dir = new File(dirName);
writer.setDirectory(dir);
}
controller = new WriteProgressController(this, writer, PROGRESS_DIALOG);
controller.setOnCompletionListener(new WriteProgressController.OnCompletionListener() {
@Override
public void onComplete() {
onWriteComplete();
}
});
controller.startWrite();
}
private void onWriteComplete() {
if (shareFile) {
shareWrittenFile();
} else {
showResultDialog();
}
}
private void shareWrittenFile() {
if (!writer.wasSuccess()) {
showResultDialog();
return;
}
// Share the file.
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getResources().getText(R.string.send_track_subject).toString());
shareIntent.putExtra(Intent.EXTRA_TEXT,
getResources().getText(R.string.send_track_body_format)
.toString());
shareIntent.setType(format.getMimeType());
Uri u = Uri.fromFile(new File(writer.getAbsolutePath()));
shareIntent.putExtra(Intent.EXTRA_STREAM, u);
startActivity(Intent.createChooser(shareIntent,
getResources().getText(R.string.share_track).toString()));
}
private void showResultDialog() {
removeDialog(RESULT_DIALOG);
showDialog(RESULT_DIALOG);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case RESULT_DIALOG:
return createResultDialog();
case PROGRESS_DIALOG:
if (controller != null) {
return controller.createProgressDialog();
}
//$FALL-THROUGH$
default:
return super.onCreateDialog(id);
}
}
private Dialog createResultDialog() {
boolean success = writer.wasSuccess();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(writer.getErrorMessage());
builder.setNeutralButton(R.string.ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int arg1) {
dialog.dismiss();
finish();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
dialog.dismiss();
finish();
}
});
builder.setIcon(success ? android.R.drawable.ic_dialog_info :
android.R.drawable.ic_dialog_alert);
builder.setTitle(success ? R.string.success : R.string.error);
return builder.create();
}
public static void handleExportTrackAction(Context ctx, long trackId, int actionCode) {
if (trackId < 0) {
return;
}
TrackFileFormat exportFormat = null;
switch (actionCode) {
case Constants.SAVE_GPX_FILE:
case Constants.SHARE_GPX_FILE:
exportFormat = TrackFileFormat.GPX;
break;
case Constants.SAVE_KML_FILE:
case Constants.SHARE_KML_FILE:
exportFormat = TrackFileFormat.KML;
break;
case Constants.SAVE_CSV_FILE:
case Constants.SHARE_CSV_FILE:
exportFormat = TrackFileFormat.CSV;
break;
case Constants.SAVE_TCX_FILE:
case Constants.SHARE_TCX_FILE:
exportFormat = TrackFileFormat.TCX;
break;
default:
throw new IllegalArgumentException("Warning unhandled action code: " + actionCode);
}
boolean shareFile = false;
switch (actionCode) {
case Constants.SHARE_GPX_FILE:
case Constants.SHARE_KML_FILE:
case Constants.SHARE_CSV_FILE:
case Constants.SHARE_TCX_FILE:
shareFile = true;
}
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
Intent intent = new Intent(ctx, SaveActivity.class);
intent.setAction(ctx.getString(R.string.save_intent_action));
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
intent.putExtra(EXTRA_FILE_FORMAT, exportFormat.ordinal());
intent.putExtra(EXTRA_SHARE_FILE, shareFile);
ctx.startActivity(intent);
}
}
@@ -71,6 +71,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
timestampFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@SuppressWarnings("hiding")
@Override
public void prepare(Track track, OutputStream out) {
this.track = track;
@@ -187,7 +188,7 @@ public class TcxTrackWriter implements TrackFormatWriter {
&& sensorData.getCadence().getState() == Sensor.SensorState.SENDING
&& sensorData.getCadence().hasValue()) {
pw.print(" <Cadence>");
pw.print(sensorData.getCadence().getValue());
pw.print(Math.min(254, sensorData.getCadence().getValue()));
pw.println("</Cadence>");
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,7 +15,10 @@
*/
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import android.os.Environment;
import android.util.Log;
import java.io.File;
@@ -24,18 +27,18 @@ import java.io.File;
* @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)) {
@@ -64,7 +67,9 @@ public class TempFileCleaner {
long oldest = currentTimeMillis - 1000 * 3600;
for (File f : dir.listFiles()) {
if (f.lastModified() < oldest) {
f.delete();
if (!f.delete()) {
Log.w(TAG, "Failed to delete file " + f.getAbsolutePath());
}
count++;
}
}
@@ -16,10 +16,13 @@
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import android.content.Context;
import android.util.Log;
/**
* A factory to produce track writers for any format.
@@ -90,6 +93,11 @@ public class TrackWriterFactory {
MyTracksProviderUtils providerUtils,
long trackId, TrackFileFormat format) {
Track track = providerUtils.getTrack(trackId);
if (track == null) {
Log.w(TAG, "Trying to create a writer for an invalid track, id=" + trackId);
return null;
}
return newWriter(context, providerUtils, track, format);
}
@@ -15,6 +15,8 @@
*/
package com.google.android.apps.mytracks.io.file;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.content.MyTracksLocation;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
@@ -120,7 +122,9 @@ class TrackWriterImpl implements TrackWriter {
} catch (InterruptedException e) {
Log.i(Constants.TAG, "The track write was interrupted");
if (file != null) {
file.delete();
if (!file.delete()) {
Log.w(TAG, "Failed to delete file " + file.getAbsolutePath());
}
}
success = false;
errorMessage = R.string.error_operation_cancelled;
@@ -195,7 +199,7 @@ class TrackWriterImpl implements TrackWriter {
directory, track.getName(), writer.getExtension());
if (fileName == null) {
Log.e(Constants.TAG,
"Unable to get a unique filename for " + fileName);
"Unable to get a unique filename for " + track.getName());
return false;
}
@@ -276,9 +280,7 @@ class TrackWriterImpl implements TrackWriter {
}
}
} finally {
if (cursor != null) {
cursor.close();
}
cursor.close();
}
}
}
@@ -13,9 +13,8 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks;
package com.google.android.apps.mytracks.io.file;
import com.google.android.apps.mytracks.io.file.TrackWriter;
import com.google.android.maps.mytracks.R;
import android.app.Activity;
@@ -41,14 +40,15 @@ class WriteProgressController {
* dialog has been dismissed. Whether the write succeeded can be
* determined by examining the {@link TrackWriter}.
*/
public void onComplete(TrackWriter writer);
public void onComplete();
}
private final Activity activity;
private final TrackWriter writer;
private final ProgressDialog dialog;
private ProgressDialog dialog;
private OnCompletionListener onCompletionListener;
private final int progressDialogId;
/**
* @param activity the activity associated with this write
@@ -57,17 +57,10 @@ class WriteProgressController {
* interested in notification upon completion of the write, they should
* use {@link #setOnCompletionListener}.
*/
public WriteProgressController(Activity activity, TrackWriter writer) {
public WriteProgressController(Activity activity, TrackWriter writer, int progressDialogId) {
this.activity = activity;
this.writer = writer;
dialog = new ProgressDialog(activity);
dialog.setIcon(android.R.drawable.ic_dialog_info);
dialog.setTitle(activity.getString(R.string.progress_title));
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage(activity.getString(R.string.write_progress_message));
dialog.setIndeterminate(true);
dialog.setOnCancelListener(dialogCancelListener);
this.progressDialogId = progressDialogId;
writer.setOnCompletionListener(writerCompleteListener);
writer.setOnWriteListener(writerWriteListener);
@@ -78,9 +71,20 @@ class WriteProgressController {
this.onCompletionListener = onCompletionListener;
}
public ProgressDialog createProgressDialog() {
dialog = new ProgressDialog(activity);
dialog.setIcon(android.R.drawable.ic_dialog_info);
dialog.setTitle(activity.getString(R.string.progress_title));
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage(activity.getString(R.string.write_progress_message));
dialog.setIndeterminate(true);
dialog.setOnCancelListener(dialogCancelListener);
return dialog;
}
/** Initiate an asynchronous write. */
public void startWrite() {
DialogManager.showDialogSafely(activity, dialog);
activity.showDialog(progressDialogId);
writer.writeTrackAsync();
}
@@ -92,7 +96,7 @@ class WriteProgressController {
private final DialogInterface.OnCancelListener dialogCancelListener =
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
public void onCancel(DialogInterface dialogInterface) {
writer.stopWriteTrack();
}
};
@@ -101,10 +105,10 @@ class WriteProgressController {
new TrackWriter.OnCompletionListener() {
@Override
public void onComplete() {
DialogManager.dismissDialogSafely(activity, dialog);
activity.dismissDialog(progressDialogId);
if (onCompletionListener != null) {
onCompletionListener.onComplete(writer);
onCompletionListener.onComplete();
}
}
};
@@ -1,12 +1,12 @@
/*
* 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
@@ -137,7 +137,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
*
*
* @see GDataClient#encodeUri(java.lang.String)
*/
public String encodeUri(String uri) {
@@ -155,7 +155,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
*
*
* @see com.google.wireless.gdata.client.GDataClient#createQueryParams()
*/
public QueryParams createQueryParams() {
@@ -273,7 +273,7 @@ public class AndroidGDataClient implements GDataClient {
String errorMessage = null;
HttpEntity entity = response.getEntity();
try {
if (response != null && entity != null) {
if (entity != null) {
InputStream in = entity.getContent();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
@@ -329,7 +329,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
*
*
* @see GDataClient#getFeedAsStream(java.lang.String, java.lang.String)
*/
public InputStream getFeedAsStream(String feedUrl, String authToken)
@@ -358,7 +358,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
*
*
* @see GDataClient#createEntry
*/
public InputStream createEntry(String feedUrl, String authToken,
@@ -377,7 +377,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
*
*
* @see GDataClient#updateEntry
*/
public InputStream updateEntry(String editUri, String authToken,
@@ -395,7 +395,7 @@ public class AndroidGDataClient implements GDataClient {
/*
* (non-Javadoc)
*
*
* @see GDataClient#deleteEntry
*/
public void deleteEntry(String editUri, String authToken)
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -17,6 +17,7 @@ package com.google.android.apps.mytracks.io.gdata;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
import android.util.Log;
@@ -26,6 +27,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -132,7 +134,7 @@ public class GDataWrapper<C> {
public static final int ERROR_CLEANED_UP = 7;
// An unknown error occurred.
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;
@@ -141,7 +143,7 @@ public class GDataWrapper<C> {
private C gdataServiceClient;
private AuthManager auth;
private boolean retryOnAuthFailure;
public GDataWrapper() {
errorType = ERROR_NO_ERROR;
errorMessage = null;
@@ -179,7 +181,7 @@ public class GDataWrapper<C> {
return false;
}
}
Log.d(Constants.TAG, "retrying function/query");
}
return false;
@@ -189,7 +191,7 @@ public class GDataWrapper<C> {
* 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,
private void runOne(final AuthenticatedFunction function,
final QueryFunction<C> query) {
try {
if (function != null) {
@@ -210,7 +212,7 @@ public class GDataWrapper<C> {
errorType = ERROR_AUTH;
errorMessage = e.getMessage();
} catch (HttpException e) {
Log.e(Constants.TAG,
Log.e(Constants.TAG,
"HttpException, code " + e.getStatusCode() + " message " + e.getMessage(), e);
errorMessage = e.getMessage();
if (e.getStatusCode() == 401) {
@@ -241,10 +243,10 @@ public class GDataWrapper<C> {
}
}
/**
/**
* 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.
*/
@@ -252,19 +254,26 @@ public class GDataWrapper<C> {
Log.d(Constants.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() {
final FutureTask<?> whenFinishedFuture = new FutureTask<Object>(new Runnable() {
public void run() {}
}, null);
auth.invalidateAndRefresh(whenFinishedFuture);
final AtomicBoolean finalSuccess = new AtomicBoolean(false);
auth.invalidateAndRefresh(new AuthCallback() {
@Override
public void onAuthResult(boolean success) {
finalSuccess.set(success);
whenFinishedFuture.run();
}
});
try {
Log.d(Constants.TAG, "waiting for invalidate");
whenFinishedFuture.get(AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT,
whenFinishedFuture.get(AUTH_TOKEN_INVALIDATE_REFRESH_TIMEOUT,
TimeUnit.MILLISECONDS);
Log.d(Constants.TAG, "invalidate finished");
return true;
boolean success = finalSuccess.get();
Log.d(Constants.TAG, "invalidate finished, success = " + success);
return success;
} catch (InterruptedException e) {
Log.e(Constants.TAG, "Failed to invalidate", e);
} catch (ExecutionException e) {
@@ -274,7 +283,7 @@ public class GDataWrapper<C> {
} finally {
whenFinishedFuture.cancel(false);
}
return false;
}
@@ -289,11 +298,11 @@ public class GDataWrapper<C> {
public void setAuthManager(AuthManager auth) {
this.auth = auth;
}
public AuthManager getAuthManager() {
return auth;
}
public void setRetryOnAuthFailure(boolean retry) {
retryOnAuthFailure = retry;
}
@@ -3,7 +3,7 @@ package com.google.android.apps.mytracks.io.mymaps;
import com.google.android.maps.GeoPoint;
import java.io.Serializable;
import java.util.Random;
import java.util.Vector;
/**
@@ -12,7 +12,7 @@ import java.util.Vector;
* {@link MyMapsFeatureMetadata} object so that it can be more efficiently
* transmitted to other activities.
*/
class MyMapsFeature implements Serializable {
class MyMapsFeature {
private static final long serialVersionUID = 8439035544430497236L;
@@ -49,6 +49,8 @@ class MyMapsFeature implements Serializable {
/** The metadata of this feature in a format efficient for transmission. */
private MyMapsFeatureMetadata featureInfo = new MyMapsFeatureMetadata();
private final Random random = new Random();
/**
* Initializes a valid but empty feature. It will default to a
* {@link #MARKER} with a blue placemark with a dot as an icon at the
@@ -62,7 +64,7 @@ class MyMapsFeature implements Serializable {
*
* @param point The new point to add
*/
public synchronized void addPoint(GeoPoint point) {
public void addPoint(GeoPoint point) {
latitudeE6.add(point.getLatitudeE6());
longitudeE6.add(point.getLongitudeE6());
}
@@ -71,10 +73,10 @@ class MyMapsFeature implements Serializable {
* Generates a new local id for this feature based on the current time and
* a random number.
*/
public synchronized void generateAndroidId() {
Long time = System.currentTimeMillis();
Integer random = (int) (Math.random() * 10000);
androidId = time.toString() + "." + random.toString();
void generateAndroidId() {
long time = System.currentTimeMillis();
int rand = random.nextInt(10000);
androidId = time + "." + rand;
}
/**
@@ -82,7 +84,7 @@ class MyMapsFeature implements Serializable {
*
* @return The local id for this feature
*/
public String getAndroidId() {
String getAndroidId() {
return androidId;
}
@@ -102,7 +104,7 @@ class MyMapsFeature implements Serializable {
*
* @param description The new description of this feature
*/
public synchronized void setDescription(String description) {
public void setDescription(String description) {
featureInfo.setDescription(description);
}
@@ -196,7 +198,7 @@ class MyMapsFeature implements Serializable {
*
* @param title The new title of this feature
*/
public synchronized void setTitle(String title) {
public void setTitle(String title) {
featureInfo.setTitle(title);
}
@@ -207,7 +209,7 @@ class MyMapsFeature implements Serializable {
* @param type The new type of the feature. That type must be one of
* {@link #MARKER}, {@link #LINE}, or {@link #SHAPE}
*/
public synchronized void setType(int type) {
public void setType(int type) {
featureInfo.setType(type);
}
@@ -217,7 +219,7 @@ class MyMapsFeature implements Serializable {
*
* @param color The new ARGB color of this feature
*/
public synchronized void setColor(int color) {
public void setColor(int color) {
featureInfo.setColor(color);
}
@@ -227,7 +229,7 @@ class MyMapsFeature implements Serializable {
*
* @param url The new icon url of the feature
*/
public synchronized void setIconUrl(String url) {
public void setIconUrl(String url) {
featureInfo.setIconUrl(url);
}
}
@@ -82,6 +82,6 @@ class MyMapsFeatureMetadata {
}
public void setIconUrl(String url) {
iconUrl = new String(url);
iconUrl = url;
}
}
@@ -2,6 +2,7 @@
package com.google.android.apps.mytracks.io.mymaps;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
import com.google.android.apps.mytracks.io.gdata.GDataClientFactory;
import com.google.android.common.gdata.AndroidXmlParserFactory;
import com.google.wireless.gdata.client.GDataClient;
@@ -126,11 +127,12 @@ class MyMapsGDataWrapper {
}
Log.d(MyMapsConstants.TAG, "GData error encountered: " + errorMessage);
if (errorType == ERROR_AUTH && auth != null) {
Runnable whenFinished = null;
AuthCallback whenFinished = null;
if (retryOnAuthFailure) {
retriesPending++;
whenFinished = new Runnable() {
public void run() {
whenFinished = new AuthCallback() {
@Override
public void onAuthResult(boolean success) {
retriesPending--;
retryOnAuthFailure = false;
runQuery(query);
@@ -23,7 +23,7 @@ class MyMapsMapMetadata {
}
public void setTitle(String title) {
this.title = new String(title);
this.title = title;
}
public String getDescription() {
@@ -31,7 +31,7 @@ class MyMapsMapMetadata {
}
public void setDescription(String description) {
this.description = new String(description);
this.description = description;
}
public boolean getSearchable() {
@@ -47,6 +47,6 @@ class MyMapsMapMetadata {
}
public void setGDataEditUri(String editUri) {
this.gdataEditUri = new String(editUri);
this.gdataEditUri = editUri;
}
}
@@ -85,8 +85,8 @@ class XmlMapsGDataParserFactory implements GDataParserFactory {
@Override
public GDataSerializer createSerializer(Entry en) {
if (en.getClass() == MapFeatureEntry.class) {
return new XmlMapsGDataSerializer(xmlFactory, en);
if (en instanceof MapFeatureEntry) {
return new XmlMapsGDataSerializer(xmlFactory, (MapFeatureEntry) en);
} else {
return new XmlEntryGDataSerializer(xmlFactory, en);
}
@@ -1,7 +1,6 @@
// Copyright 2010 Google Inc. All Rights Reserved.
package com.google.android.apps.mytracks.io.mymaps;
import com.google.wireless.gdata.data.Entry;
import com.google.wireless.gdata.data.StringUtils;
import com.google.wireless.gdata.parser.ParseException;
import com.google.wireless.gdata.parser.xml.XmlGDataParser;
@@ -10,14 +9,14 @@ import com.google.wireless.gdata.serializer.xml.XmlEntryGDataSerializer;
import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
/**
* Serializer of maps data for GData.
*/
@@ -29,11 +28,11 @@ class XmlMapsGDataSerializer extends XmlEntryGDataSerializer {
private XmlParserFactory factory;
private OutputStream stream;
public XmlMapsGDataSerializer(XmlParserFactory factory, Entry entry) {
public XmlMapsGDataSerializer(XmlParserFactory factory, MapFeatureEntry entry) {
super(factory, entry);
this.factory = factory;
this.entry = (MapFeatureEntry) entry;
this.entry = entry;
}
@Override
@@ -58,7 +58,8 @@ public class ResultDialogFactory {
*/
public static AlertDialog makeDialog(Activity activity, List<SendResult> results,
DialogInterface.OnClickListener onOkClickListener,
DialogInterface.OnClickListener onShareClickListener) {
DialogInterface.OnClickListener onShareClickListener,
DialogInterface.OnCancelListener onCancelListener) {
boolean success = true;
for (SendResult result : results) {
if (!result.isSuccess()) {
@@ -84,6 +85,8 @@ public class ResultDialogFactory {
onShareClickListener);
}
builder.setOnCancelListener(onCancelListener);
return builder.create();
}
@@ -0,0 +1,922 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.io.sendtogoogle;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.google.android.apps.mytracks.AccountChooser;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.MyMapsList;
import com.google.android.apps.mytracks.ProgressIndicator;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
import com.google.android.apps.mytracks.content.Track;
import com.google.android.apps.mytracks.content.TracksColumns;
import com.google.android.apps.mytracks.io.AuthManager;
import com.google.android.apps.mytracks.io.AuthManager.AuthCallback;
import com.google.android.apps.mytracks.io.AuthManagerFactory;
import com.google.android.apps.mytracks.io.SendToDocs;
import com.google.android.apps.mytracks.io.SendToFusionTables;
import com.google.android.apps.mytracks.io.SendToFusionTables.OnSendCompletedListener;
import com.google.android.apps.mytracks.io.SendToMyMaps;
import com.google.android.apps.mytracks.io.mymaps.MapsFacade;
import com.google.android.apps.mytracks.io.mymaps.MyMapsConstants;
import com.google.android.apps.mytracks.util.SystemUtils;
import com.google.android.apps.mytracks.util.UriUtils;
import com.google.android.maps.mytracks.R;
import android.accounts.Account;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Helper activity for managing the sending of tracks to Google services.
*
* @author Rodrigo Damazio
*/
public class SendActivity extends Activity implements ProgressIndicator {
// Keys for saved state variables.
private static final String STATE_SEND_TO_MAPS = "mapsSend";
private static final String STATE_SEND_TO_FUSION_TABLES = "fusionSend";
private static final String STATE_SEND_TO_DOCS = "docsSend";
private static final String STATE_DOCS_MESSAGE = "docsMsg";
private static final String STATE_FUSION_TABLES_MESSAGE = "fusionMsg";
private static final String STATE_MAPS_MESSAGE = "mapsMsg";
private static final String STATE_DOCS_SUCCESS = "docsSuccess";
private static final String STATE_FUSION_SUCCESS = "fusionSuccess";
private static final String STATE_MAPS_SUCCESS = "mapsSuccess";
private static final String STATE_STATE = "state";
private static final String EXTRA_SHARE_LINK = "shareLink";
private static final String STATE_ACCOUNT_TYPE = "accountType";
private static final String STATE_ACCOUNT_NAME = "accountName";
private static final String STATE_TABLE_ID = "tableId";
private static final String STATE_MAP_ID = "mapId";
/** States for the state machine that defines the upload process. */
private enum SendState {
SEND_OPTIONS,
START,
AUTHENTICATE_MAPS,
PICK_MAP,
SEND_TO_MAPS,
SEND_TO_MAPS_DONE,
AUTHENTICATE_FUSION_TABLES,
SEND_TO_FUSION_TABLES,
SEND_TO_FUSION_TABLES_DONE,
AUTHENTICATE_DOCS,
AUTHENTICATE_TRIX,
SEND_TO_DOCS,
SEND_TO_DOCS_DONE,
SHOW_RESULTS,
SHARE_LINK,
FINISH,
DONE,
NOT_READY
}
private static final int SEND_DIALOG = 1;
private static final int PROGRESS_DIALOG = 2;
/* @VisibleForTesting */
static final int DONE_DIALOG = 3;
// UI
private ProgressDialog progressDialog;
// Services
private MyTracksProviderUtils providerUtils;
private SharedPreferences sharedPreferences;
private GoogleAnalyticsTracker tracker;
// Authentication
private AuthManager lastAuth;
private final HashMap<String, AuthManager> authMap = new HashMap<String, AuthManager>();
private AccountChooser accountChooser;
private String lastAccountName;
private String lastAccountType;
// Send request information.
private boolean shareRequested = false;
private long sendTrackId;
private boolean sendToMyMaps;
private boolean sendToMyMapsNewMap;
private boolean sendToFusionTables;
private boolean sendToDocs;
// Send result information, used by results dialog.
private boolean sendToMyMapsSuccess = false;
private boolean sendToFusionTablesSuccess = false;
private boolean sendToDocsSuccess = false;
// TODO: Make these be used for showing results
private String sendToMyMapsMessage;
private String sendToFusionTablesMessage;
private String sendToDocsMessage;
// Send result information, used to share a link.
private String sendToMyMapsMapId;
private String sendToFusionTablesTableId;
// Current sending state.
private SendState currentState;
private final OnCancelListener finishOnCancelListener = new OnCancelListener() {
@Override
public void onCancel(DialogInterface arg0) {
onAllDone();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "SendActivity.onCreate");
super.onCreate(savedInstanceState);
providerUtils = MyTracksProviderUtils.Factory.get(this);
sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, 0);
tracker = GoogleAnalyticsTracker.getInstance();
// Start the tracker in manual dispatch mode...
tracker.start(getString(R.string.google_analytics_id),
getApplicationContext());
tracker.setProductVersion("android-mytracks",
SystemUtils.getMyTracksVersion(this));
resetState();
if (savedInstanceState != null) {
restoreInstanceState(savedInstanceState);
}
// If we had the instance restored after it was done, reset it.
if (currentState == SendState.DONE) {
resetState();
}
// Only consider the intent if we're not restoring from a previous state.
if (currentState == SendState.SEND_OPTIONS) {
if (!handleIntent()) {
finish();
return;
}
}
// Execute the state machine, at the start or restored state.
Log.w(TAG, "Starting at state " + currentState);
executeStateMachine(currentState);
}
private boolean handleIntent() {
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
Uri data = intent.getData();
if (!Intent.ACTION_SEND.equals(action) ||
!TracksColumns.CONTENT_ITEMTYPE.equals(type) ||
!UriUtils.matchesContentUri(data, TracksColumns.CONTENT_URI)) {
Log.e(TAG, "Got bad send intent: " + intent);
return false;
}
sendTrackId = ContentUris.parseId(data);
shareRequested = intent.getBooleanExtra(EXTRA_SHARE_LINK, false);
return true;
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case SEND_DIALOG:
return createSendDialog();
case PROGRESS_DIALOG:
return createProgressDialog();
case DONE_DIALOG:
return createDoneDialog();
}
return null;
}
private Dialog createSendDialog() {
final SendDialog sendDialog = new SendDialog(this);
sendDialog.setOnClickListener(new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which != DialogInterface.BUTTON_POSITIVE) {
finish();
return;
}
dialog.dismiss();
sendToMyMaps = sendDialog.getSendToMyMaps();
sendToMyMapsNewMap = sendDialog.getCreateNewMap();
sendToFusionTables = sendDialog.getSendToFusionTables();
sendToDocs = sendDialog.getSendToDocs();
executeStateMachine(SendState.START);
}
});
sendDialog.setOnCancelListener(finishOnCancelListener);
return sendDialog;
}
private void restoreInstanceState(Bundle savedInstanceState) {
currentState = SendState.values()[savedInstanceState.getInt(STATE_STATE)];
sendToMyMaps = savedInstanceState.getBoolean(STATE_SEND_TO_MAPS);
sendToFusionTables = savedInstanceState.getBoolean(STATE_SEND_TO_FUSION_TABLES);
sendToDocs = savedInstanceState.getBoolean(STATE_SEND_TO_DOCS);
sendToMyMapsSuccess = savedInstanceState.getBoolean(STATE_MAPS_SUCCESS);
sendToFusionTablesSuccess = savedInstanceState.getBoolean(STATE_FUSION_SUCCESS);
sendToDocsSuccess = savedInstanceState.getBoolean(STATE_DOCS_SUCCESS);
sendToMyMapsMessage = savedInstanceState.getString(STATE_MAPS_MESSAGE);
sendToFusionTablesMessage = savedInstanceState.getString(STATE_FUSION_TABLES_MESSAGE);
sendToDocsMessage = savedInstanceState.getString(STATE_DOCS_MESSAGE);
sendToMyMapsMapId = savedInstanceState.getString(STATE_MAP_ID);
sendToFusionTablesTableId = savedInstanceState.getString(STATE_TABLE_ID);
lastAccountName = savedInstanceState.getString(STATE_ACCOUNT_NAME);
lastAccountType = savedInstanceState.getString(STATE_ACCOUNT_TYPE);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_STATE, currentState.ordinal());
outState.putBoolean(STATE_MAPS_SUCCESS, sendToMyMapsSuccess);
outState.putBoolean(STATE_FUSION_SUCCESS, sendToFusionTablesSuccess);
outState.putBoolean(STATE_DOCS_SUCCESS, sendToDocsSuccess);
outState.putString(STATE_MAPS_MESSAGE, sendToMyMapsMessage);
outState.putString(STATE_FUSION_TABLES_MESSAGE, sendToFusionTablesMessage);
outState.putString(STATE_DOCS_MESSAGE, sendToDocsMessage);
outState.putString(STATE_MAP_ID, sendToMyMapsMapId);
outState.putString(STATE_TABLE_ID, sendToFusionTablesTableId);
outState.putString(STATE_ACCOUNT_NAME, lastAccountName);
outState.putString(STATE_ACCOUNT_TYPE, lastAccountType);
// TODO: Ideally we should serialize/restore the authenticator map and lastAuth somehow,
// but it's highly unlikely we'll get killed while an auth dialog is displayed.
}
@Override
protected void onStop() {
Log.d(TAG, "SendActivity.onStop, state=" + currentState);
tracker.dispatch();
tracker.stop();
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG, "SendActivity.onDestroy, state=" + currentState);
super.onDestroy();
}
private void executeStateMachine(SendState startState) {
currentState = startState;
// If a state handler returns NOT_READY, it means it's waiting for some
// event, and will call this method again when it happens.
while (currentState != SendState.DONE &&
currentState != SendState.NOT_READY) {
Log.d(TAG, "Executing state " + currentState);
currentState = executeState(currentState);
Log.d(TAG, "New state is " + currentState);
}
}
private SendState executeState(SendState state) {
switch (state) {
case SEND_OPTIONS:
return showSendOptions();
case START:
return startSend();
case AUTHENTICATE_MAPS:
return authenticateToGoogleMaps();
case PICK_MAP:
return pickMap();
case SEND_TO_MAPS:
return sendToGoogleMaps();
case SEND_TO_MAPS_DONE:
return onSendToGoogleMapsDone();
case AUTHENTICATE_FUSION_TABLES:
return authenticateToFusionTables();
case SEND_TO_FUSION_TABLES:
return sendToFusionTables();
case SEND_TO_FUSION_TABLES_DONE:
return onSendToFusionTablesDone();
case AUTHENTICATE_DOCS:
return authenticateToGoogleDocs();
case AUTHENTICATE_TRIX:
return authenticateToGoogleTrix();
case SEND_TO_DOCS:
return sendToGoogleDocs();
case SEND_TO_DOCS_DONE:
return onSendToGoogleDocsDone();
case SHOW_RESULTS:
return onSendToGoogleDone();
case SHARE_LINK:
return shareLink();
case FINISH:
return onAllDone();
default:
Log.e(TAG, "Reached a non-executable state");
return null;
}
}
private SendState showSendOptions() {
showDialog(SEND_DIALOG);
return SendState.NOT_READY;
}
/**
* Initiates the process to send tracks to google.
* This is called once the user has selected sending options via the
* SendToGoogleDialog.
*/
private SendState startSend() {
showDialog(PROGRESS_DIALOG);
if (sendToMyMaps) {
return SendState.AUTHENTICATE_MAPS;
} else if (sendToFusionTables) {
return SendState.AUTHENTICATE_FUSION_TABLES;
} else if (sendToDocs) {
return SendState.AUTHENTICATE_DOCS;
} else {
Log.w(TAG, "Nowhere to upload to");
return SendState.FINISH;
}
}
private Dialog createProgressDialog() {
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);
progressDialog.setIcon(android.R.drawable.ic_dialog_info);
progressDialog.setTitle(R.string.progress_title);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMax(100);
progressDialog.setProgress(0);
return progressDialog;
}
private SendState authenticateToGoogleMaps() {
Log.d(TAG, "SendActivity.authenticateToGoogleMaps");
progressDialog.setProgress(0);
progressDialog.setMessage(getString(
R.string.progress_message_authenticating_mymaps));
authenticate(Constants.AUTHENTICATE_TO_MY_MAPS, MyMapsConstants.SERVICE_NAME);
// AUTHENTICATE_TO_MY_MAPS callback calls sendToGoogleMaps
return SendState.NOT_READY;
}
private SendState pickMap() {
if (!sendToMyMapsNewMap) {
// Ask the user to choose a map to upload into
Intent listIntent = new Intent(this, MyMapsList.class);
listIntent.putExtra(MyMapsList.EXTRA_ACCOUNT_NAME, lastAccountName);
listIntent.putExtra(MyMapsList.EXTRA_ACCOUNT_TYPE, lastAccountType);
startActivityForResult(listIntent, Constants.GET_MAP);
// The callback for GET_MAP calls authenticateToGoogleMaps
return SendState.NOT_READY;
} else {
return SendState.SEND_TO_MAPS;
}
}
private SendState sendToGoogleMaps() {
tracker.trackPageView("/send/maps");
SendToMyMaps.OnSendCompletedListener onCompletion = new SendToMyMaps.OnSendCompletedListener() {
@Override
public void onSendCompleted(String mapId, boolean success, int statusMessage) {
// TODO: Use this message
sendToMyMapsMessage = getString(statusMessage);
sendToMyMapsSuccess = success;
if (sendToMyMapsSuccess) {
sendToMyMapsMapId = mapId;
// Update the map id for this track:
try {
Track track = providerUtils.getTrack(sendTrackId);
if (track != null) {
track.setMapId(mapId);
providerUtils.updateTrack(track);
} else {
Log.w(TAG, "Updating map id failed.");
}
} catch (RuntimeException e) {
// If that fails whatever reasons we'll just log an error, but
// continue.
Log.w(TAG, "Updating map id failed.", e);
}
}
executeStateMachine(SendState.SEND_TO_MAPS_DONE);
}
};
if (sendToMyMapsMapId == null) {
sendToMyMapsMapId = SendToMyMaps.NEW_MAP_ID;
}
final SendToMyMaps sender = new SendToMyMaps(this, sendToMyMapsMapId, lastAuth,
sendTrackId, this /*progressIndicator*/, onCompletion);
new Thread(sender, "SendToMyMaps").start();
return SendState.NOT_READY;
}
private SendState onSendToGoogleMapsDone() {
if (sendToFusionTables) {
return SendState.AUTHENTICATE_FUSION_TABLES;
} else if (sendToDocs) {
return SendState.AUTHENTICATE_DOCS;
} else {
return SendState.SHOW_RESULTS;
}
}
private SendState authenticateToFusionTables() {
progressDialog.setProgress(0);
progressDialog.setMessage(getString(
R.string.progress_message_authenticating_fusiontables));
authenticate(Constants.AUTHENTICATE_TO_FUSION_TABLES, SendToFusionTables.SERVICE_ID);
// AUTHENTICATE_TO_FUSION_TABLES callback calls sendToFusionTables
return SendState.NOT_READY;
}
private SendState sendToFusionTables() {
tracker.trackPageView("/send/fusion_tables");
OnSendCompletedListener onCompletion = new OnSendCompletedListener() {
@Override
public void onSendCompleted(String tableId, boolean success,
int statusMessage) {
// TODO: Use this message
sendToFusionTablesMessage = getString(statusMessage);
sendToFusionTablesSuccess = success;
if (sendToFusionTablesSuccess) {
sendToFusionTablesTableId = tableId;
// Update the table id for this track:
try {
Track track = providerUtils.getTrack(sendTrackId);
if (track != null) {
track.setTableId(tableId);
providerUtils.updateTrack(track);
} else {
Log.w(TAG, "Updating table id failed.");
}
} catch (RuntimeException e) {
// If that fails whatever reasons we'll just log an error, but
// continue.
Log.w(TAG, "Updating table id failed.", e);
}
}
executeStateMachine(SendState.SEND_TO_FUSION_TABLES_DONE);
}
};
final SendToFusionTables sender = new SendToFusionTables(this, lastAuth,
sendTrackId, this /*progressIndicator*/, onCompletion);
new Thread(sender, "SendToFusionTables").start();
return SendState.NOT_READY;
}
private SendState onSendToFusionTablesDone() {
if (sendToDocs) {
return SendState.AUTHENTICATE_DOCS;
} else {
return SendState.SHOW_RESULTS;
}
}
private SendState authenticateToGoogleDocs() {
setProgressValue(0);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(Constants.AUTHENTICATE_TO_DOCLIST, SendToDocs.GDATA_SERVICE_NAME_DOCLIST);
// AUTHENTICATE_TO_DOCLIST callback calls authenticateToGoogleTrix
return SendState.NOT_READY;
}
private SendState authenticateToGoogleTrix() {
setProgressValue(30);
setProgressMessage(
R.string.progress_message_authenticating_docs);
authenticate(Constants.AUTHENTICATE_TO_TRIX, SendToDocs.GDATA_SERVICE_NAME_TRIX);
// AUTHENTICATE_TO_TRIX callback calls sendToGoogleDocs
return SendState.NOT_READY;
}
private SendState sendToGoogleDocs() {
Log.d(TAG, "Sending to Docs....");
tracker.trackPageView("/send/docs");
setProgressValue(50);
setProgressMessage(R.string.progress_message_sending_docs);
final SendToDocs sender = new SendToDocs(this,
authMap.get(SendToDocs.GDATA_SERVICE_NAME_TRIX),
authMap.get(SendToDocs.GDATA_SERVICE_NAME_DOCLIST),
this);
Runnable onCompletion = new Runnable() {
public void run() {
setProgressValue(100);
// TODO: Use this message
sendToDocsMessage = sender.getStatusMessage();
sendToDocsSuccess = sender.wasSuccess();
executeStateMachine(SendState.SEND_TO_DOCS_DONE);
}
};
sender.setOnCompletion(onCompletion);
sender.sendToDocs(sendTrackId);
return SendState.NOT_READY;
}
private SendState onSendToGoogleDocsDone() {
return SendState.SHOW_RESULTS;
}
private SendState onSendToGoogleDone() {
tracker.dispatch();
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Sending to Google done.");
dismissDialog(PROGRESS_DIALOG);
dismissDialog(SEND_DIALOG);
// Ensure a new done dialog is created each time.
// This is required because the send results must be available at the
// time the dialog is created.
removeDialog(DONE_DIALOG);
showDialog(DONE_DIALOG);
}
});
return SendState.NOT_READY;
}
private Dialog createDoneDialog() {
Log.d(TAG, "Creating done dialog");
// We've finished sending the track to the user-selected services. Now
// we tell them the results of the upload, and optionally share the track.
// There are a few different paths through this code:
//
// 1. The user pre-requested a share (shareRequested == true). We're going
// to display the result dialog *without* the share button (the share
// listener will be null). The OK button listener will initiate the
// share.
//
// 2. The user did not pre-request a share, and the set of services to
// which we succeeded in uploading the track are compatible with
// sharing. We'll display a share button (the share listener will be
// non-null), and will share the link if the user clicks it.
//
// 3. The user did not pre-request a share, and the set of services to
// which we succeeded in uploading the track are incompatible with
// sharing. We won't display a share button.
List<SendResult> results = makeSendToGoogleResults();
final boolean canShare = sendToFusionTablesTableId != null || sendToMyMapsMapId != null;
final OnClickListener finishListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
executeStateMachine(SendState.FINISH);
}
};
DialogInterface.OnClickListener doShareListener = null;
if (canShare) {
doShareListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
executeStateMachine(SendState.SHARE_LINK);
}
};
}
DialogInterface.OnClickListener onOkListener = (canShare && shareRequested)
? doShareListener : finishListener;
DialogInterface.OnClickListener onShareListener = (canShare && !shareRequested)
? doShareListener : null;
return ResultDialogFactory.makeDialog(this, results, onOkListener, onShareListener, finishOnCancelListener);
}
private SendState onAllDone() {
Log.d(TAG, "All sending done.");
removeDialog(PROGRESS_DIALOG);
removeDialog(SEND_DIALOG);
removeDialog(DONE_DIALOG);
progressDialog = null;
finish();
return SendState.DONE;
}
private SendState shareLink() {
String url = null;
if (sendToMyMaps && sendToMyMapsSuccess) {
// Prefer a link to My Maps
url = MapsFacade.buildMapUrl(sendToMyMapsMapId);
} else if (sendToFusionTables && sendToFusionTablesSuccess) {
// Otherwise try using the link to fusion tables
url = getFusionTablesUrl(sendTrackId);
}
if (url != null) {
shareLinkToMap(url);
} else {
Log.w(TAG, "Failed to share link");
}
return SendState.FINISH;
}
/**
* Shares a link to a My Map or Fusion Table via external app (email, gmail, ...)
* A chooser with apps that support text/plain will be shown to the user.
*/
private void shareLinkToMap(String url) {
boolean shareUrlOnly = sharedPreferences.getBoolean(
getString(R.string.share_url_only_key), false);
String msg = shareUrlOnly ? url : String.format(
getResources().getText(R.string.share_map_body_format).toString(), url);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_SUBJECT,
getResources().getText(R.string.share_map_subject).toString());
shareIntent.putExtra(Intent.EXTRA_TEXT, msg);
startActivity(Intent.createChooser(shareIntent,
getResources().getText(R.string.share_map).toString()));
}
protected String getFusionTablesUrl(long trackId) {
Track track = providerUtils.getTrack(trackId);
return SendToFusionTables.getMapVisualizationUrl(track);
}
/**
* Creates a list of {@link SendResult} instances based on the set of
* services selected in {@link SendDialog} and the results as known to
* this class.
*/
private List<SendResult> makeSendToGoogleResults() {
List<SendResult> results = new ArrayList<SendResult>();
if (sendToMyMaps) {
results.add(new SendResult(SendType.MYMAPS, sendToMyMapsSuccess));
}
if (sendToFusionTables) {
results.add(new SendResult(SendType.FUSION_TABLES,
sendToFusionTablesSuccess));
}
if (sendToDocs) {
results.add(new SendResult(SendType.DOCS, sendToDocsSuccess));
}
return results;
}
/**
* Initializes the authentication manager which obtains an authentication
* token, prompting the user for a login and password if needed.
*/
private void authenticate(final int requestCode, final String service) {
lastAuth = authMap.get(service);
if (lastAuth == null) {
Log.i(TAG, "Creating a new authentication for service: " + service);
lastAuth = AuthManagerFactory.getAuthManager(this,
Constants.GET_LOGIN,
null,
true,
service);
authMap.put(service, lastAuth);
}
Log.d(TAG, "Logging in to " + service + "...");
if (AuthManagerFactory.useModernAuthManager()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
chooseAccount(requestCode, service);
}
});
} else {
doLogin(requestCode, service, null);
}
}
private void chooseAccount(final int requestCode, final String service) {
if (accountChooser == null) {
accountChooser = new AccountChooser();
// Restore state if necessary.
if (lastAccountName != null && lastAccountType != null) {
accountChooser.setChosenAccount(lastAccountName, lastAccountType);
}
}
accountChooser.chooseAccount(SendActivity.this,
new AccountChooser.AccountHandler() {
@Override
public void onAccountSelected(Account account) {
if (account == null) {
dismissDialog(PROGRESS_DIALOG);
finish();
return;
}
lastAccountName = account.name;
lastAccountType = account.type;
doLogin(requestCode, service, account);
}
});
}
private void doLogin(final int requestCode, final String service, final Object account) {
lastAuth.doLogin(new AuthCallback() {
@Override
public void onAuthResult(boolean success) {
Log.i(TAG, "Login success for " + service + ": " + success);
if (!success) {
executeStateMachine(SendState.SHOW_RESULTS);
return;
}
onLoginSuccess(requestCode);
}
}, account);
}
@Override
public void onActivityResult(int requestCode, int resultCode,
final Intent results) {
SendState nextState = null;
switch (requestCode) {
case Constants.GET_LOGIN: {
if (resultCode == RESULT_CANCELED || lastAuth == null) {
nextState = SendState.FINISH;
break;
}
// This will invoke onAuthResult appropriately.
lastAuth.authResult(resultCode, results);
break;
}
case Constants.GET_MAP: {
// User picked a map to upload to
Log.d(TAG, "Get map result: " + resultCode);
if (resultCode == RESULT_OK) {
if (results.hasExtra("mapid")) {
sendToMyMapsMapId = results.getStringExtra("mapid");
}
nextState = SendState.SEND_TO_MAPS;
} else {
nextState = SendState.FINISH;
}
break;
}
default: {
Log.e(TAG, "Unrequested result: " + requestCode);
return;
}
}
if (nextState != null) {
executeStateMachine(nextState);
}
}
private void onLoginSuccess(int requestCode) {
SendState nextState;
switch (requestCode) {
case Constants.AUTHENTICATE_TO_MY_MAPS:
// Authenticated with Google My Maps
nextState = SendState.PICK_MAP;
break;
case Constants.AUTHENTICATE_TO_FUSION_TABLES:
// Authenticated with Google Fusion Tables
nextState = SendState.SEND_TO_FUSION_TABLES;
break;
case Constants.AUTHENTICATE_TO_DOCLIST:
// Authenticated with Google Docs
nextState = SendState.AUTHENTICATE_TRIX;
break;
case Constants.AUTHENTICATE_TO_TRIX:
// Authenticated with Trix
nextState = SendState.SEND_TO_DOCS;
break;
default: {
Log.e(TAG, "Unrequested login code: " + requestCode);
return;
}
}
executeStateMachine(nextState);
}
/**
* Resets status information for sending to MyMaps/Docs.
*/
private void resetState() {
currentState = SendState.SEND_OPTIONS;
sendToMyMapsMapId = null;
sendToMyMapsMessage = "";
sendToMyMapsSuccess = true;
sendToFusionTablesMessage = "";
sendToFusionTablesSuccess = true;
sendToDocsMessage = "";
sendToDocsSuccess = true;
sendToFusionTablesTableId = null;
}
@Override
public void setProgressMessage(final int resId) {
runOnUiThread(new Runnable() {
public void run() {
if (progressDialog != null) {
progressDialog.setMessage(getString(resId));
}
}
});
}
@Override
public void setProgressValue(final int percent) {
runOnUiThread(new Runnable() {
public void run() {
if (progressDialog != null) {
progressDialog.setProgress(percent);
}
}
});
}
@Override
public void clearProgressMessage() {
progressDialog.setMessage("");
}
public static void sendToGoogle(Context ctx, long trackId, boolean shareLink) {
Uri uri = ContentUris.withAppendedId(TracksColumns.CONTENT_URI, trackId);
Intent intent = new Intent(ctx, SendActivity.class);
intent.setAction(Intent.ACTION_SEND);
intent.setDataAndType(uri, TracksColumns.CONTENT_ITEMTYPE);
intent.putExtra(SendActivity.EXTRA_SHARE_LINK, shareLink);
ctx.startActivity(intent);
}
}
@@ -16,8 +16,6 @@
package com.google.android.apps.mytracks.io.sendtogoogle;
import com.google.android.apps.mytracks.Constants;
import com.google.android.apps.mytracks.DialogManager;
import com.google.android.apps.mytracks.MyTracks;
import com.google.android.maps.mytracks.R;
import android.app.Dialog;
@@ -42,15 +40,13 @@ import android.widget.RadioGroup;
*/
public class SendDialog extends Dialog {
private RadioGroup groupMyMaps;
private RadioButton createNewMapRadioButton;
private RadioButton pickMapRadioButton;
private CheckBox sendToMyMapsCheckBox;
private CheckBox sendToFusionTablesCheckBox;
private CheckBox sendToDocsCheckBox;
private RadioButton sendStatsRadioButton;
private RadioButton sendStatsAndPointsRadioButton;
private Button sendButton;
private OnClickListener clickListener;
public SendDialog(Context context) {
super(context);
@@ -62,18 +58,38 @@ public class SendDialog extends Dialog {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mytracks_send_to_google);
final Button sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
final RadioGroup groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
sendToMyMapsCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
sendToFusionTablesCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
createNewMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
pickMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
RadioButton sendStatsRadioButton = (RadioButton) findViewById(R.id.sendtogoogle_send_stats);
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
R.id.sendtogoogle_send_stats_and_points);
Button cancel = (Button) findViewById(R.id.sendtogoogle_cancel);
cancel.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
MyTracks.getInstance().dismissDialog(DialogManager.DIALOG_SEND_TO_GOOGLE);
if (clickListener != null) {
clickListener.onClick(SendDialog.this, BUTTON_NEGATIVE);
}
dismiss();
}
});
Button send = (Button) findViewById(R.id.sendtogoogle_send_now);
send.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(SendDialog.this, BUTTON_POSITIVE);
}
dismiss();
MyTracks.getInstance().sendToGoogle();
}
});
@@ -86,25 +102,9 @@ public class SendDialog extends Dialog {
groupMyMaps.setVisibility(sendToMyMapsCheckBox.isChecked() ? View.VISIBLE : View.INVISIBLE);
}
};
sendButton = (Button) findViewById(R.id.sendtogoogle_send_now);
groupMyMaps = (RadioGroup) findViewById(R.id.sendtogoogle_group_mymaps);
sendToMyMapsCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_mymaps);
sendToMyMapsCheckBox.setOnCheckedChangeListener(checkBoxListener);
sendToFusionTablesCheckBox =
(CheckBox) findViewById(R.id.sendtogoogle_google_fusiontables);
sendToFusionTablesCheckBox.setOnCheckedChangeListener(checkBoxListener);
sendToDocsCheckBox = (CheckBox) findViewById(R.id.sendtogoogle_google_docs);
sendToDocsCheckBox.setOnCheckedChangeListener(checkBoxListener);
createNewMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_create_new_map);
pickMapRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_pick_existing_map);
sendStatsRadioButton =
(RadioButton) findViewById(R.id.sendtogoogle_send_stats);
sendStatsAndPointsRadioButton = (RadioButton) findViewById(
R.id.sendtogoogle_send_stats_and_points);
SharedPreferences prefs =
getContext().getSharedPreferences(Constants.SETTINGS_NAME, 0);
@@ -163,6 +163,10 @@ public class SendDialog extends Dialog {
super.onStop();
}
public void setOnClickListener(OnClickListener clickListener) {
this.clickListener = clickListener;
}
public boolean getSendToMyMaps() {
return sendToMyMapsCheckBox.isChecked();
}
@@ -182,4 +186,4 @@ public class SendDialog extends Dialog {
public boolean getSendStatsAndPoints() {
return sendStatsAndPointsRadioButton.isChecked();
}
}
}
@@ -1,12 +1,12 @@
/*
* 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
@@ -24,7 +24,7 @@ import android.util.Log;
/**
* A class that manages reading the shared preferences for the service.
*
*
* @author Sandor Dornbush
*/
public class PreferenceManager implements OnSharedPreferenceChangeListener {
@@ -39,6 +39,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
private final String minRecordingIntervalKey;
private final String minRequiredAccuracyKey;
private final String recordingTrackKey;
private final String selectedTrackKey;
private final String splitFrequencyKey;
public PreferenceManager(TrackRecordingService service) {
@@ -51,14 +52,14 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
throw new IllegalStateException("Couldn't get shared preferences");
}
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
announcementFrequencyKey =
service.getString(R.string.announcement_frequency_key);
autoResumeTrackCurrentRetryKey =
service.getString(R.string.auto_resume_track_current_retry_key);
autoResumeTrackTimeoutKey =
service.getString(R.string.auto_resume_track_timeout_key);
maxRecordingDistanceKey =
maxRecordingDistanceKey =
service.getString(R.string.max_recording_distance_key);
metricUnitsKey =
service.getString(R.string.metric_units_key);
@@ -70,9 +71,11 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
service.getString(R.string.min_required_accuracy_key);
recordingTrackKey =
service.getString(R.string.recording_track_key);
selectedTrackKey =
service.getString(R.string.selected_track_key);
splitFrequencyKey =
service.getString(R.string.split_frequency_key);
// Refresh all properties.
onSharedPreferenceChanged(sharedPreferences, null);
}
@@ -84,7 +87,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
* @param key the key that changed (may be null to update all preferences)
*/
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
public void onSharedPreferenceChanged(SharedPreferences preferences,
String key) {
if (service == null) {
Log.w(Constants.TAG,
@@ -93,13 +96,13 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
return;
}
if (key == null || key.equals(minRecordingDistanceKey)) {
service.setMinRecordingDistance(
sharedPreferences.getInt(
minRecordingDistanceKey,
Constants.DEFAULT_MIN_RECORDING_DISTANCE));
int minRecordingDistance = sharedPreferences.getInt(
minRecordingDistanceKey,
Constants.DEFAULT_MIN_RECORDING_DISTANCE);
service.setMinRecordingDistance(minRecordingDistance);
Log.d(Constants.TAG,
"TrackRecordingService: minRecordingDistance = "
+ service.getMinRecordingDistance());
+ minRecordingDistance);
}
if (key == null || key.equals(maxRecordingDistanceKey)) {
service.setMaxRecordingDistance(sharedPreferences.getInt(
@@ -149,7 +152,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
if (key == null || key.equals(recordingTrackKey)) {
long recordingTrackId = sharedPreferences.getLong(recordingTrackKey, -1);
// Only read the id if it is valid.
// Setting it to -1 should only happen in
// Setting it to -1 should only happen in
// TrackRecordingService.endCurrentTrack()
if (recordingTrackId > 0) {
service.setRecordingTrackId(recordingTrackId);
@@ -164,7 +167,7 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
sharedPreferences.getBoolean(metricUnitsKey, true));
}
}
public void setAutoResumeTrackCurrentRetry(int retryAttempts) {
sharedPreferences
.edit()
@@ -178,7 +181,14 @@ public class PreferenceManager implements OnSharedPreferenceChangeListener {
.putLong(recordingTrackKey, id)
.commit();
}
public void setSelectedTrack(long id) {
sharedPreferences
.edit()
.putLong(selectedTrackKey, id)
.commit();
}
public void shutdown() {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
service = null;
@@ -0,0 +1,88 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.Constants;
import com.google.android.maps.mytracks.R;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
/**
* Helper for reading service state.
*
* @author Rodrigo Damazio
*/
public class ServiceUtils {
/**
* Checks whether we're currently recording.
* The checking is done by calling the service, if provided, or alternatively by reading
* recording state saved to preferences.
*
* @param ctx the current context
* @param service the service, or null if not bound to it
* @param preferences the preferences, or null if not available
* @return true if the service is recording (or supposed to be recording), false otherwise
*/
public static boolean isRecording(Context ctx, ITrackRecordingService service, SharedPreferences preferences) {
if (service != null) {
try {
return service.isRecording();
} catch (RemoteException e) {
Log.e(TAG, "Failed to check if service is recording", e);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to check if service is recording", e);
}
}
if (preferences == null) {
preferences = ctx.getSharedPreferences(Constants.SETTINGS_NAME, 0);
}
return preferences.getLong(ctx.getString(R.string.recording_track_key), -1) > 0;
}
/**
* Checks whether the recording service is currently running.
*
* @param ctx the current context
* @return true if the service is running, false otherwise
*/
public static boolean isServiceRunning(Context ctx) {
ActivityManager activityManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE);
for (RunningServiceInfo serviceInfo : services) {
ComponentName componentName = serviceInfo.service;
String serviceName = componentName.getClassName();
if (serviceName.equals(TrackRecordingService.class.getName())) {
return true;
}
}
return false;
}
private ServiceUtils() {}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,170 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services;
import static com.google.android.apps.mytracks.Constants.TAG;
import com.google.android.apps.mytracks.util.SystemUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
/**
* Wrapper for the connection to the track recording service.
* This handles connection/disconnection internally, only returning a real
* service for use if one is available and connected.
*
* @author Rodrigo Damazio
*/
public class TrackRecordingServiceConnection {
private ITrackRecordingService boundService;
private final DeathRecipient deathRecipient = new DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "Service died");
setBoundService(null);
}
};
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
Log.i(TAG, "Connected to service");
try {
service.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
Log.e(TAG, "Failed to bind a death recipient", e);
}
setBoundService(ITrackRecordingService.Stub.asInterface(service));
}
@Override
public void onServiceDisconnected(ComponentName className) {
Log.i(TAG, "Disconnected from service");
setBoundService(null);
}
};
private final Context context;
private final Runnable bindChangedCallback;
/**
* Constructor.
*
* @param context the current context
* @param bindChangedCallback a callback to be executed when the state of the
* service binding changes
*/
public TrackRecordingServiceConnection(Context context, Runnable bindChangedCallback) {
this.context = context;
this.bindChangedCallback = bindChangedCallback;
}
/**
* Binds to the service, starting it if necessary.
*/
public void startAndBind() {
bindService(true);
}
/**
* Binds to the service, only if it's already running.
*/
public void bindIfRunning() {
bindService(false);
}
/**
* Unbinds from and stops the service.
*/
public void stop() {
unbind();
Log.d(TAG, "Stopping service");
Intent intent = new Intent(context, TrackRecordingService.class);
context.stopService(intent);
}
/**
* Unbinds from the service (but leaves it running).
*/
public void unbind() {
Log.d(TAG, "Unbinding from the service");
try {
context.unbindService(serviceConnection);
} catch (IllegalArgumentException e) {
// Means we weren't bound, which is ok.
}
setBoundService(null);
}
/**
* Returns the service if connected to it, or null if not connected.
*/
public ITrackRecordingService getServiceIfBound() {
checkBindingAlive();
return boundService;
}
private void checkBindingAlive() {
if (boundService != null &&
!boundService.asBinder().isBinderAlive()) {
setBoundService(null);
}
}
private void bindService(boolean startIfNeeded) {
if (boundService != null) {
// Already bound.
return;
}
if (!startIfNeeded && !ServiceUtils.isServiceRunning(context)) {
// Not running, start not requested.
Log.d(TAG, "Service not running, not binding to it.");
return;
}
if (startIfNeeded) {
Log.i(TAG, "Starting the service");
Intent intent = new Intent(context, TrackRecordingService.class);
context.startService(intent);
}
Log.i(TAG, "Binding to the service");
Intent intent = new Intent(context, TrackRecordingService.class);
int flags = SystemUtils.isRelease(context) ? 0 : Context.BIND_DEBUG_UNBIND;
context.bindService(intent, serviceConnection, flags);
}
private void setBoundService(ITrackRecordingService service) {
boundService = service;
if (bindChangedCallback != null) {
bindChangedCallback.run();
}
}
}
@@ -394,8 +394,9 @@ public class BluetoothConnectionManager {
offset = 0;
// Send the obtained bytes to the UI Activity
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
// Send copy of the obtained bytes to the UI Activity.
// Avoids memory inconsistency issues.
handler.obtainMessage(MESSAGE_READ, bytes, -1, buffer.clone())
.sendToTarget();
} catch (IOException e) {
Log.e(Constants.TAG, "disconnected", e);
@@ -0,0 +1,140 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import com.google.android.apps.mytracks.content.Sensor;
/**
* An implementation of a Sensor MessageParser for Polar Wearlink Bluetooth HRM.
*
* Polar Bluetooth Wearlink packet example;
* Hdr Len Chk Seq Status HeartRate RRInterval_16-bits
* FE 08 F7 06 F1 48 03 64
* where;
* Hdr always = 254 (0xFE),
* Chk = 255 - Len
* Seq range 0 to 15
* Status = Upper nibble may be battery voltage
* bit 0 is Beat Detection flag.
*
* Additional packet examples;
* FE 08 F7 06 F1 48 03 64
* FE 0A F5 06 F1 48 03 64 03 70
*
* @author John R. Gerthoffer
*/
public class PolarMessageParser implements MessageParser {
private int lastHeartRate = 0;
/**
* Applies Polar packet validation rules to buffer.
* Polar packets are checked for following;
* offset 0 = header byte, 254 (0xFE).
* offset 1 = packet length byte, 8, 10, 12, 14.
* offset 2 = check byte, 255 - packet length.
* offset 3 = sequence byte, range from 0 to 15.
*
* @param an array of bytes to parse
* @param buffer offset to beginning of packet.
* @return whether buffer has a valid packet at offset i
*/
private boolean packetValid (byte[] buffer, int i) {
boolean headerValid = (buffer[i] & 0xFF) == 0xFE;
boolean checkbyteValid = (buffer[i + 2] & 0xFF) == (0xFF - (buffer[i + 1] & 0xFF));
boolean sequenceValid = (buffer[i + 3] & 0xFF) < 16;
return headerValid && checkbyteValid && sequenceValid;
}
@Override
public Sensor.SensorDataSet parseBuffer(byte[] buffer) {
int heartRate = 0;
boolean heartrateValid = false;
// Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends.
for (int i = 0; i < buffer.length - 8; i++) {
heartrateValid = packetValid(buffer,i);
if (heartrateValid) {
heartRate = buffer[i + 5] & 0xFF;
break;
}
}
// If our buffer is corrupted, use decaying last good value.
if(!heartrateValid) {
heartRate = (int) (lastHeartRate * 0.8);
if(heartRate < 50)
heartRate = 0;
}
lastHeartRate = heartRate; // Remember good value for next time.
// Heart Rate
Sensor.SensorData.Builder b = Sensor.SensorData.newBuilder()
.setValue(heartRate)
.setState(Sensor.SensorState.SENDING);
Sensor.SensorDataSet sds = Sensor.SensorDataSet.newBuilder()
.setCreationTime(System.currentTimeMillis())
.setHeartRate(b)
.build();
return sds;
}
/**
* Applies packet validation rules to buffer
*
* @param an array of bytes to parse
* @return whether buffer has a valid packet starting at index zero
*/
@Override
public boolean isValid(byte[] buffer) {
return packetValid(buffer,0);
}
/**
* Polar uses variable packet sizes; 8, 10, 12, 14 and rarely 16.
* The most frequent are 8 and 10.
* We will wait for 16 bytes.
* This way, we are assured of getting one good one.
*
* @return the size of buffer needed to parse a good packet
*/
@Override
public int getFrameSize() {
return 16;
}
/**
* Searches buffer for the beginning of a valid packet.
*
* @param an array of bytes to parse
* @return index to beginning of good packet, or -1 if none found.
*/
@Override
public int findNextAlignment(byte[] buffer) {
// Minimum length Polar packets is 8, so stop search 8 bytes before buffer ends.
for (int i = 0; i < buffer.length - 8; i++) {
if (packetValid(buffer,i)) {
return i;
}
}
return -1;
}
}
@@ -0,0 +1,28 @@
/*
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.android.apps.mytracks.services.sensors;
import android.content.Context;
/**
* PolarSensorManager - A sensor manager for Polar heart rate monitors.
*/
public class PolarSensorManager extends BluetoothSensorManager {
public PolarSensorManager(Context context) {
super(context, new PolarMessageParser());
}
}

Some files were not shown because too many files have changed in this diff Show More