Compare commits

...

78 Commits

Author SHA1 Message Date
Román 16c20287ca Release v3.9.3 2020-08-31 12:45:20 +02:00
Román a733d87c37 Bugfix: export all failed trying to convert to string the Track.Id. 2020-08-31 12:25:58 +02:00
Dennis Guse 79d8ba9ef8 Configure Dependabot for Gradle and Github actions 2020-08-30 23:28:50 +02:00
Dennis Guse 3ee7d4504c Release v3.9.2 2020-08-30 23:05:51 +02:00
Dennis Guse 579509aad8 Upgrade dependencies. 2020-08-30 19:01:27 +02:00
Peter Storch 24ff73ab06 Fix force close on stop button for older devices (#393)
* correct thread handling in ButtonDelay

Discovered in #390 for devices with API Level 25. Not fixing the whole issue, but a first step.

* fix #390 crash when stopping for older devices

The VectorDrawable ic_button_stop.xml referenced from withing the AnimatedVectorDrawable ic_button_stop_anim.xml was a problem for older devices. Now the VectorDrawable is expanded (duplicated) inside the AnimatedVectorDrawable. Now it is working. Even with the animation.
2020-08-27 08:11:10 +02:00
Dennis Guse 60d5e7e515 Reset recordingTrackId on application Startup.
This is necessary if the TrackRecordingService crashed.
2020-08-23 17:36:30 +02:00
Dennis Guse c610758ae1 Move CursorLoader to ContentProviderUtils. 2020-08-23 00:09:02 +02:00
Dennis Guse 17d2755844 Bugfix: MarkerDetailActivity: compare waypointId properly using equals.
Introduced in bcbaf4985a
2020-08-23 00:08:58 +02:00
Dennis Guse 0ab14e0f3d Implement Track.Id and Waypoint.Id (with tests).
Fixes #276.
2020-08-22 23:18:48 +02:00
Dennis Guse bcbaf4985a Implement Track.Id and Waypoint.Id (without tests).
Fixes #276.
2020-08-22 23:18:48 +02:00
Dennis Guse b2e8713db0 Desugar: removed manual date parsing on API26. 2020-08-21 07:01:16 +02:00
Dennis Guse af60e1fdfb Enable Google's desugar plugin (enables stream/map etc.).
Fixes  #347.
2020-08-20 18:31:47 +02:00
Román 75043325ff Fix comment. 2020-08-19 22:04:40 +02:00
Dennis Guse 337e5b9e4d Release v3.9.1 2020-08-19 18:13:10 +02:00
Román 1ff986f1e6 Gps Status: bugfixes.
Bugfix 1: race condition issue fixed with a flag.
Bugfix 2: location time issue fixed setting the time every time a location is received.
2020-08-19 14:02:47 +02:00
thrillfall 6412bb9fcd remove useless parameter 2020-08-18 16:37:44 +02:00
thrillfall 8425997c29 add overload for exporter that takes single track 2020-08-18 16:37:44 +02:00
thrillfall f4b585b830 do not pass complete track data to exporter factory method just to knwo if there is more then one track to export 2020-08-18 16:37:44 +02:00
thrillfall 47c259fa9a move tracks array from class state to method argument 2020-08-18 16:37:44 +02:00
thrillfall 80208e3188 move condition for writing multitrack begin and end outside of methods 2020-08-18 16:37:44 +02:00
thrillfall 2655d3e773 fix decorator TrackExporter interface 2020-08-18 16:37:44 +02:00
Dennis Guse ca8814c478 Release v3.9.0 2020-08-16 20:37:38 +02:00
Dennis Guse 6a397922d2 Cleanup using IntelliJ's analyze code. 2020-08-16 20:13:09 +02:00
Dennis Guse ee062f8a38 Cleanup. 2020-08-16 20:13:09 +02:00
Dennis Guse c8bdf8e641 Wheel circumference is now a textfield accepting input as mm.
Fixes #278.
2020-08-16 20:12:20 +02:00
Dennis Guse 0b2bd6899f Pull request template: add note about adding new libraries. 2020-08-16 12:49:50 +02:00
Román 32d811b46e Bugfix: NPE fixed because listeners/client were null in some cases.
TrackRecordingService: before destroying it sends GPS Status NONE to all listeners.
Cleanup Logs used for debugging.
2020-08-16 07:27:44 +02:00
Román 80a8017a18 GPS Status: animation icon for ready but not fixed status (GPS bad signal). 2020-08-16 07:27:44 +02:00
Román 2b90bfd054 ServiceUtils deleted because it doesn't need anymore.
It uses gps status received from service.
2020-08-16 07:27:44 +02:00
Román 8cb6d63abe Cleanup and rename. 2020-08-16 07:27:44 +02:00
Román 9837680540 GPS Status: TrackListActivity uses the value of gps status from the service to select appropiate icon. 2020-08-16 07:27:44 +02:00
Román e2b741dd08 Added GPS Status to the service so activities can access to the GPS Status. 2020-08-16 07:27:44 +02:00
Peter Storch 18546fd3cc fix #241 stop button lock (#362)
* fix #241 stop button lock

Adds a delay and animation to the stop and pause buttons

- short vibration when starting to push the buttons
- long vibration when action was executed
- Toast to show "Hold to stop/pause recording"
2020-08-15 21:50:06 +02:00
Dennis Guse a87d2e5b09 Bugfix: prevent NPE.
Fixes #374.
2020-08-15 21:23:14 +02:00
Dennis Guse 3ae30e2468 Settings: do prevent import of already existing tracks by default. 2020-08-15 21:12:10 +02:00
Dennis Guse f821c8ba86 Fixed tests. 2020-08-15 21:12:10 +02:00
Dennis Guse 5c96774e79 ContentProvider can now lookup tracks via UUID. 2020-08-15 21:12:10 +02:00
Dennis Guse c26a5edc03 Make track import behavior on duplicate UUID configurable (default: import with a new UUID). 2020-08-15 21:12:10 +02:00
Dennis Guse f8afa9dbad Import/export track UUID (necessary for synchronization).
If a track with a UUID is presented in OpenTracks, it cannot be imported again (enforced by database constraint).
Fixes #307.
2020-08-15 21:12:10 +02:00
Dennis Guse db1167ead5 Find track by UUID. 2020-08-15 21:12:10 +02:00
Dennis Guse 0008673ab0 Add class diagram for export/import code. 2020-08-15 21:12:10 +02:00
Dennis Guse 59d4bfa4ac Export KML: report OpenTracks as generator. 2020-08-15 21:12:10 +02:00
Dennis Guse 04f146c641 Bugfix: formatting of pace was not formatted correctly (e.g., 4:60).
Fixes #370.
2020-08-15 20:19:46 +02:00
Dennis Guse ea97ff65df MarkerEditActivity: fixed NPE on orientation change. 2020-08-15 09:12:19 +02:00
Peter Storch 9827b3fc92 fix #371 mountain bike icon not shown 2020-08-12 20:53:58 +02:00
Peter Storch a35ed2e9b1 move debug resources into debug and cleanup build.gradle 2020-08-11 21:42:38 +02:00
Dennis Guse 26d2a861d7 Add missing bluetooth icon (SVG). 2020-08-10 16:31:38 +02:00
Dennis Guse 389ed9b362 Update dependencies. 2020-08-10 15:52:52 +02:00
Dennis Guse c5e5e862ad App launcher icon: moved arrow slightly too the left. 2020-08-10 15:52:11 +02:00
Dennis Guse b676fd6614 SearchEngineTest: do not use production database.
Partly fixes #143.
2020-08-10 15:39:53 +02:00
Peter Storch 0194efafbc added more .idea files to gitignore 2020-08-08 12:41:50 +02:00
Peter Storch 9ba1357dbb added new activity types (#360)
* added new activity types

- escooter
- inline_skating
- motorbike
- skate_boarding
- skiing
- snowboarding
- climbing
- sailing
- mtb
2020-08-08 12:38:51 +02:00
Dennis Guse 64b66a0c57 Github sponsor button points to OpenTracks-team (liberapay). 2020-08-07 16:15:36 +02:00
Dennis Guse 7787dae9f6 Bluetooth preference use vector icon. 2020-08-06 12:34:01 +02:00
Dennis Guse d0ca06f964 Switch Liberapay to separate OpenTracks-team. 2020-08-05 17:19:01 +02:00
Dennis Guse f7d1a53622 File open intent-filters now expose host (required by lint). 2020-08-05 12:49:30 +02:00
Dennis Guse 5f13d027af CI: CodeQL use custom build command. 2020-08-04 22:58:57 +02:00
Dennis Guse cf45a8079c Simplified getting the BluetoothAdapter. 2020-08-04 22:56:35 +02:00
Dennis Guse 9e553cc900 CI: enable CodeQL 2020-08-04 22:49:14 +02:00
transifex-integration[bot] 1f3466e4b2 Translate /src/main/res/values/strings.xml in eu
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'eu' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:49:12 +02:00
transifex-integration[bot] c813934bcb Translate /src/main/res/values/strings.xml in cs
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'cs' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:48:49 +02:00
transifex-integration[bot] 480202853d Translate /src/main/res/values/strings.xml in da
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'da' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:48:29 +02:00
transifex-integration[bot] 4c58bd81bb Translate /src/main/res/values/strings.xml in nl
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'nl' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:48:12 +02:00
transifex-integration[bot] 9f9b6966a8 Translate /src/main/res/values/strings.xml in et
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'et' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:47:41 +02:00
transifex-integration[bot] 52c97b63d5 Translate /src/main/res/values/strings.xml in fi
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'fi' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:47:24 +02:00
transifex-integration[bot] eee18ec7a4 Translate /src/main/res/values/strings.xml in fr
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'fr' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:46:53 +02:00
transifex-integration[bot] 4d444046de Translate /src/main/res/values/strings.xml in gl
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'gl' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:46:34 +02:00
transifex-integration[bot] 4d5e31b231 Translate /src/main/res/values/strings.xml in de
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'de' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:46:10 +02:00
transifex-integration[bot] 8544f7d20a Translate /src/main/res/values/strings.xml in es
at least 95% translated updated for the source file '/src/main/res/values/strings.xml'
on the 'es' language.

 Manual sync of partially translated files: untranslated content is included with an empty translation or source language content depending on file format
2020-08-03 21:45:16 +02:00
Dennis Guse 1feb5cbb65 Release v3.8.1 2020-08-03 08:03:13 +02:00
Dennis Guse 1d2fde6e69 DashboardAPI: report if track is recording and start Dashboard on same task.
Necessary for Picture-in-Picture mode.
2020-08-03 07:59:17 +02:00
Dennis Guse 20423121ee Dashboard API: only forward fullscreen setting, when recording. 2020-08-03 07:59:17 +02:00
Dennis Guse ebf7b0a0c3 TrackRecordingActivity: added setting for fullscreen.
Fixes #342.
2020-08-03 07:59:17 +02:00
Dennis Guse 1189a07794 TrackController: center time. 2020-08-02 16:36:03 +02:00
Román 63e9041922 Orientation change when edit marker has to update photo uri in waypoint for delete button update (hide/show). 2020-07-31 20:29:56 +02:00
Dennis Guse 699be450b0 Fixed too optimistic intent-filter for imports.
Fixes #340.
2020-07-29 23:19:32 +02:00
Dennis Guse 66b175c56c Updated dependencies. 2020-07-26 17:39:30 +02:00
158 changed files with 8112 additions and 1618 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# These are supported funding model platforms # These are supported funding model platforms
liberapay: dennis.guse liberapay: OpenTracks
custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore custom: https://play.google.com/store/apps/details?id=de.dennisguse.opentracks.playstore
+3
View File
@@ -8,3 +8,6 @@ A clear and concise description of what the pull request changes/adds.
**License agreement** **License agreement**
By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)). By opening this pull request, you are providing your contribution under the _Apache License 2.0_ (see [LICENSE.md](LICENSE.md)).
**Note: new dependencies/libraries**
Please refrain from introducing new libraries without consulting the team.
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
+34
View File
@@ -0,0 +1,34 @@
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1'
jobs:
analyse:
name: Analyse
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: java
- run: |
./gradlew build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+3
View File
@@ -66,6 +66,9 @@ captures/
.idea/sqlDataSources.xml .idea/sqlDataSources.xml
.idea/vcs.xml .idea/vcs.xml
.idea/uiDesigner.xml .idea/uiDesigner.xml
.idea/jarRepositories.xml
.idea/markdown-navigator-enh.xml
.idea/markdown-navigator.xml
# Keystore files # Keystore files
*.jks *.jks
+1 -1
View File
@@ -21,7 +21,7 @@ _OpenTracks_ is a sport tracking application that completely respects your priva
</a> </a>
</td> </td>
<td> <td>
<a href="https://liberapay.com/dennis.guse/donate"> <a href="https://liberapay.com/OpenTracks/donate">
<img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle"> <img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" height="45" align="middle">
</a> </a>
</td> </td>
+15 -25
View File
@@ -6,7 +6,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.0' classpath 'com.android.tools.build:gradle:4.0.1'
} }
} }
@@ -37,6 +37,8 @@ def getVersionName = { ->
android { android {
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
@@ -45,13 +47,8 @@ android {
defaultConfig { defaultConfig {
applicationId "de.dennisguse.opentracks" applicationId "de.dennisguse.opentracks"
manifestPlaceholders = [ versionCode 3544
applicationLogo: "@mipmap/ic_launcher", versionName "v3.9.3"
applicationName: "@string/app_name"
]
versionCode 3466
versionName "v3.8.0"
buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\"" buildConfigField "String", "VERSION_NAME_FULL", "\"${getVersionName()}\""
@@ -67,10 +64,6 @@ android {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
versionNameSuffix "-debug" versionNameSuffix "-debug"
manifestPlaceholders = [
applicationLogo: "@mipmap/ic_launcher_debug",
applicationName: "OpenTracks (Debug)"
]
} }
release { release {
@@ -89,20 +82,17 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'com.google.android.material:material:1.1.0' implementation 'com.google.android.material:material:1.2.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support.constraint:constraint-layout:2.0.1'
androidTestImplementation 'androidx.test:core:1.2.0' androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0' androidTestImplementation 'androidx.test:rules:1.3.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'org.mockito:mockito-android:3.4.6'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'org.mockito:mockito-android:3.0.0'
testImplementation 'androidx.test:core:1.2.0'
testImplementation 'org.mockito:mockito-core:3.0.0'
} }
+29
View File
@@ -0,0 +1,29 @@
@startuml
interface TrackExporter {
}
class KmzTrackExporter implements TrackExporter
KmzTrackExporter o-- FileTrackExporter
class FileTrackExporter implements TrackExporter {
/' One track only '/
}
FileTrackExporter o-- TrackWriter
interface TrackWriter {
}
class GpxTrackWriter implements TrackWriter
class KmlTrackWriter implements TrackWriter
interface TrackImporter {
}
class KmzTrackImporter implements TrackImporter {
}
KmzTrackImporter o-- KmlFileTrackImporter
class AbstractFileTrackImporter implements TrackImporter {
}
class GpxFileTrackImporter extends AbstractFileTrackImporter {
}
class KmlFileTrackImporter extends AbstractFileTrackImporter {
/' Supports multiple tracks via folders '/
}
@enduml
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://opentracksapp.com/xmlschemas/v1">
<xs:element name="trackuuid"
type="xs:string" />
</xs:schema>
+474
View File
@@ -0,0 +1,474 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="ic_activity_climbing.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="0"
inkscape:window-y="64"
inkscape:window-x="219"
inkscape:window-height="880"
inkscape:window-width="1500"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="12.271037"
inkscape:cx="7.0404004"
inkscape:zoom="22.390336"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
id="path920"
d="M 6.3144531 -0.15234375 L 5.6464844 0.34179688 L 4.8945312 2.8847656 L 3.7050781 3.2324219 L 3.0292969 4.9121094 L 1.6992188 5.1113281 L 1.5371094 5.4570312 L -0.2265625 5.8183594 L -0.17382812 6.0761719 L 1.7207031 5.6914062 L 1.8789062 5.3515625 L 3.21875 5.1503906 L 3.9023438 3.4492188 L 5.1074219 3.0976562 L 5.875 0.50195312 L 6.4726562 0.060546875 L 6.3144531 -0.15234375 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<g
transform="matrix(1.226984,0,0,1.226984,-0.81596813,-1.2462535)"
id="g924">
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.168524;stroke-opacity:1"
id="path17"
d="M 2.4564013,2.0252139 C 2.6387748,1.9919855 2.7608027,1.8155843 2.7275743,1.6332107 2.6943459,1.4508371 2.5179443,1.3288092 2.3355708,1.3620376 2.1531971,1.395266 2.0311693,1.5716675 2.0643978,1.7540412 2.0976262,1.9364148 2.2740276,2.0584424 2.4564013,2.0252139 Z M 2.0390821,4.4361706 2.2561417,3.8057362 2.6726641,3.9140028 3.0571092,4.4259571 3.3404272,4.1900271 2.8859586,3.5506757 2.4962919,3.3764255 2.6074394,2.8541847 C 2.8682831,3.0636061 3.230079,3.168985 3.5948261,3.1025284 L 3.5344109,2.7709399 C 3.219402,2.8283344 2.9239238,2.7108724 2.7489979,2.5029267 L 2.5348714,2.2678637 C 2.4504294,2.1804703 2.3388697,2.1322773 2.2228138,2.1534226 c -0.049738,0.00906 -0.079877,0.031683 -0.1296145,0.040745 L 1.9734996,2.8024787 1.8537998,3.4107898 1.689464,4.211545 1.0707466,5.2436753 1.3712806,5.4904898 Z"
sodipodi:nodetypes="sssssccccccccccccsccccccc" />
<path
id="rect915"
style="opacity:0.997;fill:#ffffff;stroke:#ffffff;stroke-width:0.248933;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers"
transform="matrix(0.99300752,0.11805114,-0.2203786,0.97541441,0,0)"
d="m 2.276527,2.111288 v 0 0.8237278 -0.08059 z"
sodipodi:nodetypes="ccccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+496
View File
@@ -0,0 +1,496 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_escooter.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.390336"
inkscape:cx="13.383174"
inkscape:cy="12.569634"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1444"
inkscape:window-height="780"
inkscape:window-x="245"
inkscape:window-y="142"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g3168"
style="stroke:#909090;stroke-opacity:1">
<path
id="path901"
d="M 2.3632812 1.984375 A 0.1324995 0.1324995 0 0 0 2.25 2.1347656 A 0.1324995 0.1324995 0 0 0 2.3984375 2.2480469 C 2.6424435 2.2158044 2.9061719 2.3043649 3.0820312 2.4765625 C 3.2048822 2.5968554 3.2894144 2.7590671 3.3085938 2.9257812 C 3.3213476 3.0366427 3.3054638 3.1596364 3.2929688 3.2949219 C 3.2804368 3.4306071 3.2701901 3.583197 3.3105469 3.7382812 C 3.3438455 3.8662421 3.4102315 3.9828889 3.5019531 4.078125 C 3.5936748 4.1733611 3.7093185 4.2451189 3.8359375 4.2832031 A 0.1324995 0.1324995 0 0 0 4.0019531 4.1933594 A 0.1324995 0.1324995 0 0 0 3.9121094 4.0292969 C 3.8297238 4.0045171 3.7530387 3.9564973 3.6933594 3.8945312 C 3.63368 3.8325652 3.5900254 3.7531805 3.5683594 3.6699219 C 3.5418024 3.5678678 3.5466893 3.4492033 3.5585938 3.3203125 C 3.5705351 3.191022 3.5879256 3.0476308 3.5703125 2.8945312 C 3.5435298 2.6617257 3.4320694 2.4481756 3.2675781 2.2871094 C 3.0309201 2.0553789 2.691646 1.9409856 2.3632812 1.984375 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
transform="scale(0.26458333)"
id="path905"
d="M 6 5.5 A 0.50083507 0.50083507 0 0 0 5.5 6 L 5.5 10 A 0.50083507 0.50083507 0 0 0 6 10.5 L 7 10.5 C 8.3752137 10.5 9.5000001 9.3752139 9.5 8 C 9.4999999 6.6247863 8.3752137 5.5000001 7 5.5 L 6 5.5 z M 6.5 6.5 L 7 6.5 C 7.8339253 6.5000001 8.4999999 7.1660747 8.5 8 C 8.5000001 8.8339251 7.8339253 9.4999999 7 9.5 L 6.5 9.5 L 6.5 6.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path909"
d="M 1.0585938 1.71875 A 0.1324995 0.1324995 0 0 0 0.92578125 1.8515625 A 0.1324995 0.1324995 0 0 0 1.0585938 1.984375 L 1.5878906 1.984375 A 0.1324995 0.1324995 0 0 0 1.7207031 1.8515625 A 0.1324995 0.1324995 0 0 0 1.5878906 1.71875 L 1.0585938 1.71875 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path913"
d="M 1.0585938 2.2480469 A 0.1324995 0.1324995 0 0 0 0.92578125 2.3808594 A 0.1324995 0.1324995 0 0 0 1.0585938 2.5136719 L 1.5878906 2.5136719 A 0.1324995 0.1324995 0 0 0 1.7207031 2.3808594 A 0.1324995 0.1324995 0 0 0 1.5878906 2.2480469 L 1.0585938 2.2480469 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#909090;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
</g>
<path
id="path924"
style="opacity:1;fill:#fdbc4b;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke"
d="m -1.3252903,1.5032615 -0.4100651,0.6834418 a 0.12845631,0.12845631 60.481872 0 0 0.1101504,0.1945466 l 0.2495783,-10e-8 -0.1100188,0.5495576 a 0.08982544,0.08982544 21.142221 0 0 0.1651025,0.063848 L -0.91047797,2.3112131 A 0.12845634,0.12845634 60.481877 0 0 -1.0206284,2.1166665 H -1.2702067 L -1.1601879,1.567109 a 0.08982541,0.08982541 21.142225 0 0 -0.1651024,-0.063848 z"
sodipodi:nodetypes="ccccccc"
inkscape:original-d="m -1.0583333,1.0583332 -0.79375,1.3229167 0.4764566,-10e-8 L -1.5875,3.4395832 -0.79375003,2.1166665 H -1.2702067 Z"
inkscape:path-effect="#path-effect950" />
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
transform="scale(0.26458333)"
id="path887"
d="M 13.994141 4.5 A 0.49999949 0.49999949 0 0 0 13.552734 4.7773438 A 0.49999949 0.49999949 0 0 0 13.777344 5.4472656 L 14.908203 6.0136719 C 15.328243 6.2241517 15.636226 6.6066586 15.75 7.0625 L 17.390625 13.623047 C 15.381402 13.909129 13.837817 15.475476 13.601562 17.5 L 8.3984375 17.5 C 8.1378934 15.267332 6.3043435 13.50201 4.0039062 13.5 C 3.982495 13.499838 3.9608645 13.499856 3.9394531 13.5 L 2 13.5 A 0.49999949 0.49999949 0 0 0 1.5 14 A 0.49999949 0.49999949 0 0 0 2 14.5 L 3.9394531 14.5 A 0.50004949 0.50004949 0 0 0 3.9433594 14.5 C 3.9610145 14.499866 3.9784387 14.499866 3.9960938 14.5 A 0.50004949 0.50004949 0 0 0 4 14.5 C 5.9389193 14.5 7.5000003 16.061081 7.5 18 A 0.50004949 0.50004949 0 0 0 8 18.5 L 14 18.5 A 0.50004949 0.50004949 0 0 0 14.5 18 C 14.5 16.061081 16.061081 14.5 18 14.5 A 0.50004949 0.50004949 0 0 0 18.484375 13.878906 L 16.71875 6.8203125 C 16.534472 6.0819921 16.037756 5.460052 15.357422 5.1191406 A 0.50004949 0.50004949 0 0 0 15.355469 5.1191406 L 14.222656 4.5527344 A 0.49999949 0.49999949 0 0 0 13.994141 4.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157481;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path970"
d="M 4.7617188 4.1367188 C 4.4265639 4.1367188 4.1523438 4.4109389 4.1523438 4.7460938 C 4.1523438 5.0812486 4.4265639 5.3554687 4.7617188 5.3554688 C 5.0968736 5.3554687 5.3710937 5.0812486 5.3710938 4.7460938 C 5.3710937 4.4109389 5.0968736 4.1367188 4.7617188 4.1367188 z M 4.7617188 4.3378906 C 4.9879388 4.3378906 5.1679687 4.5198737 5.1679688 4.7460938 C 5.1679687 4.9723138 4.9879388 5.1542969 4.7617188 5.1542969 C 4.5354987 5.1542969 4.3535156 4.9723138 4.3535156 4.7460938 C 4.3535156 4.5198737 4.5354987 4.3378906 4.7617188 4.3378906 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.201567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:0.997" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.997;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.201567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 1.0221536,4.1367188 c -0.33515491,0 -0.60937501,0.2742201 -0.60937501,0.609375 0,0.3351548 0.2742201,0.6093749 0.60937501,0.609375 0.3351548,-10e-8 0.6093749,-0.2742202 0.609375,-0.609375 -1e-7,-0.3351549 -0.2742202,-0.609375 -0.609375,-0.609375 z m 0,0.2011718 c 0.22622,0 0.4062499,0.1819831 0.40625,0.4082032 -1e-7,0.2262201 -0.18003,0.4082031 -0.40625,0.4082031 -0.22622011,0 -0.40820321,-0.181983 -0.40820321,-0.4082031 0,-0.2262201 0.1819831,-0.4082032 0.40820321,-0.4082032 z"
id="path970-7" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

+515
View File
@@ -0,0 +1,515 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="ic_activity_inlineskater.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="27"
inkscape:window-x="67"
inkscape:window-height="1025"
inkscape:window-width="1853"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="10.868962"
inkscape:cx="8.471442"
inkscape:zoom="22.390336"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<path
d="m 3.3722093,1.2527829 c 0.2415068,0 0.4391032,-0.1975962 0.4391032,-0.43910305 0,-0.24150691 -0.1975964,-0.43910339 -0.4391032,-0.43910339 -0.241507,0 -0.4391033,0.19759648 -0.4391033,0.43910339 0,0.24150685 0.1975963,0.43910305 0.4391033,0.43910305 z M 2.5818232,4.3045509 2.8013749,3.3385235 3.2624334,3.7776271 V 5.0949369 H 3.7015366 V 3.4482998 L 3.2404782,3.0091963 3.3722093,2.3505413 c 0.285417,0.3293275 0.7245203,0.5488792 1.207534,0.5488792 V 2.4603172 c -0.4171482,0 -0.7684308,-0.2195517 -0.944072,-0.5269241 L 3.4161195,1.5821105 C 3.3282991,1.4503796 3.1965678,1.3625589 3.0428817,1.3625589 c -0.065865,0 -0.1097755,0.021955 -0.1756413,0.021955 L 1.7255717,1.867528 V 2.8994208 H 2.1646753 V 2.1529448 L 2.5598682,1.9992587 2.2085854,3.7776271 1.1327827,3.5580754 1.0449623,3.9971789 Z"
id="path17"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.219552;stroke-opacity:1" />
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<g
transform="matrix(-0.30905888,1.6151387,-1.6151387,-0.30905888,9.3627856,1.8488419)"
id="g943">
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892"
cx="1.6512442"
cy="4.9452085"
r="0.12575205" />
<circle
r="0.12575205"
cy="4.9452085"
cx="1.9359355"
id="path892-3"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892-3-6"
cx="2.2206268"
cy="4.9452085"
r="0.12575205" />
<circle
r="0.12575205"
cy="4.9452085"
cx="2.5053182"
id="path892-3-6-7"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
</g>
<g
id="g943-2"
transform="matrix(1.6443153,-0.0204346,0.0204346,1.6443153,-0.01361945,-2.6499566)">
<circle
r="0.12575205"
cy="4.9452085"
cx="1.6512442"
id="path892-7"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892-3-0"
cx="1.9359355"
cy="4.9452085"
r="0.12575205" />
<circle
r="0.12575205"
cy="4.9452085"
cx="2.2206268"
id="path892-3-6-9"
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-linejoin:round;paint-order:stroke fill markers"
id="path892-3-6-7-3"
cx="2.5053182"
cy="4.9452085"
r="0.12575205" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+546
View File
@@ -0,0 +1,546 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="ic_activity_motorbike.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect1386"
effect="spiro" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect1382"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.99999977,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect1373"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect1369"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect1357"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,2,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.093080149,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3277"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,2,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0,0,1 @ F,0,1,1,0,0,0,1 @ F,0,0,1,0,3.210527,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="1"
inkscape:window-y="27"
inkscape:window-x="67"
inkscape:window-height="1025"
inkscape:window-width="1853"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="12.219337"
inkscape:cx="11.776234"
inkscape:zoom="27.983802"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<g
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
id="path901"
d="M 1.8574219 3.3066406 A 0.1322915 0.1322915 0 0 0 1.7363281 3.375 L 1.0742188 4.5664062 A 0.13230472 0.13230472 0 0 0 1.2050781 4.7617188 L 2.3964844 4.6289062 A 0.1322915 0.1322915 0 0 0 2.5136719 4.4824219 A 0.1322915 0.1322915 0 0 0 2.3671875 4.3671875 L 1.4316406 4.4707031 L 1.9667969 3.5039062 A 0.1322915 0.1322915 0 0 0 1.9160156 3.3242188 A 0.1322915 0.1322915 0 0 0 1.8574219 3.3066406 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path905"
d="M 3.1757812 1.984375 A 0.1322915 0.1322915 0 0 0 3.0429688 2.1171875 A 0.1322915 0.1322915 0 0 0 3.1757812 2.2480469 L 3.6113281 2.2480469 L 4.5058594 4.6757812 A 0.1322915 0.1322915 0 0 0 4.6757812 4.7539062 A 0.1322915 0.1322915 0 0 0 4.7539062 4.5839844 L 3.828125 2.0703125 A 0.13230472 0.13230472 0 0 0 3.7050781 1.984375 L 3.1757812 1.984375 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
transform="scale(0.26458333)"
id="path909"
d="M 4.9765625 11.5 A 0.50004949 0.50004949 0 0 0 4.5 12 L 4.5 13 A 0.50004949 0.50004949 0 0 0 5 13.5 L 8 13.5 A 0.50004949 0.50004949 0 0 0 8.4882812 13.064453 A 0.50004949 0.50004949 0 0 0 8.4921875 13.005859 A 0.50004949 0.50004949 0 0 0 8.2871094 12.595703 A 0.50004949 0.50004949 0 0 0 8.2382812 12.564453 A 0.50004949 0.50004949 0 0 0 8.0703125 12.505859 A 0.50004949 0.50004949 0 0 0 8.0117188 12.5 A 0.50004949 0.50004949 0 0 0 8 12.5 C 7.0833333 12.5 6.4130278 12.27292 5.9726562 12.052734 C 5.5322847 11.832549 5.3535156 11.646484 5.3535156 11.646484 A 0.50004949 0.50004949 0 0 0 4.9765625 11.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
id="path913"
d="M 2.7773438 4.1015625 A 0.13249999 0.13249999 0 0 0 2.6464844 4.2324219 A 0.13249999 0.13249999 0 0 0 2.7773438 4.3652344 L 3.0429688 4.3652344 A 0.13249999 0.13249999 0 0 0 3.1757812 4.2324219 A 0.13249999 0.13249999 0 0 0 3.0429688 4.1015625 L 2.7773438 4.1015625 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<g
style="opacity:1"
transform="scale(0.26458333)"
id="g915">
<path
id="path917"
d="m 16.5,10.5 c -0.271556,0.0063 -0.500074,0.228371 -0.5,0.5 0,0.276144 0.223858,0.5 0.5,0.5 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1.0;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;" />
<path
id="path919"
d="M 16.488281 10 C 15.950439 10.012478 15.499849 10.447427 15.5 11 C 15.5 11.546787 15.953215 12 16.5 12 A 0.50083507 0.50083507 0 0 0 17 11.5 L 17 10.5 A 0.50083507 0.50083507 0 0 0 16.488281 10 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1.0;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;" />
</g>
<path
transform="scale(0.26458333)"
id="path923"
d="M 11 11.5 C 10.371809 11.5 9.9307721 11.946836 9.7089844 12.5 L 8 12.5 A 0.50004949 0.50004949 0 0 0 7.5234375 13.152344 L 9.1230469 18.152344 A 0.50004949 0.50004949 0 0 0 9.5996094 18.5 L 13 18.5 A 0.50004949 0.50004949 0 0 0 13.5 18 C 13.5 17.777537 13.499839 17.669786 13.5 17.5 C 13.500093 15.822204 14.542706 14.325749 16.117188 13.746094 A 0.50004949 0.50004949 0 0 0 16.414062 13.103516 L 15.943359 11.826172 A 0.50004949 0.50004949 0 0 0 15.474609 11.5 L 11 11.5 z M 11 12.5 L 15.126953 12.5 L 15.357422 13.128906 C 13.674401 13.966365 12.500106 15.589879 12.5 17.5 L 9.9648438 17.5 L 8.6855469 13.5 L 10 13.5 A 0.50004949 0.50004949 0 0 0 10.5 13 C 10.5 12.717935 10.717935 12.5 11 12.5 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.999999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:531.552;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:0.994135" />
<path
id="path927"
d="M 2.7773438 3.703125 A 0.13249999 0.13249999 0 0 0 2.6464844 3.8359375 A 0.13249999 0.13249999 0 0 0 2.7773438 3.96875 L 3.0429688 3.96875 A 0.13249999 0.13249999 0 0 0 3.1757812 3.8359375 A 0.13249999 0.13249999 0 0 0 3.0429688 3.703125 L 2.7773438 3.703125 z "
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.265;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.997;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.261354;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 4.6258209,3.8250746 c -0.4345653,0 -0.7901218,0.3555565 -0.7901218,0.7901218 0,0.4345652 0.3555565,0.7901217 0.7901218,0.7901218 0.4345652,-1e-7 0.7901217,-0.3555566 0.7901217,-0.7901218 0,-0.4345653 -0.3555565,-0.7901218 -0.7901217,-0.7901218 z m 0,0.2608413 c 0.2933192,0 0.5267476,0.2359612 0.5267479,0.5292805 0,0.2933193 -0.2334287,0.5292803 -0.5267479,0.5292803 -0.2933193,0 -0.5292805,-0.235961 -0.5292805,-0.5292803 0,-0.2933193 0.2359612,-0.5292805 0.5292805,-0.5292805 z"
id="path970-7-3" />
<path
id="path970-7-3-6"
d="m 1.2138007,3.8250746 c -0.43456528,0 -0.79012178,0.3555565 -0.79012178,0.7901218 0,0.4345652 0.3555565,0.7901217 0.79012178,0.7901218 0.4345652,-1e-7 0.7901217,-0.3555566 0.7901217,-0.7901218 0,-0.4345653 -0.3555565,-0.7901218 -0.7901217,-0.7901218 z m 0,0.2608413 c 0.2933192,0 0.5267476,0.2359612 0.5267479,0.5292805 0,0.2933193 -0.2334287,0.5292803 -0.5267479,0.5292803 -0.29331928,0 -0.52928048,-0.235961 -0.52928048,-0.5292803 0,-0.2933193 0.2359612,-0.5292805 0.52928048,-0.5292805 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.997;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.261354;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

+464
View File
@@ -0,0 +1,464 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_mtb.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="15.832358"
inkscape:cx="2.2323103"
inkscape:cy="8.8776466"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1596"
inkscape:window-height="917"
inkscape:window-x="123"
inkscape:window-y="27"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
style="fill:#ffffff;fill-opacity:1;stroke-width:0.196349"
id="path51"
d="M 1.8042087,1.0709424 C 1.9783861,0.94322723 2.0164009,0.69622363 1.8886857,0.52204593 1.7609704,0.34786823 1.5139667,0.30985363 1.3397894,0.43756873 1.1656114,0.56528423 1.1275967,0.81228773 1.2553124,0.98646553 1.3830273,1.1606434 1.6300308,1.198658 1.8042087,1.0709424 Z M 0.89628461,3.3192757 C 0.4529229,3.6443694 0.35999832,4.2481554 0.68509192,4.6915169 1.0101856,5.1348783 1.6139715,5.2278033 2.0573332,4.9027093 2.5006949,4.5776158 2.5936197,3.9738298 2.2685259,3.5304682 1.9434323,3.0871067 1.3396463,2.9941819 0.89628461,3.3192757 Z M 1.883176,4.6651945 C 1.5823234,4.8857933 1.143206,4.8182123 0.92260684,4.5173598 0.70200762,4.2165074 0.76958946,3.77739 1.0704419,3.5567908 1.3712945,3.3361914 1.8104113,3.4037735 2.0310104,3.7046258 2.2516097,4.0054783 2.1840285,4.4445951 1.883176,4.6651945 Z M 1.6405189,2.4083524 1.7418913,1.7496767 1.9614496,1.7834677 c 0.3567831,0.05491 0.7188509,-0.015794 1.051372,-0.2596139 L 2.7806119,1.2071669 C 2.5430963,1.3813245 2.2834214,1.4256443 2.0364186,1.3876293 L 1.5149667,1.3073761 C 1.3893532,1.3020906 1.2869605,1.3284748 1.1919542,1.3981379 1.0969485,1.4678006 1.0409979,1.5575218 1.0399363,1.6556909 L 0.92166874,2.4241458 c -0.0168959,0.1097797 0.009488,0.2121721 0.0675406,0.2913438 0.0696627,0.095006 0.15938086,0.150959 0.25755306,0.1520177 L 2.0785546,2.9393333 2.6590789,3.7310504 2.9757655,3.4988407 2.2559153,2.5171116 Z M 3.1130918,1.6938078 C 2.6697307,2.0189011 2.5768054,2.6226876 2.9018991,3.0660491 3.2269927,3.5094107 3.8307793,3.602335 4.2741404,3.2772417 4.7175021,2.952148 4.8104268,2.348362 4.4853331,1.9050004 4.1602394,1.4616389 3.5564535,1.368714 3.1130918,1.6938078 Z M 4.0999832,3.0397266 C 3.7991307,3.2603259 3.3600139,3.1927439 3.1394147,2.8918914 2.9188155,2.5910391 2.9863967,2.1519221 3.2872491,1.9313229 3.5881015,1.7107237 4.0272189,1.7783052 4.2478182,2.0791576 4.4684174,2.38001 4.4008356,2.8191274 4.0999832,3.0397266 Z" />
<path
sodipodi:nodetypes="ccccccccccccc"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 6.2355927,2.3458251 4.366485,3.4225311 3.0292969,4.9121094 2.0999371,5.0657395 1.5371094,5.4570312 -0.2265625,5.8183594 -0.17382812,6.0761719 1.7207031,5.6914062 2.2796245,5.3059739 3.21875,5.1503906 4.5637507,3.639328 6.4484834,2.5587157 Z"
id="path920" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+477
View File
@@ -0,0 +1,477 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_sailing.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.390336"
inkscape:cx="13.656644"
inkscape:cy="9.1983306"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1596"
inkscape:window-height="917"
inkscape:window-x="123"
inkscape:window-y="27"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="false"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
sodipodi:nodetypes="ccccc"
id="path900"
d="M 0.56041665,4.4708333 1.1604167,5.4 h 3.5 l 0.6,-0.9291667 z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
sodipodi:nodetypes="ccccc"
id="path902"
d="M 2.8024332,0.32721116 V 4.5 H 3.0184001 V 0.32721116 Z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.244823px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path904"
d="M 2.6527855,1.0066933 2.6459669,4.192806 0.73848581,4.2035269 Z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.257218px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.281927px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 3.1616713,0.35463877 0.00682,3.82765233 1.9074808,0.01288 z"
id="path904-3" />
<path
id="path921"
d="M 2.8105515,0.32721116 V 0.75450025 L 2.0324942,0.57664397 Z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.286457px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

+482
View File
@@ -0,0 +1,482 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_skateboarding.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
guidetolerance="7.7"
objecttolerance="7.7"
inkscape:lockguides="true"
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="11.195168"
inkscape:cx="12.030411"
inkscape:cy="9.6162221"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1853"
inkscape:window-height="1025"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
snapvisiblegridlinesonly="false"
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="true"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
inkscape:locked="true"
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:nodetypes="sssssccccccccccccsccccccccccc"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.244651;stroke-opacity:1"
id="path17"
d="M 3.4497594,1.2723995 C 3.7188321,1.2675879 3.9350456,1.043501 3.9302338,0.7744294 3.9254222,0.50535723 3.7013352,0.28914379 3.4322626,0.29395542 3.1631898,0.29876705 2.9469764,0.5228543 2.9517881,0.7919262 2.9565981,1.0609979 3.1806867,1.2772111 3.4497594,1.2723995 Z M 3.0637352,4.3310097 2.8499094,3.4654121 3.5050565,3.7377246 4.0318851,5.1078311 4.4885872,4.9322216 3.8300515,3.2195885 3.1749046,2.9472761 3.3770914,2.4212785 C 3.7016475,2.7825092 4.1834322,3.0452834 4.7215778,3.0356596 L 4.7128291,2.5464372 C 4.2480673,2.5547482 3.9586663,2.3644029 3.7568534,2.0254461 L 3.5052429,1.6384419 C 3.4047741,1.4934249 3.2562572,1.3982048 3.0850292,1.4012668 c -0.073384,0.00131 -0.1218679,0.026649 -0.1952518,0.02796 L 1.627421,1.9901187 1.2471269,3.0265669 1.6538376,3.2423177 2.0691965,2.3071052 2.5595693,2.120264 2.1081061,3.2524111 2.4089713,4.1591882 1.3164273,4.8578585 1.9480117,4.971638 Z" />
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
sodipodi:nodetypes="cccccccccccccc"
d="M 0.70720657,4.618942 0.50519813,4.8871827 c -3e-8,0 0.1915452,0.1543669 0.29349555,0.2268814 0.0408697,0.022353 0.0297057,0.028139 0.10131117,0.035126 l 2.08762525,-0.0023 2.0876253,0.0023 c 0.071605,-0.00699 0.061055,-0.012773 0.1019258,-0.035126 C 5.2791315,5.04155 5.4700628,4.8871827 5.4700628,4.8871827 L 5.2680537,4.618942 5.0181529,4.8124246 3.1356059,4.811273 H 2.8396543 l -1.88193269,0.00115 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.304444px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
id="path894-3" />
<circle
r="0.1149179"
cy="5.4143333"
cx="1.4282604"
id="path999"
style="opacity:0.997;fill:#ffffff;stroke:#ffffff;stroke-width:0.264999;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers" />
<circle
style="opacity:0.997;fill:#ffffff;stroke:#ffffff;stroke-width:0.264999;stroke-linejoin:round;stroke-opacity:1;paint-order:stroke fill markers"
id="path999-5"
cx="4.4387093"
cy="5.4143333"
r="0.1149179" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+469
View File
@@ -0,0 +1,469 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
sodipodi:docname="ic_activity_skiing.svg"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
id="svg8"
version="1.1"
viewBox="0 0 5.8208333 5.8208333"
height="22"
width="22">
<defs
id="defs2">
<inkscape:path-effect
lpeversion="1"
is_visible="true"
id="path-effect3162"
effect="spiro" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
lpeversion="1"
is_visible="true"
id="path-effect3149"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect2514"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect950"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect946"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
lpeversion="1"
is_visible="true"
id="path-effect918"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect912"
effect="fillet_chamfer" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
lpeversion="1"
is_visible="true"
id="path-effect894"
effect="fillet_chamfer" />
</defs>
<sodipodi:namedview
inkscape:window-maximized="0"
inkscape:window-y="175"
inkscape:window-x="67"
inkscape:window-height="780"
inkscape:window-width="1444"
inkscape:guide-bbox="true"
showguides="false"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-midpoints="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-intersection-paths="true"
inkscape:object-paths="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:bbox-nodes="true"
inkscape:bbox-paths="true"
inkscape:snap-bbox="true"
units="px"
showgrid="false"
inkscape:document-rotation="0"
inkscape:current-layer="layer1"
inkscape:document-units="mm"
inkscape:cy="11.008507"
inkscape:cx="12.634965"
inkscape:zoom="15.832358"
inkscape:pageshadow="2"
inkscape:pageopacity="1"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#232629"
id="base">
<inkscape:grid
dotted="true"
empspacing="22"
id="grid833"
type="xygrid" />
<inkscape:grid
originy="0.26458333"
originx="0.26458333"
spacingx="1.0583333"
spacingy="1.0583333"
empspacing="1"
id="grid835"
type="xygrid" />
<sodipodi:guide
inkscape:color="rgb(0,0,255)"
inkscape:locked="false"
inkscape:label=""
id="guide887"
orientation="0.96592583,0.25881905"
position="4.195,4.2333333" />
<sodipodi:guide
id="guide900"
orientation="0,-1"
position="0.26458333,1.9843751" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:groupmode="layer"
inkscape:label="Layer 1">
<path
sodipodi:nodetypes="ssssscccccccccccsacccccc"
d="M 4.5585535,1.8682464 C 4.730575,2.037758 5.0100109,2.0357042 5.1795224,1.8636824 5.3490338,1.6916608 5.3469796,1.4122249 5.1749586,1.2427137 5.0029369,1.0732022 4.7235009,1.075256 4.5539893,1.2472777 4.3844781,1.419299 4.3865322,1.6987351 4.5585535,1.8682464 Z M 2.5616473,2.8895379 2.9818066,3.7412695 2.1627798,4.156586 2.4890714,4.5159652 3.6644142,3.9783412 3.3324214,3.2071044 3.7880462,2.6501643 C 3.760192,3.0850707 3.9188577,3.5496566 4.2629009,3.8886795 L 4.5711034,3.575913 C 4.2739752,3.2831205 4.1778634,2.8801752 4.2684987,2.5379577 L 4.3586776,2.1336426 C 4.3885856,1.9781722 4.356396,1.8231572 4.2469278,1.7152862 4.2000132,1.6690561 4.0431309,1.5310148 3.9171527,1.4852939 3.6109432,1.3741621 2.9436171,1.4000918 2.9436171,1.4000918 L 1.9817647,1.9917143 2.2945313,2.2999171 3.0666299,1.8154812 3.4559921,1.8297725 Z"
id="path17"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.219552;stroke-opacity:1" />
<g
transform="matrix(0.38576851,0,0,0.38576851,152.97482,-12.494408)"
id="g954">
<rect
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect919"
width="68.791664"
height="79.375"
x="-357.1875"
y="-7.9375" />
<rect
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect851"
width="10.583333"
height="10.583333"
x="-354.54169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect853"
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect855"
width="10.583333"
height="10.583333"
x="-333.375"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect857"
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect859"
width="10.583333"
height="10.583333"
x="-312.20834"
y="-5.2916665" />
<rect
y="5.291667"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect861"
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect863"
width="10.583333"
height="10.583333"
x="-343.95834"
y="5.291667" />
<rect
y="5.291667"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect865"
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect867"
width="10.583333"
height="10.583333"
x="-322.79169"
y="5.291667" />
<rect
y="5.291667"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect869"
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect871"
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect873"
width="10.583333"
height="10.583333"
x="-343.95834"
y="21.166666" />
<rect
y="21.166666"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect875"
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect877"
width="10.583333"
height="10.583333"
x="-322.79169"
y="21.166666" />
<rect
y="21.166666"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect879"
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect881"
width="10.583333"
height="10.583333"
x="-354.54169"
y="31.75" />
<rect
y="31.75"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect883"
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect885"
width="10.583333"
height="10.583333"
x="-333.375"
y="31.75" />
<rect
y="31.75"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect887"
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect889"
width="10.583333"
height="10.583333"
x="-312.20834"
y="31.75" />
<rect
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect891"
width="10.583333"
height="10.583333"
x="-301.625"
y="21.166666" />
<rect
y="31.75"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect893"
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect895"
width="10.583333"
height="10.583333"
x="-354.54166"
y="47.625" />
<rect
y="47.625"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect897"
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect899"
width="10.583333"
height="10.583333"
x="-333.37497"
y="47.625" />
<rect
y="47.625"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect901"
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect903"
width="10.583333"
height="10.583333"
x="-312.20831"
y="47.625" />
<rect
y="58.208336"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect905"
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect907"
width="10.583333"
height="10.583333"
x="-343.95831"
y="58.208336" />
<rect
y="58.208336"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect909"
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect911"
width="10.583333"
height="10.583333"
x="-322.79166"
y="58.208336" />
<rect
y="58.208336"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect913"
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
</g>
<path
sodipodi:nodetypes="cccccccc"
id="path894-3"
d="m 0.42966147,3.4124371 3.27271563,1.98984 c 0.1037203,0.05566 0.091268,0.04209 0.159023,0.05977 0.1768227,0.031256 0.4686557,-0.030884 0.4686557,-0.030884 L 4.2534675,5.1832708 3.8763995,5.1892147 0.56600626,3.186415 Z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.369514px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1" />
<path
d="M 1.3650688,1.2665262 1.2347897,1.4356529 1.0541865,1.3107744 0.939761,1.4515535 1.1246616,1.5780728 0.99349664,1.748178 1.1114823,1.8390193 1.247937,1.6625738 4.3621692,3.7943987 4.471835,3.6701937 1.357598,1.5201785 1.4830545,1.3573675 Z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.238916px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
id="path898" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

+470
View File
@@ -0,0 +1,470 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="ic_activity_snowboarding.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="spiro"
id="path-effect3162"
is_visible="true"
lpeversion="1" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect3149"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.9999999,0,1 @ F,0,0,1,0,2.0000001,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
hide_knots="false"
only_selected="false"
apply_with_radius="true"
apply_no_radius="true"
use_knot_distance="true"
flexible="false"
chamfer_steps="1"
radius="0"
mode="F"
method="auto"
unit="px"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.97014234,0,1 @ F,0,0,1,0,0,0,1"
lpeversion="1"
is_visible="true"
id="path-effect2514"
effect="fillet_chamfer" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect950"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.51887107,0,1 @ F,0,0,1,0,0.22687835,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect946"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.34892149,0,1 @ F,0,0,1,0,0.18398697,0,1 @ F,0,1,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect918"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,2.00309,0,1 @ F,0,0,1,0,2.00309,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect912"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,7.0000002,0,1 @ F,0,0,1,0,0,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect894"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,0.49832891,0,1 @ F,0,0,1,0,0.49832891,0,1 @ F,0,1,1,0,0.5316463,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49999976,0,1 @ F,0,0,1,0,0.50000024,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0000001,0,1 @ F,0,0,1,0,0.99999982,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0295214,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5316463,0,1"
unit="px"
method="auto"
mode="F"
radius="0"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
guidetolerance="7.7"
objecttolerance="7.7"
inkscape:lockguides="true"
id="base"
pagecolor="#232629"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.390336"
inkscape:cx="3.9483707"
inkscape:cy="10.41996"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showguides="false"
inkscape:guide-bbox="true"
inkscape:window-width="1853"
inkscape:window-height="1025"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1">
<inkscape:grid
snapvisiblegridlinesonly="false"
type="xygrid"
id="grid833"
empspacing="22"
dotted="true" />
<inkscape:grid
type="xygrid"
id="grid835"
empspacing="1"
spacingy="1.0583333"
spacingx="1.0583333"
originx="0.26458333"
originy="0.26458333" />
<sodipodi:guide
position="4.195,4.2333333"
orientation="0.96592583,0.25881905"
id="guide887"
inkscape:label=""
inkscape:locked="true"
inkscape:color="rgb(0,0,255)" />
<sodipodi:guide
inkscape:locked="true"
position="0.26458333,1.9843751"
orientation="0,-1"
id="guide900" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:nodetypes="sssssccccccccccccsccccccccccc"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.244651;stroke-opacity:1"
id="path17"
d="M 3.4497594,1.6623572 C 3.7188321,1.6575456 3.9350456,1.4334587 3.9302338,1.1643861 3.9254222,0.89531328 3.7013352,0.67909984 3.4322626,0.68391147 3.1631898,0.6887231 2.9469764,0.91281035 2.9517881,1.181883 2.9566019,1.4509556 3.1806867,1.6671688 3.4497594,1.6623572 Z M 3.0637352,4.7209674 2.8499094,3.8553698 3.5050565,4.1276823 4.0318851,5.4977888 4.4885872,5.3221793 3.8300515,3.6095462 3.1749046,3.3372338 3.3770914,2.8112362 C 3.7016475,3.1724669 4.1834322,3.4352411 4.7215778,3.4256173 L 4.7128291,2.9363949 C 4.2480673,2.9447059 3.9586663,2.7543606 3.7568534,2.4154038 L 3.5052429,2.0283996 C 3.4047741,1.8833826 3.2562572,1.7881625 3.0850292,1.7912245 3.0116457,1.7925394 2.9631613,1.8178734 2.8897774,1.819185 L 1.627421,2.3800764 1.2471269,3.4165246 1.6538376,3.6322754 2.0691965,2.6970629 2.5595693,2.5102217 2.1081061,3.6423688 2.4089713,4.5491459 1.3164273,5.2478162 1.9480117,5.3615957 Z" />
<g
id="g954"
transform="matrix(0.38576851,0,0,0.38576851,152.56385,-12.494408)">
<rect
y="-7.9375"
x="-357.1875"
height="79.375"
width="68.791664"
id="rect919"
style="fill:#cacaca;fill-opacity:1;stroke-width:14.6855;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="-5.2916665"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect851"
style="fill:#e74c3c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#ed1515;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect853"
width="10.583333"
height="10.583333"
x="-343.95834"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect855"
style="fill:#f47750;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#fdbc4b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect857"
width="10.583333"
height="10.583333"
x="-322.79169"
y="-5.2916665" />
<rect
y="-5.2916665"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect859"
style="fill:#c9ce3b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#da4453;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect861"
width="10.583333"
height="10.583333"
x="-354.54169"
y="5.291667" />
<rect
y="5.291667"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect863"
style="fill:#c0392b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#d35400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect865"
width="10.583333"
height="10.583333"
x="-333.375"
y="5.291667" />
<rect
y="5.291667"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect867"
style="fill:#f67400;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#f39c1f;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect869"
width="10.583333"
height="10.583333"
x="-312.20834"
y="5.291667" />
<rect
style="fill:#1cdc9a;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect871"
width="10.583333"
height="10.583333"
x="-354.54169"
y="21.166666" />
<rect
y="21.166666"
x="-343.95834"
height="10.583333"
width="10.583333"
id="rect873"
style="fill:#2ecc71;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#1abc9c;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect875"
width="10.583333"
height="10.583333"
x="-333.375"
y="21.166666" />
<rect
y="21.166666"
x="-322.79169"
height="10.583333"
width="10.583333"
id="rect877"
style="fill:#3498db;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#3daee9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect879"
width="10.583333"
height="10.583333"
x="-312.20834"
y="21.166666" />
<rect
y="31.75"
x="-354.54169"
height="10.583333"
width="10.583333"
id="rect881"
style="fill:#10d116;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#27ae60;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect883"
width="10.583333"
height="10.583333"
x="-343.95834"
y="31.75" />
<rect
y="31.75"
x="-333.375"
height="10.583333"
width="10.583333"
id="rect885"
style="fill:#16a085;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2980b9;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect887"
width="10.583333"
height="10.583333"
x="-322.79169"
y="31.75" />
<rect
y="31.75"
x="-312.20834"
height="10.583333"
width="10.583333"
id="rect889"
style="fill:#1d99f3;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
y="21.166666"
x="-301.625"
height="10.583333"
width="10.583333"
id="rect891"
style="fill:#9b59b6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#8e44ad;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect893"
width="10.583333"
height="10.583333"
x="-301.625"
y="31.75" />
<rect
y="47.625"
x="-354.54166"
height="10.583333"
width="10.583333"
id="rect895"
style="fill:#34495e;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#31363b;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect897"
width="10.583333"
height="10.583333"
x="-343.95831"
y="47.625" />
<rect
y="47.625"
x="-333.37497"
height="10.583333"
width="10.583333"
id="rect899"
style="fill:#7f8c8d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#bdc3c7;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect901"
width="10.583333"
height="10.583333"
x="-322.79166"
y="47.625" />
<rect
y="47.625"
x="-312.20831"
height="10.583333"
width="10.583333"
id="rect903"
style="fill:#fcfcfc;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#2c3e50;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect905"
width="10.583333"
height="10.583333"
x="-354.54166"
y="58.208336" />
<rect
y="58.208336"
x="-343.95831"
height="10.583333"
width="10.583333"
id="rect907"
style="fill:#232629;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#4d4d4d;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect909"
width="10.583333"
height="10.583333"
x="-333.37497"
y="58.208336" />
<rect
y="58.208336"
x="-322.79166"
height="10.583333"
width="10.583333"
id="rect911"
style="fill:#95a5a6;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552" />
<rect
style="fill:#eff0f1;fill-opacity:1;stroke-width:14.955;stroke-linecap:square;stroke-linejoin:round;stroke-dashoffset:531.552"
id="rect913"
width="10.583333"
height="10.583333"
x="-312.20831"
y="58.208336" />
</g>
<path
sodipodi:nodetypes="cccccccccccccc"
d="M 0.70720657,5.0088997 0.50519813,5.2771404 c -3e-8,0 0.1915452,0.1543669 0.29349555,0.2268814 0.0408697,0.022353 0.0297057,0.028139 0.10131117,0.035126 l 2.08762525,-0.0023 2.0876253,0.0023 c 0.071605,-0.00699 0.061055,-0.012773 0.1019258,-0.035126 C 5.2791315,5.4315077 5.4700628,5.2771404 5.4700628,5.2771404 L 5.2680537,5.0088997 5.0181529,5.2023823 3.1356059,5.2012307 H 2.8396543 l -1.88193269,0.00115 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.304444px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
id="path894-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 19 KiB

+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
id="svg6"
version="1.1"
height="24px"
width="24px"
fill="black"
viewBox="0 0 24 24">
<metadata id="metadata12">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<path
id="dotSmall"
d="m 14.24,12.01 2.32,2.32 C 16.84,13.61 17,12.82 17,12 17,11.18 16.84,10.41 16.57,9.69 Z" />
<path
id="dotBig"
d="m 19.53,6.71 -1.26,1.26 c 0.63,1.21 0.98,2.57 0.98,4.02 0,1.45 -0.36,2.82 -0.98,4.02 l 1.2,1.2 C 20.44,15.67 21.01,13.85 21.01,11.9 21,10.01 20.46,8.23 19.53,6.71 Z" />
<path
d="M 12.88,16.29 11,18.17 V 14.41 Z M 11,5.83 12.88,7.71 11,9.59 Z M 15.71,7.71 10,2 H 9 V 9.59 L 4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41 V 22 h 1 L 15.71,16.29 11.41,12 Z"
id="path4" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,8 @@
v3.8.1: OpenTracks
Changes:
- While recording: add option for fullscreen
- Dashboard is started on the same task
Bugfixes:
- Intent filters for importing files were far too optimistic
@@ -0,0 +1,14 @@
v3.9.0: OpenTracks
Changes:
- Export tracks with their UUID
- Prevent import of already existing tracks by UUID (configurable; default yes)
- Pause/stop button now require long press
- GPS pre-start shows status of fix
- Added new activity icons
- Speed sensor: wheelsize now configurable in millimeter
Bugfixes:
- Fixed some rare crashes (NPE)
- Pace formatting corrected
- App icon in launcher repositioned
@@ -0,0 +1,4 @@
v3.9.1: OpenTracks
Bugfixes:
- GPS pre-start shows status of fix
@@ -0,0 +1,5 @@
v3.9.2: OpenTracks
Bugfixes:
- Fixed force close on API<25 (due to an animation)
- Reset recording state (on app startup) if crashed while recording
@@ -0,0 +1,4 @@
v3.9.3: OpenTracks
Bugfixes:
- Fixed export all tracks bug.
@@ -67,7 +67,7 @@ public class DescriptionGeneratorTest {
track.setTrackStatistics(stats); track.setTrackStatistics(stats);
track.setCategory("hiking"); track.setCategory("hiking");
String expected = //"Created by" String expected = //"Created by"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>" "<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
+ "Name: -<br>" + "Name: -<br>"
+ "Activity type: hiking<br>" + "Activity type: hiking<br>"
+ "Description: -<br>" + "Description: -<br>"
@@ -103,7 +103,7 @@ public class DescriptionGeneratorTest {
track.setTrackStatistics(stats); track.setTrackStatistics(stats);
track.setCategory("hiking"); track.setCategory("hiking");
String expected = //"Created by" String expected = //"Created by"
"<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks</a><p>" "<a href='https://github.com/OpenTracksApp/OpenTracks'>OpenTracks (Debug)</a><p>"
+ "Name: -<br>" + "Name: -<br>"
+ "Activity type: hiking<br>" + "Activity type: hiking<br>"
+ "Description: -<br>" + "Description: -<br>"
@@ -17,18 +17,17 @@
package de.dennisguse.opentracks.content; package de.dennisguse.opentracks.content;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.Context;
import android.location.Location; import android.location.Location;
import android.net.Uri; import android.net.Uri;
import androidx.test.core.app.ApplicationProvider; import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.rule.provider.ProviderTestRule;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -39,6 +38,8 @@ import de.dennisguse.opentracks.content.SearchEngine.SearchQuery;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.CustomSQLiteOpenHelper;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
/** /**
@@ -48,28 +49,32 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
* *
* @author Rodrigo Damazio * @author Rodrigo Damazio
*/ */
@RunWith(AndroidJUnit4.class)
public class SearchEngineTest { public class SearchEngineTest {
private static final String TAG = SearchEngineTest.class.getSimpleName();
private static final Location HERE = new Location("gps"); private static final Location HERE = new Location("gps");
private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000; // After OLDEST_ALLOWED_TIMESTAMP private static final long NOW = SearchEngine.OLDEST_ALLOWED_TIMESTAMP + 1000;
@Rule
public ProviderTestRule sqliteContentProviderRule = new ProviderTestRule.Builder(CustomContentProvider.class, ContentProviderUtils.AUTHORITY_PACKAGE).setPrefix(TAG).build();
private ContentProviderUtils providerUtils; private ContentProviderUtils providerUtils;
private SearchEngine engine; private SearchEngine engine;
private final Context context = ApplicationProvider.getApplicationContext();
@Before @Before
public void setUp() { public void setUp() {
providerUtils = new ContentProviderUtils(context); providerUtils = new ContentProviderUtils(sqliteContentProviderRule.getResolver());
engine = new SearchEngine(providerUtils); engine = new SearchEngine(providerUtils);
} }
@After @After
public void tearDown() { public void tearDown() {
providerUtils.deleteAllTracks(context); InstrumentationRegistry.getInstrumentation().getTargetContext().deleteDatabase(TAG + CustomSQLiteOpenHelper.DATABASE_NAME);
} }
private long insertTrack(String title, String description, String category, long hoursAgo) { private Track.Id insertTrack(String title, String description, String category, long hoursAgo) {
Track track = new Track(); Track track = new Track();
track.setName(title); track.setName(title);
track.setDescription(description); track.setDescription(description);
@@ -83,18 +88,18 @@ public class SearchEngineTest {
} }
Uri uri = providerUtils.insertTrack(track); Uri uri = providerUtils.insertTrack(track);
return ContentUris.parseId(uri); return new Track.Id(ContentUris.parseId(uri));
} }
private long insertTrack(String title, String description, String category) { private Track.Id insertTrack(String title, String description, String category) {
return insertTrack(title, description, category, -1); return insertTrack(title, description, category, -1);
} }
private long insertTrack(String title, long hoursAgo) { private Track.Id insertTrack(String title, long hoursAgo) {
return insertTrack(title, "", "", hoursAgo); return insertTrack(title, "", "", hoursAgo);
} }
private long insertWaypoint(String title, String description, String category, double distance, long hoursAgo, long trackId) { private Waypoint.Id insertWaypoint(String title, String description, String category, double distance, long hoursAgo, Track.Id trackId) {
Location location = new Location(HERE); Location location = new Location(HERE);
location.setLatitude(location.getLatitude() + distance); location.setLatitude(location.getLatitude() + distance);
location.setLongitude(location.getLongitude() + distance); location.setLongitude(location.getLongitude() + distance);
@@ -108,22 +113,22 @@ public class SearchEngineTest {
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
Uri uri = providerUtils.insertWaypoint(waypoint); Uri uri = providerUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri); return new Waypoint.Id(ContentUris.parseId(uri));
} }
private long insertWaypoint(String title, String description, String category) { private Waypoint.Id insertWaypoint(String title, String description, String category) {
return insertWaypoint(title, description, category, 0.0, -1, -1); return insertWaypoint(title, description, category, 0.0, -1, null);
} }
private long insertWaypoint(String title, double distance) { private Waypoint.Id insertWaypoint(String title, double distance) {
return insertWaypoint(title, "", "", distance, -1, -1); return insertWaypoint(title, "", "", distance, -1, null);
} }
private long insertWaypoint(String title, long hoursAgo) { private Waypoint.Id insertWaypoint(String title, long hoursAgo) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, -1); return insertWaypoint(title, "", "", 0.0, hoursAgo, null);
} }
private long insertWaypoint(String title, long hoursAgo, long trackId) { private Waypoint.Id insertWaypoint(String title, long hoursAgo, Track.Id trackId) {
return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId); return insertWaypoint(title, "", "", 0.0, hoursAgo, trackId);
} }
@@ -138,14 +143,14 @@ public class SearchEngineTest {
// - one which will match in title and description // - one which will match in title and description
// - one which will match in all fields // - one which will match in all fields
insertTrack("bb", "cc", "dd"); insertTrack("bb", "cc", "dd");
long descriptionMatchId = insertTrack("bb", "aa", "cc"); Track.Id descriptionMatchId = insertTrack("bb", "aa", "cc");
long categoryMatchId = insertTrack("bb", "cc", "aa"); Track.Id categoryMatchId = insertTrack("bb", "cc", "aa");
long titleMatchId = insertTrack("aa", "bb", "cc"); Track.Id titleMatchId = insertTrack("aa", "bb", "cc");
long titleCategoryMatchId = insertTrack("aa", "bb", "ca"); Track.Id titleCategoryMatchId = insertTrack("aa", "bb", "ca");
long titleDescriptionMatchId = insertTrack("aa", "ba", "cc"); Track.Id titleDescriptionMatchId = insertTrack("aa", "ba", "cc");
long allMatchId = insertTrack("aa", "ba", "ca"); Track.Id allMatchId = insertTrack("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW); SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category. // Title > Description > Category.
@@ -163,14 +168,14 @@ public class SearchEngineTest {
// - one which will match in title and description // - one which will match in title and description
// - one which will match in all fields // - one which will match in all fields
insertWaypoint("bb", "cc", "dd"); insertWaypoint("bb", "cc", "dd");
long descriptionMatchId = insertWaypoint("bb", "aa", "cc"); Waypoint.Id descriptionMatchId = insertWaypoint("bb", "aa", "cc");
long categoryMatchId = insertWaypoint("bb", "cc", "aa"); Waypoint.Id categoryMatchId = insertWaypoint("bb", "cc", "aa");
long titleMatchId = insertWaypoint("aa", "bb", "cc"); Waypoint.Id titleMatchId = insertWaypoint("aa", "bb", "cc");
long titleCategoryMatchId = insertWaypoint("aa", "bb", "ca"); Waypoint.Id titleCategoryMatchId = insertWaypoint("aa", "bb", "ca");
long titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc"); Waypoint.Id titleDescriptionMatchId = insertWaypoint("aa", "ba", "cc");
long allMatchId = insertWaypoint("aa", "ba", "ca"); Waypoint.Id allMatchId = insertWaypoint("aa", "ba", "ca");
SearchQuery query = new SearchQuery("a", null, -1, NOW); SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category. // Title > Description > Category.
@@ -185,13 +190,13 @@ public class SearchEngineTest {
// - one waypoint which will match by title // - one waypoint which will match by title
// - one track which won't match // - one track which won't match
// - one track which will match by title // - one track which will match by title
long descriptionWaypointId = insertWaypoint("bb", "aa", "cc"); Waypoint.Id descriptionWaypointId = insertWaypoint("bb", "aa", "cc");
insertWaypoint("bb", "cc", "dd"); insertWaypoint("bb", "cc", "dd");
long titleWaypointId = insertWaypoint("aa", "bb", "cc"); Waypoint.Id titleWaypointId = insertWaypoint("aa", "bb", "cc");
insertTrack("bb", "cc", "dd"); insertTrack("bb", "cc", "dd");
long trackId = insertTrack("aa", "bb", "cc"); Track.Id trackId = insertTrack("aa", "bb", "cc");
SearchQuery query = new SearchQuery("a", null, -1, NOW); SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Title > Description > Category. // Title > Description > Category.
@@ -204,11 +209,11 @@ public class SearchEngineTest {
@Test @Test
public void testSearchWaypointDistance() { public void testSearchWaypointDistance() {
// All results match text, but they're at difference distances from the user. // All results match text, but they're at difference distances from the user.
long farFarAwayId = insertWaypoint("aa", 0.3); Waypoint.Id farFarAwayId = insertWaypoint("aa", 0.3);
long nearId = insertWaypoint("ab", 0.1); Waypoint.Id nearId = insertWaypoint("ab", 0.1);
long farId = insertWaypoint("ac", 0.2); Waypoint.Id farId = insertWaypoint("ac", 0.2);
SearchQuery query = new SearchQuery("a", HERE, -1, NOW); SearchQuery query = new SearchQuery("a", HERE, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Distance order. // Distance order.
@@ -217,11 +222,11 @@ public class SearchEngineTest {
public void testSearchTrackRecent() { public void testSearchTrackRecent() {
// All results match text, but they're were recorded at different times. // All results match text, but they're were recorded at different times.
long oldestId = insertTrack("aa", 3); Track.Id oldestId = insertTrack("aa", 3);
long recentId = insertTrack("ab", 1); Track.Id recentId = insertTrack("ab", 1);
long oldId = insertTrack("ac", 2); Track.Id oldId = insertTrack("ac", 2);
SearchQuery query = new SearchQuery("a", null, -1, NOW); SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order. // Reverse time order.
@@ -231,11 +236,11 @@ public class SearchEngineTest {
@Test @Test
public void testSearchWaypointRecent() { public void testSearchWaypointRecent() {
// All results match text, but they're were recorded at different times. // All results match text, but they're were recorded at different times.
long oldestId = insertWaypoint("aa", 2); Waypoint.Id oldestId = insertWaypoint("aa", 2);
long recentId = insertWaypoint("ab", 0); Waypoint.Id recentId = insertWaypoint("ab", 0);
long oldId = insertWaypoint("ac", 1); Waypoint.Id oldId = insertWaypoint("ac", 1);
SearchQuery query = new SearchQuery("a", null, -1, NOW); SearchQuery query = new SearchQuery("a", null, null, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Reverse time order. // Reverse time order.
@@ -245,8 +250,8 @@ public class SearchEngineTest {
@Test @Test
public void testSearchCurrentTrack() { public void testSearchCurrentTrack() {
// All results match text, but one of them is the current track. // All results match text, but one of them is the current track.
long currentId = insertTrack("ab", 1); Track.Id currentId = insertTrack("ab", 1);
long otherId = insertTrack("aa", 1); Track.Id otherId = insertTrack("aa", 1);
SearchQuery query = new SearchQuery("a", null, currentId, NOW); SearchQuery query = new SearchQuery("a", null, currentId, NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
@@ -258,24 +263,29 @@ public class SearchEngineTest {
@Test @Test
public void testSearchCurrentTrackWaypoint() { public void testSearchCurrentTrackWaypoint() {
// All results match text, but one of them is in the current track. // All results match text, but one of them is in the current track.
long otherId = insertWaypoint("aa", 1, 456); Waypoint.Id otherId = insertWaypoint("aa", 1, new Track.Id(456));
long currentId = insertWaypoint("ab", 1, 123); Waypoint.Id currentId = insertWaypoint("ab", 1, new Track.Id(123));
SearchQuery query = new SearchQuery("a", null, 123, NOW); SearchQuery query = new SearchQuery("a", null, new Track.Id(123), NOW);
ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query)); ArrayList<ScoredResult> results = new ArrayList<>(engine.search(query));
// Waypoint in current track should be promoted. // Waypoint in current track should be promoted.
assertWaypointResults(results, currentId, otherId); assertWaypointResults(results, currentId, otherId);
} }
private void assertTrackResult(long trackId, ScoredResult result) { private void assertTrackResult(Track.Id trackId, ScoredResult result) {
Assert.assertNotNull("Not a track", result.track); Assert.assertNotNull("Not a track", result.track);
Assert.assertNull("Ambiguous result", result.waypoint); Assert.assertNull("Ambiguous result", result.waypoint);
Assert.assertEquals(trackId, result.track.getId()); Assert.assertEquals(trackId, result.track.getId());
} }
private void assertTrackResults(List<ScoredResult> results, long... trackIds) { private void assertTrackResults(List<ScoredResult> results, Track.Id... trackIds) {
String errMsg = "Expected IDs=" + Arrays.toString(trackIds) + "; results=" + results; long[] longTrackIds = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
longTrackIds[i] = trackIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longTrackIds) + "; results=" + results;
Assert.assertEquals(results.size(), trackIds.length); Assert.assertEquals(results.size(), trackIds.length);
for (int i = 0; i < results.size(); i++) { for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i); ScoredResult result = results.get(i);
@@ -285,14 +295,19 @@ public class SearchEngineTest {
} }
} }
private void assertWaypointResult(long waypointId, ScoredResult result) { private void assertWaypointResult(Waypoint.Id waypointId, ScoredResult result) {
Assert.assertNotNull("Not a waypoint", result.waypoint); Assert.assertNotNull("Not a waypoint", result.waypoint);
Assert.assertNull("Ambiguous result", result.track); Assert.assertNull("Ambiguous result", result.track);
Assert.assertEquals(waypointId, result.waypoint.getId()); Assert.assertEquals(waypointId, result.waypoint.getId());
} }
private void assertWaypointResults(List<ScoredResult> results, long... waypointIds) { private void assertWaypointResults(List<ScoredResult> results, Waypoint.Id... waypointIds) {
String errMsg = "Expected IDs=" + Arrays.toString(waypointIds) + "; results=" + results; long[] longWaypointIds = new long[waypointIds.length];
for (int i = 0; i < waypointIds.length; i++) {
longWaypointIds[i] = waypointIds[i].getId();
}
String errMsg = "Expected IDs=" + Arrays.toString(longWaypointIds) + "; results=" + results;
Assert.assertEquals(results.size(), waypointIds.length); Assert.assertEquals(results.size(), waypointIds.length);
for (int i = 0; i < results.size(); i++) { for (int i = 0; i < results.size(); i++) {
ScoredResult result = results.get(i); ScoredResult result = results.get(i);
@@ -20,10 +20,10 @@ public class TestDataUtil {
/** /**
* Create a track without any trackPoints. * Create a track without any trackPoints.
*/ */
public static Track createTrack(long trackId) { public static Track createTrack(Track.Id trackId) {
Track track = new Track(); Track track = new Track();
track.setId(trackId); track.setId(trackId);
track.setName("Test: " + trackId); track.setName("Test: " + trackId.getId());
return track; return track;
} }
@@ -34,7 +34,7 @@ public class TestDataUtil {
* @param trackId the trackId of the track * @param trackId the trackId of the track
* @param numPoints the trackPoints number in the track * @param numPoints the trackPoints number in the track
*/ */
public static Pair<Track, TrackPoint[]> createTrack(long trackId, int numPoints) { public static Pair<Track, TrackPoint[]> createTrack(Track.Id trackId, int numPoints) {
Track track = createTrack(trackId); Track track = createTrack(trackId);
TrackPoint[] trackPoints = new TrackPoint[numPoints]; TrackPoint[] trackPoints = new TrackPoint[numPoints];
@@ -45,7 +45,7 @@ public class TestDataUtil {
return new Pair<>(track, trackPoints); return new Pair<>(track, trackPoints);
} }
public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, long trackId, int numPoints) { public static Track createTrackAndInsert(ContentProviderUtils contentProviderUtils, Track.Id trackId, int numPoints) {
Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints); Pair<Track, TrackPoint[]> pair = createTrack(trackId, numPoints);
insertTrackWithLocations(contentProviderUtils, pair.first, pair.second); insertTrackWithLocations(contentProviderUtils, pair.first, pair.second);
@@ -93,12 +93,11 @@ public class TestDataUtil {
* @param location The location. * @param location The location.
* @return the Waypoint created. * @return the Waypoint created.
*/ */
public static Waypoint createWaypointWithPhoto(Context context, long trackId, Location location) throws IOException { public static Waypoint createWaypointWithPhoto(Context context, Track.Id trackId, Location location) throws IOException {
String photoUrl = "";
File dstFile = new File(FileUtils.getImageUrl(context, trackId)); File dstFile = new File(FileUtils.getImageUrl(context, trackId));
dstFile.createNewFile(); dstFile.createNewFile();
Uri photoUri = FileUtils.getUriForFile(context, dstFile); Uri photoUri = FileUtils.getUriForFile(context, dstFile);
photoUrl = photoUri.toString(); String photoUrl = photoUri.toString();
return new Waypoint("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl); return new Waypoint("Waypoint name", "Waypoint description", "Waypoint category", "", trackId, 0.0, 0, location, photoUrl);
} }
@@ -22,7 +22,6 @@ import android.net.Uri;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -30,6 +29,9 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/** /**
* Tests {@link CustomContentProvider}. * Tests {@link CustomContentProvider}.
* *
@@ -51,7 +53,7 @@ public class CustomContentProviderTest {
*/ */
@Test @Test
public void testOnCreate() { public void testOnCreate() {
Assert.assertTrue(customContentProvider.onCreate(context)); assertTrue(customContentProvider.onCreate(context));
} }
/** /**
@@ -59,13 +61,13 @@ public class CustomContentProviderTest {
*/ */
@Test @Test
public void testGetType() { public void testGetType() {
Assert.assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI)); assertEquals(TracksColumns.CONTENT_TYPE, customContentProvider.getType(TracksColumns.CONTENT_URI));
Assert.assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build())); assertEquals(TracksColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), 1).build()));
Assert.assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID)); assertEquals(TrackPointsColumns.CONTENT_TYPE, customContentProvider.getType(TrackPointsColumns.CONTENT_URI_BY_ID));
Assert.assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build())); assertEquals(TrackPointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(TrackPointsColumns.CONTENT_URI_BY_TRACKID.buildUpon(), 1).build()));
Assert.assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI)); assertEquals(WaypointsColumns.CONTENT_TYPE, customContentProvider.getType(WaypointsColumns.CONTENT_URI));
Assert.assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build())); assertEquals(WaypointsColumns.CONTENT_ITEMTYPE, customContentProvider.getType(ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), 1).build()));
} }
} }
@@ -83,39 +83,39 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testLocationIterator_noPoints() { public void testLocationIterator_noPoints() {
testIterator(1, 0, 1, false); testIterator(new Track.Id(1), 0, 1, false);
} }
@Test @Test
public void testLocationIterator_noBatchAscending() { public void testLocationIterator_noBatchAscending() {
testIterator(1, 50, 100, false); testIterator(new Track.Id(1), 50, 100, false);
testIterator(2, 50, 50, false); testIterator(new Track.Id(2), 50, 50, false);
} }
@Test @Test
public void testLocationIterator_noBatchDescending() { public void testLocationIterator_noBatchDescending() {
testIterator(1, 50, 100, true); testIterator(new Track.Id(1), 50, 100, true);
testIterator(2, 50, 50, true); testIterator(new Track.Id(2), 50, 50, true);
} }
@Test @Test
public void testLocationIterator_batchAscending() { public void testLocationIterator_batchAscending() {
testIterator(1, 50, 11, false); testIterator(new Track.Id(1), 50, 11, false);
testIterator(2, 50, 25, false); testIterator(new Track.Id(2), 50, 25, false);
} }
@Test @Test
public void testLocationIterator_batchDescending() { public void testLocationIterator_batchDescending() {
testIterator(1, 50, 11, true); testIterator(new Track.Id(1), 50, 11, true);
testIterator(2, 50, 25, true); testIterator(new Track.Id(2), 50, 25, true);
} }
@Test @Test
public void testLocationIterator_largeTrack() { public void testLocationIterator_largeTrack() {
testIterator(1, 20000, 2000, false); testIterator(new Track.Id(1), 20000, 2000, false);
} }
private void testIterator(long trackId, int numPoints, int batchSize, boolean descending) { private void testIterator(Track.Id trackId, int numPoints, int batchSize, boolean descending) {
long lastPointId = initializeTrack(trackId, numPoints); long lastPointId = initializeTrack(trackId, numPoints);
contentProviderUtils.setDefaultCursorBatchSize(batchSize); contentProviderUtils.setDefaultCursorBatchSize(batchSize);
List<TrackPoint> locations = new ArrayList<>(numPoints); List<TrackPoint> locations = new ArrayList<>(numPoints);
@@ -132,10 +132,10 @@ public class CustomContentProviderUtilsTest {
} }
} }
private long initializeTrack(long id, int numPoints) { private long initializeTrack(Track.Id id, int numPoints) {
Track track = new Track(); Track track = new Track();
track.setId(id); track.setId(id);
track.setName("Test: " + id); track.setName("Test: " + id.getId());
contentProviderUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
track = contentProviderUtils.getTrack(id); track = contentProviderUtils.getTrack(id);
Assert.assertNotNull(track); Assert.assertNotNull(track);
@@ -173,13 +173,13 @@ public class CustomContentProviderUtilsTest {
*/ */
@Test @Test
public void testCreateTrack() { public void testCreateTrack() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
int columnIndex = 1; int columnIndex = 1;
// Id // Id
when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex); when(cursorMock.getColumnIndexOrThrow(TracksColumns._ID)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false); when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getLong(columnIndex)).thenReturn(trackId); when(cursorMock.getLong(columnIndex)).thenReturn(trackId.getId());
//Uuid //Uuid
columnIndex++; columnIndex++;
@@ -189,7 +189,7 @@ public class CustomContentProviderUtilsTest {
// Name // Name
columnIndex++; columnIndex++;
String name = NAME_PREFIX + trackId; String name = NAME_PREFIX + trackId.getId();
when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex); when(cursorMock.getColumnIndexOrThrow(TracksColumns.NAME)).thenReturn(columnIndex);
when(cursorMock.isNull(columnIndex)).thenReturn(false); when(cursorMock.isNull(columnIndex)).thenReturn(false);
when(cursorMock.getString(columnIndex)).thenReturn(name); when(cursorMock.getString(columnIndex)).thenReturn(name);
@@ -205,7 +205,7 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testDeleteAllTracks() { public void testDeleteAllTracks() {
// Insert track, points and waypoint at first. // Insert track, points and waypoint at first.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId)); Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
@@ -235,7 +235,7 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException { public void testDeleteAllTracks_withWaypointAndPhoto() throws IOException {
// Insert track, points and waypoint with photo at first. // Insert track, points and waypoint with photo at first.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId); TrackPoint trackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
@@ -268,20 +268,23 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}. * Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
*/ */
@Test @Test
public void testDeleteTrack() { public void testDeleteTrack() {
// Insert three tracks, points of two tracks and way point of one track. // Insert three tracks, points of two tracks and way point of one track.
long random = System.currentTimeMillis();
Track.Id trackId1 = new Track.Id(random);
Track.Id trackId2 = new Track.Id(random + 1);
Track.Id trackId3 = new Track.Id(random + 2);
long trackId = System.currentTimeMillis(); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 0);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId + 1)); Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId2));
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId1);
contentProviderUtils.insertWaypoint(waypoint); contentProviderUtils.insertWaypoint(waypoint);
ContentResolver contentResolver = context.getContentResolver(); ContentResolver contentResolver = context.getContentResolver();
@@ -292,7 +295,7 @@ public class CustomContentProviderUtilsTest {
Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID); Cursor waypointCursor = contentResolver.query(WaypointsColumns.CONTENT_URI, null, null, null, WaypointsColumns._ID);
Assert.assertEquals(1, waypointCursor.getCount()); Assert.assertEquals(1, waypointCursor.getCount());
// Delete one track. // Delete one track.
contentProviderUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted. // Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID); tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount()); Assert.assertEquals(2, tracksCursor.getCount());
@@ -303,26 +306,30 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#deleteTrack(Context, long)}. * Tests the method {@link ContentProviderUtils#deleteTrack(Context, Track.Id)}.
*/ */
@Test @Test
public void testDeleteTrack_withWaypointPhoto() throws IOException { public void testDeleteTrack_withWaypointPhoto() throws IOException {
// Insert three tracks. // Insert three tracks.
long trackId = System.currentTimeMillis(); long random = System.currentTimeMillis();
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); Track.Id trackId1 = new Track.Id(random);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 1, 10); Track.Id trackId2 = new Track.Id(random + 1);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId + 2, 10); Track.Id trackId3 = new Track.Id(random + 2);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId1, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId2, 10);
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId3, 10);
// Insert a waypoint in tracks trackId and trackId + 1. // Insert a waypoint in tracks trackId and trackId + 1.
TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId); TrackPoint trackPoint1 = contentProviderUtils.getLastValidTrackPoint(trackId1);
Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint1.getLocation()); Waypoint waypoint1 = TestDataUtil.createWaypointWithPhoto(context, trackId1, trackPoint1.getLocation());
contentProviderUtils.insertWaypoint(waypoint1); contentProviderUtils.insertWaypoint(waypoint1);
File dir1 = FileUtils.getPhotoDir(context, trackId); File dir1 = FileUtils.getPhotoDir(context, trackId1);
TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId + 1); TrackPoint trackPoint2 = contentProviderUtils.getLastValidTrackPoint(trackId2);
Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId + 1, trackPoint2.getLocation()); Waypoint waypoint2 = TestDataUtil.createWaypointWithPhoto(context, trackId2, trackPoint2.getLocation());
contentProviderUtils.insertWaypoint(waypoint2); contentProviderUtils.insertWaypoint(waypoint2);
File dir2 = FileUtils.getPhotoDir(context, trackId + 1); File dir2 = FileUtils.getPhotoDir(context, trackId2);
// Check. // Check.
ContentResolver contentResolver = context.getContentResolver(); ContentResolver contentResolver = context.getContentResolver();
@@ -340,7 +347,7 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(1, dir2.list().length); Assert.assertEquals(1, dir2.list().length);
Assert.assertTrue(dir2.exists()); Assert.assertTrue(dir2.exists());
// Delete one track. // Delete one track.
contentProviderUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId1);
// Check whether all data of a track has been deleted. // Check whether all data of a track has been deleted.
tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID); tracksCursor = contentResolver.query(TracksColumns.CONTENT_URI, null, null, null, TracksColumns._ID);
Assert.assertEquals(2, tracksCursor.getCount()); Assert.assertEquals(2, tracksCursor.getCount());
@@ -353,17 +360,17 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getAllTracks()} * Tests the method {@link ContentProviderUtils#getTracks()}
*/ */
@Test @Test
public void testGetAllTracks() { public void testGetAllTracks() {
// given // given
int initialTrackNumber = contentProviderUtils.getAllTracks().size(); int initialTrackNumber = contentProviderUtils.getTracks().size();
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId)); contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
// when // when
List<Track> allTracks = contentProviderUtils.getAllTracks(); List<Track> allTracks = contentProviderUtils.getTracks();
// then // then
Assert.assertEquals(initialTrackNumber + 1, allTracks.size()); Assert.assertEquals(initialTrackNumber + 1, allTracks.size());
@@ -375,28 +382,45 @@ public class CustomContentProviderUtilsTest {
*/ */
@Test @Test
public void testGetLastTrack() { public void testGetLastTrack() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId)); contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId()); Assert.assertEquals(trackId, contentProviderUtils.getLastTrack().getId());
} }
/** /**
* Tests the method {@link ContentProviderUtils#getTrack(long)} * Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
*/ */
@Test @Test
public void testGetTrack() { public void testGetTrack_by_id() {
long trackId = System.currentTimeMillis(); // given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId)); contentProviderUtils.insertTrack(TestDataUtil.createTrack(trackId));
// when / then
Assert.assertNotNull(contentProviderUtils.getTrack(trackId)); Assert.assertNotNull(contentProviderUtils.getTrack(trackId));
} }
/**
* Tests the method {@link ContentProviderUtils#getTrack(Track.Id)}
*/
@Test
public void testGetTrack_by_uuid() {
// given
Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId);
contentProviderUtils.insertTrack(track);
// when / then
Assert.assertNotNull(contentProviderUtils.getTrack(track.getUuid()));
}
/** /**
* Tests the method {@link ContentProviderUtils#updateTrack(Track)} * Tests the method {@link ContentProviderUtils#updateTrack(Track)}
*/ */
@Test @Test
public void testUpdateTrack() { public void testUpdateTrack() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrack(trackId); Track track = TestDataUtil.createTrack(trackId);
String nameOld = "name1"; String nameOld = "name1";
String nameNew = "name2"; String nameNew = "name2";
@@ -415,7 +439,7 @@ public class CustomContentProviderUtilsTest {
*/ */
@Test @Test
public void testCreateContentValues_waypoint() { public void testCreateContentValues_waypoint() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10); Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
// Bottom // Bottom
@@ -441,10 +465,10 @@ public class CustomContentProviderUtilsTest {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(contentResolverMock);
long waypointId = System.currentTimeMillis(); Waypoint.Id waypointId = new Waypoint.Id(System.currentTimeMillis());
waypoint.setId(waypointId); waypoint.setId(waypointId);
ContentValues contentValues = contentProviderUtils.createContentValues(waypoint); ContentValues contentValues = contentProviderUtils.createContentValues(waypoint);
Assert.assertEquals(waypointId, contentValues.get(WaypointsColumns._ID)); Assert.assertEquals(waypointId.getId(), contentValues.get(WaypointsColumns._ID));
Assert.assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE)); Assert.assertEquals((int) (TestDataUtil.INITIAL_LONGITUDE * 1000000), contentValues.get(WaypointsColumns.LONGITUDE));
Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION)); Assert.assertEquals(TEST_DESC, contentValues.get(WaypointsColumns.DESCRIPTION));
} }
@@ -478,19 +502,19 @@ public class CustomContentProviderUtilsTest {
when(cursorMock.getLong(columnIndex++)).thenReturn(trackId); when(cursorMock.getLong(columnIndex++)).thenReturn(trackId);
Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock); Waypoint waypoint = contentProviderUtils.createWaypoint(cursorMock);
Assert.assertEquals(id, waypoint.getId()); Assert.assertEquals(id, waypoint.getId().getId());
Assert.assertEquals(name, waypoint.getName()); Assert.assertEquals(name, waypoint.getName());
Assert.assertEquals(trackId, waypoint.getTrackId()); Assert.assertEquals(trackId, waypoint.getTrackId().getId());
} }
/** /**
* Tests the method * Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long)} * {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)}
* when there is only one waypoint in the track. * when there is only one waypoint in the track.
*/ */
@Test @Test
public void testDeleteWaypoint_onlyOneWayPoint() { public void testDeleteWaypoint_onlyOneWayPoint() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first. // Insert at first.
@@ -503,7 +527,7 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1); Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
// Get waypoint id that needs to delete. // Get waypoint id that needs to delete.
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)); Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
// Delete // Delete
contentProviderUtils.deleteWaypoint(context, waypoint1Id); contentProviderUtils.deleteWaypoint(context, waypoint1Id);
@@ -513,12 +537,12 @@ public class CustomContentProviderUtilsTest {
/** /**
* Tests the method * Tests the method
* {@link ContentProviderUtils#deleteWaypoint(Context, long)} * {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)}
* when there is only one waypoint in the track. * when there is only one waypoint in the track.
*/ */
@Test @Test
public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException { public void testDeleteWaypoint_onlyOneWayPointWithPhotoUrl() throws IOException {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first. // Insert at first.
@@ -530,7 +554,7 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1); Assert.assertEquals(contentProviderUtils.getWaypointCount(trackId), 1);
// Get waypoint id that needs to delete. // Get waypoint id that needs to delete.
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)); Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
// Check waypoint has photo and it's in the external storage. // Check waypoint has photo and it's in the external storage.
Assert.assertTrue(waypoint1.hasPhoto()); Assert.assertTrue(waypoint1.hasPhoto());
@@ -548,11 +572,11 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, long)} when there is more than one waypoint in the track. * Tests the method {@link ContentProviderUtils#deleteWaypoint(Context, Waypoint.Id)} when there is more than one waypoint in the track.
*/ */
@Test @Test
public void testDeleteWaypoint_hasNextWayPoint() { public void testDeleteWaypoint_hasNextWayPoint() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Track track = TestDataUtil.createTrackAndInsert(trackId, 10); // Track track = TestDataUtil.createTrackAndInsert(trackId, 10);
@@ -575,12 +599,12 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId)); Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint1.setDescription(MOCK_DESC); waypoint1.setDescription(MOCK_DESC);
waypoint1.setTrackId(trackId); waypoint1.setTrackId(trackId);
long waypoint1Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)); Waypoint.Id waypoint1Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint1)));
Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId)); Waypoint waypoint2 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint2.setDescription(MOCK_DESC); waypoint2.setDescription(MOCK_DESC);
waypoint2.setTrackId(trackId); waypoint2.setTrackId(trackId);
long waypoint2Id = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2)); Waypoint.Id waypoint2Id = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint2)));
// Delete // Delete
Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id)); Assert.assertNotNull(contentProviderUtils.getWaypoint(waypoint1Id));
@@ -591,11 +615,11 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getNextWaypointNumber(long)}. * Tests the method {@link ContentProviderUtils#getNextWaypointNumber(Track.Id)}.
*/ */
@Test @Test
public void testGetNextWaypointNumber() { public void testGetNextWaypointNumber() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId)); Waypoint waypoint1 = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
@@ -616,17 +640,17 @@ public class CustomContentProviderUtilsTest {
/** /**
* Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and * Tests the method {@link ContentProviderUtils#insertWaypoint(Waypoint)} and
* {@link ContentProviderUtils#getWaypoint(long)}. * {@link ContentProviderUtils#getWaypoint(Waypoint.Id)}.
*/ */
@Test @Test
public void testInsertAndGetWaypoint() { public void testInsertAndGetWaypoint() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId)); Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)); Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription()); Assert.assertEquals(TEST_DESC, contentProviderUtils.getWaypoint(waypointId).getDescription());
} }
@@ -636,14 +660,14 @@ public class CustomContentProviderUtilsTest {
*/ */
@Test @Test
public void testUpdateWaypoint() { public void testUpdateWaypoint() {
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first. // Insert at first.
Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId)); Waypoint waypoint = new Waypoint(contentProviderUtils.getLastValidTrackPoint(trackId));
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)); Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
// Update // Update
waypoint = contentProviderUtils.getWaypoint(waypointId); waypoint = contentProviderUtils.getWaypoint(waypointId);
@@ -660,7 +684,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_withPhoto() throws IOException { public void testUpdateWaypoint_withPhoto() throws IOException {
// tests after update waypoint with photo the photo remains in the storage. // tests after update waypoint with photo the photo remains in the storage.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first. // Insert at first.
@@ -668,9 +692,9 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)); Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
Assert.assertTrue(dir.exists()); Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory()); Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length); Assert.assertEquals(1, dir.list().length);
@@ -696,7 +720,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_delPhotoAndDir() throws IOException { public void testUpdateWaypoint_delPhotoAndDir() throws IOException {
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted. // tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Also empty directory is deleted.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert at first. // Insert at first.
@@ -704,9 +728,9 @@ public class CustomContentProviderUtilsTest {
Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); Waypoint waypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
waypoint.setDescription(TEST_DESC); waypoint.setDescription(TEST_DESC);
waypoint.setTrackId(trackId); waypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)); Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
Assert.assertTrue(dir.exists()); Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory()); Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(1, dir.list().length); Assert.assertEquals(1, dir.list().length);
@@ -731,7 +755,7 @@ public class CustomContentProviderUtilsTest {
public void testUpdateWaypoint_delPhotoNotDir() throws IOException { public void testUpdateWaypoint_delPhotoNotDir() throws IOException {
// tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints. // tests after update waypoint if user deletes the photo then file photo is deleted from the storage. Directory remains if there are more photos from other waypoints.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
// Insert two waypoints with photos. // Insert two waypoints with photos.
@@ -742,10 +766,10 @@ public class CustomContentProviderUtilsTest {
Waypoint otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation()); Waypoint otherWaypoint = TestDataUtil.createWaypointWithPhoto(context, trackId, trackPoint.getLocation());
otherWaypoint.setDescription(TEST_DESC); otherWaypoint.setDescription(TEST_DESC);
otherWaypoint.setTrackId(trackId); otherWaypoint.setTrackId(trackId);
long waypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)); Waypoint.Id waypointId = new Waypoint.Id(ContentUris.parseId(contentProviderUtils.insertWaypoint(waypoint)));
long otherWaypointId = ContentUris.parseId(contentProviderUtils.insertWaypoint(otherWaypoint)); contentProviderUtils.insertWaypoint(otherWaypoint);
File dir = new File(FileUtils.getPhotoDir(context), "" + trackId); File dir = new File(FileUtils.getPhotoDir(context), "" + trackId.getId());
Assert.assertTrue(dir.exists()); Assert.assertTrue(dir.exists());
Assert.assertTrue(dir.isDirectory()); Assert.assertTrue(dir.isDirectory());
Assert.assertEquals(2, dir.list().length); Assert.assertEquals(2, dir.list().length);
@@ -763,12 +787,12 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], long)}. * Tests the method {@link ContentProviderUtils#bulkInsertTrackPoint(TrackPoint[], Track.Id)}.
*/ */
@Test @Test
public void testBulkInsertTrackPoint() { public void testBulkInsertTrackPoint() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10); Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second); TestDataUtil.insertTrackWithLocations(contentProviderUtils, track.first, track.second);
@@ -824,12 +848,12 @@ public class CustomContentProviderUtilsTest {
/** /**
* Tests the method * Tests the method
* {@link ContentProviderUtils#insertTrackPoint(TrackPoint, long)}. * {@link ContentProviderUtils#insertTrackPoint(TrackPoint, Track.Id)}.
*/ */
@Test @Test
public void testInsertTrackPoint() { public void testInsertTrackPoint() {
// Insert track, point at first. // Insert track, point at first.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId); contentProviderUtils.insertTrackPoint(TestDataUtil.createTrackPoint(22), trackId);
@@ -839,7 +863,7 @@ public class CustomContentProviderUtilsTest {
@Test @Test
public void testInsertAndLoadTrackPoint() { public void testInsertAndLoadTrackPoint() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint trackPoint = TestDataUtil.createTrackPoint(5); TrackPoint trackPoint = TestDataUtil.createTrackPoint(5);
@@ -859,12 +883,12 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(long)}. * Tests the method {@link ContentProviderUtils#getLastValidTrackPoint(Track.Id)}.
*/ */
@Test @Test
public void testGetLastValidTrackPoint() { public void testGetLastValidTrackPoint() {
// Insert track, points at first. // Insert track, points at first.
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10); Track track = TestDataUtil.createTrackAndInsert(contentProviderUtils, trackId, 10);
TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId); TrackPoint lastTrackPoint = contentProviderUtils.getLastValidTrackPoint(trackId);
@@ -872,12 +896,12 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in descending. * Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in descending.
*/ */
@Test @Test
public void testGetTrackPointCursor_desc() { public void testGetTrackPointCursor_desc() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10); Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first); contentProviderUtils.insertTrack(track.first);
@@ -894,12 +918,12 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getTrackPointCursor(long, long, int, boolean)} in ascending. * Tests the method {@link ContentProviderUtils#getTrackPointCursor(Track.Id, long, int, boolean)} in ascending.
*/ */
@Test @Test
public void testGetTrackPointCursor_asc() { public void testGetTrackPointCursor_asc() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10); Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first); contentProviderUtils.insertTrack(track.first);
@@ -916,12 +940,12 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in descending. * Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in descending.
*/ */
@Test @Test
public void testGetTrackPointLocationIterator_desc() { public void testGetTrackPointLocationIterator_desc() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10); Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first); contentProviderUtils.insertTrack(track.first);
@@ -945,12 +969,12 @@ public class CustomContentProviderUtilsTest {
} }
/** /**
* Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(long, long, boolean)} in ascending. * Tests the method {@link ContentProviderUtils#getTrackPointLocationIterator(Track.Id, long, boolean)} in ascending.
*/ */
@Test @Test
public void testGetTrackPointLocationIterator_asc() { public void testGetTrackPointLocationIterator_asc() {
// given // given
long trackId = System.currentTimeMillis(); Track.Id trackId = new Track.Id(System.currentTimeMillis());
Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10); Pair<Track, TrackPoint[]> track = TestDataUtil.createTrack(trackId, 10);
contentProviderUtils.insertTrack(track.first); contentProviderUtils.insertTrack(track.first);
@@ -978,7 +1002,7 @@ public class CustomContentProviderUtilsTest {
/** /**
* Checks the value of a location. * Checks the value of a location.
* *
* @param i the index of this location which created in the method {@link TestDataUtil#createTrack(long, int)} * @param i the index of this location which created in the method {@link TestDataUtil#createTrack(Track.Id, int)}
* @param location the location to be checked * @param location the location to be checked
*/ */
private void checkLocation(int i, Location location) { private void checkLocation(int i, Location location) {
@@ -988,12 +1012,10 @@ public class CustomContentProviderUtilsTest {
Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01); Assert.assertEquals(i * TestDataUtil.ALTITUDE_INTERVAL, location.getAltitude(), 0.01);
} }
@Test @Test
public void testFormatIdListForUri() { public void testFormatIdListForUri() {
Assert.assertEquals("", ContentProviderUtils.formatIdListForUri(new long[]{})); Assert.assertEquals("", ContentProviderUtils.formatIdListForUri());
Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new long[]{12})); Assert.assertEquals("12", ContentProviderUtils.formatIdListForUri(new Track.Id(12)));
Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new long[]{42, 43, 44})); Assert.assertEquals("42,43,44", ContentProviderUtils.formatIdListForUri(new Track.Id(42), new Track.Id(43), new Track.Id(44)));
} }
} }
@@ -2,6 +2,7 @@ package de.dennisguse.opentracks.content.provider;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@@ -9,7 +10,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -21,6 +22,10 @@ import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class CustomSQLiteOpenHelperTest { public class CustomSQLiteOpenHelperTest {
@@ -88,6 +93,7 @@ public class CustomSQLiteOpenHelperTest {
} }
@Before @Before
@After
public void setUp() { public void setUp() {
context.deleteDatabase(DATABASE_NAME); context.deleteDatabase(DATABASE_NAME);
} }
@@ -95,13 +101,15 @@ public class CustomSQLiteOpenHelperTest {
@Test @Test
public void onCreate() { public void onCreate() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) { try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
Assert.assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE)); assertTrue(hasSqlCreate(db, TracksColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE)); assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX)); assertTrue(hasSqlCreate(db, TrackPointsColumns.CREATE_TABLE_INDEX));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE)); assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE));
Assert.assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX)); assertTrue(hasSqlCreate(db, WaypointsColumns.CREATE_TABLE_INDEX));
} catch (Exception e) {
fail();
} }
} }
@@ -127,18 +135,18 @@ public class CustomSQLiteOpenHelperTest {
} }
// then - verify table structure // then - verify table structure
Assert.assertEquals(3, tableByUpgrade.size()); assertEquals(3, tableByUpgrade.size());
Assert.assertEquals(tableByUpgrade.size(), tablesByCreate.size()); assertEquals(tableByUpgrade.size(), tablesByCreate.size());
Assert.assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME)); assertEquals(tablesByCreate.get(TracksColumns.TABLE_NAME), tableByUpgrade.get(TracksColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME)); assertEquals(tablesByCreate.get(TrackPointsColumns.TABLE_NAME), tableByUpgrade.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME)); assertEquals(tablesByCreate.get(WaypointsColumns.TABLE_NAME), tableByUpgrade.get(WaypointsColumns.TABLE_NAME));
// then - verify custom indices // then - verify custom indices
Assert.assertEquals(3, indicesByCreate.size()); assertEquals(3, indicesByCreate.size());
Assert.assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME)); assertEquals(indicesByUpgrade.get(TracksColumns.TABLE_NAME), indicesByCreate.get(TracksColumns.TABLE_NAME));
Assert.assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME)); assertEquals(indicesByUpgrade.get(TrackPointsColumns.TABLE_NAME), indicesByCreate.get(TrackPointsColumns.TABLE_NAME));
Assert.assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME)); assertEquals(indicesByUpgrade.get(WaypointsColumns.TABLE_NAME), indicesByCreate.get(WaypointsColumns.TABLE_NAME));
} }
@Test @Test
@@ -155,12 +163,23 @@ public class CustomSQLiteOpenHelperTest {
} }
// then - verify table structure // then - verify table structure
Assert.assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks")); assertEquals(TRACKS_CREATE_TABLE_V23, tablesByDowngrade.get("tracks"));
Assert.assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints")); assertEquals(TRACKPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("trackpoints"));
Assert.assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints")); assertEquals(WAYPOINTS_CREATE_TABLE_V23, tablesByDowngrade.get("waypoints"));
// then - verify custom indices // then - verify custom indices
Assert.assertEquals(0, indicesByDowngrade.size()); assertEquals(0, indicesByDowngrade.size());
}
@Test
public void track_uuid_unique() {
try (SQLiteDatabase db = new CustomSQLiteOpenHelper(context, DATABASE_NAME).getWritableDatabase()) {
db.execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
db.execSQL("INSERT INTO tracks (uuid) VALUES (0x00)");
fail("unique constraint not enforced");
} catch (SQLiteConstraintException e) {
assertTrue(e.getMessage().contains("UNIQUE constraint failed: tracks.uuid"));
}
} }
private void createVersion23() { private void createVersion23() {
@@ -6,6 +6,10 @@ import android.util.Pair;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
public class ShareContentProviderTest { public class ShareContentProviderTest {
@@ -14,20 +18,25 @@ public class ShareContentProviderTest {
public void testCreateandParseURI_invalid() { public void testCreateandParseURI_invalid() {
Uri uri = Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/1"); Uri uri = Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/1");
Assert.assertArrayEquals(new long[]{}, ShareContentProvider.parseURI(uri)); Assert.assertEquals(0, ShareContentProvider.parseURI(uri).size());
} }
@Test @Test
public void testCreateandParseURI_valid() { public void testCreateandParseURI_valid() {
long[] trackIds = {1, 3, 5}; Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
trackIds.add(new Track.Id(3));
trackIds.add(new Track.Id(5));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK); Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "TrackName", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertArrayEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first)); Assert.assertEquals(trackIds, ShareContentProvider.parseURI(shareURIandMIME.first));
} }
@Test @Test
public void testCreateURIescapeFilename() { public void testCreateURIescapeFilename() {
long[] trackIds = {1}; Set<Track.Id> trackIds = new HashSet<>();
trackIds.add(new Track.Id(1));
Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK); Pair<Uri, String> shareURIandMIME = ShareContentProvider.createURI(trackIds, "../../&1=1", TrackFileFormat.KML_ONLY_TRACK);
Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first); Assert.assertEquals(Uri.parse("content://de.dennisguse.opentracks.debug.content/tracks/kml_only_track/1/..%2F..%2F%261%3D1.kml"), shareURIandMIME.first);
@@ -253,7 +253,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint1.getTime() + 222); trackPoint2.setTime(trackPoint1.getTime() + 222);
trackPoint2.setSpeed(130); trackPoint2.setSpeed(130);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(130.0 * UnitConversions.MS_TO_KMH, point.getSpeed(), 0.01); Assert.assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01);
} }
/** /**
@@ -278,7 +278,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222); trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102); trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(102.0 * UnitConversions.MS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01); Assert.assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01);
} }
/** /**
@@ -303,7 +303,7 @@ public class ChartFragmentTest {
trackPoint2.setTime(trackPoint2.getTime() + 222); trackPoint2.setTime(trackPoint2.getTime() + 222);
trackPoint2.setSpeed(102); trackPoint2.setSpeed(102);
point = chartFragment.createPendingPoint(trackPoint2); point = chartFragment.createPendingPoint(trackPoint2);
Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MS_TO_KMH), point.getPace(), 0.01); Assert.assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01);
} }
/** /**
@@ -29,13 +29,13 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.util.Locale; import java.util.Locale;
import java.util.SimpleTimeZone; import java.util.SimpleTimeZone;
import java.util.TimeZone;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -64,11 +64,11 @@ public abstract class AbstractTestFileTrackImporter {
static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US); static final SimpleDateFormat DATE_FORMAT_0 = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); static final SimpleDateFormat DATE_FORMAT_1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
static final long TRACK_ID_0 = 1;
static final long TRACK_POINT_ID_0 = 1; static final long TRACK_POINT_ID_0 = 1;
static final long TRACK_POINT_ID_1 = 2; static final long TRACK_POINT_ID_1 = 2;
static final long TRACK_POINT_ID_3 = 4; static final long TRACK_POINT_ID_3 = 4;
static final Uri TRACK_ID_0_URI = ContentUris.appendId(TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID_0).build();
private static final long WAYPOINT_ID_0 = 1; private static final long WAYPOINT_ID_0 = 1;
private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build(); private static final Uri WAYPOINT_ID_O_URI = ContentUris.appendId(WaypointsColumns.CONTENT_URI.buildUpon(), WAYPOINT_ID_0).build();
@@ -76,7 +76,7 @@ public abstract class AbstractTestFileTrackImporter {
static { static {
// We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is. // We can't omit the timezones in the test, otherwise it'll use the local timezone and fail depending on where the test runner is.
SimpleTimeZone utc = new SimpleTimeZone(0, "UTC"); TimeZone utc = SimpleTimeZone.getTimeZone(ZoneOffset.UTC);
DATE_FORMAT_0.setTimeZone(utc); DATE_FORMAT_0.setTimeZone(utc);
DATE_FORMAT_1.setTimeZone(utc); DATE_FORMAT_1.setTimeZone(utc);
} }
@@ -100,7 +100,7 @@ public abstract class AbstractTestFileTrackImporter {
* @param trackId the track id * @param trackId the track id
* @param trackPointId the track point id * @param trackPointId the track point id
*/ */
protected void expectFirstTrackPoint(TrackPoint trackPoint, long trackId, long trackPointId) { protected void expectFirstTrackPoint(TrackPoint trackPoint, Track.Id trackId, long trackPointId) {
when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1); when(contentProviderUtils.bulkInsertTrackPoint(trackPoint != null ? (TrackPoint[]) any() : (TrackPoint[]) any(), eq(trackId))).thenReturn(1);
} }
@@ -111,10 +111,10 @@ public abstract class AbstractTestFileTrackImporter {
* @param lastTrack true if it is the last track in the gpx * @param lastTrack true if it is the last track in the gpx
* @param trackId the track id * @param trackId the track id
*/ */
protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, long trackId) { protected void expectTrackUpdate(ArgumentCaptor<Track> trackCaptor, boolean lastTrack, Track.Id trackId) {
contentProviderUtils.updateTrack(trackCaptor.capture()); contentProviderUtils.updateTrack(trackCaptor.capture());
when(contentProviderUtils.insertWaypoint((Waypoint) any())).thenReturn(WAYPOINT_ID_O_URI); when(contentProviderUtils.insertWaypoint(any())).thenReturn(WAYPOINT_ID_O_URI);
if (lastTrack) { if (lastTrack) {
// Return null to not add waypoints // Return null to not add waypoints
when(contentProviderUtils.getTrack(trackId)).thenReturn(null); when(contentProviderUtils.getTrack(trackId)).thenReturn(null);
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.TestDataUtil;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
@@ -28,9 +29,11 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
import de.dennisguse.opentracks.io.file.exporter.TrackExporter; import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/** /**
* Export a track to {@link TrackFileFormat} and verify that the import is identical. * Export a track to {@link TrackFileFormat} and verify that the import is identical.
@@ -53,8 +56,8 @@ public class ExportImportTest {
private final List<Waypoint> waypoints = new ArrayList<>(); private final List<Waypoint> waypoints = new ArrayList<>();
private final List<TrackPoint> trackPoints = new ArrayList<>(); private final List<TrackPoint> trackPoints = new ArrayList<>();
private long importTrackId; private Track.Id importTrackId;
private final long trackId = System.currentTimeMillis(); private final Track.Id trackId = new Track.Id(System.currentTimeMillis());
@Before @Before
public void setUp() { public void setUp() {
@@ -103,16 +106,17 @@ public class ExportImportTest {
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context, new Track[]{track}); TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL.newTrackExporter(context);
// when // when
// 1. export // 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream); trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L); AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream);
// then // then
@@ -123,6 +127,7 @@ public class ExportImportTest {
assertEquals(track.getDescription(), importedTrack.getDescription()); assertEquals(track.getDescription(), importedTrack.getDescription());
assertEquals(track.getName(), importedTrack.getName()); assertEquals(track.getName(), importedTrack.getName());
assertEquals(track.getIcon(), importedTrack.getIcon()); assertEquals(track.getIcon(), importedTrack.getIcon());
assertEquals(track.getUuid(), importedTrack.getUuid());
// 2. waypoints // 2. waypoints
assertWaypoints(); assertWaypoints();
@@ -137,16 +142,17 @@ public class ExportImportTest {
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context, new Track[]{track}); TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
// when // when
// 1. export // 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream); trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context, -1L); AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream); importTrackId = trackImporter.importFile(inputStream);
// then // then
@@ -165,6 +171,30 @@ public class ExportImportTest {
assertTrackpoints(true, true, true); assertTrackpoints(true, true, true);
} }
@LargeTest
@Test
public void kml_with_trackdetail_and_sensordata_duplicate_trackUUID() {
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(track, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new KmlFileTrackImporter(context);
importTrackId = trackImporter.importFile(inputStream);
// then
Track importedTrack = contentProviderUtils.getTrack(importTrackId);
assertNull(importedTrack);
}
@LargeTest @LargeTest
@Test @Test
public void kmz_only_track() { public void kmz_only_track() {
@@ -199,12 +229,13 @@ public class ExportImportTest {
// given // given
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context, new Track[]{track}); TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
// when // when
// 1. export // 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(context, outputStream); trackExporter.writeTrack(track, outputStream);
contentProviderUtils.deleteTrack(context, trackId);
// 2. import // 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
@@ -229,6 +260,31 @@ public class ExportImportTest {
assertTrackpoints(false, true, true); assertTrackpoints(false, true, true);
} }
@LargeTest
@Test
public void gpx_duplicate_trackUUID() {
// given
PreferencesUtils.setBoolean(context, R.string.import_prevent_reimport_key, true);
Track track = contentProviderUtils.getTrack(trackId);
TrackExporter trackExporter = TrackFileFormat.GPX.newTrackExporter(context);
// when
// 1. export
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
trackExporter.writeTrack(track, outputStream);
// 2. import
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
AbstractFileTrackImporter trackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
importTrackId = trackImporter.importFile(inputStream);
// then
// 1. track
Track trackImported = contentProviderUtils.getTrack(importTrackId);
assertNull(trackImported);
}
private void assertWaypoints() { private void assertWaypoints() {
assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId)); assertEquals(waypoints.size(), contentProviderUtils.getWaypointCount(importTrackId));
@@ -1,217 +0,0 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.io.file.importer;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link GpxFileTrackImporter}.
*
* @author Steffen Horlacher
*/
@RunWith(MockitoJUnitRunner.class)
public class GpxFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<gpx><trk>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</trkseg></trk></gpx>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<gpx><trk>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</trkseg><trkseg>"
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3) + "</trkseg></trk></gpx>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX = "<gpx><trk>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<trkseg>"
+ getTrackPoint(0, null) + getTrackPoint(1, null) + "</trkseg><trkseg>"
+ getTrackPoint(2, null) + getTrackPoint(3, null) + "</trkseg></trk></gpx>";
private static final String INVALID_XML_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.substring(0, VALID_ONE_TRACK_ONE_SEGMENT_GPX.length() - 50);
private static final String INVALID_LOCATION_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "1000.0");
private static final String INVALID_TIME_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(TRACK_TIME_0, "invalid");
private static final String INVALID_ALTITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_ELEVATION), "invalid");
private static final String INVALID_LATITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LATITUDE), "invalid");
private static final String INVALID_LONGITUDE_GPX = VALID_ONE_TRACK_ONE_SEGMENT_GPX.replaceAll(Double.toString(TRACK_LONGITUDE), "invalid");
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name>" + "<desc><![CDATA[" + description + "]]></desc>";
}
private static String getTrackPoint(int index, String time) {
String latitude = Double.toString(TRACK_LATITUDE + index);
String longitude = Double.toString(TRACK_LONGITUDE + index);
String elevation = Double.toString(TRACK_ELEVATION + index);
StringBuilder buffer = new StringBuilder();
buffer.append("<trkpt lat=\"").append(latitude).append("\" lon=\"").append(longitude).append("\"><ele>").append(elevation).append("</ele>");
if (time != null) {
buffer.append("<time>").append(time).append("</time>");
}
buffer.append("</trkpt>");
return buffer.toString();
}
@Test
public void testOneTrackOneSegment() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPoint1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
Assert.assertEquals(TRACK_ID_0, trackId);
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
@Test
public void testOneTrackTwoSegments() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
@Test
public void testOneTrackTwoSegmentsNoTime() {
// given
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
expectFirstTrackPoint(null, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_NO_TIME_GPX.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
Assert.assertEquals(0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, -1L);
}
@Test
public void testInvalidXml() {
testInvalidGpx(INVALID_XML_GPX);
}
@Test
public void testInvalidLocation() {
testInvalidGpx(INVALID_LOCATION_GPX);
}
@Test
public void testInvalidTime() {
testInvalidGpx(INVALID_TIME_GPX);
}
@Test
public void testInvalidAltitude() {
testInvalidGpx(INVALID_ALTITUDE_GPX);
}
@Test
public void testInvalidLatitude() {
testInvalidGpx(INVALID_LATITUDE_GPX);
}
@Test
public void testInvalidLongitude() {
testInvalidGpx(INVALID_LONGITUDE_GPX);
}
private void testInvalidGpx(String xml) {
when(contentProviderUtils.insertTrack((Track) any())).thenReturn(TRACK_ID_0_URI);
// For the following, use StubReturn since we don't care whether they are invoked or not.
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), anyLong())).thenReturn(1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
contentProviderUtils.deleteTrack(context, TRACK_ID_0);
InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
GpxFileTrackImporter gpxFileTrackImporter = new GpxFileTrackImporter(context, contentProviderUtils);
long trackId = gpxFileTrackImporter.importFile(inputStream);
Assert.assertEquals(-1L, trackId);
}
}
@@ -1,133 +0,0 @@
/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package de.dennisguse.opentracks.io.file.importer;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.util.PreferencesUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link KmlFileTrackImporter}.
*
* @author Jimmy Shih
*/
@RunWith(MockitoJUnitRunner.class)
public class KmlFileTrackImporterTest extends AbstractTestFileTrackImporter {
private static final String VALID_ONE_TRACK_ONE_SEGMENT_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1)
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
private static final String VALID_ONE_TRACK_TWO_SEGMENTS_GPX = "<kml xmlns:gx=\"http://www.google.com/kml/ext/2.2\"><Placemark>"
+ getNameAndDescription(TRACK_NAME_0, TRACK_DESCRIPTION_0) + "<gx:MultiTrack><gx:Track>"
+ getTrackPoint(0, TRACK_TIME_0) + getTrackPoint(1, TRACK_TIME_1) + "</gx:Track><gx:Track>"
+ getTrackPoint(2, TRACK_TIME_2) + getTrackPoint(3, TRACK_TIME_3)
+ "</gx:Track></gx:MultiTrack></Placemark></kml>";
private static String getNameAndDescription(String name, String description) {
return "<name><![CDATA[" + name + "]]></name><description><![CDATA[" + description
+ "]]></description>";
}
private static String getTrackPoint(int index, String time) {
String latitude = Double.toString(TRACK_LATITUDE + index);
String longitude = Double.toString(TRACK_LONGITUDE + index);
String altitude = Double.toString(TRACK_ELEVATION + index);
return "<when>" + time + "</when>" + "<gx:coord>" + longitude + " " + latitude + " " + altitude + "</gx:coord>";
}
@Test
public void testOneTrackOneSegment() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
TrackPoint trackPointl1 = createTrackPoint(1, DATE_FORMAT_1.parse(TRACK_TIME_1).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(1);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_1);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_ONE_SEGMENT_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
Assert.assertEquals(time1 - time0, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, time0);
}
@Test
public void testOneTrackTwoSegments() throws Exception {
// given
TrackPoint trackPoint0 = createTrackPoint(0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
contentProviderUtils.clearTrack(context, TRACK_ID_0);
expectFirstTrackPoint(trackPoint0, TRACK_ID_0, TRACK_POINT_ID_0);
// A flush happens at the end
when(contentProviderUtils.bulkInsertTrackPoint((TrackPoint[]) any(), eq(TRACK_ID_0))).thenReturn(5);
when(contentProviderUtils.getLastTrackPointId(TRACK_ID_0)).thenReturn(TRACK_POINT_ID_3);
when(contentProviderUtils.getTrack(PreferencesUtils.getRecordingTrackId(context))).thenReturn(null);
ArgumentCaptor<Track> trackCaptor = ArgumentCaptor.forClass(Track.class);
expectTrackUpdate(trackCaptor, true, TRACK_ID_0);
// when
InputStream inputStream = new ByteArrayInputStream(VALID_ONE_TRACK_TWO_SEGMENTS_GPX.getBytes());
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, TRACK_ID_0, contentProviderUtils);
long trackId = kmlFileTrackImporter.importFile(inputStream);
// then
verify(contentProviderUtils, atLeastOnce()).updateTrack(trackCaptor.capture());
Assert.assertEquals(TRACK_ID_0, trackId);
long time0 = DATE_FORMAT_0.parse(TRACK_TIME_0).getTime();
long time1 = DATE_FORMAT_1.parse(TRACK_TIME_1).getTime();
long time2 = DATE_FORMAT_1.parse(TRACK_TIME_2).getTime();
long time3 = DATE_FORMAT_1.parse(TRACK_TIME_3).getTime();
Assert.assertEquals(time1 - time0 + time3 - time2, trackCaptor.getValue().getTrackStatistics().getTotalTime());
verifyTrack(trackCaptor.getValue(), TRACK_NAME_0, TRACK_DESCRIPTION_0, DATE_FORMAT_0.parse(TRACK_TIME_0).getTime());
}
}
@@ -21,6 +21,7 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.location.Location; import android.location.Location;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -33,6 +34,7 @@ import androidx.test.rule.ServiceTestRule;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -69,10 +71,17 @@ public class TrackRecordingServiceTest {
@Rule @Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION); public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
@BeforeClass
public static void preSetUp() {
// Prepare looper for Android's message queue
if (Looper.myLooper() == null) Looper.prepare();
}
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private final long trackId = Math.abs(new Random().nextLong()); private final Track.Id trackId = new Track.Id(Math.abs(new Random().nextLong()));
static Intent createStartIntent(Context context) { static Intent createStartIntent(Context context) {
return new Intent(context, TrackRecordingService.class); return new Intent(context, TrackRecordingService.class);
@@ -125,7 +134,7 @@ public class TrackRecordingServiceTest {
@Test @Test
public void testRecording_noTracks() throws Exception { public void testRecording_noTracks() throws Exception {
// given // given
List<Track> tracks = contentProviderUtils.getAllTracks(); List<Track> tracks = contentProviderUtils.getTracks();
Assert.assertTrue(tracks.isEmpty()); Assert.assertTrue(tracks.isEmpty());
// when // when
@@ -136,7 +145,7 @@ public class TrackRecordingServiceTest {
// then // then
// Test if we start in no-recording mode by default. // Test if we start in no-recording mode by default.
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId()); Assert.assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@@ -150,7 +159,7 @@ public class TrackRecordingServiceTest {
// then // then
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); Assert.assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@@ -234,7 +243,7 @@ public class TrackRecordingServiceTest {
// then // then
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); Assert.assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@@ -245,13 +254,13 @@ public class TrackRecordingServiceTest {
service.startNewTrack(); service.startNewTrack();
Assert.assertTrue(service.isRecording()); Assert.assertTrue(service.isRecording());
long trackId = service.getRecordingTrackId(); Track.Id trackId = service.getRecordingTrackId();
// when // when
long newTrackId = service.startNewTrack(); Track.Id newTrackId = service.startNewTrack();
// then // then
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, newTrackId); Assert.assertNull(newTrackId);
Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context)); Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(trackId, service.getRecordingTrackId()); Assert.assertEquals(trackId, service.getRecordingTrackId());
@@ -270,7 +279,7 @@ public class TrackRecordingServiceTest {
// then // then
Assert.assertFalse(PreferencesUtils.isRecording(context)); Assert.assertFalse(PreferencesUtils.isRecording(context));
Assert.assertEquals(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, service.getRecordingTrackId()); Assert.assertNull(service.getRecordingTrackId());
} }
@MediumTest @MediumTest
@@ -281,10 +290,10 @@ public class TrackRecordingServiceTest {
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
// when // when
long waypointId = service.insertWaypoint(null, null, null, null); Waypoint.Id waypointId = service.insertWaypoint(null, null, null, null);
// then // then
Assert.assertEquals(-1L, waypointId); Assert.assertNull(waypointId);
} }
@MediumTest @MediumTest
@@ -295,10 +304,10 @@ public class TrackRecordingServiceTest {
service.startNewTrack(); service.startNewTrack();
Assert.assertTrue(service.isRecording()); Assert.assertTrue(service.isRecording());
newTrackPoint(service); newTrackPoint(service);
long trackId = service.getRecordingTrackId(); Track.Id trackId = service.getRecordingTrackId();
// when // when
long waypointId = service.insertWaypoint(null, null, null, null); Waypoint.Id waypointId = service.insertWaypoint(null, null, null, null);
// then // then
Assert.assertNotEquals(-1L, waypointId); Assert.assertNotEquals(-1L, waypointId);
@@ -313,15 +322,15 @@ public class TrackRecordingServiceTest {
} }
private void addTrack(Track track, boolean isRecording) { private void addTrack(Track track, boolean isRecording) {
Assert.assertTrue(track.getId() >= 0); Assert.assertTrue(track.getId().isValid());
contentProviderUtils.insertTrack(track); contentProviderUtils.insertTrack(track);
Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId()); Assert.assertEquals(track.getId(), contentProviderUtils.getTrack(track.getId()).getId());
PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); PreferencesUtils.setLong(context, R.string.recording_track_id_key, isRecording ? track.getId().getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording); PreferencesUtils.setBoolean(context, R.string.recording_track_paused_key, !isRecording);
} }
// NOTE: Do not use to create a track that is currently recording. // NOTE: Do not use to create a track that is currently recording.
private void createDummyTrack(long id, long stopTime, boolean isRecording) { private void createDummyTrack(Track.Id id, long stopTime, boolean isRecording) {
Track dummyTrack = new Track(); Track dummyTrack = new Track();
dummyTrack.setId(id); dummyTrack.setId(id);
dummyTrack.setName("Dummy Track"); dummyTrack.setName("Dummy Track");
@@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.TrackPointsColumns; import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
@@ -89,7 +90,7 @@ public class TrackRecordingServiceTestLocation {
@Test @Test
public void testOnLocationChangedAsync_movingAccurate() throws Exception { public void testOnLocationChangedAsync_movingAccurate() throws Exception {
// given // given
long trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -113,7 +114,7 @@ public class TrackRecordingServiceTestLocation {
@Test @Test
public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception { public void testOnLocationChangedAsync_slowMovingAccurate() throws Exception {
// given // given
long trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 5, 15);
@@ -155,7 +156,7 @@ public class TrackRecordingServiceTestLocation {
@Test @Test
public void testOnLocationChangedAsync_idle() throws Exception { public void testOnLocationChangedAsync_idle() throws Exception {
// given // given
long trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -181,7 +182,7 @@ public class TrackRecordingServiceTestLocation {
@Test @Test
public void testOnLocationChangedAsync_idle_withMovement() throws Exception { public void testOnLocationChangedAsync_idle_withMovement() throws Exception {
// given // given
long trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 15);
@@ -209,7 +210,7 @@ public class TrackRecordingServiceTestLocation {
@Test @Test
public void testOnLocationChangedAsync_idle_withSensorData() throws Exception { public void testOnLocationChangedAsync_idle_withSensorData() throws Exception {
// given // given
long trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) { service.setRemoteSensorManager(new BluetoothRemoteSensorManager(context) {
@@ -253,7 +254,7 @@ public class TrackRecordingServiceTestLocation {
@Test @Test
public void testOnLocationChangedAsync_segment() throws Exception { public void testOnLocationChangedAsync_segment() throws Exception {
// given // given
long trackId = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
// when // when
TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0); TrackRecordingServiceTest.newTrackPoint(service, 45.0, 35.0, 1, 0);
@@ -187,7 +187,7 @@ public class TrackRecordingServiceTestLooper {
@MediumTest @MediumTest
@Test @Test
public void testIntegration_completeRecordingSession() throws TimeoutException { public void testIntegration_completeRecordingSession() throws TimeoutException {
List<Track> tracks = contentProviderUtils.getAllTracks(); List<Track> tracks = contentProviderUtils.getTracks();
Assert.assertTrue(tracks.isEmpty()); Assert.assertTrue(tracks.isEmpty());
fullRecordingSession(); fullRecordingSession();
} }
@@ -197,14 +197,14 @@ public class TrackRecordingServiceTestLooper {
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
// Start a track. // Start a track.
long id = service.startNewTrack(); Track.Id trackId = service.startNewTrack();
Assert.assertTrue(id >= 0); Assert.assertTrue(trackId.isValid());
Assert.assertTrue(service.isRecording()); Assert.assertTrue(service.isRecording());
Track track = contentProviderUtils.getTrack(id); Track track = contentProviderUtils.getTrack(trackId);
Assert.assertNotNull(track); Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId()); Assert.assertEquals(trackId, track.getId());
Assert.assertEquals(id, PreferencesUtils.getRecordingTrackId(context)); Assert.assertEquals(trackId, PreferencesUtils.getRecordingTrackId(context));
Assert.assertEquals(id, service.getRecordingTrackId()); Assert.assertEquals(trackId, service.getRecordingTrackId());
// Insert a few points, markers and statistics. // Insert a few points, markers and statistics.
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@@ -228,10 +228,10 @@ public class TrackRecordingServiceTestLooper {
// Stop the track. Validate if it has correct data. // Stop the track. Validate if it has correct data.
service.endCurrentTrack(); service.endCurrentTrack();
Assert.assertFalse(service.isRecording()); Assert.assertFalse(service.isRecording());
Assert.assertEquals(-1L, service.getRecordingTrackId()); Assert.assertNull(service.getRecordingTrackId());
track = contentProviderUtils.getTrack(id); track = contentProviderUtils.getTrack(trackId);
Assert.assertNotNull(track); Assert.assertNotNull(track);
Assert.assertEquals(id, track.getId()); Assert.assertEquals(trackId, track.getId());
TrackStatistics trackStatistics = track.getTrackStatistics(); TrackStatistics trackStatistics = track.getTrackStatistics();
Assert.assertNotNull(trackStatistics); Assert.assertNotNull(trackStatistics);
Assert.assertTrue(trackStatistics.getStartTime_ms() > 0); Assert.assertTrue(trackStatistics.getStartTime_ms() > 0);
@@ -212,4 +212,16 @@ public class StringUtilsTest {
Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1)); Assert.assertEquals("1", StringUtils.formatDecimal(0.99, 1));
} }
@Test
public void testGetSpeedParts() {
Assert.assertEquals("4:59", StringUtils.getSpeedParts(context, 3.34, true, false).first);
Assert.assertEquals("5:00", StringUtils.getSpeedParts(context, 3.33, true, false).first);
Assert.assertEquals("11.99", StringUtils.getSpeedParts(context, 3.33, true, true).first);
Assert.assertEquals("7.45", StringUtils.getSpeedParts(context, 3.33, false, true).first);
Assert.assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second);
Assert.assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second);
}
} }
@@ -30,6 +30,7 @@ import java.util.Date;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
/** /**
* Tests {@link TrackNameUtils}. * Tests {@link TrackNameUtils}.
@@ -39,7 +40,7 @@ import de.dennisguse.opentracks.R;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class TrackNameUtilsTest { public class TrackNameUtilsTest {
private static final long TRACK_ID = 1L; private static final Track.Id TRACK_ID = new Track.Id(1L);
private static final long START_TIME = 1288213406000L; private static final long START_TIME = 1288213406000L;
private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
@@ -69,6 +70,6 @@ public class TrackNameUtilsTest {
@Test @Test
public void testTrackName_number() { public void testTrackName_number() {
PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value)); PreferencesUtils.setString(CONTEXT, R.string.track_name_key, CONTEXT.getString(R.string.settings_recording_track_name_number_value));
Assert.assertEquals("Track " + TRACK_ID, TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME)); Assert.assertEquals("Track " + TRACK_ID.getId(), TrackNameUtils.getTrackName(CONTEXT, TRACK_ID, START_TIME));
} }
} }
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_debug_background" /> <background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_logo_color_108dp" /> <foreground android:drawable="@drawable/ic_logo_color_108dp" />
</adaptive-icon> </adaptive-icon>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="ic_launcher_debug_background">#8F0000</color> <color name="ic_launcher_background">#8F0000</color>
</resources> </resources>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?><!--
Copyright 2013 Google Inc. Copyright 2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <resources>
<item android:drawable="@drawable/ic_button_stop_pressed" android:state_pressed="true" /> <string name="app_name">OpenTracks (Debug)</string>
<item android:drawable="@drawable/ic_button_stop" /> </resources>
</selector>
+9 -18
View File
@@ -43,6 +43,7 @@ limitations under the License.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -52,8 +53,8 @@ limitations under the License.
android:allowBackup="false" android:allowBackup="false"
android:hasFragileUserData="true" android:hasFragileUserData="true"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="${applicationLogo}" android:icon="@mipmap/ic_launcher"
android:label="${applicationName}" android:label="@string/app_name"
android:theme="@style/ThemeCustom"> android:theme="@style/ThemeCustom">
<activity android:name=".AboutActivity" /> <activity android:name=".AboutActivity" />
@@ -75,42 +76,32 @@ limitations under the License.
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kmz" />
<data <data
android:scheme="content"
android:host="*" android:host="*"
android:mimeType="application/vnd.google-earth.kmz" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="*kmz" /> android:pathPattern="*kmz" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.google-earth.kml+xml" />
<data <data
android:scheme="content"
android:host="*" android:host="*"
android:mimeType="application/vnd.google-earth.kml+xml" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="*kml" /> android:pathPattern="*kml" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/gpx+xml" />
<data <data
android:scheme="content"
android:host="*" android:host="*"
android:mimeType="application/gpx+xml" />
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern="*gpx" /> android:pathPattern="*gpx" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".MarkerDetailActivity"> <activity android:name=".MarkerDetailActivity">
@@ -2,7 +2,6 @@ package de.dennisguse.opentracks;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu; import android.view.Menu;
import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -30,7 +29,7 @@ public class AboutActivity extends AbstractActivity {
TextView textURL = findViewById(R.id.about_app_url); TextView textURL = findViewById(R.id.about_app_url);
textURL.setText(getString(R.string.about_url, getString(R.string.app_web_url))); textURL.setText(getString(R.string.about_url, getString(R.string.app_web_url)));
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content)); ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
} }
protected int getLayoutResId() { protected int getLayoutResId() {
@@ -18,6 +18,10 @@ package de.dennisguse.opentracks;
import android.content.Intent; import android.content.Intent;
import java.util.ArrayList;
import java.util.Arrays;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment.ConfirmDeleteCaller;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -55,17 +59,17 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
* *
* @param trackIds the track ids * @param trackIds the track ids
*/ */
protected void deleteTracks(long[] trackIds) { protected void deleteTracks(Track.Id... trackIds) {
ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds); ConfirmDeleteDialogFragment.showDialog(getSupportFragmentManager(), trackIds);
} }
@Override @Override
public void onConfirmDeleteDone(long[] trackIds) { public void onConfirmDeleteDone(Track.Id... trackIds) {
boolean stopRecording = false; boolean stopRecording = false;
long recordingTrackId = PreferencesUtils.getRecordingTrackId(this); Track.Id recordingTrackId = PreferencesUtils.getRecordingTrackId(this);
for (long trackId : trackIds) { for (Track.Id trackId : trackIds) {
if (trackId == recordingTrackId) { if (trackId.equals(recordingTrackId)) {
stopRecording = true; stopRecording = true;
break; break;
} }
@@ -75,7 +79,7 @@ public abstract class AbstractListActivity extends AbstractActivity implements C
getTrackRecordingServiceConnection().stopRecording(this, false); getTrackRecordingServiceConnection().stopRecording(this, false);
} }
Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class); Intent intent = IntentUtils.newIntent(this, TrackDeleteActivity.class);
intent.putExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, trackIds); intent.putParcelableArrayListExtra(TrackDeleteActivity.EXTRA_TRACK_IDS, new ArrayList<>(Arrays.asList(trackIds)));
startActivityForResult(intent, DELETE_REQUEST_CODE); startActivityForResult(intent, DELETE_REQUEST_CODE);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
} }
@@ -1,7 +1,6 @@
package de.dennisguse.opentracks; package de.dennisguse.opentracks;
import android.os.Bundle; import android.os.Bundle;
import android.view.ViewGroup;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -12,7 +11,7 @@ public class HelpActivity extends AbstractActivity {
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ViewUtils.makeClickableLinks((ViewGroup) findViewById(android.R.id.content)); ViewUtils.makeClickableLinks(findViewById(android.R.id.content));
} }
@Override @Override
@@ -48,32 +48,33 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
private static final String TAG = MarkerDetailActivity.class.getSimpleName(); private static final String TAG = MarkerDetailActivity.class.getSimpleName();
private List<Long> markerIds; private List<Waypoint.Id> waypointIds;
@Override @Override
protected void onCreate(Bundle bundle) { protected void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L); Waypoint.Id waypointId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
if (markerId == -1L) { if (waypointId == null) {
Log.d(TAG, "invalid marker id"); Log.d(TAG, "invalid marker id");
finish(); finish();
return; return;
} }
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); Waypoint waypoint = contentProviderUtils.getWaypoint(waypointId);
markerIds = new ArrayList<>(); waypointIds = new ArrayList<>();
int markerIndex = -1; int markerIndex = -1;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), -1L, -1)) { //TODO Load only waypointIds, not the whole waypoint
try (Cursor cursor = contentProviderUtils.getWaypointCursor(waypoint.getTrackId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) { for (int i = 0; i < cursor.getCount(); i++) {
Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor); Waypoint currentMarker = contentProviderUtils.createWaypoint(cursor);
markerIds.add(currentMarker.getId()); waypointIds.add(currentMarker.getId());
if (currentMarker.getId() == markerId) { if (waypointId.equals(currentMarker.getId())) {
markerIndex = markerIds.size() - 1; markerIndex = waypointIds.size() - 1;
} }
cursor.moveToNext(); cursor.moveToNext();
@@ -126,7 +127,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Override @Override
@NonNull @NonNull
public Fragment getItem(int position) { public Fragment getItem(int position) {
return MarkerDetailFragment.newInstance(markerIds.get(position)); return MarkerDetailFragment.newInstance(waypointIds.get(position));
} }
@Nullable @Nullable
@@ -137,7 +138,7 @@ public class MarkerDetailActivity extends AbstractActivity implements DeleteMark
@Override @Override
public int getCount() { public int getCount() {
return markerIds.size(); return waypointIds.size();
} }
} }
} }
@@ -42,6 +42,7 @@ import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
@@ -64,7 +65,7 @@ public class MarkerEditActivity extends AbstractActivity {
private static final int GALLERY_IMG_REQUEST_CODE = 7; private static final int GALLERY_IMG_REQUEST_CODE = 7;
private static final String TAG = MarkerEditActivity.class.getSimpleName(); private static final String TAG = MarkerEditActivity.class.getSimpleName();
private long trackId; private Track.Id trackId;
private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection(); private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private Waypoint waypoint; private Waypoint waypoint;
@@ -85,8 +86,8 @@ public class MarkerEditActivity extends AbstractActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
long markerId = getIntent().getLongExtra(EXTRA_MARKER_ID, -1L); Waypoint.Id waypointId = getIntent().getParcelableExtra(EXTRA_MARKER_ID);
hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); hasCamera = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
@@ -111,7 +112,7 @@ public class MarkerEditActivity extends AbstractActivity {
Button cancel = findViewById(R.id.marker_edit_cancel); Button cancel = findViewById(R.id.marker_edit_cancel);
cancel.setOnClickListener(v -> finish()); cancel.setOnClickListener(v -> finish());
final boolean isNewMarker = markerId == -1L; final boolean isNewMarker = waypointId == null;
setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit); setTitle(isNewMarker ? R.string.menu_insert_marker : R.string.menu_edit);
Button done = findViewById(R.id.marker_edit_done); Button done = findViewById(R.id.marker_edit_done);
@@ -126,7 +127,7 @@ public class MarkerEditActivity extends AbstractActivity {
}); });
if (isNewMarker) { if (isNewMarker) {
int nextWaypointNumber = trackId == -1L ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId); int nextWaypointNumber = trackId == null ? -1 : new ContentProviderUtils(this).getNextWaypointNumber(trackId);
if (nextWaypointNumber == -1) { if (nextWaypointNumber == -1) {
nextWaypointNumber = 0; nextWaypointNumber = 0;
} }
@@ -135,7 +136,7 @@ public class MarkerEditActivity extends AbstractActivity {
waypointMarkerType.setText(""); waypointMarkerType.setText("");
waypointDescription.setText(""); waypointDescription.setText("");
} else { } else {
waypoint = new ContentProviderUtils(this).getWaypoint(markerId); waypoint = new ContentProviderUtils(this).getWaypoint(waypointId);
if (waypoint == null) { if (waypoint == null) {
Log.d(TAG, "waypoint is null"); Log.d(TAG, "waypoint is null");
finish(); finish();
@@ -151,6 +152,9 @@ public class MarkerEditActivity extends AbstractActivity {
if (savedInstanceState != null) { if (savedInstanceState != null) {
photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI); photoUri = savedInstanceState.getParcelable(BUNDLE_PHOTO_URI);
if (waypoint != null) {
waypoint.setPhotoUrl(photoUri != null ? photoUri.toString() : null);
}
} }
if (photoUri != null) { if (photoUri != null) {
setWaypointImageView(photoUri); setWaypointImageView(photoUri);
@@ -257,8 +261,8 @@ public class MarkerEditActivity extends AbstractActivity {
/** /**
* Returns the trackId. If trackId == -1 then get it from waypoint object. * Returns the trackId. If trackId == -1 then get it from waypoint object.
*/ */
private long getTrackId() { private Track.Id getTrackId() {
return trackId == -1 ? waypoint.getTrackId() : trackId; return trackId == null ? waypoint.getTrackId() : trackId;
} }
/** /**
@@ -32,10 +32,10 @@ import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.ResourceCursorAdapter; import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment; import de.dennisguse.opentracks.fragments.DeleteMarkerDialogFragment;
@@ -57,16 +57,11 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
private static final String TAG = MarkerListActivity.class.getSimpleName(); private static final String TAG = MarkerListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; private Track.Id recordingTrackId;
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@@ -117,19 +112,19 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
track = trackId != -1L ? contentProviderUtils.getTrack(trackId) : null; track = trackId != null ? contentProviderUtils.getTrack(trackId) : null;
listView = findViewById(R.id.marker_list); listView = findViewById(R.id.marker_list);
listView.setEmptyView(findViewById(R.id.marker_list_empty)); listView.setEmptyView(findViewById(R.id.marker_list_empty));
listView.setOnItemClickListener((parent, view, position, id) -> { listView.setOnItemClickListener((parent, view, position, id) -> {
Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class) Intent intent = IntentUtils.newIntent(MarkerListActivity.this, MarkerDetailActivity.class)
.putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, id); .putExtra(MarkerDetailActivity.EXTRA_MARKER_ID, new Waypoint.Id(id));
startActivity(intent); startActivity(intent);
}); });
resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) { resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.list_item, null, 0) {
@@ -163,14 +158,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@NonNull @NonNull
@Override @Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
if (track != null) { return ContentProviderUtils.getWaypointsLoader(MarkerListActivity.this, track != null ? track.getId() : null);
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(track.getId())}, null);
} else {
return new CursorLoader(MarkerListActivity.this, WaypointsColumns.CONTENT_URI, PROJECTION,
null, null, null);
}
} }
@Override @Override
@@ -223,7 +211,7 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
insertMarkerMenuItem.setVisible(track != null && track.getId() == recordingTrackId && !recordingTrackPaused); insertMarkerMenuItem.setVisible(track != null && track.getId().equals(recordingTrackId) && !recordingTrackPaused);
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
} }
@@ -242,29 +230,34 @@ public class MarkerListActivity extends AbstractActivity implements DeleteMarker
* Handles a context item selection. * Handles a context item selection.
* *
* @param itemId the menu item id * @param itemId the menu item id
* @param markerIds the marker ids * @param longWaypointIds the marker ids
* @return true if handled. * @return true if handled.
*/ */
private boolean handleContextItem(int itemId, long[] markerIds) { private boolean handleContextItem(int itemId, long... longWaypointIds) {
Waypoint.Id[] waypointIds = new Waypoint.Id[longWaypointIds.length];
for (int i = 0; i < longWaypointIds.length; i++) {
waypointIds[i] = new Waypoint.Id(longWaypointIds[i]);
}
Intent intent; Intent intent;
switch (itemId) { switch (itemId) {
case R.id.list_context_menu_show_on_map: case R.id.list_context_menu_show_on_map:
if (markerIds.length == 1) { if (waypointIds.length == 1) {
IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(markerIds[0])); IntentUtils.showCoordinateOnMap(this, contentProviderUtils.getWaypoint(waypointIds[0]));
} }
return true; return true;
case R.id.list_context_menu_edit: case R.id.list_context_menu_edit:
if (markerIds.length == 1) { if (waypointIds.length == 1) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class) intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerIds[0]); .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointIds[0]);
startActivity(intent); startActivity(intent);
} }
return true; return true;
case R.id.list_context_menu_delete: case R.id.list_context_menu_delete:
if (markerIds.length > 1 && markerIds.length == listView.getCount()) { if (waypointIds.length > 1 && waypointIds.length == listView.getCount()) {
markerIds = new long[]{-1L}; waypointIds = null;
} }
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), markerIds); DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), waypointIds);
return true; return true;
case R.id.list_context_menu_select_all: case R.id.list_context_menu_select_all:
for (int i = 0; i < listView.getCount(); i++) { for (int i = 0; i < listView.getCount(); i++) {
@@ -97,7 +97,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
private boolean metricUnits = true; private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; private Track.Id recordingTrackId;
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
@@ -165,7 +165,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
sharedPreferences = PreferencesUtils.getSharedPreferences(this); sharedPreferences = PreferencesUtils.getSharedPreferences(this);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null); trackRecordingServiceConnection = new TrackRecordingServiceConnection();
searchEngine = new SearchEngine(contentProviderUtils); searchEngine = new SearchEngine(contentProviderUtils);
arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) { arrayAdapter = new ArrayAdapter<Map<String, Object>>(this, R.layout.list_item, R.id.list_item_name) {
@@ -203,7 +203,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
listView.setEmptyView(findViewById(R.id.search_list_empty)); listView.setEmptyView(findViewById(R.id.search_list_empty));
listView.setOnItemClickListener((parent, view, position, id) -> { listView.setOnItemClickListener((parent, view, position, id) -> {
Map<String, Object> item = arrayAdapter.getItem(position); Map<String, Object> item = arrayAdapter.getItem(position);
Long trackId = (Long) item.get(TRACK_ID_FIELD); Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD); Long markerId = (Long) item.get(MARKER_ID_FIELD);
Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class); Intent intent = IntentUtils.newIntent(SearchListActivity.this, TrackRecordedActivity.class);
if (markerId != null) { if (markerId != null) {
@@ -270,22 +270,22 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
return false; return false;
} }
Map<String, Object> item = arrayAdapter.getItem(positions[0]); Map<String, Object> item = arrayAdapter.getItem(positions[0]);
Long trackId = (Long) item.get(TRACK_ID_FIELD); Track.Id trackId = (Track.Id) item.get(TRACK_ID_FIELD);
Long markerId = (Long) item.get(MARKER_ID_FIELD); Waypoint.Id waypointId = (Waypoint.Id) item.get(MARKER_ID_FIELD);
Intent intent; Intent intent;
switch (itemId) { switch (itemId) {
case R.id.list_context_menu_show_on_map: case R.id.list_context_menu_show_on_map:
IntentUtils.showCoordinateOnMap(this, (double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + ""); IntentUtils.showCoordinateOnMap(this, (double) item.get(MARKER_LATITUDE_FIELD), (double) item.get(MARKER_LONGITUDE_FIELD), item.get(NAME_FIELD) + "");
return true; return true;
case R.id.list_context_menu_share: case R.id.list_context_menu_share:
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId}); intent = IntentUtils.newShareFileIntent(this, trackId);
intent = Intent.createChooser(intent, null); intent = Intent.createChooser(intent, null);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.list_context_menu_edit: case R.id.list_context_menu_edit:
if (markerId != null) { if (waypointId != null) {
intent = IntentUtils.newIntent(this, MarkerEditActivity.class) intent = IntentUtils.newIntent(this, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId); .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointId);
} else { } else {
intent = IntentUtils.newIntent(this, TrackEditActivity.class) intent = IntentUtils.newIntent(this, TrackEditActivity.class)
.putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId); .putExtra(TrackEditActivity.EXTRA_TRACK_ID, trackId);
@@ -296,10 +296,10 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
finish(); finish();
return true; return true;
case R.id.list_context_menu_delete: case R.id.list_context_menu_delete:
if (markerId != null) { if (waypointId != null) {
DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), new long[]{markerId}); DeleteMarkerDialogFragment.showDialog(getSupportFragmentManager(), waypointId);
} else { } else {
deleteTracks(new long[]{trackId}); deleteTracks(trackId);
} }
return true; return true;
default: default:
@@ -331,7 +331,7 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
* @param textQuery the query * @param textQuery the query
*/ */
private void doSearch(String textQuery) { private void doSearch(String textQuery) {
SearchQuery query = new SearchQuery(textQuery, null, -1L, System.currentTimeMillis()); SearchQuery query = new SearchQuery(textQuery, null, null, System.currentTimeMillis());
SortedSet<ScoredResult> scoredResults = searchEngine.search(query); SortedSet<ScoredResult> scoredResults = searchEngine.search(query);
final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults); final List<Map<String, Object>> displayResults = prepareResultsforDisplay(scoredResults);
@@ -372,8 +372,8 @@ public class SearchListActivity extends AbstractListActivity implements DeleteMa
//TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the searchable. //TODO: It may be more appropriate to obtain the track name as a join in the retrieval phase of the searchable.
String trackName = null; String trackName = null;
long trackId = waypoint.getTrackId(); Track.Id trackId = waypoint.getTrackId();
if (trackId != -1L) { if (!trackId.isValid()) {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
if (track != null) { if (track != null) {
trackName = track.getName(); trackName = track.getName();
@@ -8,6 +8,9 @@ import android.util.Pair;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import java.util.Set;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns; import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.provider.ShareContentProvider; import de.dennisguse.opentracks.content.provider.ShareContentProvider;
import de.dennisguse.opentracks.io.file.TrackFileFormat; import de.dennisguse.opentracks.io.file.TrackFileFormat;
@@ -26,7 +29,7 @@ public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
long[] trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent()); Set<Track.Id> trackIds = IntentDashboardUtils.extractTrackIdsFromIntent(getIntent());
showTrackTrackfileFormat(this, trackFileFormat, trackIds); showTrackTrackfileFormat(this, trackFileFormat, trackIds);
@@ -39,14 +42,14 @@ public abstract class ShowOnMapProxyActivity extends AppCompatActivity {
* @param context the context * @param context the context
* @param trackIds the track ids * @param trackIds the track ids
*/ */
private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, long[] trackIds) { private static void showTrackTrackfileFormat(Context context, TrackFileFormat trackFileFormat, Set<Track.Id> trackIds) {
if (trackIds.length == 0) { if (trackIds.size() == 0) {
return; return;
} }
Intent intent = new Intent(android.content.Intent.ACTION_VIEW); Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(TrackPointsColumns.TRACKID, trackIds[0]); intent.putExtra(TrackPointsColumns.TRACKID, trackIds.toArray(new Track.Id[0])[0]);
Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", trackFileFormat); Pair<Uri, String> uriAndMime = ShareContentProvider.createURI(trackIds, "SharingTrack", trackFileFormat);
intent.setDataAndType(uriAndMime.first, uriAndMime.second); intent.setDataAndType(uriAndMime.first, uriAndMime.second);
@@ -32,6 +32,11 @@ public class Startup extends Application {
PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits); PreferencesUtils.setString(this, R.string.stats_units_key, statsUnits);
} }
//TODO Workaround to reset recordingTrackId on app startup as the TrackRecordingService (likely) crashed.
if (PreferencesUtils.isRecording(this)) {
Log.e(TAG, "Reset recordingTrackId; likely the TrackRecordingService crashed.");
PreferencesUtils.setLong(this, R.string.recording_track_id_key, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT);
}
//In debug builds: show thread and VM warnings. //In debug builds: show thread and VM warnings.
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
@@ -16,15 +16,24 @@
package de.dennisguse.opentracks; package de.dennisguse.opentracks;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.UnitConversions; import de.dennisguse.opentracks.util.UnitConversions;
@@ -46,6 +55,7 @@ public class TrackController {
private final ImageButton recordImageButton; private final ImageButton recordImageButton;
private final ImageButton stopImageButton; private final ImageButton stopImageButton;
private final boolean alwaysShow; private final boolean alwaysShow;
private ButtonDelay buttonDelay;
private boolean isRecording; private boolean isRecording;
private boolean isPaused; private boolean isPaused;
@@ -65,6 +75,7 @@ public class TrackController {
} }
}; };
@SuppressLint("ClickableViewAccessibility")
TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) { TrackController(Activity activity, TrackRecordingServiceConnection trackRecordingServiceConnection, boolean alwaysShow, OnClickListener recordListener, OnClickListener stopListener) {
this.activity = activity; this.activity = activity;
this.trackRecordingServiceConnection = trackRecordingServiceConnection; this.trackRecordingServiceConnection = trackRecordingServiceConnection;
@@ -75,10 +86,91 @@ public class TrackController {
totalTimeTextView = activity.findViewById(R.id.track_controller_total_time); totalTimeTextView = activity.findViewById(R.id.track_controller_total_time);
recordImageButton = activity.findViewById(R.id.track_controller_record); recordImageButton = activity.findViewById(R.id.track_controller_record);
recordImageButton.setOnClickListener(recordListener); recordImageButton.setOnTouchListener((view, motionEvent) -> onRecordTouch(activity, recordListener, motionEvent));
stopImageButton = activity.findViewById(R.id.track_controller_stop); stopImageButton = activity.findViewById(R.id.track_controller_stop);
stopImageButton.setOnClickListener(stopListener); stopImageButton.setOnTouchListener((view, motionEvent) -> onStopTouch(activity, stopListener, motionEvent));
}
private boolean onRecordTouch(final Activity activity, final OnClickListener recordListener, final MotionEvent motionEvent) {
if (isRecording && !isPaused) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
buttonDelay = new ButtonDelay(activity, recordImageButton, R.drawable.ic_button_pause_anim, R.string.hold_to_pause, recordListener);
new Thread(buttonDelay).start();
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP ) {
recordImageButton.setImageResource(R.drawable.ic_button_pause);
if (buttonDelay != null) {
buttonDelay.canceled = true;
}
return true;
}
} else if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
recordListener.onClick(null);
return true;
}
return false;
}
private boolean onStopTouch(final Activity activity, final OnClickListener stopListener, final MotionEvent motionEvent) {
if (isRecording) {
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
buttonDelay = new ButtonDelay(activity, stopImageButton, R.drawable.ic_button_stop_anim, R.string.hold_to_stop, stopListener);
new Thread(buttonDelay).start();
return true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP ) {
stopImageButton.setImageResource(R.drawable.ic_button_stop);
if (buttonDelay != null) {
buttonDelay.canceled = true;
}
return true;
}
}
return false;
}
private static class ButtonDelay implements Runnable {
private boolean canceled = false;
private final ImageButton imageButton;
private final Activity activity;
private final OnClickListener clickListener;
private final int delayMillis;
private final Drawable drawable;
private final int delayMessageId;
private ButtonDelay(final Activity activity, final ImageButton imageButton, final int animDrawableId, final int delayMessageId, final OnClickListener clickListener) {
this.activity = activity;
this.clickListener = clickListener;
this.delayMillis = activity.getResources().getInteger(R.integer.buttonDelayMillis);
this.imageButton = imageButton;
this.drawable = ContextCompat.getDrawable(activity, animDrawableId);
this.delayMessageId = delayMessageId;
}
@Override
public void run() {
activity.runOnUiThread(()->{
imageButton.setImageDrawable(drawable);
if (drawable instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) drawable).start();
}
ActivityUtils.vibrate(activity, 150);
ActivityUtils.toast(activity, delayMessageId, Toast.LENGTH_SHORT, Gravity.TOP);
});
try {
Thread.sleep(delayMillis);
} catch (InterruptedException ignored) {
}
if (!canceled) {
activity.runOnUiThread(()-> {
clickListener.onClick(null);
ActivityUtils.vibrate(activity, 1000);
});
}
}
} }
public void update(boolean recording, boolean paused) { public void update(boolean recording, boolean paused) {
@@ -95,10 +187,10 @@ public class TrackController {
return; return;
} }
recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.button_pause : R.drawable.button_record); recordImageButton.setImageResource(isRecording && !isPaused ? R.drawable.ic_button_pause : R.drawable.button_record);
recordImageButton.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record)); recordImageButton.setContentDescription(activity.getString(isRecording && !isPaused ? R.string.image_pause : R.string.image_record));
stopImageButton.setImageResource(isRecording ? R.drawable.button_stop : R.drawable.ic_button_stop_disabled); stopImageButton.setImageResource(isRecording ? R.drawable.ic_button_stop : R.drawable.ic_button_stop_disabled);
stopImageButton.setEnabled(isRecording); stopImageButton.setEnabled(isRecording);
statusTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE); statusTextView.setVisibility(isRecording ? View.VISIBLE : View.INVISIBLE);
@@ -21,6 +21,9 @@ import android.os.Bundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.view.View; import android.view.View;
import java.util.List;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.SystemUtils; import de.dennisguse.opentracks.util.SystemUtils;
@@ -33,7 +36,7 @@ public class TrackDeleteActivity extends AbstractActivity {
public static final String EXTRA_TRACK_IDS = "track_ids"; public static final String EXTRA_TRACK_IDS = "track_ids";
private long[] trackIds; private List<Track.Id> trackIds;
private Thread deleteThread; private Thread deleteThread;
@@ -44,13 +47,13 @@ public class TrackDeleteActivity extends AbstractActivity {
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
Intent intent = getIntent(); Intent intent = getIntent();
trackIds = intent.getLongArrayExtra(EXTRA_TRACK_IDS); trackIds = intent.getParcelableArrayListExtra(EXTRA_TRACK_IDS);
deleteThread = new Thread(() -> { deleteThread = new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(TrackDeleteActivity.this);
PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null); PowerManager.WakeLock wakeLock = SystemUtils.acquireWakeLock(TrackDeleteActivity.this, null);
for (long id : trackIds) { for (Track.Id id : trackIds) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
break; break;
} }
@@ -64,9 +64,9 @@ public class TrackEditActivity extends AbstractActivity implements ChooseActivit
protected void onCreate(Bundle bundle) { protected void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null); trackRecordingServiceConnection = new TrackRecordingServiceConnection();
long trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, -1L); Track.Id trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (trackId == -1L) { if (!trackId.isValid()) {
Log.e(TAG, "invalid trackId"); Log.e(TAG, "invalid trackId");
finish(); finish();
return; return;
@@ -24,6 +24,7 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.provider.Settings; import android.provider.Settings;
@@ -42,24 +43,23 @@ import androidx.core.app.ActivityCompat;
import androidx.cursoradapter.widget.ResourceCursorAdapter; import androidx.cursoradapter.widget.ResourceCursorAdapter;
import androidx.loader.app.LoaderManager; import androidx.loader.app.LoaderManager;
import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment; import de.dennisguse.opentracks.fragments.ConfirmDeleteDialogFragment;
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection; import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface; import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.settings.SettingsActivity;
import de.dennisguse.opentracks.util.ActivityUtils; import de.dennisguse.opentracks.util.ActivityUtils;
import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.IntentDashboardUtils;
import de.dennisguse.opentracks.util.IntentUtils; import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.ListItemUtils; import de.dennisguse.opentracks.util.ListItemUtils;
import de.dennisguse.opentracks.util.PreferencesUtils; import de.dennisguse.opentracks.util.PreferencesUtils;
import de.dennisguse.opentracks.util.ServiceUtils;
import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.TrackIconUtils; import de.dennisguse.opentracks.util.TrackIconUtils;
import de.dennisguse.opentracks.util.TrackUtils;
/** /**
* An activity displaying a list of tracks. * An activity displaying a list of tracks.
@@ -70,10 +70,6 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private static final String TAG = TrackListActivity.class.getSimpleName(); private static final String TAG = TrackListActivity.class.getSimpleName();
private static final String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
// The following are set in onCreate // The following are set in onCreate
private ContentProviderUtils contentProviderUtils; private ContentProviderUtils contentProviderUtils;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
@@ -81,12 +77,12 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private TrackController trackController; private TrackController trackController;
private ListView listView; private ListView listView;
private ResourceCursorAdapter resourceCursorAdapter; private ResourceCursorAdapter resourceCursorAdapter;
private GpsStatusValue gpsStatusValue;
private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() { private final LoaderCallbacks<Cursor> loaderCallbacks = new LoaderCallbacks<Cursor>() {
@Override @Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
return new CursorLoader(TrackListActivity.this, TracksColumns.CONTENT_URI, PROJECTION, null, return ContentProviderUtils.getTracksCursorLoader(TrackListActivity.this);
null, TrackUtils.TRACK_SORT_ORDER);
} }
@Override @Override
@@ -102,7 +98,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Preferences // Preferences
private boolean metricUnits = true; private boolean metricUnits = true;
private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; private Track.Id recordingTrackId = null;
// Callback when an item is selected in the contextual action mode // Callback when an item is selected in the contextual action mode
private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() {
@@ -123,8 +119,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
private final OnSharedPreferenceChangeListener private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) { if (PreferencesUtils.isKey(TrackListActivity.this, R.string.stats_units_key, key)) {
@@ -162,28 +157,37 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
} }
}; };
private boolean startGps = false; // true to start gps
// Callback when the trackRecordingServiceConnection binding changes. // Callback when the trackRecordingServiceConnection binding changes.
private final Runnable bindChangedCallback = new Runnable() { private final Runnable bindChangedCallback = new Runnable() {
@Override @Override
public void run() { public void run() {
// After binding changes (e.g., becomes available), update the total time in trackController. boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
runOnUiThread(() -> trackController.update(PreferencesUtils.isRecording(recordingTrackId), recordingTrackPaused));
if (!startGps) { // After binding changes (e.g., becomes available), update the total time in trackController.
return; runOnUiThread(() -> trackController.update(isRecording, recordingTrackPaused));
}
TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound(); TrackRecordingServiceInterface service = trackRecordingServiceConnection.getServiceIfBound();
if (service == null) { if (service == null) {
Log.d(TAG, "service not available to start gps or a new recording"); Log.d(TAG, "service not available to start gps or a new recording");
gpsStatusValue = GpsStatusValue.GPS_NONE;
return; return;
} }
if (startGps) {
service.startGps(); // Get GPS status and listen GPS status changes.
startGps = false; gpsStatusValue = service.getGpsStatus();
updateGpsMenuItem(true, isRecording);
service.addListener(newStatus -> {
gpsStatusValue = newStatus;
updateGpsMenuItem(true, isRecording);
});
if (isGpsStarted()) {
return;
} }
service.startGps();
gpsStatusValue = GpsStatusValue.GPS_ENABLED;
updateGpsMenuItem(true, isRecording);
} }
}; };
@@ -212,6 +216,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
gpsStatusValue = GpsStatusValue.GPS_NONE;
recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this); recordingTrackPaused = PreferencesUtils.isRecordingTrackPausedDefault(this);
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
@@ -232,14 +238,14 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
listView.setEmptyView(findViewById(R.id.track_list_empty_view)); listView.setEmptyView(findViewById(R.id.track_list_empty_view));
listView.setOnItemClickListener((parent, view, position, trackId) -> { listView.setOnItemClickListener((parent, view, position, trackId) -> {
Intent newIntent; Intent newIntent;
if (trackId == recordingTrackId) { if (trackId == recordingTrackId.getId()) {
// Is recording -> open record activity. // Is recording -> open record activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class) newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordingActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId); .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
} else { } else {
// Not recording -> open detail activity. // Not recording -> open detail activity.
newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class) newIntent = IntentUtils.newIntent(TrackListActivity.this, TrackRecordedActivity.class)
.putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, trackId); .putExtra(TrackRecordedActivity.EXTRA_TRACK_ID, new Track.Id(trackId));
} }
startActivity(newIntent); startActivity(newIntent);
}); });
@@ -256,7 +262,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY); int categoryIndex = cursor.getColumnIndex(TracksColumns.CATEGORY);
int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION); int descriptionIndex = cursor.getColumnIndex(TracksColumns.DESCRIPTION);
long trackId = cursor.getLong(idIndex); Track.Id trackId = new Track.Id(cursor.getLong(idIndex));
boolean isRecording = trackId == recordingTrackId; boolean isRecording = trackId == recordingTrackId;
String icon = cursor.getString(iconIndex); String icon = cursor.getString(iconIndex);
int iconId = TrackIconUtils.getIconDrawable(icon); int iconId = TrackIconUtils.getIconDrawable(icon);
@@ -346,7 +352,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
boolean isGpsStarted = ServiceUtils.isTrackRecordingServiceRunning(this); boolean isGpsStarted = isGpsStarted();
boolean isRecording = PreferencesUtils.isRecording(recordingTrackId); boolean isRecording = PreferencesUtils.isRecording(recordingTrackId);
updateMenuItems(isGpsStarted, isRecording); updateMenuItems(isGpsStarted, isRecording);
@@ -366,10 +372,8 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent); startActivity(intent);
} else { } else {
startGps = !ServiceUtils.isTrackRecordingServiceRunning(this);
// Invoke trackRecordingService // Invoke trackRecordingService
if (startGps) { if (!isGpsStarted()) {
trackRecordingServiceConnection.startAndBind(this); trackRecordingServiceConnection.startAndBind(this);
bindChangedCallback.run(); bindChangedCallback.run();
} else { } else {
@@ -425,6 +429,10 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
// Do nothing // Do nothing
} }
private boolean isGpsStarted() {
return gpsStatusValue != GpsStatusValue.GPS_NONE && gpsStatusValue != GpsStatusValue.GPS_DISABLED;
}
private void requestGPSPermissions() { private void requestGPSPermissions() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_REQUEST_CODE); ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_REQUEST_CODE);
@@ -432,17 +440,30 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
} }
/** /**
* Updates the menu items. * Updates the menu items with not fixed icon for gps option.
* *
* @param isGpsStarted true if gps is started * @param isGpsStarted true if gps is started
* @param isRecording true if recording * @param isRecording true if recording
*/ */
private void updateMenuItems(boolean isGpsStarted, boolean isRecording) { private void updateMenuItems(boolean isGpsStarted, boolean isRecording) {
updateGpsMenuItem(isGpsStarted, isRecording);
}
/**
* Updates the menu items with the icon specified.
*
* @param isGpsStarted true if gps is started
* @param isRecording true if recording
*/
private void updateGpsMenuItem(boolean isGpsStarted, boolean isRecording) {
if (startGpsMenuItem != null) { if (startGpsMenuItem != null) {
startGpsMenuItem.setVisible(!isRecording); startGpsMenuItem.setVisible(!isRecording);
if (!isRecording) { if (!isRecording) {
startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps); startGpsMenuItem.setTitle(isGpsStarted ? R.string.menu_stop_gps : R.string.menu_start_gps);
startGpsMenuItem.setIcon(isGpsStarted ? R.drawable.ic_gps_fixed_24dp : R.drawable.ic_gps_off_24dp); startGpsMenuItem.setIcon(isGpsStarted ? gpsStatusValue.icon : R.drawable.ic_gps_off_24dp);
if (startGpsMenuItem.getIcon() instanceof AnimatedVectorDrawable) {
((AnimatedVectorDrawable) startGpsMenuItem.getIcon()).start();
}
} }
} }
} }
@@ -451,14 +472,19 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
* Handles a context item selection. * Handles a context item selection.
* *
* @param itemId the menu item id * @param itemId the menu item id
* @param trackIds the track ids * @param longTrackIds the track ids
* @return true if handled. * @return true if handled.
*/ */
private boolean handleContextItem(int itemId, long[] trackIds) { private boolean handleContextItem(int itemId, long... longTrackIds) {
Track.Id[] trackIds = new Track.Id[longTrackIds.length];
for (int i = 0; i < longTrackIds.length; i++) {
trackIds[i] = new Track.Id(longTrackIds[i]);
}
Intent intent; Intent intent;
switch (itemId) { switch (itemId) {
case R.id.list_context_menu_show_on_map: case R.id.list_context_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, trackIds); IntentDashboardUtils.startDashboard(this, false, trackIds);
return true; return true;
case R.id.list_context_menu_share: case R.id.list_context_menu_share:
intent = IntentUtils.newShareFileIntent(this, trackIds); intent = IntentUtils.newShareFileIntent(this, trackIds);
@@ -66,7 +66,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
private ViewPager pager; private ViewPager pager;
// From intent. // From intent.
private long trackId; private Track.Id trackId;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -133,7 +133,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
// Update UI // Update UI
this.invalidateOptionsMenu(); this.invalidateOptionsMenu();
if (trackId != -1L) { if (trackId.isValid()) {
trackDataHub.loadTrack(trackId); trackDataHub.loadTrack(trackId);
} }
} }
@@ -182,12 +182,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
Intent intent; Intent intent;
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.track_detail_share: case R.id.track_detail_share:
intent = IntentUtils.newShareFileIntent(this, new long[]{trackId}); intent = IntentUtils.newShareFileIntent(this, trackId);
intent = Intent.createChooser(intent, null); intent = Intent.createChooser(intent, null);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.track_detail_menu_show_on_map: case R.id.track_detail_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, trackId); IntentDashboardUtils.startDashboard(this, false, trackId);
return true; return true;
case R.id.track_detail_markers: case R.id.track_detail_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class) intent = IntentUtils.newIntent(this, MarkerListActivity.class)
@@ -200,7 +200,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.track_detail_delete: case R.id.track_detail_delete:
deleteTracks(new long[]{trackId}); deleteTracks(trackId);
return true; return true;
case R.id.track_detail_resume_track: case R.id.track_detail_resume_track:
Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class) Intent newIntent = IntentUtils.newIntent(TrackRecordedActivity.this, TrackRecordingActivity.class)
@@ -238,12 +238,12 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
} }
private void handleIntent(Intent intent) { private void handleIntent(Intent intent) {
trackId = intent.getLongExtra(EXTRA_TRACK_ID, -1L); trackId = intent.getParcelableExtra(EXTRA_TRACK_ID);
long markerId = intent.getLongExtra(EXTRA_MARKER_ID, -1L); Waypoint.Id waypointId = intent.getParcelableExtra(EXTRA_MARKER_ID);
if (markerId != -1L) { if (waypointId != null) {
// Use the trackId from the marker // Use the trackId from the marker
Waypoint waypoint = contentProviderUtils.getWaypoint(markerId); Waypoint waypoint = contentProviderUtils.getWaypoint(waypointId);
if (waypoint == null) { if (waypoint == null) {
finish(); finish();
return; return;
@@ -251,7 +251,7 @@ public class TrackRecordedActivity extends AbstractListActivity implements Choos
trackId = waypoint.getTrackId(); trackId = waypoint.getTrackId();
} }
if (trackId == -1L) { if (trackId == null) {
Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID."); Log.e(TAG, "TrackDetailActivity needs EXTRA_TRACK_ID.");
finish(); finish();
} }
@@ -60,7 +60,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
private TrackController trackController; private TrackController trackController;
// Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService // Initialized from Intent; if a new track recording is started, a new TrackId will be provided by TrackRecordingService
private long trackId; private Track.Id trackId;
// Preferences // Preferences
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
@@ -78,7 +78,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
if (!service.isRecording()) { if (!service.isRecording()) {
// Starts or resumes a track. // Starts or resumes a track.
if (trackId == -1L) { if (trackId == null) {
// trackId isn't initialized -> leads a new recording. // trackId isn't initialized -> leads a new recording.
trackId = service.startNewTrack(); trackId = service.startNewTrack();
} else { } else {
@@ -110,6 +110,9 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) { if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_keep_screen_on_while_recording_key, key)) {
setScreenOnPolicy(); setScreenOnPolicy();
} }
if (PreferencesUtils.isKey(TrackRecordingActivity.this, R.string.stats_fullscreen_while_recording_key, key)) {
setFullscreenPolicy();
}
if (key == null) return; if (key == null) return;
@@ -158,13 +161,14 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
contentProviderUtils = new ContentProviderUtils(this); contentProviderUtils = new ContentProviderUtils(this);
trackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; trackId = null;
if (savedInstanceState != null) { if (savedInstanceState != null) {
//Activity was recreated. //Activity was recreated.
trackId = savedInstanceState.getLong(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); trackId = savedInstanceState.getParcelable(EXTRA_TRACK_ID);
} else { } else {
trackId = getIntent().getLongExtra(EXTRA_TRACK_ID, PreferencesUtils.RECORDING_TRACK_ID_DEFAULT); trackId = getIntent().getParcelableExtra(EXTRA_TRACK_ID);
if (trackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT && contentProviderUtils.getTrack(trackId) == null) { if (trackId != null && contentProviderUtils.getTrack(trackId) == null) {
Log.w(TAG, "TrackId does not exists; cannot continue the recording.");
finish(); finish();
} }
} }
@@ -190,12 +194,12 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
public void onAttachedToWindow() { public void onAttachedToWindow() {
setLockscreenPolicy(); setLockscreenPolicy();
setScreenOnPolicy(); setScreenOnPolicy();
setFullscreenPolicy();
super.onAttachedToWindow(); super.onAttachedToWindow();
} }
private void setLockscreenPolicy() { private void setLockscreenPolicy() {
boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this) boolean showOnLockScreen = PreferencesUtils.shouldShowStatsOnLockscreen(TrackRecordingActivity.this);
&& PreferencesUtils.isRecording(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(showOnLockScreen); setShowWhenLocked(showOnLockScreen);
@@ -207,8 +211,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
private void setScreenOnPolicy() { private void setScreenOnPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this) boolean keepScreenOn = PreferencesUtils.shouldKeepScreenOn(TrackRecordingActivity.this);
&& PreferencesUtils.isRecording(this);
if (keepScreenOn) { if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
@@ -217,6 +220,16 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
} }
} }
private void setFullscreenPolicy() {
boolean keepScreenOn = PreferencesUtils.shouldUseFullscreen(TrackRecordingActivity.this);
if (keepScreenOn) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
@@ -235,7 +248,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
// Update UI // Update UI
this.invalidateOptionsMenu(); this.invalidateOptionsMenu();
if (trackId != -1L) { if (trackId != null) {
trackDataHub.loadTrack(trackId); trackDataHub.loadTrack(trackId);
trackController.onResume(true, recordingTrackPaused); trackController.onResume(true, recordingTrackPaused);
} }
@@ -253,7 +266,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
protected void onSaveInstanceState(@NonNull Bundle outState) { protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem()); outState.putInt(CURRENT_TAB_TAG_KEY, pager.getCurrentItem());
outState.putLong(EXTRA_TRACK_ID, trackId); outState.putParcelable(EXTRA_TRACK_ID, trackId);
} }
@Override @Override
@@ -302,7 +315,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.track_detail_menu_show_on_map: case R.id.track_detail_menu_show_on_map:
IntentDashboardUtils.startDashboard(this, trackId); IntentDashboardUtils.startDashboard(this, true, trackId);
return true; return true;
case R.id.track_detail_markers: case R.id.track_detail_markers:
intent = IntentUtils.newIntent(this, MarkerListActivity.class) intent = IntentUtils.newIntent(this, MarkerListActivity.class)
@@ -43,7 +43,7 @@ public class ChartPoint {
elevation *= UnitConversions.M_TO_FT; elevation *= UnitConversions.M_TO_FT;
} }
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MS_TO_KMH; speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MPS_TO_KMH;
if (!metricUnits) { if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI; speed *= UnitConversions.KM_TO_MI;
} }
@@ -185,7 +185,7 @@ public class DescriptionGenerator {
*/ */
@VisibleForTesting @VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) { void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MS_TO_KMH; double speedInKmHr = speed * UnitConversions.MPS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI; double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr)); builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
builder.append(lineBreak); builder.append(lineBreak);
@@ -123,8 +123,8 @@ public class SearchEngine {
} }
// Finally, use arbitrary ordering, by ID. // Finally, use arbitrary ordering, by ID.
long id1 = r1.track != null ? r1.track.getId() : r1.waypoint.getId(); long id1 = r1.track != null ? r1.track.getId().getId() : r1.waypoint.getId().getId();
long id2 = r2.track != null ? r2.track.getId() : r2.waypoint.getId(); long id2 = r2.track != null ? r2.track.getId().getId() : r2.waypoint.getId().getId();
long idDiff = id2 - id1; long idDiff = id2 - id1;
return Long.signum(idDiff); return Long.signum(idDiff);
}; };
@@ -290,7 +290,7 @@ public class SearchEngine {
score *= getTimeBoost(query, location.getTime()); score *= getTimeBoost(query, location.getTime());
// Score waypoints in the currently-selected track higher (searching inside the current track). // Score waypoints in the currently-selected track higher (searching inside the current track).
if (query.currentTrackId != -1 && waypoint.getTrackId() == query.currentTrackId) { if (waypoint.getTrackId() != null && waypoint.getTrackId().equals(query.currentTrackId)) {
score *= CURRENT_TRACK_WAYPOINT_PROMOTION; score *= CURRENT_TRACK_WAYPOINT_PROMOTION;
} }
@@ -382,10 +382,10 @@ public class SearchEngine {
public static class SearchQuery { public static class SearchQuery {
final String textQuery; final String textQuery;
final Location currentLocation; final Location currentLocation;
final long currentTrackId; final Track.Id currentTrackId;
final long currentTimestamp; final long currentTimestamp;
public SearchQuery(String textQuery, Location currentLocation, long currentTrackId, long currentTimestamp) { public SearchQuery(String textQuery, Location currentLocation, Track.Id currentTrackId, long currentTimestamp) {
this.textQuery = textQuery.toLowerCase(Locale.getDefault()); this.textQuery = textQuery.toLowerCase(Locale.getDefault());
this.currentLocation = currentLocation; this.currentLocation = currentLocation;
this.currentTrackId = currentTrackId; this.currentTrackId = currentTrackId;
@@ -417,8 +417,8 @@ public class SearchEngine {
@Override @Override
public String toString() { public String toString() {
return "ScoredResult [" return "ScoredResult ["
+ (track != null ? ("trackId=" + track.getId() + ", ") : "") + (track != null ? ("trackId=" + track.getId().getId() + ", ") : "")
+ (waypoint != null ? ("wptId=" + waypoint.getId() + ", ") : "") + (waypoint != null ? ("wptId=" + waypoint.getId().getId() + ", ") : "")
+ "score=" + score + "]"; + "score=" + score + "]";
} }
} }
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import java.util.Collections; import java.util.Collections;
@@ -78,8 +79,8 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
private Handler handler; private Handler handler;
// Preference values // Preference values
private long selectedTrackId; private Track.Id selectedTrackId;
private long recordingTrackId; private Track.Id recordingTrackId;
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
// Track points sampling state // Track points sampling state
@@ -174,10 +175,10 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
handler = null; handler = null;
} }
public void loadTrack(final long trackId) { public void loadTrack(final @NonNull Track.Id trackId) {
runInHandlerThread(() -> { runInHandlerThread(() -> {
if (trackId == selectedTrackId) { if (trackId.equals(selectedTrackId)) {
Log.i(TAG, "Not reloading track " + trackId); Log.i(TAG, "Not reloading track " + trackId.getId());
return; return;
} }
selectedTrackId = trackId; selectedTrackId = trackId;
@@ -212,14 +213,14 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
* Returns true if the selected track is recording. * Returns true if the selected track is recording.
*/ */
public boolean isSelectedTrackRecording() { public boolean isSelectedTrackRecording() {
return selectedTrackId == recordingTrackId && PreferencesUtils.isRecording(recordingTrackId); return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && PreferencesUtils.isRecording(recordingTrackId);
} }
/** /**
* Returns true if the selected track is paused. * Returns true if the selected track is paused.
*/ */
public boolean isSelectedTrackPaused() { public boolean isSelectedTrackPaused() {
return selectedTrackId == recordingTrackId && recordingTrackPaused; return selectedTrackId != null && selectedTrackId.equals(recordingTrackId) && recordingTrackPaused;
} }
@Override @Override
@@ -311,7 +312,7 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
trackDataListener.clearWaypoints(); trackDataListener.clearWaypoints();
} }
try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, -1L, MAX_DISPLAYED_WAYPOINTS)) { try (Cursor cursor = contentProviderUtils.getWaypointCursor(selectedTrackId, null, MAX_DISPLAYED_WAYPOINTS)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
do { do {
Waypoint waypoint = contentProviderUtils.createWaypoint(cursor); Waypoint waypoint = contentProviderUtils.createWaypoint(cursor);
@@ -356,6 +357,11 @@ public class TrackDataHub implements SharedPreferences.OnSharedPreferenceChangeL
long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L; long localLastSeenTrackPointIdId = updateSamplingState ? lastSeenTrackPointId : -1L;
long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId; long maxPointId = updateSamplingState ? -1L : lastSeenTrackPointId;
if (selectedTrackId == null) {
Log.w(TAG, "This should not happen, but it does"); //TODO
return;
}
long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId); long lastTrackPointId = contentProviderUtils.getLastTrackPointId(selectedTrackId);
int samplingFrequency = -1; int samplingFrequency = -1;
boolean includeNextPoint = false; boolean includeNextPoint = false;
@@ -16,9 +16,17 @@
package de.dennisguse.opentracks.content.data; package de.dennisguse.opentracks.content.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
* A track. * A track.
@@ -28,7 +36,7 @@ import de.dennisguse.opentracks.stats.TrackStatistics;
*/ */
public class Track { public class Track {
private long id = -1L; private Track.Id id;
private UUID uuid = UUID.randomUUID(); private UUID uuid = UUID.randomUUID();
private String name = ""; private String name = "";
@@ -42,11 +50,15 @@ public class Track {
public Track() { public Track() {
} }
public long getId() { /**
* May be null if the track was not loaded from the database.
*/
public @Nullable
Id getId() {
return id; return id;
} }
public void setId(long id) { public void setId(Id id) {
this.id = id; this.id = id;
} }
@@ -97,4 +109,66 @@ public class Track {
public void setTrackStatistics(TrackStatistics trackStatistics) { public void setTrackStatistics(TrackStatistics trackStatistics) {
this.trackStatistics = trackStatistics; this.trackStatistics = trackStatistics;
} }
public static class Id implements Parcelable {
private final long id;
public Id(long id) {
this.id = id;
}
public Id(@NonNull String id) {
this(Long.parseLong(id));
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Deprecated //TODO Use a Track.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public int describeContents() {
return 0;
}
@NonNull
@Override
public String toString() {
throw new RuntimeException("Not supported");
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Parcelable.Creator<Track.Id> CREATOR = new Parcelable.Creator<Track.Id>() {
public Track.Id createFromParcel(Parcel in) {
return new Track.Id(in.readLong());
}
public Track.Id[] newArray(int size) {
return new Track.Id[size];
}
};
}
} }
@@ -33,7 +33,7 @@ public interface TrackPointsColumns extends BaseColumns {
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid"); Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint"; String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.trackpoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint"; String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.trackpoint";
String DEFAULT_SORT_ORDER = "_id"; String DEFAULT_SORT_ORDER = _ID;
// Columns // Columns
String TRACKID = "trackid"; String TRACKID = "trackid";
@@ -32,7 +32,7 @@ public interface TracksColumns extends BaseColumns {
Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME); Uri CONTENT_URI = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME);
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track"; String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.track";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track"; String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.track";
String DEFAULT_SORT_ORDER = "_id"; String DEFAULT_SORT_ORDER = _ID;
// Columns // Columns
String UUID = "uuid"; String UUID = "uuid";
@@ -18,10 +18,17 @@ package de.dennisguse.opentracks.content.data;
import android.location.Location; import android.location.Location;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import java.util.Objects;
import de.dennisguse.opentracks.util.PreferencesUtils;
/** /**
* A waypoint. * A waypoint.
* *
@@ -30,12 +37,12 @@ import androidx.annotation.VisibleForTesting;
*/ */
public final class Waypoint { public final class Waypoint {
private long id = -1L; private Id id;
private String name = ""; private String name = "";
private String description = ""; private String description = "";
private String category = ""; private String category = "";
private String icon = ""; private String icon = "";
private long trackId = -1L; private Track.Id trackId;
private double length = 0.0; private double length = 0.0;
private long duration = 0; private long duration = 0;
private Location location; private Location location;
@@ -51,7 +58,7 @@ public final class Waypoint {
this.location = location; this.location = location;
} }
public Waypoint(String name, String description, String category, String icon, long trackId, double length, long duration, @NonNull Location location, String photoUrl) { public Waypoint(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, long duration, @NonNull Location location, String photoUrl) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.category = category; this.category = category;
@@ -63,11 +70,15 @@ public final class Waypoint {
this.photoUrl = photoUrl; this.photoUrl = photoUrl;
} }
public long getId() { /**
* May be null if the waypoint was not loaded from the database.
*/
public @Nullable
Id getId() {
return id; return id;
} }
public void setId(long id) { public void setId(Id id) {
this.id = id; this.id = id;
} }
@@ -103,11 +114,11 @@ public final class Waypoint {
this.icon = icon; this.icon = icon;
} }
public long getTrackId() { public Track.Id getTrackId() {
return trackId; return trackId;
} }
public void setTrackId(long trackId) { public void setTrackId(Track.Id trackId) {
this.trackId = trackId; this.trackId = trackId;
} }
@@ -147,4 +158,66 @@ public final class Waypoint {
public boolean hasPhoto() { public boolean hasPhoto() {
return photoUrl != null && !"".equals(photoUrl); return photoUrl != null && !"".equals(photoUrl);
} }
public static class Id implements Parcelable {
private final long id;
public Id(long id) {
this.id = id;
}
public Id(@NonNull String id) {
this(Long.parseLong(id));
}
//TOOD Limit visibility to TrackRecordingService / ContentProvider
public long getId() {
return id;
}
@Deprecated //TODO Use a Waypoint.Id of null instead
public boolean isValid() {
return id != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Id id1 = (Id) o;
return id == id1.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@NonNull
@Override
public String toString() {
throw new RuntimeException("Not supported");
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(id);
}
public static final Parcelable.Creator<Id> CREATOR = new Parcelable.Creator<Id>() {
public Id createFromParcel(Parcel in) {
return new Id(in.readLong());
}
public Id[] newArray(int size) {
return new Id[size];
}
};
}
} }
@@ -33,7 +33,7 @@ public interface WaypointsColumns extends BaseColumns {
Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid"); Uri CONTENT_URI_BY_TRACKID = Uri.parse(ContentProviderUtils.CONTENT_BASE_URI + "/" + TABLE_NAME + "/trackid");
String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint"; String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.de.dennisguse.waypoint";
String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint"; String CONTENT_ITEMTYPE = "vnd.android.cursor.item/vnd.de.dennisguse.waypoint";
String DEFAULT_SORT_ORDER = "_id"; String DEFAULT_SORT_ORDER = _ID;
// Columns // Columns
String NAME = "name"; // waypoint name String NAME = "name"; // waypoint name
@@ -23,13 +23,19 @@ import android.database.Cursor;
import android.location.Location; import android.location.Location;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID;
import de.dennisguse.opentracks.BuildConfig; import de.dennisguse.opentracks.BuildConfig;
import de.dennisguse.opentracks.android.ContentResolverWrapper; import de.dennisguse.opentracks.android.ContentResolverWrapper;
@@ -42,6 +48,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.data.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.FileUtils;
import de.dennisguse.opentracks.util.TrackUtils;
import de.dennisguse.opentracks.util.UUIDUtils; import de.dennisguse.opentracks.util.UUIDUtils;
/** /**
@@ -56,7 +63,8 @@ public class ContentProviderUtils {
private static final int MAX_LATITUDE = 90000000; private static final int MAX_LATITUDE = 90000000;
// The authority (the first part of the URI) for the app's content provider. // The authority (the first part of the URI) for the app's content provider.
static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content"; @VisibleForTesting
public static final String AUTHORITY_PACKAGE = BuildConfig.APPLICATION_ID + ".content";
// The base URI for the app's content provider. // The base URI for the app's content provider.
public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE; public static final String CONTENT_BASE_URI = "content://" + AUTHORITY_PACKAGE;
@@ -86,8 +94,8 @@ public class ContentProviderUtils {
* *
* @param trackId the track id * @param trackId the track id
*/ */
public void clearTrack(Context context, long trackId) { public void clearTrack(Track.Id trackId) {
deleteTrackPointsAndWaypoints(context, trackId); deleteTrackPointsAndWaypoints(trackId);
Track track = new Track(); Track track = new Track();
track.setId(trackId); track.setId(trackId);
updateTrack(track); updateTrack(track);
@@ -118,7 +126,7 @@ public class ContentProviderUtils {
Track track = new Track(); Track track = new Track();
TrackStatistics trackStatistics = track.getTrackStatistics(); TrackStatistics trackStatistics = track.getTrackStatistics();
if (!cursor.isNull(idIndex)) { if (!cursor.isNull(idIndex)) {
track.setId(cursor.getLong(idIndex)); track.setId(new Track.Id(cursor.getLong(idIndex)));
} }
if (!cursor.isNull(uuidIndex)) { if (!cursor.isNull(uuidIndex)) {
track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex))); track.setUuid(UUIDUtils.fromBytes(cursor.getBlob(uuidIndex)));
@@ -181,14 +189,17 @@ public class ContentProviderUtils {
* *
* @param trackId the track id * @param trackId the track id
*/ */
public void deleteTrack(Context context, long trackId) { public void deleteTrack(Context context, Track.Id trackId) {
deleteTrackPointsAndWaypoints(context, trackId); if (trackId == null) {
return;
}
deleteTrackPointsAndWaypoints(trackId);
// Delete track folder resources. // Delete track folder resources.
FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId)); FileUtils.deleteDirectoryRecurse(FileUtils.getPhotoDir(context, trackId));
// Delete track last since it triggers a database vacuum call // Delete track last since it triggers a database vacuum call
contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId)}); contentResolver.delete(TracksColumns.CONTENT_URI, TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())});
} }
/** /**
@@ -196,21 +207,15 @@ public class ContentProviderUtils {
* *
* @param trackId the track id * @param trackId the track id
*/ */
private void deleteTrackPointsAndWaypoints(Context context, long trackId) { private void deleteTrackPointsAndWaypoints(@NonNull Track.Id trackId) {
String where = TrackPointsColumns.TRACKID + "=?"; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
String[] selectionArgs = new String[]{Long.toString(trackId)};
contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, where, selectionArgs);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", new String[]{Long.toString(trackId)}); contentResolver.delete(TrackPointsColumns.CONTENT_URI_BY_ID, TrackPointsColumns.TRACKID + "=?", selectionArgs);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns.TRACKID + "=?", selectionArgs);
} }
/**
* Gets all the tracks.
*
* @return the tracks do not have any trackPoints attached.
*/
@VisibleForTesting @VisibleForTesting
public List<Track> getAllTracks() { public List<Track> getTracks() {
ArrayList<Track> tracks = new ArrayList<>(); ArrayList<Track> tracks = new ArrayList<>();
try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) { try (Cursor cursor = getTrackCursor(null, null, TracksColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
@@ -223,6 +228,17 @@ public class ContentProviderUtils {
return tracks; return tracks;
} }
public static Loader<Cursor> getTracksCursorLoader(Context context) {
String[] PROJECTION = new String[]{TracksColumns._ID, TracksColumns.NAME,
TracksColumns.DESCRIPTION, TracksColumns.CATEGORY, TracksColumns.STARTTIME,
TracksColumns.TOTALDISTANCE, TracksColumns.TOTALTIME, TracksColumns.ICON};
return new CursorLoader(context, TracksColumns.CONTENT_URI, PROJECTION, null, null, TrackUtils.TRACK_SORT_ORDER);
}
@Deprecated
//TODO Do not use; rather get the proper TrackId (e.g., resume a track might give you some weird effects).
@VisibleForTesting
public Track getLastTrack() { public Track getLastTrack() {
try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) { try (Cursor cursor = getTrackCursor(null, null, TracksColumns.STARTTIME + " DESC")) {
// Using the same order as shown in the track list // Using the same order as shown in the track list
@@ -235,13 +251,25 @@ public class ContentProviderUtils {
/** /**
* @param trackId the track id. * @param trackId the track id.
* @return the track doesn't have any trackPoints attached
*/ */
public Track getTrack(long trackId) { public Track getTrack(Track.Id trackId) {
if (trackId < 0) { if (trackId == null || !trackId.isValid()) {
return null; return null;
} }
try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId)}, TracksColumns._ID)) { try (Cursor cursor = getTrackCursor(TracksColumns._ID + "=?", new String[]{Long.toString(trackId.getId())}, null)) {
if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor);
}
}
return null;
}
/**
* @param trackUUID the track uuid.
*/
public Track getTrack(@NonNull UUID trackUUID) {
String trackUUIDsearch = UUIDUtils.toHex(trackUUID);
try (Cursor cursor = getTrackCursor("hex(" + TracksColumns.UUID + ")=?", new String[]{trackUUIDsearch}, null)) {
if (cursor != null && cursor.moveToNext()) { if (cursor != null && cursor.moveToNext()) {
return createTrack(cursor); return createTrack(cursor);
} }
@@ -279,7 +307,7 @@ public class ContentProviderUtils {
* @param track the track * @param track the track
*/ */
public void updateTrack(Track track) { public void updateTrack(Track track) {
contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId())}); contentResolver.update(TracksColumns.CONTENT_URI, createContentValues(track), TracksColumns._ID + "=?", new String[]{Long.toString(track.getId().getId())});
} }
private ContentValues createContentValues(Track track) { private ContentValues createContentValues(Track track) {
@@ -287,8 +315,8 @@ public class ContentProviderUtils {
TrackStatistics trackStatistics = track.getTrackStatistics(); TrackStatistics trackStatistics = track.getTrackStatistics();
// Value < 0 indicates no id is available // Value < 0 indicates no id is available
if (track.getId() >= 0) { if (track.getId() != null && track.getId().isValid()) {
values.put(TracksColumns._ID, track.getId()); values.put(TracksColumns._ID, track.getId().getId());
} }
values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid())); values.put(TracksColumns.UUID, UUIDUtils.toBytes(track.getUuid()));
values.put(TracksColumns.NAME, track.getName()); values.put(TracksColumns.NAME, track.getName());
@@ -354,7 +382,7 @@ public class ContentProviderUtils {
Waypoint waypoint = new Waypoint(location); Waypoint waypoint = new Waypoint(location);
if (!cursor.isNull(idIndex)) { if (!cursor.isNull(idIndex)) {
waypoint.setId(cursor.getLong(idIndex)); waypoint.setId(new Waypoint.Id(cursor.getLong(idIndex)));
} }
if (!cursor.isNull(nameIndex)) { if (!cursor.isNull(nameIndex)) {
waypoint.setName(cursor.getString(nameIndex)); waypoint.setName(cursor.getString(nameIndex));
@@ -369,7 +397,7 @@ public class ContentProviderUtils {
waypoint.setIcon(cursor.getString(iconIndex)); waypoint.setIcon(cursor.getString(iconIndex));
} }
if (!cursor.isNull(trackIdIndex)) { if (!cursor.isNull(trackIdIndex)) {
waypoint.setTrackId(cursor.getLong(trackIdIndex)); waypoint.setTrackId(new Track.Id(cursor.getLong(trackIdIndex)));
} }
if (!cursor.isNull(lengthIndex)) { if (!cursor.isNull(lengthIndex)) {
waypoint.setLength(cursor.getFloat(lengthIndex)); waypoint.setLength(cursor.getFloat(lengthIndex));
@@ -384,10 +412,10 @@ public class ContentProviderUtils {
return waypoint; return waypoint;
} }
public void deleteWaypoint(Context context, long waypointId) { public void deleteWaypoint(Context context, Waypoint.Id waypointId) {
final Waypoint waypoint = getWaypoint(waypointId); final Waypoint waypoint = getWaypoint(waypointId);
deleteWaypointPhoto(context, waypoint); deleteWaypointPhoto(context, waypoint);
contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId)}); contentResolver.delete(WaypointsColumns.CONTENT_URI, WaypointsColumns._ID + "=?", new String[]{Long.toString(waypointId.getId())});
} }
/** /**
@@ -396,13 +424,13 @@ public class ContentProviderUtils {
* @param trackId the track id * @param trackId the track id
* @return -1 if not able to get the next waypoint number. * @return -1 if not able to get the next waypoint number.
*/ */
public int getNextWaypointNumber(long trackId) { public int getNextWaypointNumber(Track.Id trackId) {
if (trackId < 0) { if (!trackId.isValid()) {
return -1; return -1;
} }
String[] projection = {WaypointsColumns._ID}; String[] projection = {WaypointsColumns._ID};
String selection = WaypointsColumns.TRACKID + "=?"; String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)}; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) { try (Cursor cursor = getWaypointCursor(projection, selection, selectionArgs, WaypointsColumns._ID, -1)) {
if (cursor != null) { if (cursor != null) {
return cursor.getCount(); return cursor.getCount();
@@ -411,12 +439,12 @@ public class ContentProviderUtils {
return -1; return -1;
} }
public Waypoint getWaypoint(long waypointId) { public Waypoint getWaypoint(Waypoint.Id waypointId) {
if (waypointId < 0) { if (!waypointId.isValid()) {
return null; return null;
} }
try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?", try (Cursor cursor = getWaypointCursor(null, WaypointsColumns._ID + "=?",
new String[]{Long.toString(waypointId)}, WaypointsColumns._ID, 1)) { new String[]{Long.toString(waypointId.getId())}, WaypointsColumns._ID, 1)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return createWaypoint(cursor); return createWaypoint(cursor);
} }
@@ -442,30 +470,30 @@ public class ContentProviderUtils {
* The caller owns the returned cursor and is responsible for closing it. * The caller owns the returned cursor and is responsible for closing it.
* *
* @param trackId the track id * @param trackId the track id
* @param minWaypointId the minimum waypoint id. -1L to ignore * @param minWaypointId the minimum waypoint id. null to ignore
* @param maxWaypoints the maximum number of waypoints to return. -1 for no limit * @param maxWaypoints the maximum number of waypoints to return. -1 for no limit
*/ */
public Cursor getWaypointCursor(long trackId, long minWaypointId, int maxWaypoints) { public Cursor getWaypointCursor(Track.Id trackId, @Nullable Waypoint.Id minWaypointId, int maxWaypoints) {
if (trackId < 0) { if (!trackId.isValid()) {
return null; return null;
} }
String selection; String selection;
String[] selectionArgs; String[] selectionArgs;
if (minWaypointId >= 0) { if (minWaypointId != null && minWaypointId.isValid()) {
selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?"; selection = WaypointsColumns.TRACKID + "=? AND " + WaypointsColumns._ID + ">=?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(minWaypointId)}; selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(minWaypointId.getId())};
} else { } else {
selection = WaypointsColumns.TRACKID + "=?"; selection = WaypointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)}; selectionArgs = new String[]{Long.toString(trackId.getId())};
} }
return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints); return getWaypointCursor(null, selection, selectionArgs, WaypointsColumns._ID, maxWaypoints);
} }
@VisibleForTesting @VisibleForTesting
public List<Waypoint> getWaypoints(long trackId) { public List<Waypoint> getWaypoints(Track.Id trackId) {
ArrayList<Waypoint> waypoints = new ArrayList<>(); ArrayList<Waypoint> waypoints = new ArrayList<>();
try (Cursor cursor = getWaypointCursor(trackId, -1L, -1)) { try (Cursor cursor = getWaypointCursor(trackId, null, -1)) {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
waypoints.add(createWaypoint(cursor)); waypoints.add(createWaypoint(cursor));
@@ -475,19 +503,34 @@ public class ContentProviderUtils {
return waypoints; return waypoints;
} }
public static Loader<Cursor> getWaypointsLoader(Context context, @Nullable Track.Id trackId) {
final String[] PROJECTION = new String[]{WaypointsColumns._ID,
WaypointsColumns.NAME, WaypointsColumns.DESCRIPTION, WaypointsColumns.CATEGORY,
WaypointsColumns.TIME, WaypointsColumns.PHOTOURL,
WaypointsColumns.LATITUDE, WaypointsColumns.LONGITUDE};
if (trackId != null) {
return new CursorLoader(context, WaypointsColumns.CONTENT_URI, PROJECTION,
WaypointsColumns.TRACKID + "=?",
new String[]{String.valueOf(trackId.getId())}, null);
} else {
return new CursorLoader(context, WaypointsColumns.CONTENT_URI, PROJECTION, null, null, null);
}
}
/** /**
* Gets the number of waypoints for a track. * Gets the number of waypoints for a track.
* *
* @param trackId the track id * @param trackId the track id
*/ */
public int getWaypointCount(long trackId) { public int getWaypointCount(Track.Id trackId) {
if (trackId < 0) { if (!trackId.isValid()) {
return 0; return 0;
} }
String[] projection = new String[]{"count(*) AS count"}; String[] projection = new String[]{"count(*) AS count"};
String selection = WaypointsColumns.TRACKID + "=?"; String selection = WaypointsColumns.TRACKID + "=?";
String[] selectionArgs = new String[]{Long.toString(trackId)}; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID)) { try (Cursor cursor = contentResolver.query(WaypointsColumns.CONTENT_URI, projection, selection, selectionArgs, WaypointsColumns._ID)) {
if (cursor == null) { if (cursor == null) {
return 0; return 0;
@@ -503,8 +546,8 @@ public class ContentProviderUtils {
* @param waypoint the waypoint * @param waypoint the waypoint
* @return the content provider URI of the inserted waypoint. * @return the content provider URI of the inserted waypoint.
*/ */
public Uri insertWaypoint(Waypoint waypoint) { public Uri insertWaypoint(@NonNull Waypoint waypoint) {
waypoint.setId(-1L); waypoint.setId(null);
return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint)); return contentResolver.insert(WaypointsColumns.CONTENT_URI, createContentValues(waypoint));
} }
@@ -539,22 +582,23 @@ public class ContentProviderUtils {
if (!updatedWaypoint.hasPhoto()) { if (!updatedWaypoint.hasPhoto()) {
deleteWaypointPhoto(context, savedWaypoint); deleteWaypointPhoto(context, savedWaypoint);
} }
int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId())}); int rows = contentResolver.update(WaypointsColumns.CONTENT_URI, createContentValues(updatedWaypoint), WaypointsColumns._ID + "=?", new String[]{Long.toString(updatedWaypoint.getId().getId())});
return rows == 1; return rows == 1;
} }
ContentValues createContentValues(Waypoint waypoint) { ContentValues createContentValues(@NonNull Waypoint waypoint) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// Value < 0 indicates no id is available if (waypoint.getId() != null && waypoint.getId().isValid()) {
if (waypoint.getId() >= 0) { values.put(WaypointsColumns._ID, waypoint.getId().getId());
values.put(WaypointsColumns._ID, waypoint.getId());
} }
values.put(WaypointsColumns.NAME, waypoint.getName()); values.put(WaypointsColumns.NAME, waypoint.getName());
values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription()); values.put(WaypointsColumns.DESCRIPTION, waypoint.getDescription());
values.put(WaypointsColumns.CATEGORY, waypoint.getCategory()); values.put(WaypointsColumns.CATEGORY, waypoint.getCategory());
values.put(WaypointsColumns.ICON, waypoint.getIcon()); values.put(WaypointsColumns.ICON, waypoint.getIcon());
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId()); if (waypoint.getTrackId() != null) {
values.put(WaypointsColumns.TRACKID, waypoint.getTrackId().getId());
}
values.put(WaypointsColumns.LENGTH, waypoint.getLength()); values.put(WaypointsColumns.LENGTH, waypoint.getLength());
values.put(WaypointsColumns.DURATION, waypoint.getDuration()); values.put(WaypointsColumns.DURATION, waypoint.getDuration());
@@ -647,7 +691,7 @@ public class ContentProviderUtils {
* @return the number of trackPoints inserted * @return the number of trackPoints inserted
*/ */
//TODO Only used for testing and file import; might be better to replace it. //TODO Only used for testing and file import; might be better to replace it.
public int bulkInsertTrackPoint(TrackPoint[] trackPoints, long trackId) { public int bulkInsertTrackPoint(TrackPoint[] trackPoints, Track.Id trackId) {
ContentValues[] values = new ContentValues[trackPoints.length]; ContentValues[] values = new ContentValues[trackPoints.length];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
values[i] = createContentValues(trackPoints[i], trackId); values[i] = createContentValues(trackPoints[i], trackId);
@@ -657,23 +701,23 @@ public class ContentProviderUtils {
/** /**
* Gets the first location id for a track. * Gets the first location id for a track.
* Returns -1L if it doesn't exist. * Returns null if it doesn't exist.
* *
* @param trackId the track id * @param trackId the track id
*/ */
@Deprecated @Deprecated
public long getFirstTrackPointId(long trackId) { public Track.Id getFirstTrackPointId(Track.Id trackId) {
if (trackId < 0) { if (!trackId.isValid()) {
return -1L; return null;
} }
String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)"; String selection = TrackPointsColumns._ID + "=(SELECT MIN(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId)}; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); return new Track.Id(cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)));
} }
} }
return -1L; return null;
} }
/** /**
@@ -683,12 +727,13 @@ public class ContentProviderUtils {
* @param trackId the track id * @param trackId the track id
*/ */
@Deprecated @Deprecated
public long getLastTrackPointId(long trackId) { public long getLastTrackPointId(Track.Id trackId) {
if (trackId < 0) { if (trackId == null || !trackId.isValid()) {
Log.w(TAG, "Fix callers who do this.");
return -1L; return -1L;
} }
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)"; String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") from " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId)}; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
@@ -704,12 +749,12 @@ public class ContentProviderUtils {
* @param location the location * @param location the location
* @return trackPoint id if the location is in the track. -1L otherwise. * @return trackPoint id if the location is in the track. -1L otherwise.
*/ */
public long getTrackPointId(long trackId, Location location) { public long getTrackPointId(Track.Id trackId, Location location) {
if (trackId < 0) { if (!trackId.isValid()) {
return -1L; return -1L;
} }
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)"; String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.TIME + "=?)";
String[] selectionArgs = new String[]{Long.toString(trackId), Long.toString(location.getTime())}; String[] selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(location.getTime())};
try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) { try (Cursor cursor = getTrackPointCursor(new String[]{TrackPointsColumns._ID}, selection, selectionArgs, TrackPointsColumns._ID)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID)); return cursor.getLong(cursor.getColumnIndexOrThrow(TrackPointsColumns._ID));
@@ -735,8 +780,8 @@ public class ContentProviderUtils {
* @param maxLocations maximum number of locations to return. -1 for no limit * @param maxLocations maximum number of locations to return. -1 for no limit
* @param descending true to sort the result in descending order (latest location first) * @param descending true to sort the result in descending order (latest location first)
*/ */
public Cursor getTrackPointCursor(long trackId, long startTrackPointId, int maxLocations, boolean descending) { public Cursor getTrackPointCursor(Track.Id trackId, long startTrackPointId, int maxLocations, boolean descending) {
if (trackId < 0) { if (!trackId.isValid()) {
return null; return null;
} }
@@ -745,10 +790,10 @@ public class ContentProviderUtils {
if (startTrackPointId >= 0) { if (startTrackPointId >= 0) {
String comparison = descending ? "<=" : ">="; String comparison = descending ? "<=" : ">=";
selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?"; selection = TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns._ID + comparison + "?";
selectionArgs = new String[]{Long.toString(trackId), Long.toString(startTrackPointId)}; selectionArgs = new String[]{Long.toString(trackId.getId()), Long.toString(startTrackPointId)};
} else { } else {
selection = TrackPointsColumns.TRACKID + "=?"; selection = TrackPointsColumns.TRACKID + "=?";
selectionArgs = new String[]{Long.toString(trackId)}; selectionArgs = new String[]{Long.toString(trackId.getId())};
} }
String sortOrder = TrackPointsColumns._ID; String sortOrder = TrackPointsColumns._ID;
@@ -768,12 +813,12 @@ public class ContentProviderUtils {
* @param trackId the track id * @param trackId the track id
*/ */
@Deprecated @Deprecated
public TrackPoint getLastValidTrackPoint(long trackId) { public TrackPoint getLastValidTrackPoint(Track.Id trackId) {
if (trackId < 0) { if (!trackId.isValid()) {
return null; return null;
} }
String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")"; String selection = TrackPointsColumns._ID + "=(SELECT MAX(" + TrackPointsColumns._ID + ") FROM " + TrackPointsColumns.TABLE_NAME + " WHERE " + TrackPointsColumns.TRACKID + "=? AND " + TrackPointsColumns.LATITUDE + "<=" + MAX_LATITUDE + ")";
String[] selectionArgs = new String[]{Long.toString(trackId)}; String[] selectionArgs = new String[]{Long.toString(trackId.getId())};
return findTrackPointBy(selection, selectionArgs); return findTrackPointBy(selection, selectionArgs);
} }
@@ -784,7 +829,7 @@ public class ContentProviderUtils {
* @param trackId the track id * @param trackId the track id
* @return the content provider URI of the inserted trackPoint * @return the content provider URI of the inserted trackPoint
*/ */
public Uri insertTrackPoint(TrackPoint trackPoint, long trackId) { public Uri insertTrackPoint(TrackPoint trackPoint, Track.Id trackId) {
return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId)); return contentResolver.insert(TrackPointsColumns.CONTENT_URI_BY_ID, createContentValues(trackPoint, trackId));
} }
@@ -794,9 +839,9 @@ public class ContentProviderUtils {
* @param trackPoint the trackPoint * @param trackPoint the trackPoint
* @param trackId the track id * @param trackId the track id
*/ */
private ContentValues createContentValues(TrackPoint trackPoint, long trackId) { private ContentValues createContentValues(TrackPoint trackPoint, Track.Id trackId) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TrackPointsColumns.TRACKID, trackId); values.put(TrackPointsColumns.TRACKID, trackId.getId());
values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6)); values.put(TrackPointsColumns.LONGITUDE, (int) (trackPoint.getLongitude() * 1E6));
values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6)); values.put(TrackPointsColumns.LATITUDE, (int) (trackPoint.getLatitude() * 1E6));
@@ -837,7 +882,7 @@ public class ContentProviderUtils {
* @param startTrackPointId the starting trackPoint id. -1L to ignore * @param startTrackPointId the starting trackPoint id. -1L to ignore
* @param descending true to sort the result in descending order (latest location first) * @param descending true to sort the result in descending order (latest location first)
*/ */
public TrackPointIterator getTrackPointLocationIterator(final long trackId, final long startTrackPointId, final boolean descending) { public TrackPointIterator getTrackPointLocationIterator(final Track.Id trackId, final long startTrackPointId, final boolean descending) {
return new TrackPointIterator(this, trackId, startTrackPointId, descending); return new TrackPointIterator(this, trackId, startTrackPointId, descending);
} }
@@ -863,7 +908,7 @@ public class ContentProviderUtils {
} }
@VisibleForTesting @VisibleForTesting
public List<TrackPoint> getTrackPoints(long trackId) { public List<TrackPoint> getTrackPoints(Track.Id trackId) {
List<TrackPoint> trackPoints = null; List<TrackPoint> trackPoints = null;
try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) { try (Cursor trackPointCursor = getTrackPointCursor(trackId, -1L, -1, false)) {
@@ -894,13 +939,23 @@ public class ContentProviderUtils {
this.defaultCursorBatchSize = defaultCursorBatchSize; this.defaultCursorBatchSize = defaultCursorBatchSize;
} }
public static String formatIdListForUri(Track.Id... trackIds) {
long[] ids = new long[trackIds.length];
for (int i = 0; i < trackIds.length; i++) {
ids[i] = trackIds[i].getId();
}
return formatIdListForUri(ids);
}
/** /**
* Formats an array of IDs as comma separated string value * Formats an array of IDs as comma separated string value
* *
* @param ids array with IDs * @param ids array with IDs
* @return comma separated list of ids * @return comma separated list of ids
*/ */
public static String formatIdListForUri(long[] ids) { private static String formatIdListForUri(long[] ids) {
StringBuilder idsPathSegment = new StringBuilder(); StringBuilder idsPathSegment = new StringBuilder();
for (long id : ids) { for (long id : ids) {
if (idsPathSegment.length() > 0) { if (idsPathSegment.length() > 0) {
@@ -42,7 +42,7 @@ import de.dennisguse.opentracks.content.data.WaypointsColumns;
* *
* @author Leif Hendrik Wilden * @author Leif Hendrik Wilden
*/ */
public abstract class CustomContentProvider extends ContentProvider { public class CustomContentProvider extends ContentProvider {
private static final String TAG = CustomContentProvider.class.getSimpleName(); private static final String TAG = CustomContentProvider.class.getSimpleName();
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
import java.util.UUID; import java.util.UUID;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPointsColumns; import de.dennisguse.opentracks.content.data.TrackPointsColumns;
import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.content.data.TracksColumns;
import de.dennisguse.opentracks.content.data.WaypointsColumns; import de.dennisguse.opentracks.content.data.WaypointsColumns;
@@ -20,13 +21,14 @@ import de.dennisguse.opentracks.util.UUIDUtils;
* Database helper for creating and upgrading the database. * Database helper for creating and upgrading the database.
*/ */
@VisibleForTesting @VisibleForTesting
class CustomSQLiteOpenHelper extends SQLiteOpenHelper { public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 26; private static final int DATABASE_VERSION = 26;
private static final String DATABASE_NAME = "database.db"; @VisibleForTesting
public static final String DATABASE_NAME = "database.db";
public CustomSQLiteOpenHelper(Context context) { public CustomSQLiteOpenHelper(Context context) {
this(context, DATABASE_NAME); this(context, DATABASE_NAME);
@@ -161,7 +163,7 @@ class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
int trackIdIndex = cursor.getColumnIndexOrThrow("_id"); int trackIdIndex = cursor.getColumnIndexOrThrow("_id");
do { do {
long trackId = cursor.getLong(trackIdIndex); Track.Id trackId = new Track.Id(cursor.getLong(trackIdIndex));
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID())); cv.put("uuid", UUIDUtils.toBytes(UUID.randomUUID()));
db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId)}); db.update("tracks", cv, "_id = ?", new String[]{String.valueOf(trackId)});
@@ -17,8 +17,11 @@ import androidx.annotation.Nullable;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import de.dennisguse.opentracks.android.IContentResolver; import de.dennisguse.opentracks.android.IContentResolver;
import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.Track;
@@ -29,9 +32,9 @@ import de.dennisguse.opentracks.io.file.exporter.TrackExporter;
/** /**
* A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files. * A content provider that mimics the behavior of {@link androidx.core.content.FileProvider}, which shares virtual (non-existing) KML-files.
* The actual content of the virtual files is generated by using the functionality defined in {@link CustomContentProvider}. * The actual content of the virtual files is generated by using the functionality defined in {@link CustomContentProvider}.
* * <p>
* Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}. * Moreover, it manages access to OpenTrack's database via {@link CustomContentProvider}.
* * <p>
* Explanation: * Explanation:
* Although a request is handled by a {@link android.content.ContentProvider} (with temporarily granted permission), Android's security infrastructure prevents forwarding queries to non-exported {@link android.content.ContentProvider}. * Although a request is handled by a {@link android.content.ContentProvider} (with temporarily granted permission), Android's security infrastructure prevents forwarding queries to non-exported {@link android.content.ContentProvider}.
* Thus, if {@link ShareContentProvider} and {@link CustomContentProvider} would be two different instances, the data would not be accessible to external apps. * Thus, if {@link ShareContentProvider} and {@link CustomContentProvider} would be two different instances, the data would not be accessible to external apps.
@@ -70,14 +73,20 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES); uriMatcher.addURI(ContentProviderUtils.AUTHORITY_PACKAGE, TracksColumns.TABLE_NAME + "/" + TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getName() + "/*/*", URI_KMZ_WITH_TRACKDETAIL_SENSORDATA_AND_PICTURES);
} }
public static Pair<Uri, String> createURI(long[] trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) { public static Pair<Uri, String> createURI(Track.Id trackId, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.length == 0) { Set<Track.Id> trackIds = new HashSet<>(1);
trackIds.add(trackId);
return createURI(trackIds, trackName, trackFileFormat);
}
public static Pair<Uri, String> createURI(Set<Track.Id> trackIds, String trackName, @NonNull TrackFileFormat trackFileFormat) {
if (trackIds.size() == 0) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
StringBuilder trackIdBuilder = new StringBuilder(); StringBuilder trackIdBuilder = new StringBuilder();
for (long trackId : trackIds) { for (Track.Id trackId : trackIds) {
trackIdBuilder.append(trackId).append(TRACKID_DELIMITER); trackIdBuilder.append(trackId.getId()).append(TRACKID_DELIMITER);
} }
trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER)); trackIdBuilder.deleteCharAt(trackIdBuilder.lastIndexOf(TRACKID_DELIMITER));
@@ -89,18 +98,18 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
return new Pair<>(uri, mime); return new Pair<>(uri, mime);
} }
static long[] parseURI(Uri uri) { static Set<Track.Id> parseURI(Uri uri) {
List<String> uriPaths = uri.getPathSegments(); List<String> uriPaths = uri.getPathSegments();
if (uriPaths == null || uriPaths.size() < 3) { if (uriPaths == null || uriPaths.size() < 3) {
Log.d(TAG, "URI does not contain any trackIds."); Log.d(TAG, "URI does not contain any trackIds.");
return new long[]{}; return new HashSet<>();
} }
String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER); String[] uriTrackIds = uriPaths.get(2).split(TRACKID_DELIMITER);
long[] trackIds = new long[uriTrackIds.length]; Set<Track.Id> trackIds = new HashSet<>();
for (int i = 0; i < trackIds.length; i++) { for (String uriTrackId : uriTrackIds) {
trackIds[i] = Long.parseLong(uriTrackIds[i]); trackIds.add(new Track.Id(Long.parseLong(uriTrackId)));
} }
return trackIds; return trackIds;
} }
@@ -202,17 +211,17 @@ public class ShareContentProvider extends CustomContentProvider implements ICont
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(this);
long[] trackIds = parseURI(uri); Set<Track.Id> trackIds = parseURI(uri);
final Track[] tracks = new Track[trackIds.length]; final ArrayList<Track> tracks = new ArrayList<>();
for (int i = 0; i < trackIds.length; i++) { for (Track.Id trackId : trackIds) {
tracks[i] = contentProviderUtils.getTrack(trackIds[i]); tracks.add(contentProviderUtils.getTrack(trackId));
} }
final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext(), tracks); final TrackExporter trackExporter = getTrackFileFormat(uri).newTrackExporter(getContext());
PipeDataWriter<String> pipeDataWriter = (PipeDataWriter<String>) (output, uri1, mimeType, opts, args) -> { PipeDataWriter<String> pipeDataWriter = (output, uri1, mimeType, opts, args) -> {
try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) { try (FileOutputStream fileOutputStream = new FileOutputStream(output.getFileDescriptor())) {
trackExporter.writeTrack(getContext(), fileOutputStream); trackExporter.writeTrack(tracks.toArray(new Track[0]), fileOutputStream);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "there occurred an error while sharing a file: " + e); Log.w(TAG, "there occurred an error while sharing a file: " + e);
} }
@@ -6,6 +6,7 @@ import android.util.Log;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
/** /**
@@ -16,14 +17,14 @@ public class TrackPointIterator implements Iterator<TrackPoint>, AutoCloseable {
private static final String TAG = TrackPointIterator.class.getSimpleName(); private static final String TAG = TrackPointIterator.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private final long trackId; private final Track.Id trackId;
private final boolean descending; private final boolean descending;
private final CachedTrackPointsIndexes indexes; private final CachedTrackPointsIndexes indexes;
private long lastTrackPointId = -1L; private long lastTrackPointId = -1L;
private Cursor cursor; private Cursor cursor;
public TrackPointIterator(ContentProviderUtils contentProviderUtils, long trackId, long startTrackPointId, boolean descending) { public TrackPointIterator(ContentProviderUtils contentProviderUtils, Track.Id trackId, long startTrackPointId, boolean descending) {
this.contentProviderUtils = contentProviderUtils; this.contentProviderUtils = contentProviderUtils;
this.trackId = trackId; this.trackId = trackId;
this.descending = descending; this.descending = descending;
@@ -25,6 +25,7 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.util.DialogUtils; import de.dennisguse.opentracks.util.DialogUtils;
/** /**
@@ -44,9 +45,9 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
* *
* @param trackIds list of track ids to delete. * @param trackIds list of track ids to delete.
*/ */
public static void showDialog(FragmentManager fragmentManager, long[] trackIds) { public static void showDialog(FragmentManager fragmentManager, Track.Id... trackIds) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putLongArray(KEY_TRACK_IDS, trackIds); bundle.putParcelableArray(KEY_TRACK_IDS, trackIds);
ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment(); ConfirmDeleteDialogFragment deleteTrackDialogFragment = new ConfirmDeleteDialogFragment();
deleteTrackDialogFragment.setArguments(bundle); deleteTrackDialogFragment.setArguments(bundle);
@@ -66,7 +67,7 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
@Override @Override
@NonNull @NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] trackIds = getArguments().getLongArray(KEY_TRACK_IDS); final Track.Id[] trackIds = (Track.Id[]) getArguments().getParcelableArray(KEY_TRACK_IDS);
int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title; int titleId = trackIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.track_delete_one_confirm_title;
int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message; int messageId = trackIds.length > 1 ? R.string.track_delete_multiple_confirm_message : R.string.track_delete_one_confirm_message;
@@ -85,6 +86,6 @@ public class ConfirmDeleteDialogFragment extends DialogFragment {
*/ */
public interface ConfirmDeleteCaller { public interface ConfirmDeleteCaller {
void onConfirmDeleteDone(long[] trackIds); void onConfirmDeleteDone(Track.Id... trackIds);
} }
} }
@@ -26,6 +26,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.util.DialogUtils; import de.dennisguse.opentracks.util.DialogUtils;
@@ -41,9 +42,9 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
private DeleteMarkerCaller caller; private DeleteMarkerCaller caller;
public static void showDialog(FragmentManager fragmentManager, long[] markerIds) { public static void showDialog(FragmentManager fragmentManager, Waypoint.Id... waypointIds) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putLongArray(KEY_MARKER_IDS, markerIds); bundle.putParcelableArray(KEY_MARKER_IDS, waypointIds);
DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment(); DeleteMarkerDialogFragment deleteMarkerDialogFragment = new DeleteMarkerDialogFragment();
deleteMarkerDialogFragment.setArguments(bundle); deleteMarkerDialogFragment.setArguments(bundle);
@@ -63,23 +64,23 @@ public class DeleteMarkerDialogFragment extends DialogFragment {
@Override @Override
@NonNull @NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final long[] markerIds = getArguments().getLongArray(KEY_MARKER_IDS); final Waypoint.Id[] waypointIds = (Waypoint.Id[]) getArguments().getParcelableArray(KEY_MARKER_IDS);
final FragmentActivity fragmentActivity = getActivity(); final FragmentActivity fragmentActivity = getActivity();
int titleId; int titleId;
int messageId; int messageId;
if (markerIds.length == 1 && markerIds[0] == -1L) { if (waypointIds == null) {
titleId = R.string.generic_delete_all_confirm_title; titleId = R.string.generic_delete_all_confirm_title;
messageId = R.string.marker_delete_all_confirm_message; messageId = R.string.marker_delete_all_confirm_message;
} else { } else {
titleId = markerIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title; titleId = waypointIds.length > 1 ? R.string.generic_delete_selected_confirm_title : R.string.marker_delete_one_confirm_title;
messageId = markerIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message; messageId = waypointIds.length > 1 ? R.string.marker_delete_multiple_confirm_message : R.string.marker_delete_one_confirm_message;
} }
return DialogUtils.createConfirmationDialog( return DialogUtils.createConfirmationDialog(
fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> { fragmentActivity, titleId, getString(messageId), (dialog, which) -> new Thread(() -> {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(fragmentActivity);
for (long markerId : markerIds) { for (Waypoint.Id waypointId : waypointIds) {
contentProviderUtils.deleteWaypoint(getContext(), markerId); contentProviderUtils.deleteWaypoint(getContext(), waypointId);
} }
caller.onDeleteMarkerDone(); caller.onDeleteMarkerDone();
}).start()); }).start());
@@ -206,10 +206,10 @@ public class ExportProgressDialogFragment extends DialogFragment {
} }
private Boolean exportTrack(Context context, Track track) { private Boolean exportTrack(Context context, Track track) {
TrackExporter trackExporter = trackFileFormat.newTrackExporter(context, new Track[]{track}); TrackExporter trackExporter = trackFileFormat.newTrackExporter(context);
//TODO Move to helper function //TODO Move to helper function
String fileName = track.getId() + "." + trackFileFormat.getExtension(); String fileName = track.getId().getId() + "." + trackFileFormat.getExtension();
// Overwrite a file if it exists; DocumentFile.createFile() creates a new file appending a suffix if the displayname already exists. // Overwrite a file if it exists; DocumentFile.createFile() creates a new file appending a suffix if the displayname already exists.
DocumentFile file = directory.findFile(fileName); DocumentFile file = directory.findFile(fileName);
@@ -218,7 +218,7 @@ public class ExportProgressDialogFragment extends DialogFragment {
} }
try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) { try (OutputStream outputStream = context.getContentResolver().openOutputStream(file.getUri())) {
if (trackExporter.writeTrack(context, outputStream)) { if (trackExporter.writeTrack(track, outputStream)) {
return true; return true;
} else { } else {
if (!file.delete()) { if (!file.delete()) {
@@ -205,16 +205,16 @@ public class ImportProgressDialogFragment extends DialogFragment {
if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) { if (TrackFileFormat.GPX.getExtension().equals(fileExtension)) {
trackImporter = new GpxFileTrackImporter(context); trackImporter = new GpxFileTrackImporter(context);
} else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) { } else if (TrackFileFormat.KML_WITH_TRACKDETAIL_AND_SENSORDATA.getExtension().equals(fileExtension)) {
trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext(), -1L); trackImporter = new KmlFileTrackImporter(ImportProgressDialogFragment.this.getContext());
} else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) { } else if (TrackFileFormat.KMZ_WITH_TRACKDETAIL_AND_SENSORDATA_AND_PICTURES.getExtension().equals(fileExtension)) {
trackImporter = new KmzTrackImporter(context, -1L, file.getUri()); trackImporter = new KmzTrackImporter(context, file.getUri());
} else { } else {
Log.i(TAG, "Unsupported file format."); Log.i(TAG, "Unsupported file format.");
return false; return false;
} }
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) { try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
return trackImporter.importFile(inputStream) != -1L; return trackImporter.importFile(inputStream) != null;
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Unable to import file", e); Log.e(TAG, "Unable to import file", e);
return false; return false;
@@ -93,9 +93,9 @@ public class MarkerDetailFragment extends Fragment {
} }
}; };
public static MarkerDetailFragment newInstance(long markerId) { public static MarkerDetailFragment newInstance(Waypoint.Id waypointId) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putLong(KEY_MARKER_ID, markerId); bundle.putParcelable(KEY_MARKER_ID, waypointId);
MarkerDetailFragment fragment = new MarkerDetailFragment(); MarkerDetailFragment fragment = new MarkerDetailFragment();
fragment.setArguments(bundle); fragment.setArguments(bundle);
@@ -106,8 +106,8 @@ public class MarkerDetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
long markerId = getArguments().getLong(KEY_MARKER_ID); Waypoint.Id waypointId = getArguments().getParcelable(KEY_MARKER_ID);
if (markerId == -1L) { if (waypointId == null) {
Log.d(TAG, "invalid marker id"); Log.d(TAG, "invalid marker id");
getParentFragmentManager().popBackStack(); getParentFragmentManager().popBackStack();
return; return;
@@ -182,7 +182,7 @@ public class MarkerDetailFragment extends Fragment {
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
long markerId = getArguments().getLong(KEY_MARKER_ID); Waypoint.Id waypointId = getArguments().getParcelable(KEY_MARKER_ID); //TODO Should only happen in onCreate?
FragmentActivity fragmentActivity = getActivity(); FragmentActivity fragmentActivity = getActivity();
Intent intent; Intent intent;
switch (item.getItemId()) { switch (item.getItemId()) {
@@ -191,7 +191,7 @@ public class MarkerDetailFragment extends Fragment {
return true; return true;
case R.id.marker_detail_edit: case R.id.marker_detail_edit:
intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class) intent = IntentUtils.newIntent(fragmentActivity, MarkerEditActivity.class)
.putExtra(MarkerEditActivity.EXTRA_MARKER_ID, markerId); .putExtra(MarkerEditActivity.EXTRA_MARKER_ID, waypointId);
startActivity(intent); startActivity(intent);
return true; return true;
case R.id.marker_detail_share: case R.id.marker_detail_share:
@@ -202,7 +202,7 @@ public class MarkerDetailFragment extends Fragment {
} }
return true; return true;
case R.id.marker_detail_delete: case R.id.marker_detail_delete:
DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), new long[]{markerId}); DeleteMarkerDialogFragment.showDialog(getChildFragmentManager(), waypointId);
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
@@ -216,7 +216,7 @@ public class MarkerDetailFragment extends Fragment {
*/ */
private void updateWaypoint(boolean refresh) { private void updateWaypoint(boolean refresh) {
if (refresh || waypoint == null) { if (refresh || waypoint == null) {
waypoint = contentProviderUtils.getWaypoint(getArguments().getLong(KEY_MARKER_ID)); waypoint = contentProviderUtils.getWaypoint(getArguments().getParcelable(KEY_MARKER_ID)); //TODO Should only happen in onCreate?
if (waypoint == null) { if (waypoint == null) {
Log.d(TAG, "waypoint is null"); Log.d(TAG, "waypoint is null");
getParentFragmentManager().popBackStack(); getParentFragmentManager().popBackStack();
@@ -53,8 +53,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
private TrackDataHub trackDataHub; private TrackDataHub trackDataHub;
private Handler handlerUpdateUI; private Handler handlerUpdateUI;
//TODO Initialize immediately and remove in onDestroy() private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection();
private TrackRecordingServiceConnection trackRecordingServiceConnection;
private TrackPoint lastTrackPoint = null; private TrackPoint lastTrackPoint = null;
private TrackStatistics lastTrackStatistics = null; private TrackStatistics lastTrackStatistics = null;
@@ -77,12 +76,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
} }
if (PreferencesUtils.isKey(getContext(), R.string.recording_track_id_key, key)) { if (PreferencesUtils.isKey(getContext(), R.string.recording_track_id_key, key)) {
recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(getContext()); recordingGpsAccuracy = PreferencesUtils.getRecordingGPSAccuracy(getContext());
if (PreferencesUtils.getRecordingTrackId(getContext()) != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT) { if (PreferencesUtils.getRecordingTrackId(getContext()).isValid()) {
// A recording track id has been set -> Resumes track and starts timer. // A recording track id has been set -> Resumes track and starts timer.
resumeTrackDataHub(); resumeTrackDataHub();
if (trackRecordingServiceConnection == null) {
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
}
trackRecordingServiceConnection.startConnection(getContext()); trackRecordingServiceConnection.startConnection(getContext());
handlerUpdateUI.post(updateUIeachSecond); handlerUpdateUI.post(updateUIeachSecond);
@@ -222,8 +218,6 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
super.onResume(); super.onResume();
resumeTrackDataHub(); resumeTrackDataHub();
PreferencesUtils.register(getContext(), sharedPreferenceChangeListener); PreferencesUtils.register(getContext(), sharedPreferenceChangeListener);
trackRecordingServiceConnection = new TrackRecordingServiceConnection(null);
trackRecordingServiceConnection.startConnection(getContext()); trackRecordingServiceConnection.startConnection(getContext());
handlerUpdateUI.post(updateUIeachSecond); handlerUpdateUI.post(updateUIeachSecond);
@@ -241,11 +235,8 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
if (trackRecordingServiceConnection != null) {
trackRecordingServiceConnection.unbind(getContext()); trackRecordingServiceConnection.unbind(getContext());
} }
trackRecordingServiceConnection = null;
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
@@ -296,6 +287,12 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
longitudeValue = null; longitudeValue = null;
} }
@Override
public void onDestroy() {
super.onDestroy();
trackRecordingServiceConnection = null;
}
@Override @Override
public void onTrackUpdated(final Track track) { public void onTrackUpdated(final Track track) {
if (isResumed()) { if (isResumed()) {
@@ -333,9 +330,10 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
lastTrackPoint = null; lastTrackPoint = null;
} }
if (lastTrackPoint != null) { TrackPoint trackPoint = lastTrackPoint; //NOTE: There seems to be a race condition; just fix the symptom for now.
boolean hasFix = !LocationUtils.isLocationOld(lastTrackPoint.getLocation()); if (trackPoint != null) {
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(lastTrackPoint, recordingGpsAccuracy); boolean hasFix = !LocationUtils.isLocationOld(trackPoint.getLocation());
boolean hasGoodFix = TrackPointUtils.fulfillsAccuracy(trackPoint, recordingGpsAccuracy);
if (!hasFix || !hasGoodFix) { if (!hasFix || !hasGoodFix) {
lastTrackPoint = null; lastTrackPoint = null;
@@ -5,7 +5,6 @@ import android.content.Context;
import java.util.Locale; import java.util.Locale;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter; import de.dennisguse.opentracks.io.file.exporter.FileTrackExporter;
import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter; import de.dennisguse.opentracks.io.file.exporter.GpxTrackWriter;
@@ -21,8 +20,8 @@ public enum TrackFileFormat {
KML_ONLY_TRACK { KML_ONLY_TRACK {
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, false, false, false); return new KmlTrackWriter(context, false, false, false);
} }
@Override @Override
@@ -36,8 +35,8 @@ public enum TrackFileFormat {
}, },
KML_WITH_TRACKDETAIL { KML_WITH_TRACKDETAIL {
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, true, false, false); return new KmlTrackWriter(context, true, false, false);
} }
@Override @Override
@@ -51,8 +50,8 @@ public enum TrackFileFormat {
}, },
KML_WITH_TRACKDETAIL_AND_SENSORDATA { KML_WITH_TRACKDETAIL_AND_SENSORDATA {
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, true, true, false); return new KmlTrackWriter(context, true, true, false);
} }
@Override @Override
@@ -69,12 +68,12 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false; private static final boolean exportPhotos = false;
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, false, false, exportPhotos); return new KmlTrackWriter(context, false, false, exportPhotos);
} }
public TrackExporter newTrackExporter(Context context, Track[] tracks) { public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos); return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
} }
@Override @Override
@@ -96,12 +95,12 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false; private static final boolean exportPhotos = false;
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, true, false, exportPhotos); return new KmlTrackWriter(context, true, false, exportPhotos);
} }
public TrackExporter newTrackExporter(Context context, Track[] tracks) { public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos); return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
} }
@Override @Override
@@ -124,8 +123,8 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = false; private static final boolean exportPhotos = false;
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, true, true, exportPhotos); return new KmlTrackWriter(context, true, true, exportPhotos);
} }
@Override @Override
@@ -133,8 +132,8 @@ public enum TrackFileFormat {
return MIME_KMZ; return MIME_KMZ;
} }
public TrackExporter newTrackExporter(Context context, Track[] tracks) { public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, this.newTrackWriter(context, tracks.length > 1), tracks, exportPhotos); return newKmzTrackExporter(context, this.newTrackWriter(context), exportPhotos);
} }
public String getExtension() { public String getExtension() {
@@ -153,8 +152,8 @@ public enum TrackFileFormat {
private static final boolean exportPhotos = true; private static final boolean exportPhotos = true;
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new KmlTrackWriter(context, multiple, true, true, exportPhotos); return new KmlTrackWriter(context, true, true, exportPhotos);
} }
@Override @Override
@@ -162,8 +161,8 @@ public enum TrackFileFormat {
return MIME_KMZ; return MIME_KMZ;
} }
public TrackExporter newTrackExporter(Context context, Track[] tracks) { public TrackExporter newTrackExporter(Context context) {
return newKmzTrackExporter(context, newTrackWriter(context, tracks.length > 1), tracks, exportPhotos); return newKmzTrackExporter(context, newTrackWriter(context), exportPhotos);
} }
public String getExtension() { public String getExtension() {
@@ -178,7 +177,7 @@ public enum TrackFileFormat {
}, },
GPX { GPX {
@Override @Override
public TrackWriter newTrackWriter(Context context, boolean multiple) { public TrackWriter newTrackWriter(Context context) {
return new GpxTrackWriter(context.getString(R.string.app_name)); return new GpxTrackWriter(context.getString(R.string.app_name));
} }
@@ -196,12 +195,12 @@ public enum TrackFileFormat {
private static final String MIME_KML = "application/vnd.google-earth.kml+xml"; private static final String MIME_KML = "application/vnd.google-earth.kml+xml";
private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, Track[] tracks, boolean exportPhotos) { private static TrackExporter newKmzTrackExporter(Context context, TrackWriter trackWriter, boolean exportPhotos) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter, tracks); FileTrackExporter fileTrackExporter = new FileTrackExporter(contentProviderUtils, trackWriter);
return new KmzTrackExporter(contentProviderUtils, fileTrackExporter, tracks, exportPhotos); return new KmzTrackExporter(context, contentProviderUtils, fileTrackExporter, exportPhotos);
} }
/** /**
@@ -209,19 +208,18 @@ public enum TrackFileFormat {
*/ */
public abstract String getMimeType(); public abstract String getMimeType();
public TrackExporter newTrackExporter(Context context, Track[] tracks) { public TrackExporter newTrackExporter(Context context) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
TrackWriter trackWriter = newTrackWriter(context, tracks.length > 1); TrackWriter trackWriter = newTrackWriter(context);
return new FileTrackExporter(contentProviderUtils, trackWriter, tracks); return new FileTrackExporter(contentProviderUtils, trackWriter);
} }
/** /**
* Creates a new track writer for the format. * Creates a new track writer for the format.
* *
* @param context the context * @param context the context
* @param multiple true for writing multiple tracks
*/ */
public abstract TrackWriter newTrackWriter(Context context, boolean multiple); public abstract TrackWriter newTrackWriter(Context context);
/** /**
* Returns the file extension for each format. * Returns the file extension for each format.
@@ -16,7 +16,6 @@
package de.dennisguse.opentracks.io.file.exporter; package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.util.Log; import android.util.Log;
@@ -42,7 +41,6 @@ public class FileTrackExporter implements TrackExporter {
private static final String TAG = FileTrackExporter.class.getSimpleName(); private static final String TAG = FileTrackExporter.class.getSimpleName();
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private final Track[] tracks;
private final TrackWriter trackWriter; private final TrackWriter trackWriter;
/** /**
@@ -50,29 +48,37 @@ public class FileTrackExporter implements TrackExporter {
* *
* @param contentProviderUtils the content provider utils * @param contentProviderUtils the content provider utils
* @param trackWriter the track writer * @param trackWriter the track writer
* @param tracks the tracks
*/ */
public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter, Track[] tracks) { public FileTrackExporter(ContentProviderUtils contentProviderUtils, TrackWriter trackWriter) {
this.contentProviderUtils = contentProviderUtils; this.contentProviderUtils = contentProviderUtils;
this.tracks = tracks;
this.trackWriter = trackWriter; this.trackWriter = trackWriter;
} }
@Override @Override
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) { public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
return writeTrack(new Track[]{track}, outputStream);
}
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
try { try {
trackWriter.prepare(outputStream); trackWriter.prepare(outputStream);
trackWriter.writeHeader(tracks); trackWriter.writeHeader(tracks);
for (Track track1 : tracks) { for (Track track1 : tracks) {
writeWaypoints(track1); writeWaypoints(track1);
} }
trackWriter.writeBeginTracks(); boolean hasMultipleTracks = tracks.length > 1;
if (hasMultipleTracks) {
trackWriter.writeMultiTrackBegin();
}
long startTime = tracks[0].getTrackStatistics().getStartTime_ms(); long startTime = tracks[0].getTrackStatistics().getStartTime_ms();
for (Track track : tracks) { for (Track track : tracks) {
long offset = track.getTrackStatistics().getStartTime_ms() - startTime; long offset = track.getTrackStatistics().getStartTime_ms() - startTime;
writeLocations(track, offset); writeLocations(track, offset);
} }
trackWriter.writeEndTracks(); if (hasMultipleTracks) {
trackWriter.writeMultiTrackEnd();
}
trackWriter.writeFooter(); trackWriter.writeFooter();
trackWriter.close(); trackWriter.close();
@@ -92,7 +98,7 @@ public class FileTrackExporter implements TrackExporter {
* 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. * 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 hasWaypoints = false; boolean hasWaypoints = false;
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) { try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), null, ContentProviderUtils.MAX_LOADED_WAYPOINTS_POINTS)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) { for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
@@ -121,7 +127,6 @@ public class FileTrackExporter implements TrackExporter {
boolean wroteTrack = false; boolean wroteTrack = false;
boolean wroteSegment = false; boolean wroteSegment = false;
boolean isLastLocationValid = false; boolean isLastLocationValid = false;
int locationNumber = 0;
TrackPoint lastTrackPoint = null; TrackPoint lastTrackPoint = null;
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) { try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), -1L, false)) {
@@ -133,7 +138,6 @@ public class FileTrackExporter implements TrackExporter {
TrackPoint trackPoint = trackPointIterator.next(); TrackPoint trackPoint = trackPointIterator.next();
setLocationTime(trackPoint, offset); setLocationTime(trackPoint, offset);
locationNumber++;
boolean isLocationValid = LocationUtils.isValidLocation(trackPoint.getLocation()); boolean isLocationValid = LocationUtils.isValidLocation(trackPoint.getLocation());
boolean isSegmentValid = isLocationValid && isLastLocationValid; boolean isSegmentValid = isLocationValid && isLastLocationValid;
@@ -33,6 +33,7 @@ import de.dennisguse.opentracks.util.StringUtils;
* *
* @author Sandor Dornbush * @author Sandor Dornbush
*/ */
//TODO Export waypoints
public class GpxTrackWriter implements TrackWriter { public class GpxTrackWriter implements TrackWriter {
private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US);
@@ -93,10 +94,15 @@ public class GpxTrackWriter implements TrackWriter {
printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\""); printWriter.println("xmlns=\"http://www.topografix.com/GPX/1/1\"");
printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\""); printWriter.println("xmlns:topografix=\"http://www.topografix.com/GPX/Private/TopoGrafix/0/1\"");
printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""); printWriter.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\"");
printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\""); printWriter.println("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemes/TrackPointExtension/v2\"");
printWriter.println("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" printWriter.println("xsi:schemaLocation=" +
"\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
+ " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd" + " http://www.topografix.com/GPX/Private/TopoGrafix/0/1 http://www.topografix.com/GPX/Private/TopoGrafix/0/1/topografix.xsd"
+ " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd\">"); + " http://www.garmin.com/xmlschemas/TrackPointExtension/v2 https://www8.garmin.com/xmlschemas/TrackPointExtensionv2.xsd"
+ " http://opentracksapp.com/xmlschemas/v1 http://opentracksapp.com/xmlschemas/OpenTracks_v1.xsd\">");
printWriter.println("<metadata>"); printWriter.println("<metadata>");
Track track = tracks[0]; Track track = tracks[0];
@@ -127,7 +133,6 @@ public class GpxTrackWriter implements TrackWriter {
public void writeWaypoint(Waypoint waypoint) { public void writeWaypoint(Waypoint waypoint) {
if (printWriter != null) { if (printWriter != null) {
Location location = waypoint.getLocation(); Location location = waypoint.getLocation();
if (location != null) {
printWriter.println("<wpt " + formatLocation(location) + ">"); printWriter.println("<wpt " + formatLocation(location) + ">");
if (location.hasAltitude()) { if (location.hasAltitude()) {
printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>"); printWriter.println("<ele>" + ELEVATION_FORMAT.format(location.getAltitude()) + "</ele>");
@@ -139,15 +144,14 @@ public class GpxTrackWriter implements TrackWriter {
printWriter.println("</wpt>"); printWriter.println("</wpt>");
} }
} }
}
@Override @Override
public void writeBeginTracks() { public void writeMultiTrackBegin() {
// Do nothing // Do nothing
} }
@Override @Override
public void writeEndTracks() { public void writeMultiTrackEnd() {
// Do nothing // Do nothing
} }
@@ -158,7 +162,11 @@ public class GpxTrackWriter implements TrackWriter {
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>"); printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>"); printWriter.println("<desc>" + StringUtils.formatCData(track.getDescription()) + "</desc>");
printWriter.println("<type>" + StringUtils.formatCData(track.getCategory()) + "</type>"); printWriter.println("<type>" + StringUtils.formatCData(track.getCategory()) + "</type>");
printWriter.println("<extensions><topografix:color>c0c0c0</topografix:color></extensions>");
printWriter.println("<extensions>");
printWriter.println("<topografix:color>c0c0c0</topografix:color>");
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
printWriter.println("</extensions>");
} }
} }
@@ -57,7 +57,6 @@ public class KmlTrackWriter implements TrackWriter {
private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png"; private static final String TRACK_ICON = "http://earth.google.com/images/kml-icons/track-directional/track-0.png";
private final Context context; private final Context context;
private final boolean hasMultipleTracks;
private final boolean exportPhotos; private final boolean exportPhotos;
private final boolean exportTrackDetail; private final boolean exportTrackDetail;
private final boolean exportSensorData; private final boolean exportSensorData;
@@ -74,14 +73,12 @@ public class KmlTrackWriter implements TrackWriter {
/** /**
* @param context the context * @param context the context
* @param hasMultipleTracks should encode multiple tracks into one file?
* @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)? * @param exportTrackDetail should detailed information about the track be exported (e.g., title, description, waypoints, timing)?
* @param exportSensorData should {@link TrackPoint}'s sensor data be exported? * @param exportSensorData should {@link TrackPoint}'s sensor data be exported?
* @param exportPhotos should pictures be exported (if true: exports to KMZ)? * @param exportPhotos should pictures be exported (if true: exports to KMZ)?
*/ */
public KmlTrackWriter(Context context, boolean hasMultipleTracks, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) { public KmlTrackWriter(Context context, boolean exportTrackDetail, boolean exportSensorData, boolean exportPhotos) {
this.context = context; this.context = context;
this.hasMultipleTracks = hasMultipleTracks;
this.exportTrackDetail = exportTrackDetail; this.exportTrackDetail = exportTrackDetail;
this.exportSensorData = exportSensorData; this.exportSensorData = exportSensorData;
this.exportPhotos = exportPhotos; this.exportPhotos = exportPhotos;
@@ -108,7 +105,10 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\""); printWriter.println("<kml xmlns=\"http://www.opengis.net/kml/2.2\"");
printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\""); printWriter.println("xmlns:gx=\"http://www.google.com/kml/ext/2.2\"");
printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\">"); printWriter.println("xmlns:atom=\"http://www.w3.org/2005/Atom\"");
printWriter.println("xmlns:opentracks=\"http://opentracksapp.com/xmlschemas/v1\">");
//TODO ADD xsi:schemaLocation here!
printWriter.println("<Document>"); printWriter.println("<Document>");
printWriter.println("<open>1</open>"); printWriter.println("<open>1</open>");
printWriter.println("<visibility>1</visibility>"); printWriter.println("<visibility>1</visibility>");
@@ -116,7 +116,7 @@ public class KmlTrackWriter implements TrackWriter {
if (exportTrackDetail) { if (exportTrackDetail) {
Track track = tracks[0]; Track track = tracks[0];
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>"); printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<atom:author><atom:name>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:name></atom:author>"); printWriter.println("<atom:generator>" + StringUtils.formatCData(context.getString(R.string.app_name)) + "</atom:generator>");
} }
writeTrackStyle(); writeTrackStyle();
@@ -175,18 +175,16 @@ public class KmlTrackWriter implements TrackWriter {
} }
} }
@Override public void writeMultiTrackBegin() {
public void writeBeginTracks() { if (printWriter != null) {
if (printWriter != null && hasMultipleTracks) {
printWriter.println("<Folder id=tour>"); printWriter.println("<Folder id=tour>");
printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>"); printWriter.println("<name>" + context.getString(R.string.generic_tracks) + "</name>");
printWriter.println("<open>1</open>"); printWriter.println("<open>1</open>");
} }
} }
@Override public void writeMultiTrackEnd() {
public void writeEndTracks() { if (printWriter != null) {
if (printWriter != null && hasMultipleTracks) {
printWriter.println("</Folder>"); printWriter.println("</Folder>");
} }
} }
@@ -204,6 +202,7 @@ public class KmlTrackWriter implements TrackWriter {
printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>"); printWriter.println("<name>" + StringUtils.formatCData(track.getName()) + "</name>");
printWriter.println("<description>" + StringUtils.formatCData(track.getDescription()) + "</description>"); printWriter.println("<description>" + StringUtils.formatCData(track.getDescription()) + "</description>");
printWriter.println("<icon>" + StringUtils.formatCData(track.getIcon()) + "</icon>"); printWriter.println("<icon>" + StringUtils.formatCData(track.getIcon()) + "</icon>");
printWriter.println("<opentracks:trackid>" + track.getUuid() + "</opentracks:trackid>");
} }
printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>"); printWriter.println("<styleUrl>#" + TRACK_STYLE + "</styleUrl>");
@@ -389,7 +388,7 @@ public class KmlTrackWriter implements TrackWriter {
* @param trackId the track id containing the location * @param trackId the track id containing the location
* @param location the location * @param location the location
*/ */
private float getHeading(long trackId, Location location) { private float getHeading(Track.Id trackId, Location location) {
long trackPointId = contentProviderUtils.getTrackPointId(trackId, location); long trackPointId = contentProviderUtils.getTrackPointId(trackId, location);
if (trackPointId == -1L) { if (trackPointId == -1L) {
return location.getBearing(); return location.getBearing();
@@ -42,6 +42,7 @@ import de.dennisguse.opentracks.util.FileUtils;
* @author Jimmy Shih * @author Jimmy Shih
*/ */
public class KmzTrackExporter implements TrackExporter { public class KmzTrackExporter implements TrackExporter {
private static final String KMZ_IMAGES_DIR = "images"; private static final String KMZ_IMAGES_DIR = "images";
public static final String KMZ_KML_FILE = "doc.kml"; public static final String KMZ_KML_FILE = "doc.kml";
@@ -49,27 +50,32 @@ public class KmzTrackExporter implements TrackExporter {
private static final int BUFFER_SIZE = 4096; private static final int BUFFER_SIZE = 4096;
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private final TrackExporter fileTrackExporter; private final FileTrackExporter fileTrackExporter;
private final Track[] tracks;
private final boolean exportPhotos; private final boolean exportPhotos;
private Context context;
/** /**
* Constructor. * Constructor.
* *
* @param context the context
* @param contentProviderUtils the content provider utils * @param contentProviderUtils the content provider utils
* @param trackExporter the file track exporter * @param trackExporter the file track exporter
* @param tracks the tracks to export
*/ */
public KmzTrackExporter(ContentProviderUtils contentProviderUtils, TrackExporter trackExporter, Track[] tracks, boolean exportPhotos) { public KmzTrackExporter(Context context, ContentProviderUtils contentProviderUtils, FileTrackExporter trackExporter, boolean exportPhotos) {
this.context = context;
this.contentProviderUtils = contentProviderUtils; this.contentProviderUtils = contentProviderUtils;
this.fileTrackExporter = trackExporter; this.fileTrackExporter = trackExporter;
this.tracks = tracks;
this.exportPhotos = exportPhotos; this.exportPhotos = exportPhotos;
} }
@Override @Override
public boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream) { public boolean writeTrack(Track track, @NonNull OutputStream outputStream) {
return writeTrack(new Track[]{track}, outputStream);
}
@Override
public boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream) {
ZipOutputStream zipOutputStream = null; ZipOutputStream zipOutputStream = null;
try { try {
zipOutputStream = new ZipOutputStream(outputStream); zipOutputStream = new ZipOutputStream(outputStream);
@@ -78,7 +84,7 @@ public class KmzTrackExporter implements TrackExporter {
ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE); ZipEntry zipEntry = new ZipEntry(KMZ_KML_FILE);
zipOutputStream.putNextEntry(zipEntry); zipOutputStream.putNextEntry(zipEntry);
boolean success = fileTrackExporter.writeTrack(context, zipOutputStream); boolean success = fileTrackExporter.writeTrack(tracks, zipOutputStream);
zipOutputStream.closeEntry(); zipOutputStream.closeEntry();
if (!success) { if (!success) {
Log.e(TAG, "Unable to write kml in kmz"); Log.e(TAG, "Unable to write kml in kmz");
@@ -86,7 +92,7 @@ public class KmzTrackExporter implements TrackExporter {
} }
// Add photos // Add photos
if (exportPhotos) addImages(context, zipOutputStream); if (exportPhotos) addImages(context, tracks ,zipOutputStream);
return true; return true;
} catch (InterruptedException | IOException e) { } catch (InterruptedException | IOException e) {
Log.e(TAG, "Unable to write track", e); Log.e(TAG, "Unable to write track", e);
@@ -102,9 +108,9 @@ public class KmzTrackExporter implements TrackExporter {
} }
} }
private void addImages(Context context, ZipOutputStream zipOutputStream) throws InterruptedException, IOException { private void addImages(Context context, Track[] tracks, ZipOutputStream zipOutputStream) throws InterruptedException, IOException {
for (Track track : tracks) { for (Track track : tracks) {
try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), -1L, -1)) { try (Cursor cursor = contentProviderUtils.getWaypointCursor(track.getId(), null, -1)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) { for (int i = 0; i < cursor.getCount(); i++) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
@@ -16,12 +16,12 @@
package de.dennisguse.opentracks.io.file.exporter; package de.dennisguse.opentracks.io.file.exporter;
import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.io.OutputStream; import java.io.OutputStream;
import de.dennisguse.opentracks.content.data.Track;
/** /**
* Track exporting for exporting track to an {@link OutputStream}. * Track exporting for exporting track to an {@link OutputStream}.
* *
@@ -29,12 +29,7 @@ import java.io.OutputStream;
*/ */
public interface TrackExporter { public interface TrackExporter {
/** boolean writeTrack(Track[] tracks, @NonNull OutputStream outputStream);
* Write track to an output stream.
* Depending on the implementation a context might be required. boolean writeTrack(Track track, @NonNull OutputStream outputStream);
*
* @param context the context
* @param outputStream the output stream
*/
boolean writeTrack(@NonNull Context context, @NonNull OutputStream outputStream);
} }
@@ -32,7 +32,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* For each waypoint: * For each waypoint:
* {@link #writeWaypoint(Waypoint)} * {@link #writeWaypoint(Waypoint)}
* {@link #writeEndWaypoints()} * {@link #writeEndWaypoints()}
* {@link #writeBeginTracks()} * {@link #writeMultiTrackBegin()}
* For each track: * For each track:
* {@link #writeBeginTrack(Track, TrackPoint)} * {@link #writeBeginTrack(Track, TrackPoint)}
* For each segment: * For each segment:
@@ -41,7 +41,7 @@ import de.dennisguse.opentracks.content.data.Waypoint;
* {@link #writeTrackPoint(TrackPoint)} * {@link #writeTrackPoint(TrackPoint)}
* {@link #writeCloseSegment()} * {@link #writeCloseSegment()}
* {@link #writeEndTrack(Track, TrackPoint)} * {@link #writeEndTrack(Track, TrackPoint)}
* {@link #writeEndTracks()} * {@link #writeMultiTrackEnd()}
* {@link #writeFooter()} * {@link #writeFooter()}
* {@link #close()} * {@link #close()}
* </pre> * </pre>
@@ -96,12 +96,12 @@ public interface TrackWriter {
/** /**
* Writes the beginning of the tracks. * Writes the beginning of the tracks.
*/ */
void writeBeginTracks(); void writeMultiTrackBegin();
/** /**
* Writes the end of the tracks, * Writes the end of the tracks,
*/ */
void writeEndTracks(); void writeMultiTrackEnd();
/** /**
* Writes the beginning of a track. * Writes the beginning of a track.
@@ -17,6 +17,7 @@
package de.dennisguse.opentracks.io.file.importer; package de.dennisguse.opentracks.io.file.importer;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
@@ -62,12 +63,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The maximum number of buffered locations for bulk-insertion // The maximum number of buffered locations for bulk-insertion
private static final int MAX_BUFFERED_LOCATIONS = 512; private static final int MAX_BUFFERED_LOCATIONS = 512;
private final Context context; private final Context context;
private final long importTrackId;
private final ContentProviderUtils contentProviderUtils; private final ContentProviderUtils contentProviderUtils;
private final int recordingDistanceInterval; private final int recordingDistanceInterval;
private final List<Long> trackIds = new ArrayList<>(); private Track.Id importTrackId;
private final List<Track.Id> trackIds = new ArrayList<>();
private final List<Waypoint> waypoints = new ArrayList<>(); private final List<Waypoint> waypoints = new ArrayList<>();
// The current element content // The current element content
@@ -96,19 +98,17 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The SAX locator to get the current line information // The SAX locator to get the current line information
private Locator locator; private Locator locator;
/** AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
* @param context the context
* @param importTrackId the track id to import to. -1L to import to a new track.
*/
@Deprecated
// Remove importTrackId
AbstractFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) {
this.context = context; this.context = context;
this.importTrackId = importTrackId;
this.contentProviderUtils = contentProviderUtils; this.contentProviderUtils = contentProviderUtils;
this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context); this.recordingDistanceInterval = PreferencesUtils.getRecordingDistanceInterval(context);
} }
AbstractFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils, Track.Id importTrackId) {
this(context, contentProviderUtils);
this.importTrackId = importTrackId;
}
@Override @Override
public void setDocumentLocator(Locator locator) { public void setDocumentLocator(Locator locator) {
this.locator = locator; this.locator = locator;
@@ -126,7 +126,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
} }
@Override @Override
public long importFile(InputStream inputStream) { public Track.Id importFile(InputStream inputStream) {
try { try {
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
@@ -134,15 +134,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
saxParser.parse(inputStream, this); saxParser.parse(inputStream, this);
Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms"); Log.d(TAG, "Total import time: " + (System.currentTimeMillis() - start) + "ms");
if (trackIds.size() != 1) { if (trackIds.size() != 1) {
Log.d(TAG, trackIds.size() + " tracks imported"); throw new MultiTracksImportException();
cleanImport();
return -1L;
} }
return trackIds.get(0); return trackIds.get(0);
} catch (IOException | SAXException | ParserConfigurationException e) { } catch (MultiTracksImportException | IOException | SAXException | SQLiteConstraintException | ParserConfigurationException e) {
Log.e(TAG, "Unable to import file", e); Log.e(TAG, "Unable to import file", e);
cleanImport(); cleanImport();
return -1L; return null;
} }
} }
@@ -155,7 +153,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
if (size == 0) { if (size == 0) {
return; return;
} }
long trackId = trackIds.get(size - 1); Track.Id trackId = trackIds.get(size - 1);
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
if (track == null) { if (track == null) {
return; return;
@@ -227,16 +225,16 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
*/ */
protected void onTrackStart() throws SAXException { protected void onTrackStart() throws SAXException {
trackData = new TrackData(); trackData = new TrackData();
long trackId; Track.Id trackId;
if (importTrackId == -1L) { if (importTrackId == null) {
Uri uri = contentProviderUtils.insertTrack(trackData.track); Uri uri = contentProviderUtils.insertTrack(trackData.track);
trackId = Long.parseLong(uri.getLastPathSegment()); trackId = new Track.Id(Long.parseLong(uri.getLastPathSegment()));
} else { } else {
if (trackIds.size() > 0) { if (trackIds.size() > 0) {
throw new SAXException(createErrorMessage("Cannot import more than one track to an existing track " + importTrackId)); throw new SAXException(createErrorMessage("Cannot import more than one track to an existing track " + importTrackId));
} }
trackId = importTrackId; trackId = importTrackId;
contentProviderUtils.clearTrack(context, trackId); contentProviderUtils.clearTrack(trackId);
} }
trackIds.add(trackId); trackIds.add(trackId);
trackData.track.setId(trackId); trackData.track.setId(trackId);
@@ -275,7 +273,18 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
trackData.trackStatisticsUpdater.updateTime(trackData.importTime); trackData.trackStatisticsUpdater.updateTime(trackData.importTime);
} }
trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics()); trackData.track.setTrackStatistics(trackData.trackStatisticsUpdater.getTrackStatistics());
try {
contentProviderUtils.updateTrack(trackData.track); contentProviderUtils.updateTrack(trackData.track);
} catch (SQLiteConstraintException e) {
if (PreferencesUtils.getPreventReimportTracks(context)) {
throw e;
}
//TODO This is a workaround until we have proper UI.
trackData.track.setUuid(UUID.randomUUID());
contentProviderUtils.updateTrack(trackData.track);
}
} }
/** /**
@@ -390,7 +399,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* @param externalPhotoUrl the file name * @param externalPhotoUrl the file name
*/ */
protected String getInternalPhotoUrl(String externalPhotoUrl) { protected String getInternalPhotoUrl(String externalPhotoUrl) {
if (importTrackId == -1L) { if (importTrackId == null) {
Log.e(TAG, "Track id is invalid."); Log.e(TAG, "Track id is invalid.");
return null; return null;
} }
@@ -508,7 +517,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
* Cleans up import. * Cleans up import.
*/ */
private void cleanImport() { private void cleanImport() {
for (long trackId : trackIds) { for (Track.Id trackId : trackIds) {
contentProviderUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId);
} }
} }
@@ -544,4 +553,9 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
// The number of buffered locations // The number of buffered locations
int numBufferedTrackPoints = 0; int numBufferedTrackPoints = 0;
} }
@Deprecated
//TODO According to the reader AND writer, we support multiple tracks per file; we should implement it here also.
private static class MultiTracksImportException extends RuntimeException {
}
} }
@@ -45,6 +45,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_TRACK_SEGMENT = "trkseg"; private static final String TAG_TRACK_SEGMENT = "trkseg";
private static final String TAG_TYPE = "type"; private static final String TAG_TYPE = "type";
private static final String TAG_WAYPOINT = "wpt"; private static final String TAG_WAYPOINT = "wpt";
private static final String TAG_ID = "opentracks:trackid";
private static final String ATTRIBUTE_LAT = "lat"; private static final String ATTRIBUTE_LAT = "lat";
private static final String ATTRIBUTE_LON = "lon"; private static final String ATTRIBUTE_LON = "lon";
@@ -64,7 +65,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
@VisibleForTesting @VisibleForTesting
GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) { GpxFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
super(context, -1L, contentProviderUtils); super(context, contentProviderUtils);
} }
@Override @Override
@@ -144,6 +145,10 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter {
cadence = content.trim(); cadence = content.trim();
} }
break; break;
case TAG_ID:
if (content != null) {
uuid = content.trim();
}
} }
// Reset element content // Reset element content
@@ -57,6 +57,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private static final String TAG_STYLE_URL = "styleUrl"; private static final String TAG_STYLE_URL = "styleUrl";
private static final String TAG_VALUE = "value"; private static final String TAG_VALUE = "value";
private static final String TAG_WHEN = "when"; private static final String TAG_WHEN = "when";
private static final String TAG_UUID = "opentracks:trackid";
private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_NAME = "name";
@@ -68,23 +69,17 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
private ArrayList<Float> heartRateList; private ArrayList<Float> heartRateList;
private ArrayList<Float> powerList; private ArrayList<Float> powerList;
/** public KmlFileTrackImporter(Context context) {
* @param context the context this(context, new ContentProviderUtils(context));
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
*/
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else
public KmlFileTrackImporter(Context context, long importTrackId) {
this(context, importTrackId, new ContentProviderUtils(context));
} }
@VisibleForTesting @VisibleForTesting
KmlFileTrackImporter(Context context, long importTrackId, ContentProviderUtils contentProviderUtils) { KmlFileTrackImporter(Context context, ContentProviderUtils contentProviderUtils) {
super(context, importTrackId, contentProviderUtils); super(context, contentProviderUtils);
} }
@Override @Override
public void startElement(String uri, String localName, String tag, Attributes attributes) public void startElement(String uri, String localName, String tag, Attributes attributes) throws SAXException {
throws SAXException {
switch (tag) { switch (tag) {
case TAG_PLACEMARK: case TAG_PLACEMARK:
case TAG_PHOTO_OVERLAY: case TAG_PHOTO_OVERLAY:
@@ -109,50 +104,70 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
@Override @Override
public void endElement(String uri, String localName, String tag) throws SAXException { public void endElement(String uri, String localName, String tag) throws SAXException {
//TODO Check if order is relevant (uses localname and tag); and convert to switch statement switch (tag) {
if (tag.equals(TAG_KML)) { case TAG_KML:
onFileEnd(); onFileEnd();
} else if (tag.equals(TAG_PLACEMARK) || tag.equals(TAG_PHOTO_OVERLAY)) { break;
case TAG_PLACEMARK:
case TAG_PHOTO_OVERLAY:
// Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track. // Note that a track is contained in a Placemark, calling onWaypointend is save since waypointType is not set for a track.
onWaypointEnd(); onWaypointEnd();
} else if (localName.equals(TAG_COORDINATES)) { break;
case TAG_COORDINATES:
onWaypointLocationEnd(); onWaypointLocationEnd();
} else if (tag.equals(TAG_GX_MULTI_TRACK)) { break;
case TAG_GX_MULTI_TRACK:
onTrackEnd(); onTrackEnd();
} else if (tag.equals(TAG_GX_TRACK)) { break;
case TAG_GX_TRACK:
onTrackSegmentEnd(); onTrackSegmentEnd();
} else if (tag.equals(TAG_GX_COORD)) { break;
case TAG_GX_COORD:
onTrackPointEnd(); onTrackPointEnd();
} else if (tag.equals(TAG_GX_VALUE)) { break;
case TAG_GX_VALUE:
onExtendedDataValueEnd(); onExtendedDataValueEnd();
} else if (tag.equals(TAG_NAME)) { break;
case TAG_NAME:
if (content != null) { if (content != null) {
name = content.trim(); name = content.trim();
} }
} else if (localName.equals(TAG_DESCRIPTION)) { break;
case TAG_UUID:
if (content != null) {
uuid = content.trim();
}
break;
case TAG_DESCRIPTION:
if (content != null) { if (content != null) {
description = content.trim(); description = content.trim();
} }
} else if (localName.equals(TAG_ICON)) { break;
case TAG_ICON:
if (content != null) { if (content != null) {
icon = content.trim(); icon = content.trim();
} }
} else if (localName.equals(TAG_VALUE)) { break;
case TAG_VALUE:
if (content != null) { if (content != null) {
category = content.trim(); category = content.trim();
} }
} else if (localName.equals(TAG_WHEN)) { break;
case TAG_WHEN:
if (content != null) { if (content != null) {
time = content.trim(); time = content.trim();
} }
} else if (localName.equals(TAG_STYLE_URL)) { break;
case TAG_STYLE_URL:
if (content != null) { if (content != null) {
waypointType = content.trim(); waypointType = content.trim();
} }
} else if (localName.equals(TAG_HREF)) { break;
case TAG_HREF:
if (content != null) { if (content != null) {
photoUrl = content.trim(); photoUrl = content.trim();
} }
break;
} }
// Reset element content // Reset element content
@@ -32,6 +32,7 @@ import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.Waypoint; import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter; import de.dennisguse.opentracks.io.file.exporter.KmzTrackExporter;
@@ -52,34 +53,31 @@ public class KmzTrackImporter implements TrackImporter {
private static final int BUFFER_SIZE = 4096; private static final int BUFFER_SIZE = 4096;
private final Context context; private final Context context;
private final long importTrackId; private Track.Id importTrackId; //TODO needed?
private final Uri uriKmzFile; private final Uri uriKmzFile;
/** /**
* @param context the context * @param context the context
* @param importTrackId track id to import to. This should not be -1L so that images in the kmz file can be imported.
* @param uriFile URI of the kmz file. * @param uriFile URI of the kmz file.
*/ */
@Deprecated //TODO Do not pass importTrackId here; get it from somewhere else public KmzTrackImporter(Context context, Uri uriFile) {
public KmzTrackImporter(Context context, long importTrackId, Uri uriFile) {
this.context = context; this.context = context;
this.importTrackId = importTrackId;
this.uriKmzFile = uriFile; this.uriKmzFile = uriFile;
} }
@Override @Override
public long importFile(InputStream inputStream) { public Track.Id importFile(InputStream inputStream) {
long trackId; Track.Id trackId;
if (!copyKmzImages()) { if (!copyKmzImages()) {
cleanImport(context, importTrackId); cleanImport(context, importTrackId);
return -1L; return null;
} }
trackId = findAndParseKmlFile(inputStream); trackId = findAndParseKmlFile(inputStream);
if (trackId == -1L) { if (trackId == null) {
cleanImport(context, importTrackId); cleanImport(context, importTrackId);
return -1L; return null;
} }
deleteOrphanImages(context, trackId); deleteOrphanImages(context, trackId);
@@ -158,27 +156,28 @@ public class KmzTrackImporter implements TrackImporter {
/** /**
* Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it. * Finds KmzTrackExporter.KMZ_KML_FILE file inside kmz file (inputStream) and it parses it.
* TODO: May load multiple tracks, but only returns the last Track.Id.
* *
* @param inputStream kmz input stream. * @param inputStream kmz input stream.
* @return -1 if error or the id of the track otherwise. * @return null if error or the id of the track otherwise.
*/ */
private long findAndParseKmlFile(InputStream inputStream) { private Track.Id findAndParseKmlFile(InputStream inputStream) {
try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
ZipEntry zipEntry; ZipEntry zipEntry;
long trackId = -1L; Track.Id trackId = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) { while ((zipEntry = zipInputStream.getNextEntry()) != null) {
if (Thread.interrupted()) { if (Thread.interrupted()) {
Log.d(TAG, "Thread interrupted"); Log.d(TAG, "Thread interrupted");
return -1L; return null;
} }
String fileName = zipEntry.getName(); String fileName = zipEntry.getName();
if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) { if (KmzTrackExporter.KMZ_KML_FILE.equals(fileName)) {
trackId = parseKml(zipInputStream); trackId = parseKml(zipInputStream);
if (trackId == -1L) { if (trackId != null) {
Log.d(TAG, "Unable to parse kml in kmz"); Log.d(TAG, "Unable to parse kml in kmz");
return -1L; return null;
} }
} }
@@ -187,7 +186,7 @@ public class KmzTrackImporter implements TrackImporter {
return trackId; return trackId;
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Unable to import file", e); Log.e(TAG, "Unable to import file", e);
return -1L; return null;
} }
} }
@@ -197,8 +196,8 @@ public class KmzTrackImporter implements TrackImporter {
* @param context the Context object. * @param context the Context object.
* @param trackId the id of the Track. * @param trackId the id of the Track.
*/ */
private void deleteOrphanImages(Context context, long trackId) { private void deleteOrphanImages(Context context, Track.Id trackId) {
if (trackId != 1L) { if (!trackId.isValid()) {
// 1.- Gets all photo names in the waypoints of the track identified by id. // 1.- Gets all photo names in the waypoints of the track identified by id.
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
List<Waypoint> waypoints = contentProviderUtils.getWaypoints(trackId); List<Waypoint> waypoints = contentProviderUtils.getWaypoints(trackId);
@@ -230,7 +229,7 @@ public class KmzTrackImporter implements TrackImporter {
* *
* @param trackId the trackId * @param trackId the trackId
*/ */
private void cleanImport(Context context, long trackId) { private void cleanImport(Context context, Track.Id trackId) {
if (PreferencesUtils.isRecording(trackId)) { if (PreferencesUtils.isRecording(trackId)) {
ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context); ContentProviderUtils contentProviderUtils = new ContentProviderUtils(context);
contentProviderUtils.deleteTrack(context, trackId); contentProviderUtils.deleteTrack(context, trackId);
@@ -243,8 +242,8 @@ public class KmzTrackImporter implements TrackImporter {
* @param zipInputStream the zip input stream * @param zipInputStream the zip input stream
* @return the imported track id or -1L * @return the imported track id or -1L
*/ */
private long parseKml(ZipInputStream zipInputStream) throws IOException { private Track.Id parseKml(ZipInputStream zipInputStream) throws IOException {
KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context, importTrackId); KmlFileTrackImporter kmlFileTrackImporter = new KmlFileTrackImporter(context);
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) { try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getKml(zipInputStream))) {
return kmlFileTrackImporter.importFile(byteArrayInputStream); return kmlFileTrackImporter.importFile(byteArrayInputStream);
@@ -274,7 +273,7 @@ public class KmzTrackImporter implements TrackImporter {
* @param fileName the file name * @param fileName the file name
*/ */
private void readAndSaveImageFile(ZipInputStream zipInputStream, String fileName) throws IOException { private void readAndSaveImageFile(ZipInputStream zipInputStream, String fileName) throws IOException {
if (importTrackId == -1L || fileName.equals("")) { if (importTrackId == null || fileName.equals("")) {
return; return;
} }
@@ -17,6 +17,8 @@ package de.dennisguse.opentracks.io.file.importer;
import java.io.InputStream; import java.io.InputStream;
import de.dennisguse.opentracks.content.data.Track;
/** /**
* Interface for a track importer. * Interface for a track importer.
* *
@@ -28,8 +30,8 @@ public interface TrackImporter {
* Import a file. * Import a file.
* *
* @param inputStream the file's input stream * @param inputStream the file's input stream
* @return the imported track id or RECORDING_TRACK_ID_DEFAULT. * @return the imported track id or null.
*/ */
//TODO Figure out how can make the import an atomic operation (incl. database transaction rollback). //TODO Figure out how can make the import an atomic operation (incl. database transaction rollback).
long importFile(InputStream inputStream); Track.Id importFile(InputStream inputStream);
} }
@@ -33,6 +33,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.core.app.TaskStackBuilder; import androidx.core.app.TaskStackBuilder;
import java.util.ArrayList;
import java.util.List;
import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.TrackListActivity;
import de.dennisguse.opentracks.TrackRecordingActivity; import de.dennisguse.opentracks.TrackRecordingActivity;
@@ -43,6 +46,7 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
import de.dennisguse.opentracks.content.provider.CustomContentProvider; import de.dennisguse.opentracks.content.provider.CustomContentProvider;
import de.dennisguse.opentracks.content.provider.TrackPointIterator; import de.dennisguse.opentracks.content.provider.TrackPointIterator;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.handlers.HandlerServer; import de.dennisguse.opentracks.services.handlers.HandlerServer;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory; import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
@@ -72,7 +76,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private PeriodicTaskExecutor voiceExecutor; private PeriodicTaskExecutor voiceExecutor;
private TrackRecordingServiceNotificationManager notificationManager; private TrackRecordingServiceNotificationManager notificationManager;
private long recordingTrackId; private Track.Id recordingTrackId;
private boolean recordingTrackPaused; private boolean recordingTrackPaused;
private int recordingDistanceInterval; private int recordingDistanceInterval;
private int maxRecordingDistance; private int maxRecordingDistance;
@@ -120,6 +124,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
private HandlerServer handlerServer; private HandlerServer handlerServer;
private List<TrackRecordingServiceCallback> listeners = new ArrayList<>();
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@@ -132,7 +138,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
notificationManager = new TrackRecordingServiceNotificationManager(this); notificationManager = new TrackRecordingServiceNotificationManager(this);
// onSharedPreferenceChanged might not set recordingTrackId. // onSharedPreferenceChanged might not set recordingTrackId.
recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; recordingTrackId = null;
PreferencesUtils.register(this, sharedPreferenceChangeListener); PreferencesUtils.register(this, sharedPreferenceChangeListener);
sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null);
@@ -152,7 +158,16 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
@Override @Override
public void onDestroy() { public void onDestroy() {
if (listeners != null) {
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(GpsStatusValue.GPS_NONE);
}
listeners.clear();
listeners = null;
}
handlerServer.stop(this); handlerServer.stop(this);
handlerServer = null;
if (remoteSensorManager != null) { if (remoteSensorManager != null) {
remoteSensorManager.stop(); remoteSensorManager.stop();
@@ -189,7 +204,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
return recordingTrackPaused; return recordingTrackPaused;
} }
public long getRecordingTrackId() { public Track.Id getRecordingTrackId() {
return recordingTrackId; return recordingTrackId;
} }
@@ -215,9 +230,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* *
* @return the waypoint id * @return the waypoint id
*/ */
public long insertWaypoint(String name, String category, String description, String photoUrl) { public Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl) {
if (!isRecording() || isPaused()) { if (!isRecording() || isPaused()) {
return -1L; return null;
} }
if (name == null) { if (name == null) {
@@ -231,7 +246,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId); TrackPoint trackPoint = getLastValidTrackPointInCurrentSegment(recordingTrackId);
if (trackPoint == null) { if (trackPoint == null) {
Log.i(TAG, "Could not create a waypoint as trackPoint is unknown."); Log.i(TAG, "Could not create a waypoint as trackPoint is unknown.");
return -1L; return null;
} }
category = category != null ? category : ""; category = category != null ? category : "";
@@ -246,7 +261,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Insert waypoint // Insert waypoint
Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, trackPoint.getLocation(), photoUrl); Waypoint waypoint = new Waypoint(name, description, category, icon, recordingTrackId, length, duration, trackPoint.getLocation(), photoUrl);
Uri uri = contentProviderUtils.insertWaypoint(waypoint); Uri uri = contentProviderUtils.insertWaypoint(waypoint);
return ContentUris.parseId(uri); return new Waypoint.Id(ContentUris.parseId(uri));
} }
/** /**
@@ -254,10 +269,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* *
* @return the track id * @return the track id
*/ */
long startNewTrack() { Track.Id startNewTrack() {
if (isRecording()) { if (isRecording()) {
Log.d(TAG, "Ignore startNewTrack. Already recording."); Log.d(TAG, "Ignore startNewTrack. Already recording.");
return -1L; return null;
} }
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
trackStatisticsUpdater = new TrackStatisticsUpdater(now); trackStatisticsUpdater = new TrackStatisticsUpdater(now);
@@ -265,7 +280,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
// Insert a track // Insert a track
Track track = new Track(); Track track = new Track();
Uri uri = contentProviderUtils.insertTrack(track); Uri uri = contentProviderUtils.insertTrack(track);
long trackId = ContentUris.parseId(uri); Track.Id trackId = new Track.Id(ContentUris.parseId(uri));
// Update shared preferences // Update shared preferences
updateRecordingState(trackId, false); updateRecordingState(trackId, false);
@@ -290,7 +305,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* *
* @param trackId the id of the track to be resumed. * @param trackId the id of the track to be resumed.
*/ */
void resumeTrack(long trackId) { void resumeTrack(Track.Id trackId) {
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
if (track == null) { if (track == null) {
Log.e(TAG, "Ignore resumeTrack. Track " + trackId + " does not exists."); Log.e(TAG, "Ignore resumeTrack. Track " + trackId + " does not exists.");
@@ -318,7 +333,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
if (track == null) { if (track == null) {
if (isRecording()) { if (isRecording()) {
Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId); Log.w(TAG, "track is null, but recordingTrackId not -1L. " + recordingTrackId);
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true); updateRecordingState(null, true);
} }
showNotification(false); showNotification(false);
return; return;
@@ -388,10 +403,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
} }
// Need to remember the recordingTrackId before setting it to -1L // Need to remember the recordingTrackId before setting it to -1L
long trackId = recordingTrackId; Track.Id trackId = recordingTrackId;
boolean wasPaused = recordingTrackPaused; boolean wasPaused = recordingTrackPaused;
updateRecordingState(PreferencesUtils.RECORDING_TRACK_ID_DEFAULT, true); updateRecordingState(null, true);
// Update database // Update database
Track track = contentProviderUtils.getTrack(trackId); Track track = contentProviderUtils.getTrack(trackId);
@@ -474,7 +489,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackId the track id * @param trackId the track id
* @return the location or null * @return the location or null
*/ */
private TrackPoint getLastValidTrackPointInCurrentSegment(long trackId) { private TrackPoint getLastValidTrackPointInCurrentSegment(Track.Id trackId) {
if (!currentSegmentHasTrackPoint()) { if (!currentSegmentHasTrackPoint()) {
return null; return null;
} }
@@ -492,9 +507,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
* @param trackId the recording track id * @param trackId the recording track id
* @param paused true if the recording is paused * @param paused true if the recording is paused
*/ */
private void updateRecordingState(long trackId, boolean paused) { private void updateRecordingState(Track.Id trackId, boolean paused) {
recordingTrackId = trackId; recordingTrackId = trackId;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, trackId); long currentTrackId = trackId != null ? trackId.getId() : PreferencesUtils.RECORDING_TRACK_ID_DEFAULT;
PreferencesUtils.setLong(this, R.string.recording_track_id_key, currentTrackId);
recordingTrackPaused = paused; recordingTrackPaused = paused;
PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused); PreferencesUtils.setBoolean(this, R.string.recording_track_paused_key, recordingTrackPaused);
} }
@@ -581,6 +597,20 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
lastTrackPoint = trackPoint; lastTrackPoint = trackPoint;
} }
@Override
public void newGpsStatus(GpsStatusValue gpsStatusValue) {
if (listeners != null) {
notificationManager.updateContent(getString(gpsStatusValue.message));
for (TrackRecordingServiceCallback listener : listeners) {
listener.onGpsStatusChange(gpsStatusValue);
}
}
}
public void addListener(TrackRecordingServiceCallback listener) {
listeners.add(listener);
}
/** /**
* Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint. * Inserts a trackPoint if this trackPoint is different than lastValidTrackPoint.
* *
@@ -679,4 +709,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) { public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
this.remoteSensorManager = remoteSensorManager; this.remoteSensorManager = remoteSensorManager;
} }
public GpsStatusValue getGpsStatus() {
return handlerServer.getGpsStatus();
}
} }
@@ -2,8 +2,11 @@ package de.dennisguse.opentracks.services;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import de.dennisguse.opentracks.content.data.Track;
import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPoint;
import de.dennisguse.opentracks.content.data.Waypoint;
import de.dennisguse.opentracks.content.sensor.SensorDataSet; import de.dennisguse.opentracks.content.sensor.SensorDataSet;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager; import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
/** /**
@@ -22,6 +25,16 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
this.trackRecordingService = trackRecordingService; this.trackRecordingService = trackRecordingService;
} }
@Override
public void addListener(TrackRecordingServiceCallback listener) {
trackRecordingService.addListener(listener);
}
@Override
public GpsStatusValue getGpsStatus() {
return trackRecordingService.getGpsStatus();
}
@Override @Override
public void startGps() { public void startGps() {
trackRecordingService.tryStartGps(); trackRecordingService.tryStartGps();
@@ -32,12 +45,12 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
} }
@Override @Override
public long startNewTrack() { public Track.Id startNewTrack() {
return trackRecordingService.startNewTrack(); return trackRecordingService.startNewTrack();
} }
@Override @Override
public void resumeTrack(long trackId) { public void resumeTrack(Track.Id trackId) {
trackRecordingService.resumeTrack(trackId); trackRecordingService.resumeTrack(trackId);
} }
@@ -67,7 +80,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
} }
@Override @Override
public long getRecordingTrackId() { public Track.Id getRecordingTrackId() {
return trackRecordingService.getRecordingTrackId(); return trackRecordingService.getRecordingTrackId();
} }
@@ -77,7 +90,7 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
} }
@Override @Override
public long insertWaypoint(String name, String category, String description, String photoUrl) { public Waypoint.Id insertWaypoint(String name, String category, String description, String photoUrl) {
return trackRecordingService.insertWaypoint(name, category, description, photoUrl); return trackRecordingService.insertWaypoint(name, category, description, photoUrl);
} }
@@ -0,0 +1,10 @@
package de.dennisguse.opentracks.services;
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
/**
* Interface all activities have to implements to receive information from the service.
*/
public interface TrackRecordingServiceCallback {
void onGpsStatusChange(GpsStatusValue newStatus);
}

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