├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── deploymentTargetDropDown.xml
├── deploymentTargetSelector.xml
├── gradle.xml
├── jarRepositories.xml
├── migrations.xml
└── runConfigurations.xml
├── LICENSES
├── LICENSE_gpl-3.0.txt
├── README.md
├── app
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ ├── icons_readme
│ │ └── ic_tower1.svg
│ └── webMaps
│ │ ├── leafletjs.html
│ │ ├── leafletjs_1_9
│ │ ├── images
│ │ │ ├── layers-2x.png
│ │ │ ├── layers.png
│ │ │ ├── marker-icon-2x.png
│ │ │ ├── marker-icon.png
│ │ │ └── marker-shadow.png
│ │ ├── leaflet.css
│ │ └── leaflet.js
│ │ ├── utils.js
│ │ ├── wallpaper.css
│ │ ├── wallpaper.html
│ │ └── ya.html
│ ├── ic_launcher3-web.png
│ ├── java
│ └── truewatcher
│ │ └── tower
│ │ ├── AddPointActivity.java
│ │ ├── CellInformer.java
│ │ ├── CellPointFetcher.java
│ │ ├── CellResolverFactory.java
│ │ ├── ConfirmationDialogFragment.java
│ │ ├── DeeperRadioGroup.java
│ │ ├── EditPointActivity.java
│ │ ├── EditTextDialogFragment.java
│ │ ├── FileActivity.java
│ │ ├── ForegroundService.java
│ │ ├── GpsPointFetcher.java
│ │ ├── GpxHelper.java
│ │ ├── HttpGetRequest.java
│ │ ├── HttpPostRequest.java
│ │ ├── JSbridge.java
│ │ ├── JsonHelper.java
│ │ ├── LatLon.java
│ │ ├── ListActivity.java
│ │ ├── ListHelper.java
│ │ ├── MainActivity.java
│ │ ├── MapViewer.java
│ │ ├── Model.java
│ │ ├── MyRegistry.java
│ │ ├── PermissionAwareFragment.java
│ │ ├── Point.java
│ │ ├── PointFetcher.java
│ │ ├── PointIndicator.java
│ │ ├── PointList.java
│ │ ├── PreferencesActivity.java
│ │ ├── SingleFragmentActivity.java
│ │ ├── StorageHelper.java
│ │ ├── TestHelper.java
│ │ ├── Tests1.java
│ │ ├── TrackActivity.java
│ │ ├── TrackListener.java
│ │ ├── TrackStorage.java
│ │ ├── Trackpoint.java
│ │ └── U.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_launcher.png
│ ├── drawable-mdpi
│ └── ic_launcher.png
│ ├── drawable-xhdpi
│ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ └── ic_launcher.png
│ ├── drawable
│ ├── ic_add_white_24dp.xml
│ ├── ic_build_white_24dp.xml
│ ├── ic_check_white_24dp.xml
│ ├── ic_chevron_left_white_24dp.xml
│ ├── ic_done_black_white_24dp.xml
│ ├── ic_folder_open_white_24dp.xml
│ ├── ic_format_list_bulleted_white_24dp.xml
│ ├── ic_launcher3_background.xml
│ ├── ic_launcher3_foreground.xml
│ ├── ic_map_white_24.xml
│ ├── ic_refresh_white_24dp.xml
│ └── ic_target_variant_white.xml
│ ├── layout
│ ├── activity_fragment.xml
│ ├── activity_main.xml
│ ├── fragment_add_point.xml
│ ├── fragment_edit_point.xml
│ ├── fragment_file.xml
│ ├── fragment_list.xml
│ ├── fragment_main.xml
│ ├── fragment_tests.xml
│ ├── fragment_track.xml
│ └── list_item_simple.xml
│ ├── menu
│ ├── add_point_fragment.xml
│ ├── edit_point_fragment.xml
│ ├── file_fragment.xml
│ ├── list_fragment.xml
│ ├── main.xml
│ ├── main_fragment.xml
│ ├── prefs_activity.xml
│ └── track_fragment.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher3.xml
│ └── ic_launcher3_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher3.png
│ └── ic_launcher3_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher3.png
│ └── ic_launcher3_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher3.png
│ └── ic_launcher3_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher3.png
│ └── ic_launcher3_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher3.png
│ └── ic_launcher3_round.png
│ ├── values-night
│ └── styles.xml
│ ├── values-v11
│ └── styles.xml
│ ├── values-v14
│ └── styles.xml
│ ├── values-w820dp
│ └── dimens.xml
│ ├── values
│ ├── array.xml
│ ├── dimens.xml
│ ├── ic_launcher3_background.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── prefs.xml
├── build.gradle
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 01_waypoints_and_track_from_garmin.png
│ │ ├── 02_map_with_menu.png
│ │ ├── 03_add_new_waypoint.png
│ │ ├── 04_waypoint_list_with_distances.png
│ │ ├── 05_edit_waypoint.png
│ │ ├── 06_writing_track.png
│ │ ├── 07_settings.png
│ │ ├── 08_importing_track.png
│ │ ├── 09_export_part_of_waypoint_list.png
│ │ └── 10_settings_missing_API_key.png
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── import-summary.txt
├── settings.gradle
└── tower_stateVars.rtf
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/androidstudio
3 | # Edit at https://www.gitignore.io/?templates=androidstudio
4 |
5 | ### AndroidStudio ###
6 | # Covers files to be ignored for android development using Android Studio.
7 |
8 | # Built application files
9 | *.apk
10 | *.ap_
11 |
12 | # Files for the ART/Dalvik VM
13 | *.dex
14 |
15 | # Java class files
16 | *.class
17 |
18 | # Generated files
19 | bin/
20 | gen/
21 | out/
22 |
23 | # Gradle files
24 | .gradle
25 | .gradle/
26 | build/
27 |
28 | # Signing files
29 | .signing/
30 |
31 | # Local configuration file (sdk path, etc)
32 | local.properties
33 |
34 | # Proguard folder generated by Eclipse
35 | proguard/
36 |
37 | # Log Files
38 | *.log
39 |
40 | # Android Studio
41 | /*/build/
42 | /*/local.properties
43 | /*/out
44 | /*/*/build
45 | /*/*/production
46 | app/release
47 | captures/
48 | .navigation/
49 | *.ipr
50 | *~
51 | *.swp
52 |
53 | # Android Patch
54 | gen-external-apklibs
55 |
56 | # External native build folder generated in Android Studio 2.2 and later
57 | .externalNativeBuild
58 |
59 | # NDK
60 | obj/
61 |
62 | # IntelliJ IDEA
63 | *.iml
64 | *.iws
65 | /out/
66 |
67 | # User-specific configurations
68 | .idea/caches/
69 | .idea/libraries/
70 | .idea/shelf/
71 | .idea/workspace.xml
72 | .idea/tasks.xml
73 | .idea/.name
74 | .idea/compiler.xml
75 | .idea/copyright/profiles_settings.xml
76 | .idea/encodings.xml
77 | .idea/misc.xml
78 | .idea/modules.xml
79 | .idea/scopes/scope_settings.xml
80 | .idea/dictionaries
81 | .idea/vcs.xml
82 | .idea/jsLibraryMappings.xml
83 | .idea/datasources.xml
84 | .idea/dataSources.ids
85 | .idea/sqlDataSources.xml
86 | .idea/dynamic.xml
87 | .idea/uiDesigner.xml
88 | .idea/assetWizardSettings.xml
89 |
90 | # OS-specific files
91 | .DS_Store
92 | .DS_Store?
93 | ._*
94 | .Spotlight-V100
95 | .Trashes
96 | ehthumbs.db
97 | Thumbs.db
98 |
99 | # Legacy Eclipse project files
100 | .classpath
101 | .project
102 | .cproject
103 | .settings/
104 |
105 | # Mobile Tools for Java (J2ME)
106 | .mtj.tmp/
107 |
108 | # Package Files #
109 | *.war
110 | *.ear
111 |
112 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
113 | hs_err_pid*
114 |
115 | ## Plugin-specific files:
116 |
117 | # mpeltonen/sbt-idea plugin
118 | .idea_modules/
119 |
120 | # JIRA plugin
121 | atlassian-ide-plugin.xml
122 |
123 | # Mongo Explorer plugin
124 | .idea/mongoSettings.xml
125 |
126 | # Crashlytics plugin (for Android Studio and IntelliJ)
127 | com_crashlytics_export_strings.xml
128 | crashlytics.properties
129 | crashlytics-build.properties
130 | fabric.properties
131 |
132 | ### AndroidStudio Patch ###
133 |
134 | !/gradle/wrapper/gradle-wrapper.jar
135 |
136 | # End of https://www.gitignore.io/api/androidstudio
137 |
138 | # there may be some private files in assets
139 | /app/src/main/assets/_*
140 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | xmlns:android
11 |
12 | ^$
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | xmlns:.*
22 |
23 | ^$
24 |
25 |
26 | BY_NAME
27 |
28 |
29 |
30 |
31 |
32 |
33 | .*:id
34 |
35 | http://schemas.android.com/apk/res/android
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | .*:name
45 |
46 | http://schemas.android.com/apk/res/android
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | name
56 |
57 | ^$
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | style
67 |
68 | ^$
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | .*
78 |
79 | ^$
80 |
81 |
82 | BY_NAME
83 |
84 |
85 |
86 |
87 |
88 |
89 | .*
90 |
91 | http://schemas.android.com/apk/res/android
92 |
93 |
94 | ANDROID_ATTRIBUTE_ORDER
95 |
96 |
97 |
98 |
99 |
100 |
101 | .*
102 |
103 | .*
104 |
105 |
106 | BY_NAME
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/LICENSES:
--------------------------------------------------------------------------------
1 | License
2 |
3 | Copyright (C) 2019 Alexander Roshchin
4 |
5 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
6 |
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8 |
9 | You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
10 | -----------------------------------
11 |
12 | Third-party materials
13 |
14 | ===== LeafletJS javacsript library code =====
15 | https://leafletjs.com
16 | https://github.com/Leaflet/Leaflet
17 | Copyright (c) 2010-2019, Vladimir Agafonkin
18 | Copyright (c) 2010-2011, CloudMade
19 | License: BSD 2-Clause "Simplified" License
20 |
21 | ===== OpenStreetMap map tiles =====
22 | https://www.openstreetmap.org
23 | © OpenStreetMap contributors
24 | License: Creative Commons Attribution-ShareAlike 2.0
25 |
26 | ===== OpenTopoMap map tiles =====
27 | https://www.opentopomap.org
28 | © OpenTopoMap, OpenStreetMap contributors
29 | License: Creative Commons Attribution-ShareAlike 2.0
30 |
31 | ===== Google Maps map data =====
32 | © 2019 Google and its data providers
33 | Terms of use: https://www.google.com/permissions/geoguidelines/
34 |
35 | ===== Yandex Maps API, javascript code and map data =====
36 | https://tech.yandex.com/maps/commercial/
37 | https://tech.yandex.com/maps/jsapi/doc/2.1/terms/index-docpage/
38 | © 2015–2019 YANDEX LLC
39 | Terms of use: https://yandex.ru/legal/maps_termsofuse/
40 |
41 | ===== Yandex Locator API and cell location data =====
42 | https://yandex.ru/dev/locator/
43 | © 2015–2019 YANDEX LLC
44 | Terms of use: https://yandex.ru/legal/locator_api/?lang=en
45 |
46 | ===== mylnikov.org API and cell location data =====
47 | https://www.mylnikov.org/
48 | © Alexander Mylnikov
49 | License: MIT
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Tower: a navigation tool
3 |
4 | _Tower_ is a navigation program for OS Android
5 | for finding user location (by phone cell or GPS), viewing online maps, creating and storing waypoints and tracks, with minimal permissions and no background activity
6 |
7 | ## Features:
8 |
9 | * no Google Services dependencies, only necessary permissions (fine and coarse location, internet)
10 | * connects to several online map providers
11 | * acceptable performance on slow GPRS-EDGE networks; may find coarse location by phone cell without GPS; may create waypoints with GPS without phone and internet connections
12 | * displays and saves cell info ( MCC, MNC, LAC, CID ) and the signal strength ( RSRP/dBm )
13 | * all waypoints are stored on a memory card and may be organized in any number of files (by regions etc.)
14 | * waypoint lists may be exported or imported in the GPX format, compatible with many navigators and software
15 | * finds location only by explicit user's command, so is very mild on the battery
16 | * capable of writing tracks (by means of a foreground service) and exporting them in the GPX format
17 | * tracks from other devices (GPX files) may be viewed along with your data
18 | * NOT implemented: map offline caching, routing, editing tracks, photo and video attachments, serving cold beer :)
19 | * for pure open distributions, that lack API keys and access to some services, there are options to enter user's own keys
20 |
21 | ## How to use it and where to get it
22 |
23 | See [the project web page](http://tower.posmotrel.net) and
24 |
25 | [ ](https://f-droid.org/packages/truewatcher.tower/)
27 |
28 | ## External materials (see LICENSES):
29 |
30 | This distribution includes the _LeafletJS_ javacsript library code ver. 1.9 (https://leafletjs.com, https://github.com/Leaflet/Leaflet),
31 | which has BSD 2-Clause "Simplified" License
32 |
33 | This program uses several web APIs and loads data and javascript code, details and references are included in the LICENSES file.
34 |
35 | ## API keys
36 |
37 | Some of web services, despite being free of charge, require access keys. As these keys are not to be committed to public repositories, this app may appear in two kinds of distributions: full (with keys included in the binary) and pure open (without keys). The keyless distribution is fully operational, except for concerned services, and always has slots to enter user's own keys. See [the manual](http://tower.posmotrel.net/#external-materials-and-api-keys) for details.
38 |
39 | Builders are free to provide their own keys with their distribution; the easiest way is via Gradle files as BuildConfig.yandexLocatorKey and BuildConfig.yandexMapKey ( [instruction](https://stackoverflow.com/questions/35722904/saving-the-api-key-in-gradle-properties) ).
40 |
41 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | buildToolsVersion = '35.0.0'
5 | defaultConfig {
6 | applicationId "truewatcher.tower"
7 | minSdkVersion 21
8 | targetSdkVersion 34
9 | compileSdk 34
10 | }
11 | buildTypes {
12 | release {
13 | // no optimisation
14 | minifyEnabled false
15 |
16 | // optimisation
17 | //minifyEnabled true
18 | //shrinkResources true
19 | //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
20 |
21 | // optimise more agressively
22 | //proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt'
23 | }
24 | }
25 | namespace 'truewatcher.tower'
26 | buildFeatures {
27 | buildConfig true
28 | }
29 | }
30 |
31 | dependencies {
32 | //implementation 'com.android.support:appcompat-v7:28.0.0'
33 | //implementation 'com.android.support:preference-v7:28.0.0'
34 | implementation 'androidx.appcompat:appcompat:1.7.0'
35 | implementation "androidx.preference:preference:1.2.1"
36 | // cures "duplicate classes" error
37 | implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
42 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/assets/icons_readme/ic_tower1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/leafletjs_1_9/images/layers-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/assets/webMaps/leafletjs_1_9/images/layers-2x.png
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/leafletjs_1_9/images/layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/assets/webMaps/leafletjs_1_9/images/layers.png
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/leafletjs_1_9/images/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/assets/webMaps/leafletjs_1_9/images/marker-icon-2x.png
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/leafletjs_1_9/images/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/assets/webMaps/leafletjs_1_9/images/marker-icon.png
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/leafletjs_1_9/images/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/assets/webMaps/leafletjs_1_9/images/marker-shadow.png
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/wallpaper.css:
--------------------------------------------------------------------------------
1 |
2 | html, body, #mapDiv, table {
3 | width: 100%; height: 100%; margin: 0; padding: 0; border: 0;
4 | }
5 |
6 | #mapDiv td {
7 | vertical-align: middle; text-align: center;
8 | }
9 |
10 | #svg_logo {
11 | min-width: 20%; min-height: 20%;
12 | }
13 |
14 | #mapDiv {
15 | background-image: linear-gradient(rgba(255,255,255,0), #a0a0a0, #aaa, #592);
16 | }
17 | @media (prefers-color-scheme: dark) {
18 | #mapDiv {
19 | background-image: linear-gradient(#555, #a0a0a0, #aaa, #592);
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/assets/webMaps/wallpaper.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | wallpaper
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher3-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/ic_launcher3-web.png
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/CellPointFetcher.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 | import android.Manifest;
6 | import android.os.AsyncTask;
7 | import android.os.Build;
8 | //import android.support.annotation.RequiresApi;
9 | import androidx.annotation.RequiresApi;
10 | import android.util.Log;
11 | import androidx.fragment.app.FragmentActivity;
12 |
13 | public class CellPointFetcher extends PointFetcher implements PermissionReceiver,CellDataReceiver,HttpReceiver {
14 |
15 | private CellResolver mCellResolver;
16 | private CellInformer mCellInformer = new CellInformer();
17 |
18 | @Override
19 | protected String getPermissionType() { return Manifest.permission.ACCESS_FINE_LOCATION; }
20 |
21 | @Override
22 | protected int getPermissionCode() { return 1; }
23 |
24 | @RequiresApi(api = Build.VERSION_CODES.Q)
25 | @Override
26 | public void afterLocationPermissionOk() {
27 | mCellInformer.bindActivity(mActivity);
28 | mCellInformer.requestCellInfos(this);
29 | }
30 |
31 | public void onCellDataObtained(JSONObject cellData) {
32 | mStatus = mCellInformer.getStatus();
33 | if (mStatus.equals("forbidden")) {
34 | mPi.addProgress(mActivity.getResources().getString(R.string.permissionfailure));
35 | return;
36 | }
37 | if (mStatus.indexOf("unsupported") == 0) {
38 | mPi.addProgress(mStatus);
39 | return;
40 | }
41 | if (! mStatus.equals("available") && ! mStatus.equals("mocking") && ! mStatus.equals("noService")) {
42 | mStatus="error";
43 | mPi.addProgress("failed to get cell info");
44 | return;
45 | }
46 |
47 | mPoint=new Point("cell");
48 | mPoint.cellData=cellData.toString();
49 | mPi.showData( JsonHelper.filterQuotes(mPoint.cellData) );
50 | if (shouldNotResolve()) {
51 | onPointavailable(mPoint);
52 | return;
53 | }
54 | mPi.addProgress("trying to get location...");
55 | startResolveCell();
56 | }
57 |
58 | private boolean shouldNotResolve() {
59 | if ("none".equals( MyRegistry.getInstance().get("cellResolver") ) ) {
60 | mPi.addProgress("location service is off (see Settings)");
61 | return true;
62 | }
63 | //if (U.DEBUG) Log.i(U.TAG,"network on:"+U.isNetworkOn(mActivity));
64 | if (! U.isNetworkOn(mActivity)) {
65 | mPi.addProgress("no internet");
66 | return true;
67 | }
68 | return false;
69 | }
70 |
71 | public void onlyResolve(PointIndicator pi, PointReceiver pr, Point p) {
72 | // a shortcut entrypoint for resolving a ready cell without permission checks
73 | mPi=pi;
74 | mPointReceiver=pr;
75 | mPoint=p;
76 | mStatus="asked to resolve";
77 | mToUpdateLocation=false;
78 | if ( ! p.getType().equals("cell") || p.cellData == null || p.cellData.length() < 10) {
79 | mPi.addProgress("Not a valid cell");
80 | return;
81 | }
82 | if ( "none".equals( MyRegistry.getInstance().get("cellResolver") ) ) {
83 | mPi.addProgress("Select cell location service");
84 | return;
85 | }
86 | startResolveCell();
87 | }
88 |
89 | private void startResolveCell() {
90 | String resolverUri = "";
91 | String reqData = "";
92 | JSONObject cellData = new JSONObject();
93 |
94 | try {
95 | cellData = new JSONObject(mPoint.cellData);
96 | }
97 | catch (JSONException e) {
98 | Log.e(U.TAG,"Wrong point.cellData");
99 | Log.e(U.TAG,e.getMessage());
100 | mPi.addProgress("Wrong point.cellData");
101 | onPointavailable(mPoint);
102 | return;
103 | }
104 | try {
105 | if ( ! cellData.has("CID") || "".equals(cellData.optString("CID"))) {
106 | throw new U.DataException("No cell id");
107 | }
108 | mCellResolver = CellResolverFactory.getResolver(MyRegistry.getInstance().get("cellResolver"));
109 | resolverUri = mCellResolver.makeResolverUri(cellData);
110 | reqData = mCellResolver.makeResolverData(cellData);
111 | }
112 | catch (U.DataException e) {
113 | Log.e(U.TAG,e.getMessage());
114 | mStatus="Failure:"+e.getMessage();
115 | mPi.addProgress(mStatus);
116 | onPointavailable(mPoint);
117 | return;
118 | }
119 | if (U.DEBUG) Log.i(U.TAG,"startResolveCell:"+"About to query "+resolverUri+"\n data="+reqData);
120 | AsyncTask req = mCellResolver.getRequestTask(this);
121 | req.execute(resolverUri,reqData);
122 | }
123 |
124 | @Override
125 | public void onHttpReceived(String response) {
126 | if (U.DEBUG) Log.i(U.TAG,"onHttpReceived:"+"Response:"+response);
127 | try {
128 | JSONObject resolvedData = mCellResolver.getResolvedData(response);
129 | mStatus = "resolved";
130 | mPoint.lat=resolvedData.optString("lat");
131 | mPoint.lon=resolvedData.optString("lon");
132 | String r=resolvedData.optString("range");
133 | if ( r != null && ! r.isEmpty()) mPoint.range=r;
134 | }
135 | catch (U.DataException e) {
136 | Log.e(U.TAG,e.getMessage());
137 | mStatus="Failure:"+e.getMessage();
138 | }
139 |
140 | mPi.addProgress(mStatus);
141 | //if (mStatus != "resolved") return;
142 | if (mPoint.range != null && ! mPoint.range.isEmpty()) {
143 | String rt=PointIndicator.floor(mPoint.range);
144 | mPi.addData(" Accuracy:"+rt);
145 | }
146 | onPointavailable(mPoint);
147 | }
148 |
149 | @Override
150 | public void onHttpError(String error) {
151 | mStatus="Http Error:"+error;
152 | mPi.addProgress(mStatus);
153 | onPointavailable(mPoint);
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/CellResolverFactory.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | import android.net.Uri;
7 | import android.os.AsyncTask;
8 | import android.util.Log;
9 |
10 | interface CellResolver {
11 | public String makeResolverUri(JSONObject celldata);
12 | public String makeResolverData(JSONObject celldata) throws U.DataException;
13 | public AsyncTask getRequestTask(HttpReceiver receiver);
14 | public JSONObject getResolvedData(String response) throws U.DataException;
15 | }
16 |
17 | public class CellResolverFactory {
18 | public static CellResolver getResolver(String which) {
19 | if (which.equals("mylnikov")) return new MylnikovResolver();
20 | if (which.equals("yandex")) return new YandexResolver();
21 | throw new U.RunException("CellInformer:"+"Wrong WHICH="+which);
22 | }
23 |
24 | private static class MylnikovResolver implements CellResolver {
25 |
26 | public String makeResolverUri(JSONObject cellData) {
27 | String resolverServiceBase="api.mylnikov.org/geolocation/cell";
28 | String lacTac = cellData.has("TAC" ) ? "TAC" : "LAC";
29 | // https://api.mylnikov.org/geolocation/cell?v=1.1&mcc=250&mnc=02&cellid=200719106&lac=7840
30 | String resolverUri = Uri.parse(U.H+resolverServiceBase)
31 | .buildUpon()
32 | .appendQueryParameter("v", "1.1")
33 | //.appendQueryParameter("data", "open")
34 | .appendQueryParameter("mcc", cellData.optString("MCC"))
35 | .appendQueryParameter("mnc", cellData.optString("MNC"))
36 | .appendQueryParameter("lac", cellData.optString(lacTac))
37 | .appendQueryParameter("cellid", cellData.optString("CID"))
38 | .build().toString();
39 | return resolverUri;
40 | }
41 |
42 | public String makeResolverData(JSONObject celldata) { return ""; }
43 |
44 | public AsyncTask getRequestTask(HttpReceiver receiver) {
45 | return new HttpGetRequest(receiver);
46 | }
47 |
48 | public JSONObject getResolvedData(String response) throws U.DataException {
49 | JSONObject rd=new JSONObject();
50 | if (response == null || response.length() == 0) { throw new U.DataException("No response"); }
51 | try {
52 | rd=new JSONObject(response);
53 | int respCode=rd.optInt("result");
54 | if (respCode != 200) { throw new U.DataException("Resolver failure, code="+respCode); }
55 | rd=rd.getJSONObject("data");
56 | }
57 | catch (JSONException e) { throw new U.DataException("Unparseble response"); }
58 | String lat=String.valueOf(rd.opt("lat"));
59 | String lon=String.valueOf(rd.opt("lon"));
60 | if (lat.indexOf(".") < 0 || lon.indexOf(".") < 0) {
61 | throw new U.DataException("Wrong lat or lon:"+lat+"/"+lon);
62 | }
63 | return rd;
64 | }
65 | }
66 |
67 | private static class YandexResolver implements CellResolver {
68 |
69 | public String makeResolverUri(JSONObject cellData) {
70 | return U.H+"api.lbs.yandex.net/geolocation";
71 | }
72 |
73 | public String makeResolverData(JSONObject cellData) throws U.DataException {
74 | String key=MyRegistry.getInstance().getScrambled("yandexLocatorKey");
75 | //Log.d(U.TAG, "CellResolverFactory"+"locator key:"+MyRegistry.getInstance().getScrambled("yandexLocatorKey"));
76 | if (key.isEmpty()) { throw new U.DataException("missing API key"); }
77 | String r="json={";
78 | r+="\"common\":{\"version\":\"1.0\", \"api_key\":\""+key+"\"}";
79 | r+=", ";
80 | JSONObject data=new JSONObject();
81 | String lacTac = cellData.has("TAC" ) ? "TAC" : "LAC";
82 | try {
83 | data.put("countrycode",cellData.optInt("MCC"));
84 | data.put("operatorid",cellData.optInt("MNC"));
85 | data.put("lac",cellData.optLong(lacTac));
86 | data.put("cellid",cellData.optLong("CID"));
87 | }
88 | catch (JSONException e) {
89 | Log.e(U.TAG, "makeResolverData:"+e.getMessage());
90 | }
91 | r+="\"gsm_cells\":["+data.toString()+"]";
92 | r+="}";
93 | return r;
94 | }
95 |
96 | public AsyncTask getRequestTask(HttpReceiver receiver) {
97 | return new HttpPostRequest(receiver);
98 | }
99 |
100 | public JSONObject getResolvedData(String response) throws U.DataException {
101 | JSONObject rd=new JSONObject();
102 | JSONObject res=new JSONObject();
103 | if (response == null || response.length() == 0) { throw new U.DataException("No response"); }
104 | try {
105 | rd=new JSONObject(response);
106 | String error=rd.optString("error");
107 | if (error != null && ! error.isEmpty()) { throw new U.DataException("Resolver failure, error="+error); }
108 | rd=rd.getJSONObject("position");
109 | }
110 | catch (JSONException e) { throw new U.DataException("Unparseble response"); }
111 | String lat=String.valueOf(rd.opt("latitude"));
112 | String lon=String.valueOf(rd.opt("longitude"));
113 | if (lat.indexOf(".") < 0 || lon.indexOf(".") < 0) {
114 | throw new U.DataException("Wrong lat or lon:"+lat+"/"+lon);
115 | }
116 | String range=String.valueOf(rd.opt("precision"));
117 | try {
118 | res.put("lat",lat);
119 | res.put("lon",lon);
120 | if ( range != null && ! range.isEmpty()) res.put("range",range);
121 | }
122 | catch (JSONException e) { throw new U.DataException("Unrecodable response"); }
123 | return res;
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/ConfirmationDialogFragment.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Dialog;
5 | //import android.support.v4.app.DialogFragment;
6 | import androidx.appcompat.app.AppCompatDialogFragment;
7 | import android.content.Context;
8 | import android.content.DialogInterface;
9 | import android.os.Bundle;
10 | //import android.support.v7.app.AlertDialog;
11 | import androidx.appcompat.app.AlertDialog;
12 |
13 | public class ConfirmationDialogFragment extends AppCompatDialogFragment {
14 | // https://stackoverflow.com/questions/5393197/show-dialog-from-fragment -- answer by EpicPandaForce
15 | public interface ConfirmationDialogReceiver {
16 | public void onConfirmationPositive(int id);//DialogFragment dialog
17 | public void onConfirmationNegative(int id);
18 | }
19 |
20 | private ConfirmationDialogReceiver mListener;
21 | private AppCompatDialogFragment mMe=this;
22 | private int mId=0;
23 | private int mStringId=0;
24 |
25 | public ConfirmationDialogFragment() { super(); }
26 |
27 | @TargetApi(23)
28 | @Override
29 | public void onAttach(Context context) {
30 | super.onAttach(context);
31 | try {
32 | //mListener = (ConfirmationDialogReceiver) context;
33 | mListener = (ConfirmationDialogReceiver) getTargetFragment();
34 | }
35 | catch (ClassCastException e) {
36 | throw new ClassCastException("Host activity must implement ConfirmationDialogReceiver");
37 | }
38 | }
39 |
40 | @Override
41 | public Dialog onCreateDialog(Bundle savedInstanceState) {
42 | mId=Integer.valueOf(this.getArguments().getString("actionId"));
43 | mStringId=Integer.valueOf(this.getArguments().getString("actionStringId"));
44 | //Log.i(U.TAG, "ConfirmationDialogFragment:"+"got args="+mId+"/"+ mStringId);
45 | if (mId == 0 || mStringId == 0) throw new U.RunException("ConfirmationDialogFragment:Missing arguments");
46 |
47 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
48 | builder
49 | .setMessage(R.string.are_you_sure)
50 | .setPositiveButton(mStringId, new DialogInterface.OnClickListener() {
51 | public void onClick(DialogInterface dialog, int id) {
52 | mListener.onConfirmationPositive(mId);
53 | }
54 | })
55 | .setNegativeButton(R.string.action_cancel, new DialogInterface.OnClickListener() {
56 | public void onClick(DialogInterface dialog, int id) {
57 | mListener.onConfirmationNegative(mId);
58 | }
59 | });
60 | return builder.create();
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/DeeperRadioGroup.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.view.View.OnClickListener;
9 | import android.widget.RadioButton;
10 |
11 | public class DeeperRadioGroup {
12 |
13 | private int mCheckedRadio=-1;
14 |
15 | public DeeperRadioGroup(ViewGroup radioContainer) {
16 | setRadioExclusiveClick(radioContainer);
17 | }
18 |
19 | public int getCheckedRadioButtonId() { return mCheckedRadio; }
20 |
21 | // https://stackoverflow.com/questions/10461005/how-to-group-radiobutton-from-different-linearlayouts
22 | private void setRadioExclusiveClick(ViewGroup parent) {
23 | final List radios = getRadioButtons(parent);
24 |
25 | for (RadioButton radio: radios) {
26 | radio.setOnClickListener(new OnClickListener() {
27 | @Override
28 | public void onClick(View v) {
29 | RadioButton r = (RadioButton) v;
30 | r.setChecked(true);
31 | mCheckedRadio=r.getId();
32 | for (RadioButton r2:radios) { if (r2.getId() != r.getId()) r2.setChecked(false); }
33 | }
34 | });
35 | }
36 | }
37 |
38 | private List getRadioButtons(ViewGroup parent) {
39 | List radios = new ArrayList();
40 | for (int i=0;i < parent.getChildCount(); i++) {
41 | View v = parent.getChildAt(i);
42 | if (v instanceof RadioButton) {
43 | radios.add((RadioButton) v);
44 | if (((RadioButton)v).isChecked()) mCheckedRadio=v.getId();
45 | }
46 | else if (v instanceof ViewGroup) {
47 | List nestedRadios = getRadioButtons((ViewGroup) v);
48 | radios.addAll(nestedRadios);
49 | }
50 | }
51 | return radios;
52 | }
53 | }
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/EditTextDialogFragment.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Dialog;
5 | //import android.support.v4.app.DialogFragment;
6 | import androidx.appcompat.app.AppCompatDialogFragment;
7 | import android.content.Context;
8 | import android.content.DialogInterface;
9 | import android.os.Bundle;
10 | //import android.support.v7.app.AlertDialog;
11 | import androidx.appcompat.app.AlertDialog;
12 | import android.util.Log;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.widget.EditText;
17 | import android.widget.LinearLayout;
18 |
19 | public class EditTextDialogFragment extends AppCompatDialogFragment {
20 | // https://stackoverflow.com/questions/5393197/show-dialog-from-fragment -- answer by EpicPandaForce
21 | public interface EditTextDialogReceiver {
22 | public void onEditTextPositive(int id, String text);
23 | }
24 |
25 | private EditTextDialogReceiver mListener;
26 | private AppCompatDialogFragment mMe=this;
27 | private int mId=0;
28 | private int mStringId=0;
29 | private String mText="";
30 | private EditText mInput;
31 |
32 | public EditTextDialogFragment() { super(); }
33 |
34 | @TargetApi(23)
35 | @Override
36 | public void onAttach(Context context) {
37 | super.onAttach(context);
38 | try {
39 | //mListener = (ConfirmationDialogReceiver) context;
40 | mListener = (EditTextDialogReceiver) getTargetFragment();
41 | }
42 | catch (ClassCastException e) {
43 | throw new ClassCastException("Host activity must implement EditTextDialogReceiver");
44 | }
45 | }
46 |
47 | @Override
48 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
49 | View v=super.onCreateView(inflater,container, savedInstanceState);
50 | return v;
51 | }
52 |
53 | @Override
54 | public Dialog onCreateDialog(Bundle savedInstanceState) {
55 | mId=Integer.valueOf(this.getArguments().getString("actionId"));
56 | mStringId=Integer.valueOf(this.getArguments().getString("actionStringId"));
57 | mText=this.getArguments().getString("text");
58 | if (mId == 0 || mStringId == 0 || mText == null) throw new U.RunException("EditTextDialogFragment:Missing arguments");
59 |
60 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
61 | builder.setMessage(mStringId);
62 | // https://stackoverflow.com/questions/18799216/how-to-make-a-edittext-box-in-a-dialog
63 | mInput = new EditText(getActivity());
64 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
65 | LinearLayout.LayoutParams.MATCH_PARENT,
66 | LinearLayout.LayoutParams.MATCH_PARENT);
67 | mInput.setLayoutParams(lp);
68 | if (mText.isEmpty()) mInput.setHint("");
69 | mInput.setText(mText);
70 | builder.setView(mInput);
71 |
72 | builder
73 | .setPositiveButton(R.string.action_done, new DialogInterface.OnClickListener() {
74 | public void onClick(DialogInterface dialog, int id) {
75 | mText=mInput.getText().toString();
76 | if (U.DEBUG) Log.d(U.TAG,"EditTextDialogFragment:"+"Action confirmed by user, text="+mText);
77 | mListener.onEditTextPositive(mId, mText);
78 | }
79 | })
80 | .setNegativeButton(R.string.action_cancel, new DialogInterface.OnClickListener() {
81 | public void onClick(DialogInterface dialog, int id) {
82 | //mListener.onConfirmationNegative(mId);
83 | if (U.DEBUG) Log.d(U.TAG,"EditTextDialogFragment:"+"Action canceled by user");
84 | }
85 | });
86 | return builder.create();
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/ForegroundService.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.app.Service;
8 | import android.content.Intent;
9 | import android.content.pm.ServiceInfo;
10 | import android.os.Build;
11 | import android.os.IBinder;
12 | import androidx.annotation.Nullable;
13 | import androidx.annotation.RequiresApi;
14 | //import android.support.v4.app.NotificationCompat;
15 | import androidx.core.app.NotificationCompat;
16 | import android.util.Log;
17 |
18 | // https://www.here.com/docs/bundle/sdk-for-android-navigate-developer-guide/page/topics/get-locations-enable-background-updates.html
19 |
20 | public class ForegroundService extends Service {
21 |
22 | private static final int NOTIFICATION_ID = 12345678;
23 | private static final String CHANNEL_ID = "channel_01";
24 |
25 | @Override
26 | public void onCreate() {
27 | super.onCreate();
28 | if (U.DEBUG) Log.i(U.TAG, "ForegroundService:"+"onCreate");
29 | }
30 |
31 | @RequiresApi(api = Build.VERSION_CODES.Q)
32 | @Override
33 | public int onStartCommand(Intent intent, int flags, int startId) {
34 | if (U.DEBUG) Log.i(U.TAG, "ForegroundService:"+"onStartCommand");
35 | Model.getInstance().getTrackStorage().saveNote(
36 | "onStartCommand",
37 | String.format("intnt:%b,flags:%d,id:%d",intent,flags,startId));
38 | createNotificationChannel();
39 | try {
40 | startForeground(NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
41 | }
42 | catch (NoSuchMethodError e) { // API <= 28
43 | startForeground(NOTIFICATION_ID, getNotification());
44 | }
45 | if (U.DEBUG) Log.i(U.TAG, "ForegroundService:"+"onStartCommand"+": service started!");
46 | return START_STICKY;
47 | }
48 |
49 | private Notification getNotification() {
50 | String text="tracking is on";
51 | Intent intent = new Intent(this, MainActivity.class);
52 | PendingIntent contentIntent = PendingIntent.getActivity(
53 | this,
54 | 0,
55 | intent,
56 | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT );
57 |
58 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
59 | .setContentTitle("Tower")
60 | .setContentText(text)
61 | .setSmallIcon(R.mipmap.ic_launcher3)
62 | .setContentIntent(contentIntent)
63 | ;
64 |
65 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
66 | builder.setChannelId(CHANNEL_ID);
67 | }
68 |
69 | return builder.build();
70 | }
71 |
72 | private void createNotificationChannel() {
73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
74 | NotificationChannel serviceChannel = new NotificationChannel(
75 | CHANNEL_ID,
76 | "Foreground Service Channel",
77 | NotificationManager.IMPORTANCE_DEFAULT
78 | );
79 |
80 | NotificationManager manager = getSystemService(NotificationManager.class);
81 | manager.createNotificationChannel(serviceChannel);
82 | }
83 | }
84 |
85 | @Override
86 | public void onDestroy() {
87 | super.onDestroy();
88 | }
89 |
90 | @Nullable
91 | @Override
92 | public IBinder onBind(Intent intent) {
93 | return null;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/GpsPointFetcher.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.location.Location;
6 | import android.location.LocationListener;
7 | import android.location.LocationManager;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.os.SystemClock;
11 | import android.util.Log;
12 |
13 | public class GpsPointFetcher extends PointFetcher {
14 |
15 | private LocationManager mLocnManager;
16 | private int mFixCount=0;
17 | private int mGpsAcceptableAccuracy=MyRegistry.getInstance().getInt("gpsAcceptableAccuracy");
18 | private int mGpsMaxFixCount=MyRegistry.getInstance().getInt("gpsMaxFixCount");
19 |
20 | @Override
21 | protected String getPermissionType() { return Manifest.permission.ACCESS_FINE_LOCATION; }
22 |
23 | @Override
24 | protected int getPermissionCode() { return 2; }
25 |
26 | /* mock gps location seems to be off limits now :(
27 | @Override
28 | protected boolean tryGiveMockLocation() {
29 | if (mStatus.equals("enabled")) {
30 | giveMockLocation();
31 | return true;
32 | }
33 | return false;
34 | }*/
35 |
36 | @Override
37 | public void afterLocationPermissionOk() {
38 | mPi.addProgress("checking GPS...");
39 | mLocnManager = (LocationManager) mActivity.getSystemService(Context.LOCATION_SERVICE);
40 | boolean isGpsEnabled = mLocnManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
41 | if ( ! isGpsEnabled) {
42 | mStatus="disabled";
43 | mPi.addProgress(mStatus);
44 | return;
45 | }
46 | mStatus="enabled";
47 | mPi.addProgress("Ok, connecting");
48 | try {
49 | //the permission is checked in PointFetcher
50 | mLocnManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
51 | }
52 | catch (SecurityException e) {
53 | mStatus="forbidden";
54 | mPi.addProgress((mActivity.getResources().getString(R.string.permissionfailure)));
55 | return;
56 | }
57 | }
58 |
59 |
60 | private LocationListener mLocationListener = new LocationListener() {
61 |
62 | @Override
63 | public void onLocationChanged(Location loc) {
64 | if (U.DEBUG) Log.i(U.TAG, "mLocationListener:"+"got a location " + loc.toString());
65 | mFixCount += 1;
66 | mPi.showData(locToDisplay(loc, mFixCount));
67 | mStatus = isAcceptable(loc, mFixCount);
68 | if (!mStatus.equals("overcount") && !mStatus.equals("converged")) return;
69 | mFixCount = 0;
70 | mPoint = new Point("gps", loc);
71 | mLocnManager.removeUpdates(this);
72 | mPi.addProgress(mStatus);
73 | onPointavailable(mPoint);
74 | }
75 |
76 | @Override
77 | public void onStatusChanged(String provider, int status, Bundle extras) {
78 | if ( ! provider.equals(LocationManager.GPS_PROVIDER)) return;
79 | if (U.DEBUG) Log.i(U.TAG, "got new GPS status:"+String.valueOf(status));
80 | //mPi.showData( "GPS status:" + String.valueOf(status));
81 | }
82 |
83 | @Override
84 | public void onProviderEnabled(String provider) {}
85 |
86 | @Override
87 | public void onProviderDisabled(String provider) {}
88 | };
89 |
90 | private String locToDisplay(Location loc, int count) {
91 | String s="";
92 | s+=String.valueOf(count)+".";
93 | s+="lat="+U.truncate(loc.getLatitude(), 10)+", lon="+U.truncate(loc.getLongitude(), 10);
94 | if (loc.hasAltitude()) s+=", alt="+U.truncate(loc.getAltitude(), 6);
95 | if (loc.hasAccuracy()) s+=" Accuracy="+String.valueOf(loc.getAccuracy());
96 | return s;
97 | }
98 |
99 | private String isAcceptable(Location loc, int count) {
100 | String s="not ready";
101 | if (count >= mGpsMaxFixCount) { s="overcount"; }
102 | else if (loc.hasAccuracy() && loc.getAccuracy() <= mGpsAcceptableAccuracy) { s="converged"; }
103 | return s;
104 | }
105 |
106 | /*
107 | private void giveMockLocation() {
108 | // https://stackoverflow.com/questions/38251741/how-to-set-android-mock-gps-location
109 |
110 | //LocationManager lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
111 | //Criteria criteria = new Criteria();
112 | //criteria.setAccuracy( Criteria.ACCURACY_FINE );
113 | String mocLocationProvider = LocationManager.GPS_PROVIDER;//lm.getBestProvider( criteria, true );
114 |
115 | if ( mocLocationProvider == null ) {
116 | mPi.addProgress("No location provider found!");
117 | return;
118 | }
119 | mLocnManager.addTestProvider(mocLocationProvider, false, false,
120 | false, false, true, true, true, 0, 5);
121 | mLocnManager.setTestProviderEnabled(mocLocationProvider, true);
122 |
123 | Location mockLocation = new Location(mocLocationProvider); // a string
124 | mockLocation.setLatitude(-26.902038); // double
125 | mockLocation.setLongitude(-48.671337);
126 | mockLocation.setAltitude(234.0);
127 | mockLocation.setTime(System.currentTimeMillis());
128 | mockLocation.setAccuracy(3);
129 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
130 | mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
131 | }
132 | mLocnManager.setTestProviderLocation(mocLocationProvider, mockLocation);
133 | if (U.DEBUG) Log.d(U.TAG,"Giving mock gps location");
134 | }*/
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/HttpGetRequest.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.net.HttpURLConnection;
7 | import java.net.URL;
8 | import android.os.AsyncTask;
9 |
10 | import javax.net.ssl.HttpsURLConnection;
11 |
12 | interface HttpReceiver {
13 | public void onHttpReceived(String result);
14 | public void onHttpError(String error);
15 | }
16 |
17 | //@link https://medium.com/@JasonCromer/android-asynctask-http-request-tutorial-6b429d833e28
18 | public class HttpGetRequest extends AsyncTask {
19 | public static final String REQUEST_METHOD = "GET";
20 | public static final int READ_TIMEOUT = 15000;
21 | public static final int CONNECTION_TIMEOUT = 15000;
22 | private HttpReceiver receiver;
23 |
24 | public HttpGetRequest(HttpReceiver rec) {
25 | receiver=rec;
26 | }
27 |
28 | @Override
29 | protected String doInBackground(String... params) {
30 | String stringUrl = params[0];
31 | String result;
32 | String inputLine;
33 |
34 | try {
35 | //Create a URL object holding our url
36 | URL myUrl = new URL(stringUrl);
37 |
38 | //Create a connection
39 | HttpsURLConnection connection = (HttpsURLConnection) myUrl.openConnection();
40 | //Set methods and timeouts
41 | connection.setRequestMethod(REQUEST_METHOD);
42 | connection.setReadTimeout(READ_TIMEOUT);
43 | connection.setConnectTimeout(CONNECTION_TIMEOUT);
44 |
45 | //Connect to our url
46 | connection.connect();
47 |
48 | //Create a new InputStreamReader
49 | InputStreamReader streamReader = new InputStreamReader(connection.getInputStream());
50 |
51 | //Create a new buffered reader and String Builder
52 | BufferedReader reader = new BufferedReader(streamReader);
53 | StringBuilder stringBuilder = new StringBuilder();
54 |
55 | //Check if the line we are reading is not null
56 | while ((inputLine = reader.readLine()) != null) {
57 | stringBuilder.append(inputLine);
58 | }
59 |
60 | //Close our InputStream and Buffered reader
61 | reader.close();
62 | streamReader.close();
63 |
64 | //Set our result equal to our stringBuilder
65 | result = stringBuilder.toString();
66 | connection.disconnect();
67 | }
68 | catch (IOException e) {
69 | e.printStackTrace();
70 | result = null;
71 | }
72 |
73 | return result;
74 | }
75 |
76 | protected void onPostExecute(String result){
77 | super.onPostExecute(result);
78 | receiver.onHttpReceived(result);
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/HttpPostRequest.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.BufferedWriter;
5 | import java.io.InputStreamReader;
6 | import java.io.OutputStream;
7 | import java.io.OutputStreamWriter;
8 | import java.io.UnsupportedEncodingException;
9 | import java.net.HttpURLConnection;
10 | import java.net.URL;
11 | import java.net.URLEncoder;
12 | import java.util.Map;
13 |
14 | import android.os.AsyncTask;
15 | import android.util.Log;
16 |
17 | import javax.net.ssl.HttpsURLConnection;
18 |
19 | //@link https://stackoverflow.com/questions/9767952/how-to-add-parameters-to-httpurlconnection-using-post-using-namevaluepair
20 | public class HttpPostRequest extends AsyncTask {
21 | private HttpReceiver receiver;
22 |
23 | public HttpPostRequest(HttpReceiver rec) {
24 | receiver=rec;
25 | }
26 | public static String makePostDataString( Map params ) throws UnsupportedEncodingException{
27 | StringBuilder result = new StringBuilder();
28 | boolean first = true;
29 | for (Map.Entry entry : params.entrySet()){
30 | if (first) { first = false; }
31 | else { result.append("&"); }
32 | result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
33 | result.append("=");
34 | result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
35 | }
36 | return result.toString();
37 | }
38 |
39 | @Override
40 | protected String doInBackground(String... params) {
41 |
42 | String stringUrl = params[0];
43 | String postDataString = params[1];
44 | String response = "";
45 | URL url;
46 |
47 | try {
48 | url = new URL(stringUrl);
49 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
50 | conn.setReadTimeout(15000);
51 | conn.setConnectTimeout(15000);
52 | conn.setRequestMethod("POST");
53 | conn.setDoInput(true);
54 | conn.setDoOutput(true);
55 |
56 | OutputStream os = conn.getOutputStream();
57 | BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(os, "UTF-8"));
58 | writer.write(postDataString);
59 | writer.flush();
60 | writer.close();
61 | os.close();
62 | int responseCode=conn.getResponseCode();
63 |
64 | if (responseCode == HttpURLConnection.HTTP_OK) {
65 | String line;
66 | BufferedReader br=new BufferedReader(new InputStreamReader(conn.getInputStream()));
67 | while ((line=br.readLine()) != null) { response+=line; }
68 | }
69 | else { response=""; }
70 | }
71 | catch (Exception e) {
72 | e.printStackTrace();
73 | Log.e(U.TAG,"HttpPostRequest:"+e.getMessage());
74 | }
75 |
76 | return response;
77 | }
78 |
79 | protected void onPostExecute(String result){
80 | super.onPostExecute(result);
81 | receiver.onHttpReceived(result);
82 | }
83 |
84 |
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/JSbridge.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import android.util.Log;
6 |
7 | public class JSbridge {
8 | private MyRegistry mRegistry=MyRegistry.getInstance();
9 | private String mZoom;
10 | private String mCenterLon;
11 | private String mCenterLat;
12 | private PointList mPointList;
13 | private int mDirty=3;// load map on first use
14 | private String mViewTrackLatLonJson="[]";
15 | private String mViewTrackNamesJson="[]";
16 | private String mCurrentTrackLatLonJson="[]";
17 | private String mIsBounded="";
18 |
19 | @android.webkit.JavascriptInterface
20 | public String importLonLat() { return mCenterLon+","+mCenterLat; }
21 |
22 | @android.webkit.JavascriptInterface
23 | public String importLatLon() { return mCenterLat+","+mCenterLon; }
24 |
25 | public void exportLatLon(String lat,String lon) {
26 | mCenterLon=lon;
27 | mCenterLat=lat;
28 | }
29 |
30 | public void exportLatLon(LatLon p) {
31 | if ( ! p.hasCoords()) return;
32 | mCenterLon=p.lon;
33 | mCenterLat=p.lat;
34 | }
35 |
36 | @android.webkit.JavascriptInterface
37 | public void exportCenterLatLon(String lat,String lon) {
38 | mCenterLon=lon;
39 | mCenterLat=lat;
40 | }
41 |
42 | public String importCenterLatLon() {
43 | if (mCenterLat == null || mCenterLon == null) return "";
44 | return mCenterLat+","+mCenterLon;
45 | }
46 |
47 | @android.webkit.JavascriptInterface
48 | public String importZoom() {
49 | if (mZoom == null) mZoom=importDefaultZoom();
50 | return mZoom;
51 | }
52 |
53 | @android.webkit.JavascriptInterface
54 | public void saveZoom(String z) { mZoom=z; }
55 |
56 | public void exportZoom(String z) {
57 | mZoom=z;
58 | setDirty(2);
59 | }
60 |
61 | @android.webkit.JavascriptInterface
62 | public String importDefaultZoom() { return mRegistry.get("mapZoom"); }
63 |
64 | @android.webkit.JavascriptInterface
65 | public String importMapType() {
66 | return mRegistry.get("mapProvider");
67 | }
68 |
69 | @android.webkit.JavascriptInterface
70 | public String getKey() {
71 | boolean isYandex=mRegistry.get("mapProvider").indexOf("yandex") == 0;
72 | if (isYandex) {
73 | //Log.d(U.TAG,"JSbridge:"+"map key:"+MyRegistry.getInstance().getScrambled("yandexMapKey"));
74 | return mRegistry.getScrambled("yandexMapKey");
75 | }
76 | return "";
77 | }
78 |
79 | @android.webkit.JavascriptInterface
80 | public String getNamelessMarker() {
81 | Point loc=Model.getInstance().lastPosition;
82 | if (loc == null || ! loc.hasCoords()) return (new JSONArray()).toString();
83 | return loc.makeJsonPresentation(0).toString();
84 | }
85 |
86 | @android.webkit.JavascriptInterface
87 | public String importViewTrackLatLonJson() { return mViewTrackLatLonJson; }
88 |
89 | //public void addViewTrackLatLonJson(String json) {
90 | // mViewTrackLatLonJson=U.joinJsonArrays(mViewTrackLatLonJson,json);
91 | // setDirty(2);
92 | //}
93 |
94 | public void pushViewTrack(String json) {
95 | if (json.indexOf(",") >= 0 && ! json.startsWith("[[[")) {
96 | throw new U.RunException("Non-empty and non-3d-array argument"); }
97 | mViewTrackLatLonJson=U.pushJsonArray(mViewTrackLatLonJson,json);
98 | setDirty(2);
99 | }
100 |
101 | public void pushViewTrackName(String name) {
102 | mViewTrackNamesJson=U.joinJsonArrays(mViewTrackNamesJson, "[\"".concat(name).concat("\"]"));
103 | }
104 |
105 | @android.webkit.JavascriptInterface
106 | public String importViewTrackNamesJson() { return mViewTrackNamesJson; }
107 |
108 | @android.webkit.JavascriptInterface
109 | public String importCurrentTrackLatLonJson() { return mCurrentTrackLatLonJson; }
110 |
111 | public void replaceCurrentTrackLatLonJson(String json) {
112 | mCurrentTrackLatLonJson=json;
113 | setDirty(1);
114 | }
115 |
116 | public void consumeLocation(Point p) {
117 | if (p == null || ! p.hasCoords()) return;
118 | exportLatLon(p.lat,p.lon);
119 | setDirty(2);
120 | }
121 |
122 | public void consumeTrackpoint(Trackpoint p) {
123 | if ( ! mRegistry.getBool("enableTrack")) return;
124 | if (p == null || ! p.getType().equals("T")) return;
125 | String ll=p.makeJsonPresentation().toString();
126 | if (U.DEBUG) Log.d(U.TAG, "consumeTrackpoint:" + "Adding:" + ll);
127 | mCurrentTrackLatLonJson = StorageHelper.append2LatLonString(ll, p.isNewSegment(), mCurrentTrackLatLonJson);
128 | //Log.d(U.TAG, "consumeTrackpoint:" + "Result:" + mCurrentTrackLatLonJson);
129 | if (mRegistry.getBool("shouldCenterMapOnTrack")) {
130 | exportLatLon(p);
131 | }
132 | setDirty(1);
133 | }
134 |
135 | @android.webkit.JavascriptInterface
136 | public boolean importViewCurrentTrack() { return mRegistry.getBool("enableTrack"); }
137 |
138 | @android.webkit.JavascriptInterface
139 | public boolean importFollowCurrentTrack() { return mRegistry.getBool("shouldCenterMapOnTrack"); }
140 |
141 | @android.webkit.JavascriptInterface
142 | public String getIsBounded() { return mIsBounded; }
143 |
144 | @android.webkit.JavascriptInterface
145 | public void setBounded(String s) { mIsBounded=s; }
146 |
147 | public void setPointList(PointList pl) { mPointList=pl; }
148 |
149 | public void onPoinlistmodified() { setDirty(2); }
150 |
151 | @android.webkit.JavascriptInterface
152 | public String getMarkers() {
153 | return mPointList.makeJsonPresentation();
154 | }
155 |
156 | public int isDirty() { return mDirty; }
157 |
158 | public void setDirty(int level) {
159 | // 0 - clean, 1 - track, 2 - data, 3 - URI
160 | if (level > mDirty) mDirty=level;
161 | }
162 | public void clearDirty(int level) {
163 | if (level < mDirty) throw new U.RunException("Wrong clearDirty level="+level+", required="+mDirty);
164 | mDirty=0;
165 | }
166 |
167 | public boolean hasNoCenter() { return ( mCenterLat == null || mCenterLat.isEmpty() ); }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/JsonHelper.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | //@link https://gist.githubusercontent.com/codebutler/2339666/raw/f036bc29033bdd6478956f3d3bbeef16acb0ecd3/JsonHelper.java
4 | import org.json.JSONArray;
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 | import java.util.*;
8 |
9 | @SuppressWarnings("rawtypes")
10 | public class JsonHelper {
11 | public static Object toJSON(Object object) throws JSONException {
12 | if (object instanceof Map) {
13 | JSONObject json = new JSONObject();
14 | Map map = (Map) object;
15 | for (Object key : map.keySet()) {
16 | json.put(key.toString(), toJSON(map.get(key)));
17 | }
18 | return json;
19 | } else if (object instanceof Iterable) {
20 | JSONArray json = new JSONArray();
21 | for (Object value : ((Iterable)object)) {
22 | json.put(value);
23 | }
24 | return json;
25 | } else {
26 | return object;
27 | }
28 | }
29 |
30 | public static boolean isEmptyObject(JSONObject object) {
31 | return object.names() == null;
32 | }
33 |
34 | public static Map getMap(JSONObject object, String key) throws JSONException {
35 | return toMap(object.getJSONObject(key));
36 | }
37 |
38 | public static Map toMap(JSONObject object) throws JSONException {
39 | Map map = new HashMap();
40 | Iterator keys = object.keys();
41 | while (keys.hasNext()) {
42 | String key = (String) keys.next();
43 | map.put(key, fromJson(object.get(key)));
44 | }
45 | return map;
46 | }
47 |
48 | @SuppressWarnings("unchecked")
49 | public static List toList(JSONArray array) throws JSONException {
50 | List list = new ArrayList();
51 | for (int i = 0; i < array.length(); i++) {
52 | list.add(fromJson(array.get(i)));
53 | }
54 | return list;
55 | }
56 |
57 | private static Object fromJson(Object json) throws JSONException {
58 | if (json == JSONObject.NULL) {
59 | return null;
60 | } else if (json instanceof JSONObject) {
61 | return toMap((JSONObject) json);
62 | } else if (json instanceof JSONArray) {
63 | return toList((JSONArray) json);
64 | } else {
65 | return json;
66 | }
67 | }
68 |
69 | public static Map toMapSS(JSONObject object) throws JSONException {
70 | Map mapSO=JsonHelper.toMap(object);
71 | Map mapSS = new HashMap();
72 | for (Map.Entry entry : mapSO.entrySet()) {
73 | mapSS.put( entry.getKey(), Objects.toString(entry.getValue(),"") );
74 | }
75 | return mapSS;
76 | }
77 |
78 | public static String MapssToJSONString(Map mapSS) {
79 | return new JSONObject(mapSS).toString();
80 | }
81 |
82 | public static String filterQuotes(String js) {
83 | String r = js.replace("\"","");
84 | r = r.replaceAll("[{}]","");
85 | return r;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/LatLon.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | public class LatLon {
4 | public String lat="", lon="";
5 |
6 | public LatLon() {}
7 |
8 | public LatLon(String aLat, String aLon) {
9 | this.lat=aLat;
10 | this.lon=aLon;
11 | }
12 |
13 | public boolean hasCoords() {
14 | boolean no=( lat == null || lon == null || lat.isEmpty() || lon.isEmpty() );
15 | return ! no;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/ListHelper.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Map.Entry;
8 | import android.content.Context;
9 | import android.util.Log;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.ArrayAdapter;
14 | import android.widget.TextView;
15 |
16 | public class ListHelper {
17 | private boolean mToShowProximity=true;
18 | private Map mProximityMap=new HashMap();
19 | private Map mSortedProximityMap;
20 | private PointList mPointList;
21 | private String mSort="id";// id, rid (id, reverse), pr (proximity)
22 |
23 | public ListHelper(PointList pl) {
24 | mPointList=pl;
25 | }
26 |
27 | public void setSort(String s) {
28 | if (s.equals("pr")) {
29 | mSort=s;
30 | mToShowProximity=true;
31 | }
32 | else if (s.equals("rid")) { mSort=s; }
33 | else mSort="id";
34 | }
35 |
36 | public ArrayList getList() {
37 | ArrayList nl=new ArrayList();
38 | Point p;
39 | int l=mPointList.getSize(), i=0;
40 |
41 | if ( ! mPointList.hasProximityOrigin()) { mToShowProximity=false; }
42 | if (mToShowProximity) makeProximityMap();
43 | if (mSort.equals("pr") && mToShowProximity) {
44 | int key;
45 | sortProximityMap();
46 | for (Entry entry : mSortedProximityMap.entrySet()) {
47 | key=entry.getKey();
48 | p=mPointList.getById(key);
49 | nl.add(getPointPresentation(p));
50 | if (U.DEBUG) Log.d(U.TAG, "ListHelper:"+ "key:"+key+", proximity:"+entry.getValue());
51 | }
52 | }
53 | else if (mSort.equals("rid")) {
54 | for (i=l-1; i >= 0; i-=1) { nl.add(getPresentation(i)); }
55 | }
56 | else {// sort by id
57 | for (; i < l; i+=1) { nl.add(getPresentation(i)); }
58 | }
59 | return nl;
60 | }
61 |
62 | public int getIdByPosition(int position) {
63 | if (mSort.equals("pr") && mToShowProximity) {
64 | return (new ArrayList(mSortedProximityMap.keySet())).get(position);
65 | }
66 | else if (mSort.equals("rid")) {
67 | return mPointList.getIdByIndex(mPointList.getSize()-position-1);
68 | }
69 | return mPointList.getIdByIndex(position);
70 | }
71 |
72 | private String getPresentation(int position) {
73 | int k=mPointList.getIdByIndex(position);
74 | Point p=mPointList.getById(k);
75 | return getPointPresentation(p);
76 | }
77 |
78 | private void makeProximityMap() {
79 | mProximityMap=new HashMap();
80 | Point p;
81 | while ((p=mPointList.iterate()) != null) {
82 | mProximityMap.put(p.getId(), U.proximityM(p, mPointList.getProximityOrigin()));
83 | }
84 | mSortedProximityMap=null;
85 | }
86 |
87 | private void sortProximityMap() {
88 | mSortedProximityMap=U.sortByComparator(mProximityMap,true);
89 | }
90 |
91 | public static void printMap(Map map) {
92 | for (Entry entry : map.entrySet()) {
93 | System.out.println("Key : " + entry.getKey() + " Value : "+ entry.getValue());
94 | }
95 | }
96 |
97 | private String getPointPresentation(Point p) {
98 | String pro="";
99 | String s;
100 | if (p.isProtected()) pro="🔒 ";// R.string.lock gives an int
101 | s=pro + String.valueOf(p.getId()) + "." + p.getType() + "."+p.getComment();
102 | if (mToShowProximity) {
103 | s+=" "+proximityToKm(mProximityMap.get(p.getId()));
104 | }
105 | return s;
106 | }
107 |
108 | public String getLocationPresentation() {
109 | if ( ! mPointList.hasProximityOrigin()) { return null; }
110 | Point location=mPointList.getProximityOrigin();
111 | if ( location.getId() > 0 ) {
112 | return location.getId()+"."+location.getType()+"."+location.getComment();
113 | }
114 | return location.getType()+"."+location.time;
115 | }
116 |
117 | public static String proximityToKm(double pr) {
118 | if (pr == U.FAR) return "-";
119 | int km=(int) Math.floor(pr/1000);
120 | if (km == 0) return String.valueOf(Math.round(pr))+"m";
121 | if (km < 100) return String.valueOf( Math.round(pr/10) / 100d )+"km";// 100d , not 100 !!!
122 | return String.valueOf(km)+"km";
123 | }
124 |
125 | public boolean getShowProximity() { return mToShowProximity; }
126 | public void setShowProximity() { mToShowProximity=true; }
127 |
128 | public static class MyArrayAdapter extends ArrayAdapter {
129 | private Context mContext;
130 | private List mObjects;
131 | private int mLayoutId;
132 |
133 | public MyArrayAdapter(Context context, int textViewResourceId, List objects) {
134 | super(context, -1, objects);
135 | mContext=context;
136 | mObjects=objects;
137 | mLayoutId=textViewResourceId;
138 | }
139 |
140 | @Override
141 | public View getView(int position, View convertView, ViewGroup parent) {
142 | LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
143 | View rowView = inflater.inflate(mLayoutId, parent, false);
144 | TextView textView = (TextView) rowView.findViewById(R.id.tvItem);
145 | textView.setText(mObjects.get(position));
146 | return rowView;
147 | }
148 |
149 | public void changeObjects(List objects) {
150 | if (U.DEBUG) Log.d(U.TAG,"MyArrayAdapter:"+"Changing the data list");
151 | //mObjects=objects; // crashes on point deletion, mObjects must be kept !
152 | U.refillList(mObjects, objects);
153 | notifyDataSetChanged();
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/MapViewer.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.content.Context;
4 | import android.webkit.ConsoleMessage;
5 | import android.webkit.CookieManager;
6 | import android.webkit.WebChromeClient;
7 | import android.webkit.WebStorage;
8 | import android.widget.TextView;
9 | import android.util.Log;
10 | import android.webkit.WebSettings;
11 | import android.webkit.WebView;
12 | import android.webkit.WebViewClient;
13 |
14 | public class MapViewer extends PointIndicator implements PointReceiver {
15 |
16 | private MyRegistry mRegistry=MyRegistry.getInstance();
17 | private WebView wvWebView;
18 | private String mPageURI;
19 | private JSbridge mJSbridge = Model.getInstance().getJSbridge();
20 |
21 | public MapViewer(TextView tvP, TextView tvD, WebView wvW) {
22 | super(tvP, tvD);
23 | wvWebView=wvW;
24 | wvWebView.setWebViewClient(new WebViewClient() {
25 | @Override
26 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
27 | twData.setText("Blocked loading "+url);
28 | return true;// false will do loading
29 | }
30 | });
31 | WebSettings webSettings = wvWebView.getSettings();
32 | webSettings.setJavaScriptEnabled(true);
33 | webSettings.setAllowContentAccess(false);
34 | webSettings.setAllowFileAccessFromFileURLs(false);
35 | }
36 |
37 | @Override
38 | public void onPointavailable(Point p) {
39 | redraw();
40 | }
41 |
42 | public void redraw() {
43 | if (mJSbridge.hasNoCenter()) {
44 | if (U.DEBUG) Log.d(U.TAG,"Show wallpaper from redraw");
45 | showWallpaper();
46 | return;
47 | }
48 | int c=mJSbridge.isDirty();
49 | switch (c) {
50 | case 3:
51 | showMap();
52 | break;
53 | case 2:
54 | reloadData();
55 | break;
56 | case 1:
57 | reloadTrack();
58 | break;
59 | case 0:
60 | break;
61 | default:
62 | throw new U.RunException("Unknown JSbribge dirty="+Integer.toString(c));
63 | }
64 | }
65 |
66 | public void showMap() {
67 | if (mJSbridge.hasNoCenter()) {
68 | if (U.DEBUG) Log.d(U.TAG,"Show wallpaper from showMap");
69 | showWallpaper();
70 | return;
71 | }
72 | mPageURI=choosePage(mRegistry.get("mapProvider"));
73 | wvWebView.addJavascriptInterface(mJSbridge, "JSbridge");
74 | redirectConsole(wvWebView);
75 | wvWebView.loadUrl(mPageURI);
76 | mJSbridge.clearDirty(3);
77 | }
78 |
79 | private String choosePage(String mapProvider) {
80 | final String yandexPage="file:///android_asset/webMaps/ya.html";
81 | final String leafletjsPage="file:///android_asset/webMaps/leafletjs.html";
82 | String[] pt=mapProvider.split(" ");
83 | if (pt.length != 2) throw new U.RunException("Wrong mapProvider="+mapProvider);
84 | if ( isLeaflet(pt[0]) ) { return leafletjsPage; }
85 | else if (pt[0].equals("yandex")) { return yandexPage; }
86 | throw new U.RunException("Wrong mapProvider="+mapProvider);
87 | }
88 |
89 | private boolean isLeaflet(String provider) {
90 | final String[] known=new String[] {"osm","opentopo","blank","google"};
91 | return U.arrayContains(known,provider);
92 | }
93 |
94 | private void showWallpaper() {
95 | final String wallpaperPage="file:///android_asset/webMaps/wallpaper.html";
96 | wvWebView.loadUrl(wallpaperPage);
97 | }
98 |
99 | private void reloadTrack() {
100 | String reloadTrack="(function() { window.dispatchEvent(onTrackreloadEvent); })();";
101 | wvWebView.evaluateJavascript(reloadTrack,null);
102 | mJSbridge.clearDirty(1);
103 | }
104 |
105 | private void reloadData() {
106 | String reloadData="(function() { window.dispatchEvent(onDatareloadEvent); })();";
107 | wvWebView.evaluateJavascript(reloadData,null);
108 | mJSbridge.clearDirty(2);
109 | }
110 |
111 | public void purge(Context context) {
112 | showWallpaper();
113 | WebStorage.getInstance().deleteAllData();
114 | CookieManager.getInstance().removeAllCookies(null);
115 | CookieManager.getInstance().flush();
116 | wvWebView.clearCache(true);
117 | wvWebView.clearFormData();
118 | wvWebView.clearHistory();
119 | wvWebView.clearSslPreferences();
120 | context.deleteDatabase("webview.db");
121 | context.deleteDatabase("webviewCache.db");
122 | showMap();
123 | }
124 |
125 | private void redirectConsole(WebView myWebView) {
126 | if ( ! U.DEBUG) return;
127 | //myWebView.getSettings().setAllowUniversalAccessFromFileURLs(true);
128 | //myWebView.getSettings().setAllowFileAccessFromFileURLs(true);
129 | WebView.setWebContentsDebuggingEnabled(true);
130 | myWebView.setWebChromeClient(new WebChromeClient() {
131 | public boolean onConsoleMessage(ConsoleMessage cm) {
132 | if (U.DEBUG) {
133 | String msg = "WebView console:" + cm.message() + ", line "
134 | + cm.lineNumber() + " of " + cm.sourceId();
135 | Log.d(U.TAG, msg);
136 | }
137 | return true;
138 | }
139 | });
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/Model.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | public class Model {
7 | private static Model sModel;
8 | public Point lastCell;
9 | public Point lastGps;
10 | public Point lastPosition;
11 | private CellPointFetcher mCellPointFetcher;
12 | private GpsPointFetcher mGpsPointFetcher;
13 | private PointList mPointList;
14 | private StorageHelper mStorageHelper;
15 | private JSbridge mJSbridge;
16 | private TrackStorage mTrackStorage;
17 | private TrackListener mTrackListener;
18 | public CellPointFetcher getCellPointFetcher() { return mCellPointFetcher; }
19 | public GpsPointFetcher getGpsPointFetcher() { return mGpsPointFetcher; }
20 | public PointList getPointList() { return mPointList; }
21 | public StorageHelper getStorageHelper() { return mStorageHelper; }
22 | public JSbridge getJSbridge() { return mJSbridge; }
23 | public TrackStorage getTrackStorage() { return mTrackStorage; }
24 | public TrackListener getTrackListener() { return mTrackListener; }
25 | private boolean mIsFresh=true;
26 |
27 | public static Model getInstance() {
28 | if (sModel == null) { sModel=new Model(); }
29 | return sModel;
30 | }
31 |
32 | private Model() {
33 | mCellPointFetcher = new CellPointFetcher();
34 | mGpsPointFetcher = new GpsPointFetcher();
35 | mStorageHelper = new StorageHelper();
36 | mPointList = new PointList( 0 , mStorageHelper);// must be resized after reading StoredPreferences in MainActivity
37 | mJSbridge = new JSbridge();
38 | mJSbridge.setPointList(mPointList);
39 | mTrackStorage = new TrackStorage();
40 | mTrackListener = new TrackListener(mTrackStorage);
41 | }
42 |
43 | public U.Summary[] loadData(Context context, MyRegistry mrg) {
44 | U.Summary[] res = new U.Summary[2];
45 | res[0] = res[1] = null;
46 | if ( ! mIsFresh) return null;
47 | try {
48 | mPointList.adoptMax(mrg.getInt("maxPoints"));
49 | String targetPath = mStorageHelper.getWorkingFolder(context, mrg);
50 | mStorageHelper.init(targetPath, mrg.get("myFile"));
51 | res[0] = mPointList.load();
52 | if (U.DEBUG) Log.d(U.TAG,"MainPageFragment:"+ "Loaded "+res[0].adopted+" points");
53 |
54 | mTrackStorage.initTargetDir(targetPath);
55 | if ( mrg.getBool("enableTrack")) {
56 | TrackStorage.Track2LatLonJSON converter = mTrackStorage.getTrack2LatLonJSON();
57 | String buf = converter.file2LatLonJSON();
58 | res[1] = converter.getResults();
59 | mJSbridge.replaceCurrentTrackLatLonJson(buf);
60 | }
61 | }
62 | catch (Exception e) {
63 | Log.e(U.TAG,"MainPageFragment:"+e.getMessage());
64 | return null;
65 | }
66 | mIsFresh=false;
67 | return res;
68 | }
69 |
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/MyRegistry.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.os.Build;
6 | import android.preference.PreferenceManager;
7 | import android.util.Log;
8 |
9 | import org.json.JSONException;
10 | import org.json.JSONObject;
11 |
12 | import java.io.IOException;
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | // A Singleton registry to keep all parameters
17 | public class MyRegistry {
18 | private static MyRegistry sMe;
19 | private static Map sMap;
20 | private static Context sAppContext=null;
21 |
22 | private MyRegistry() {}
23 |
24 | public static void initMap() throws JSONException {
25 | String d = getDefaultsString();
26 | Map defaults = JsonHelper.toMapSS(new JSONObject(d));
27 | sMap=new HashMap(defaults);
28 | }
29 |
30 | public static String toJson() {
31 | return JsonHelper.MapssToJSONString(sMap);
32 | }
33 |
34 | public static MyRegistry getInstance(Context activity) {
35 | sAppContext = activity.getApplicationContext();
36 | return getInstance();
37 | }
38 | public static MyRegistry getInstance() {
39 | if(sMe == null) {
40 | sMe=new MyRegistry();
41 | try {
42 | MyRegistry.initMap();
43 | }
44 | catch (JSONException e) {
45 | Log.e(U.TAG,"MyRegistry:"+e.toString());
46 | }
47 | }
48 | return sMe;
49 | }
50 |
51 | public String get(String key) {
52 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
53 | return sMap.get(key);
54 | }
55 |
56 | public int getInt(String key) {
57 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
58 | return Integer.parseInt(sMap.get(key));
59 | }
60 |
61 | public boolean getBool(String key) {
62 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
63 | return Boolean.parseBoolean(sMap.get(key));
64 | }
65 |
66 | public String getScrambled(String key) {
67 | int[] transpose = {2,3,5,6,8,9,13,19,25,31};
68 | char[] ca = sMap.get(key).trim().toCharArray();
69 | int l=ca.length;
70 | int lHalf=(int) Math.floor(l/2.);
71 | char c;
72 | int sourceIndex,targetIndex;
73 |
74 | if (l < 3) return new String(ca);
75 | for (int i=0; i < transpose.length; i+=1) {
76 | sourceIndex = transpose[i];
77 | if (sourceIndex >= lHalf) break;
78 | targetIndex = l-sourceIndex;
79 | c=ca[sourceIndex];
80 | ca[sourceIndex] = ca[targetIndex];
81 | ca[targetIndex] = c;
82 | }
83 | return new String(ca);
84 | }
85 |
86 | public void set(String key, String val) {
87 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
88 | sMap.put(key,val);
89 | }
90 |
91 | public void set(String key, boolean val) {
92 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
93 | sMap.put(key, String.valueOf(val));
94 | }
95 |
96 | public void set(String key, int val) {
97 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
98 | sMap.put(key, String.valueOf(val));
99 | }
100 |
101 | public void set(String key, Object val) {
102 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
103 | sMap.put(key,val.toString());
104 | }
105 |
106 | public void setInt(String key, int val) {
107 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
108 | sMap.put(key, String.valueOf(val));
109 | }
110 |
111 | public void setBool(String key, boolean val) {
112 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
113 | sMap.put(key, String.valueOf(val));
114 | }
115 |
116 | public boolean keyExists(String key) {
117 | return sMap.keySet().contains(key);
118 | }
119 |
120 | private static String getDefaultsString() {
121 | String androidSince11 = Build.VERSION.SDK_INT >= 30 ? "true" : "false";
122 |
123 | String defs = "{\"cellResolver\":\"none\",\"mapProvider\":\"osm map\",\"mapZoom\":\"17\",\"maxPoints\":\"30\","
124 | + "\"useTrash\":\"false\",\"gpsAcceptableAccuracy\":\"8\",\"gpsMaxFixCount\":\"10\","
125 | + "\"myFile\":\"current.csv\","
126 | + "\"yandexMapKey\":\"\", \"yandexLocatorKey\":\"\", \"isKeylessDistro\":\"false\","
127 | + "\"gpsMinDistance\":\"12\", \"gpsMinDelayS\":\"10\", \"gpsTimeoutS\":\"120\","
128 | + "\"enableTrack\":\"true\", \"shouldCenterMapOnTrack\":\"true\", \"useTowerFolder\":\"false\","
129 | + "\"useMediaFolder\":\""+androidSince11+"\", \"askNotificationPermission\":\"true\","
130 | + "\"theme\":\"auto\""
131 | + "}";
132 | return defs;
133 | }
134 |
135 | public static final String[] INT_KEYS = new String[] {
136 | "mapZoom", "maxPoints", "gpsMinDistance", "gpsMinDelayS" } ;
137 |
138 | public static final String[] APIS = new String[] { "yandexMapKey","yandexLocatorKey" };
139 |
140 | public void readFromShared() throws U.DataException {
141 | if (sAppContext == null) throw new U.DataException("APPCONTEXT not set");
142 | String k;
143 | String v;
144 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(sAppContext);
145 | for (Map.Entry e : MyRegistry.sMap.entrySet()) {
146 | k=e.getKey();
147 | if ( prefs.contains(k) ) {
148 | v = String.valueOf(prefs.getAll().get(k));
149 | this.set(k, U.enforceInt(MyRegistry.INT_KEYS, k, v));
150 | }
151 | }
152 | }
153 |
154 | public void saveToShared(String key) {
155 | if ( ! sMap.keySet().contains(key)) throw new U.RunException("Unknown key="+key);
156 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(sAppContext);
157 | SharedPreferences.Editor editor = prefs.edit();
158 | editor.putString(key, sMap.get(key)).commit();
159 | }
160 |
161 | private void syncSecret(String key, String assetFileName) {
162 | String s="";
163 |
164 | if (sMap.get(key).length() > 3) return; // already synced
165 | // look in BuildConfig
166 | if (U.classHasField(BuildConfig.class, key)) {
167 | try {
168 | this.set(key, Class.forName("BuildConfig").getField(key));
169 | this.set(key, this.getScrambled(key));
170 | this.saveToShared(key);
171 | if (U.DEBUG) Log.d(U.TAG, "MyRegistry:"+" found a key in buildconfig: "+key);
172 | return;
173 | }
174 | catch (ClassNotFoundException e) { throw new U.RunException("This should not happen 1"); }
175 | catch (NoSuchFieldException e) { throw new U.RunException("This should not happen 2"); }
176 | }
177 |
178 | // look in assets
179 | try {
180 | s=U.readAsset(sAppContext, assetFileName).trim();
181 | this.set(key,s);
182 | this.saveToShared(key);
183 | }
184 | catch (IOException e) {
185 | if (U.DEBUG) Log.d(U.TAG, "MyRegistry:"+"Missing "+assetFileName);
186 | }
187 | }
188 |
189 | public void syncSecrets() {
190 | syncSecret( "yandexMapKey", "_yandexmap.txt");
191 | syncSecret( "yandexLocatorKey", "_yandexlocator.txt");
192 | if (notAllKeys()) {
193 | this.set("isKeylessDistro", true);
194 | this.saveToShared( "isKeylessDistro");
195 | if (U.DEBUG) Log.d(U.TAG, "MyRegistry:"+"This is a distro without API keys");
196 | }
197 | }
198 |
199 | public boolean noAnyKeys() {
200 | int i=0;
201 | String k;
202 | for (i=0; i < APIS.length; i+=1) {
203 | if ( ! sMap.get(APIS[i]).isEmpty()) return false;
204 | }
205 | return true;
206 | }
207 |
208 | public boolean notAllKeys() {
209 | int i=0;
210 | String k;
211 | for (i=0; i < APIS.length; i+=1) {
212 | if (sMap.get(APIS[i]).isEmpty()) return true;
213 | }
214 | return false;
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/PermissionAwareFragment.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.Manifest;
4 | import android.annotation.TargetApi;
5 | import android.content.pm.PackageManager;
6 | import android.os.Build;
7 | //import android.support.annotation.RequiresApi;
8 | import androidx.annotation.RequiresApi;
9 |
10 | import android.util.Log;
11 | import android.util.SparseArray;
12 |
13 | interface PermissionReceiver {
14 | public void receivePermission(int reqCode, boolean isGranted);
15 | }
16 |
17 | public abstract class PermissionAwareFragment extends androidx.fragment.app.Fragment {
18 | private SparseArray mPermissionReceivers = new SparseArray();
19 | private int mPermissionAttempts = 5; // to break loop on receiving coarse location on SDK .= 31
20 |
21 |
22 | @TargetApi(23)
23 | public void genericRequestPermission(String permCode, int reqCode, PermissionReceiver receiver) {
24 | if (mPermissionAttempts-- > 0) {
25 | mPermissionReceivers.put(reqCode, receiver);
26 | if (U.DEBUG) Log.d(U.TAG, "Requesting user. code=" + reqCode);
27 | requestPermissions(new String[]{permCode}, reqCode);
28 | }
29 | else {
30 | Log.e(U.TAG, "Permission attempts exhausted. code=" + reqCode);
31 | }
32 | }
33 |
34 | @Override
35 | public void onRequestPermissionsResult(int reqCode, String[] permissions, int[] grantResults) {
36 | boolean isGranted = ( grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED );
37 | if (U.DEBUG) Log.d(U.TAG,"grantResults length="+grantResults.length);
38 | mPermissionReceivers.get(reqCode).receivePermission(reqCode,isGranted);
39 | }
40 |
41 | @RequiresApi(api = Build.VERSION_CODES.M)
42 | protected boolean checkLocationPermission() {
43 | int cl = getActivity().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
44 | if (U.DEBUG) Log.d(U.TAG,"cl="+cl+"/"+PackageManager.PERMISSION_GRANTED);
45 | return (cl == PackageManager.PERMISSION_GRANTED);
46 | }
47 |
48 | protected void askLocationPermission(PermissionReceiver subFragment) {
49 | genericRequestPermission(Manifest.permission.ACCESS_FINE_LOCATION, 1, subFragment);
50 | }
51 |
52 | @RequiresApi(api = Build.VERSION_CODES.M)
53 | protected boolean checkStoragePermission() {
54 | int cl = getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
55 | if (U.DEBUG) Log.d(U.TAG,"cl="+cl+"/"+ PackageManager.PERMISSION_GRANTED);
56 | return (cl == PackageManager.PERMISSION_GRANTED);
57 | }
58 |
59 | protected void askStoragePermission(PermissionReceiver subFragment) {
60 | genericRequestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, 2, subFragment);
61 | }
62 |
63 | protected void askNotificationPermission(PermissionReceiver subFragment) {
64 | genericRequestPermission(Manifest.permission.POST_NOTIFICATIONS, 3, subFragment);
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/Point.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.util.Set;
4 | import org.json.JSONArray;
5 | import android.location.Location;
6 | import android.text.TextUtils;
7 | import java.text.SimpleDateFormat;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Calendar;
11 | import java.util.Collections;
12 | import java.util.HashSet;
13 | import java.util.List;
14 |
15 | public class Point extends LatLon implements Cloneable {
16 | private String mType;
17 | private static final Set TYPES = new HashSet(Arrays.asList(
18 | new String[] {"cell","gps","mark"}
19 | ));
20 | public String alt;
21 | public String range;
22 | public String cellData;
23 | public String time;
24 | private int mId;
25 | private String mComment="";
26 | private String mNote="";
27 | private String mSym="";
28 | private boolean mProtect=false;
29 | public static final List FIELDS = Collections.unmodifiableList(Arrays.asList(
30 | new String[] {"id","type","comment","protect","lat","lon","alt","range","time","cellData","note","sym"}
31 | ));
32 | public static final String SEP = ";";
33 | public static final String NL = "\n";
34 |
35 | public Point() { }
36 |
37 | public Point(String t) {
38 | setType(t);
39 | }
40 |
41 | public Point(String t, String lat, String lon) {
42 | setType(t);
43 | this.lat=lat;
44 | this.lon=lon;
45 | }
46 |
47 | public Point(String t, Location loc) {
48 | setType(t);
49 | this.lat=String.valueOf(loc.getLatitude());
50 | this.lon=String.valueOf(loc.getLongitude());
51 | if (loc.hasAltitude()) this.alt=String.valueOf(loc.getAltitude());
52 | if (loc.hasAccuracy()) this.range=String.valueOf(loc.getAccuracy());
53 | }
54 |
55 | public Object clone() {
56 | try { return super.clone(); }
57 | catch ( CloneNotSupportedException e ) { return null; }
58 | }
59 |
60 | public void setType(String t) {
61 | if ( ! TYPES.contains(t) ) throw new U.RunException("Unhnown type="+t);
62 | mType=t;
63 | }
64 |
65 | public String getType() { return mType; }
66 |
67 | public void setComment(String s) {
68 | int maxChars=20;
69 | if (s.length() > maxChars) s=s.substring(0, maxChars);
70 | mComment=filterChars(s);
71 | }
72 |
73 | public String getNote() { return mNote; }
74 |
75 | public void setNote(String s) {
76 | int maxChars=100;
77 | if (s.length() > maxChars) s=s.substring(0, maxChars);
78 | mNote=filterChars(s);
79 | }
80 |
81 | public String getComment() { return mComment; }
82 |
83 | public void setId(int i) { mId=i; }
84 | public int getId() { return mId; }
85 |
86 | public static String getDate() {
87 | SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
88 | String fd = df.format(Calendar.getInstance().getTime());
89 | return fd;
90 | }
91 |
92 | public void setCurrentTime() { this.time=Point.getDate(); }
93 |
94 | public boolean isProtected() { return mProtect; }
95 | public void protect() { mProtect=true; }
96 | public void unprotect() { mProtect=false; }
97 |
98 | public JSONArray makeJsonPresentation(int index) {
99 | JSONArray ja=new JSONArray();
100 | if (index < 0) index=mId;
101 | ja.put(mType);
102 | ja.put(lat);
103 | ja.put(lon);
104 | String text=String.valueOf(index);
105 | if (mComment != null && ! mComment.isEmpty()) text+="."+mComment;
106 | ja.put(text);
107 | return ja;//.toString();
108 | }
109 |
110 | public String toCsv() {
111 | List ls=new ArrayList();
112 | ls.add(ne(mId));
113 | ls.add(ne(mType));
114 | ls.add(ne(mComment));
115 | ls.add(ne(mProtect));
116 | ls.add(ne(lat));
117 | ls.add(ne(lon));
118 | ls.add(ne(alt));
119 | ls.add(ne(range));
120 | ls.add(ne(time));
121 | ls.add(ne(cellData));
122 | ls.add(ne(mNote));
123 | ls.add(ne(mSym));
124 | String s= TextUtils.join(Point.SEP, ls);
125 | return s;
126 | }
127 |
128 | public Point fromCsv(String s) throws U.DataException {
129 | s=s.trim();
130 | String[] ls=TextUtils.split(s, Point.SEP);
131 | if (ls.length != Point.FIELDS.size()) {
132 | throw new U.DataException("Source has "+ls.length+" fields, while "+Point.FIELDS.size()+" are required");
133 | }
134 | mId=eInt(ls[0]);
135 | if ( ! TYPES.contains(ls[1])) {
136 | throw new U.DataException("Unknown type="+ls[1]+"!");
137 | }
138 | setType(ls[1]);
139 | setComment(ls[2]);
140 | mProtect=eBool(ls[3]);
141 | lat=ls[4];
142 | lon=ls[5];
143 | alt=ls[6];
144 | range=ls[7];
145 | time=ls[8];
146 | cellData=ls[9];
147 | mNote=ls[10];
148 | mSym=ls[11];
149 | return this;
150 | }
151 |
152 | private int eInt(String s) {
153 | if (s.isEmpty()) return 0;
154 | return Integer.valueOf(s);
155 | }
156 |
157 | private boolean eBool(String s) {
158 | if (s.isEmpty() || s.equals("false")) return false;
159 | return true;
160 | }
161 |
162 | private String ne(String s) {
163 | if (s == null || s.isEmpty()) return "";
164 | if (s.contains(Point.SEP)) throw new U.RunException("Misplaced separator");
165 | if (s.contains(Point.NL)) throw new U.RunException("Misplaced NL");
166 | return s;
167 | }
168 |
169 | private String ne(int i) {
170 | if (i <= 0) return "";
171 | return String.valueOf(i);
172 | }
173 |
174 | private String ne(boolean b) {
175 | if (b) return "true";
176 | return "";
177 | }
178 |
179 | public static String filterChars(String s) {
180 | // for a safe export to CSV and GPX
181 | s=s.replace(Point.SEP,",");
182 | s=s.replace(Point.NL,"/");
183 | s=s.replaceAll("&<>","*");
184 | return s;
185 | }
186 |
187 | public static String filterCharsMore(String s) {
188 | s=s.replace("\"","");
189 | s=s.replace(" ","_");
190 | s=s.replaceAll("[,;&<>~]","*");
191 | return filterChars(s);
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/PointFetcher.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.pm.PackageManager;
5 | //import android.support.v4.app.FragmentActivity;
6 | import androidx.fragment.app.FragmentActivity;
7 | import android.util.Log;
8 |
9 | interface PointReceiver {
10 | public void onPointavailable(Point p);
11 | }
12 |
13 | public abstract class PointFetcher implements PermissionReceiver {
14 | protected FragmentActivity mActivity;
15 | protected PermissionAwareFragment mFragm;
16 | protected PointIndicator mPi;
17 | protected PointReceiver mPointReceiver;
18 | protected String mStatus="not run";
19 | protected Point mPoint=null;
20 | protected boolean mToUpdateLocation=true;
21 |
22 | abstract String getPermissionType();
23 | abstract int getPermissionCode();
24 |
25 | public String getStatus() { return mStatus; }
26 |
27 | public Point getPoint() { return mPoint; }
28 |
29 | public void setFragment(PermissionAwareFragment f) {
30 | mFragm=f;
31 | mActivity=f.getActivity();
32 | }
33 |
34 | public void clearFragment() {
35 | mFragm=null;
36 | mActivity=null;
37 | }
38 |
39 | protected boolean tryGiveMockLocation() { return false; }
40 |
41 | public void go(PointIndicator pi, PointReceiver pr) {
42 | mPi=pi;
43 | mPointReceiver=pr;
44 | if (tryGiveMockLocation()) return;
45 | mPi.initProgress();
46 | //mPi.addProgress("Checking permission...");
47 | if (U.DEBUG) Log.d(U.TAG, "Checking permission...");
48 | boolean hasLoctnPerm=checkLocalPermission();
49 | if ( ! hasLoctnPerm) {
50 | if (U.DEBUG) Log.d(U.TAG, "negative, asking user...");
51 | //mPi.addProgress("negative, asking user...");
52 | requestPermission();
53 | return;
54 | }
55 | if (U.DEBUG) Log.d(U.TAG, "positive");
56 | //mPi.addProgress("positive");
57 | afterLocationPermissionOk();
58 | }
59 |
60 | @TargetApi(23)
61 | private boolean checkLocalPermission() {
62 | if (mActivity == null) {
63 | Log.e(U.TAG, "checkLocalPermission:"+"No activity attached");
64 | return false;
65 | }
66 | int cl=mActivity.checkSelfPermission(getPermissionType());
67 | if (U.DEBUG) Log.d(U.TAG,"cl="+cl+"/"+PackageManager.PERMISSION_GRANTED);
68 | return (cl == PackageManager.PERMISSION_GRANTED);
69 | }
70 |
71 | private void requestPermission() {
72 | mFragm.genericRequestPermission(getPermissionType(), getPermissionCode(), this);
73 | }
74 |
75 | public void receivePermission(int reqCode, boolean isGranted) {
76 | if ( ! isGranted) {
77 | if (U.DEBUG) Log.d(U.TAG, "denied");
78 | mPi.addProgress("denied");
79 | mStatus="denied";
80 | return;
81 | }
82 | mPi.addProgress("granted");
83 | afterLocationPermissionOk();
84 | }
85 |
86 | abstract void afterLocationPermissionOk();
87 |
88 | protected void onPointavailable(Point p) {
89 | if ( p.hasCoords() ) mPi.hideProgress();// if it's an unresolved cell - keep progress visible
90 | p.setCurrentTime();
91 | Model mm = Model.getInstance();
92 | if (p.getType().equals("cell")) mm.lastCell=p;
93 | else if (p.getType().equals("gps")) mm.lastGps=p;
94 | if (mToUpdateLocation && p.hasCoords()) {
95 | mm.lastPosition=p;
96 | mm.getPointList().setProximityOrigin(p);
97 | mm.getJSbridge().consumeLocation(p);
98 | }
99 | mPointReceiver.onPointavailable(p);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/PointIndicator.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | public class PointIndicator {
7 |
8 | protected TextView twProgress;
9 | protected TextView twData;
10 |
11 | public PointIndicator(TextView twP, TextView twD) {
12 | twProgress=twP;
13 | twData=twD;
14 | twProgress.setText("");
15 | twData.setText("");
16 | }
17 |
18 | public void initProgress() {
19 | twProgress.setText("");
20 | showProgress();
21 | }
22 |
23 | public void showProgress(String p) {
24 | twProgress.setText(p);
25 | showProgress();
26 | }
27 |
28 | public void showProgress() {
29 | twProgress.setVisibility(View.VISIBLE);
30 | }
31 |
32 | public void addProgress(String p) { addProgress(p, ", "); }
33 |
34 | public void addProgress(String p, String separator) {
35 | String t = (String) twProgress.getText();
36 | if (t.length() > 0) t=t+separator;
37 | twProgress.setVisibility(View.VISIBLE);
38 | twProgress.setText(t+p);
39 | }
40 |
41 | public void hideProgress() {
42 | //twProgress.setText("");
43 | twProgress.setVisibility(View.GONE);
44 | }
45 |
46 | public void showData(String d) {
47 | twData.setVisibility(View.VISIBLE);
48 | twData.setText(d);
49 | }
50 |
51 | public void addData(String d, String separator) {
52 | twData.setVisibility(View.VISIBLE);
53 | String t = (String) twData.getText();
54 | if (t.length() > 0) t=t+separator;
55 | twData.setText(t+d);
56 | }
57 |
58 | public void addData(String d) { addData(d, ","); }
59 |
60 | public void hideData() {
61 | //twProgress.setText("");
62 | twData.setVisibility(View.GONE);
63 | }
64 |
65 | public static String truncate(String s,int max) {
66 | if ( s.length() > max ) return s.substring(0,max);
67 | return s;
68 | }
69 |
70 | public static String floor(String s) {
71 | if (s == null) return "";
72 | if (s.isEmpty()) return s;
73 | String r=s;
74 | int p=s.indexOf(".");
75 | if (p > 0) r=r.substring(0,p);
76 | return r;
77 | }
78 |
79 | public void clearIndicator() {
80 | twProgress.setText("");
81 | twData.setText("");
82 | }
83 |
84 | public void hideIndicator() {
85 | hideProgress();
86 | hideData();
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/PointList.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.io.IOException;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import org.json.JSONArray;
7 | import android.util.Log;
8 | import android.util.SparseArray;
9 |
10 | public class PointList {
11 |
12 | public static final String OK="OK";
13 | private SparseArray mArr = new SparseArray();
14 | private int mNext;
15 | private int mMax;
16 | private int mFirst;
17 | private boolean mDirty=false;
18 | private int ii=0;
19 | private StorageHelper mStorageHelper;
20 | private List mRemoved=new ArrayList();
21 | private Point mProximityOrigin;
22 | private boolean mUseMockRegistry=false;
23 | private boolean mShouldUseTrash;
24 |
25 | public PointList(int max) {
26 | fastClear();
27 | mMax=max;
28 | }
29 |
30 | public PointList(int max, StorageHelper sh) {
31 | mStorageHelper=sh;
32 | fastClear();
33 | mMax=max;
34 | }
35 |
36 | public void clear() {
37 | // puts removed points to trash
38 | int l=mArr.size();
39 | int i=0;
40 | for (; i < l; i+=1) {
41 | mRemoved.add(mArr.valueAt(0));
42 | mArr.removeAt(0);
43 | //onPointdeleted();
44 | }
45 | mFirst=-1;
46 | mNext=1;
47 | mDirty=true;
48 | }
49 |
50 | public void fastClear() {
51 | // does not use trash
52 | mArr.clear();
53 | mFirst=-1;
54 | mNext=1;
55 | mDirty=true;
56 | }
57 |
58 | public int adoptMax(int m) {
59 | if (m < 1) return mMax;
60 | // cannot be set to less than actual count
61 | if (m < mArr.size()) { mMax=mArr.size(); } //if m < mMax
62 | else { mMax=m; }
63 | return mMax;
64 | }
65 |
66 | public int getMax() { return mMax; }
67 |
68 | public int getSize() { return mArr.size(); }
69 |
70 | public String addAsNext(Point p) throws U.DataException {
71 | String s="";
72 | if (mNext != p.getId()) throw new U.RunException("mNext="+mNext+", id="+p.getId());
73 | if (mArr.size() >= mMax) {
74 | String r=trimFirst();
75 | if (r != null) s="Removed "+r;
76 | }
77 | mArr.append(mNext, p);
78 | mDirty=true;
79 | mNext+=1;
80 | return s;
81 | }
82 |
83 | public String addAndShiftNext(Point p) throws U.DataException {
84 | String s="";
85 | if (mArr.size() >= mMax) {
86 | String r=trimFirst();
87 | if (r != null) s="Removed "+r;
88 | }
89 | int newId = Math.max(p.getId(), mNext);
90 | p.setId(newId);
91 | mArr.append(newId, p);
92 | mDirty=true;
93 | mNext=newId+1;
94 | return s;
95 | }
96 |
97 | private String trimFirst() throws U.DataException {
98 | Point toRemove=getEdge();
99 | if (toRemove == null) return "";
100 | mFirst=toRemove.getId();
101 | Log.d(U.TAG,"PointList:"+"About to delete:"+String.valueOf(mFirst));
102 | mRemoved.add(toRemove);
103 | mArr.delete(mFirst);
104 | //onPointdeleted();
105 | String deletedId=String.valueOf(mFirst);
106 | mDirty=true;
107 | return deletedId;
108 | }
109 |
110 | public int getNext() { return mNext; }
111 | public String getNextS() { return String.valueOf(mNext); }
112 |
113 | public Point getEdge() throws U.DataException {
114 | if (U.DEBUG) Log.d(U.TAG,"PointList:"+"Array size "+mArr.size()+"/"+mMax);
115 | if (mArr.size() < mMax) {
116 | if (U.DEBUG) Log.d(U.TAG,"PointList:"+"No need to remove anything:"+mArr.size()+"/"+mMax);
117 | return null;
118 | }
119 | int i=0;
120 | Point p;
121 | while ( (p=mArr.valueAt(i)).isProtected() ) {
122 | i+=1;
123 | if (i >= mArr.size()) {
124 | if (U.DEBUG) Log.d(U.TAG,"PointList:"+"No removable points");
125 | throw new U.DataException ("No room");
126 | }
127 | }
128 | if (U.DEBUG) Log.d(U.TAG,"PointList:"+"Found edge point:"+String.valueOf(p.getId()));
129 | return p;
130 | }
131 |
132 | public boolean isEmpty() { return mArr.size() == 0; }
133 |
134 | public boolean isDirty() { return mDirty; }
135 | public void clearDirty() { mDirty=false; }
136 | public void setDirty() { mDirty=true; }
137 |
138 | public U.Summary load() throws IOException,U.DataException {
139 | U.Summary loaded=mStorageHelper.readPoints(this);
140 | mDirty=false;
141 | return loaded;
142 | }
143 |
144 | public U.Summary clearAndLoad() throws IOException,U.DataException {
145 | fastClear();
146 | U.Summary loaded=mStorageHelper.readPoints(this);
147 | mDirty=true;
148 | return loaded;
149 | }
150 |
151 | public String save() {
152 | try {
153 | mStorageHelper.savePoints(this);
154 | if ( ! mRemoved.isEmpty()) tryUseTrash();
155 | mDirty=false;
156 | return PointList.OK;
157 | }
158 | catch (IOException e) {
159 | Log.e(U.TAG,"PointList_save:"+e.getMessage());
160 | return "File error:"+e.getMessage();
161 | }
162 | catch (Exception e) {
163 | Log.e(U.TAG, "PointList_save:"+e.getMessage());
164 | return "Save error:"+e.getMessage();
165 | }
166 | }
167 |
168 | public void forceUseTrash() {// for tests
169 | mUseMockRegistry=true;
170 | mShouldUseTrash=true;
171 | }
172 |
173 | public void forceNotUseTrash() {// for tests
174 | mUseMockRegistry=true;
175 | mShouldUseTrash=false;
176 | }
177 |
178 | private boolean shouldUseTrash() {
179 | if (mUseMockRegistry) return mShouldUseTrash;
180 | return MyRegistry.getInstance().getBool("useTrash");
181 | }
182 |
183 | private void tryUseTrash() throws IOException {
184 | if (shouldUseTrash()) {
185 | for ( Point p : mRemoved ) { mStorageHelper.trashPoint(p); }// normally it is only one point
186 | }
187 | mRemoved=new ArrayList();
188 | }
189 |
190 | public String makeJsonPresentation() {
191 | JSONArray ja=new JSONArray();
192 | int size=mArr.size();
193 | if (size == 0) return ja.toString();
194 | int i=0;
195 | for( ; i < size; i+=1) {
196 | Point p=mArr.valueAt(i);
197 | if (p.hasCoords()) { ja.put(p.makeJsonPresentation(mArr.keyAt(i))); }
198 | }
199 | return ja.toString();
200 | }
201 |
202 | public int getIdByIndex(int i) {
203 | return mArr.keyAt(i);
204 | }
205 |
206 | public List getIndices() {
207 | List r = new ArrayList();
208 | Point p;
209 | int id;
210 | while ((p=iterate()) != null) {
211 | id=p.getId();
212 | r.add(String.valueOf(id));
213 | }
214 | return r;
215 | }
216 |
217 | public Point iterate() {
218 | int size=mArr.size();
219 | if (size == 0 || ii >= size) {
220 | ii=0;
221 | return null;
222 | }
223 | Point p=mArr.valueAt(ii);
224 | if (p.getId() <= 0) p.setId(mArr.keyAt(ii));
225 | ii+=1;
226 | return p;
227 | }
228 |
229 | public Point getById(int id) {
230 | return mArr.get(id);
231 | }
232 |
233 | public void update(Point p) {
234 | int id=p.getId();
235 | if (mArr.indexOfKey(id) < 0) {
236 | Log.e(U.TAG,"PointList:"+"Unknown id="+id);
237 | return;
238 | }
239 | mArr.put(id, p);
240 | mDirty=true;
241 | }
242 |
243 | public void moveUnprotectedToTrash(int id) {
244 | if (mArr.indexOfKey(id) < 0) {
245 | Log.e(U.TAG,"PointList:"+"Unknown id="+id);
246 | return;
247 | }
248 | Point p=mArr.get(id);
249 | if (p.isProtected()) return;
250 | mRemoved.add(p);
251 | mArr.delete(id);
252 | mDirty=true;
253 | }
254 |
255 | public int fastDeleteGroup(int from, int until) {
256 | int count=0, id;
257 | Point p;
258 | //Iterator ip=mArr.iterator(); does not work for SparseArray
259 | List toRemove= new ArrayList();
260 | while ((p=iterate()) != null) {
261 | id=p.getId();
262 | if (id >= from && (until < 0 || id <= until)) toRemove.add(id);
263 | }
264 | count=toRemove.size();
265 | if (count > 0) {
266 | for (int i=0; i < count; i+=1) { mArr.delete(toRemove.get(i)); }
267 | mDirty=true;
268 | }
269 | return count;
270 | }
271 |
272 | public void renumber() {
273 | SparseArray a=new SparseArray();
274 | int l=mArr.size();
275 | Point p;
276 | for (int i=0; i < l; i+=1) {
277 | p=mArr.valueAt(i);
278 | p.setId(i+1);
279 | a.append(i+1,p);
280 | }
281 | mArr=a;
282 | mDirty=true;
283 | }
284 |
285 | public StorageHelper getStorageHelper() { return mStorageHelper; }
286 |
287 | public void setProximityOrigin(Point loc) {
288 | if (loc !=null && loc.hasCoords()) mProximityOrigin=(Point) loc.clone();
289 | }
290 |
291 | public boolean hasProximityOrigin() { return mProximityOrigin != null; }
292 | public Point getProximityOrigin() { return mProximityOrigin; }
293 |
294 | public int findNearest(Point cursor) {
295 | double deg2rad = 0.0174532925199433;
296 | double cosLat=Math.cos(Double.parseDouble(cursor.lat)*deg2rad);
297 | double minSqDistance=1e99, sqDistance;
298 | int foundId=-1;
299 | Point p;
300 |
301 | while ((p=iterate()) != null) {
302 | if ( ! p.hasCoords()) continue;
303 | sqDistance=U.sqDistance(p, cursor, cosLat);
304 | if (sqDistance < minSqDistance) {
305 | minSqDistance=sqDistance;
306 | foundId=p.getId();
307 | }
308 | }
309 | return foundId;
310 | }
311 |
312 | }
313 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/SingleFragmentActivity.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.os.Bundle;
4 | //import android.support.v4.app.Fragment;
5 | import androidx.fragment.app.Fragment;
6 | //import android.support.v4.app.FragmentManager;
7 | import androidx.fragment.app.FragmentManager;
8 | //import android.support.v7.app.AppCompatActivity;
9 | import androidx.appcompat.app.AppCompatActivity;
10 |
11 | public abstract class SingleFragmentActivity extends AppCompatActivity {
12 | //protected abstract android.support.v4.app.Fragment createFragment();
13 | protected abstract androidx.fragment.app.Fragment createFragment();
14 |
15 | @Override
16 | public void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_fragment);
19 | FragmentManager fm = getSupportFragmentManager();
20 | Fragment fragment = fm.findFragmentById(R.id.fragment_container);
21 |
22 | if (fragment == null) {
23 | fragment = createFragment();
24 | fm.beginTransaction()
25 | .add(R.id.fragment_container, fragment)
26 | .commit();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/StorageHelper.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.Map;
6 |
7 | import android.content.Context;
8 | import android.text.TextUtils;
9 | import android.util.Log;
10 | import truewatcher.tower.U.DataException;
11 |
12 | public class StorageHelper {
13 | private String mMyFile;
14 | private String mMyExt="csv";
15 | private String mHeader=TextUtils.join(Point.SEP, Point.FIELDS);
16 | private String mPath;
17 | private String mMyTrashFile="trash.csv";
18 |
19 | public static String getWorkingFolder(Context context, MyRegistry mRg) throws U.FileException {
20 | String nativeFolder = context.getExternalFilesDir(null).getPath();
21 | String targetPath = nativeFolder;
22 | if (mRg.getBool("useMediaFolder")) {
23 | String appMediaFolder = getMediaDir(context);
24 | Log.i(U.TAG, "appMediaFolder=" + appMediaFolder);
25 | if (appMediaFolder.isEmpty()) throw new U.FileException("Cannot find the app's media folder");
26 | targetPath = appMediaFolder;
27 | }
28 | return targetPath;
29 | }
30 |
31 | public static String getMediaDir(Context context) {
32 | // context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) gives a subfolder of Android/data
33 | //https://stackoverflow.com/questions/69658332/how-to-create-folder-inside-android-media-in-android-11
34 | File[] dirs = new File[0];
35 | dirs = context.getExternalMediaDirs();
36 | for (int i = 0; i= from && (until < 0 || id <= until)) {
79 | sb.append(p.toCsv()).append(Point.NL);
80 | count+=1;
81 | }
82 | }
83 | buf=sb.toString();
84 | if (convertTo.equals("gpx")) {
85 | GpxHelper gh=new GpxHelper();
86 | buf=gh.csv2gpx(buf);
87 | outcome=new U.Summary("exported", fullCount, gh.mCount, targetFile);
88 | }
89 | else outcome=new U.Summary("wrote", fullCount, count, targetFile);
90 | U.filePutContents(mPath, targetFile, buf, false);
91 | return outcome;
92 | }
93 |
94 | public void trashPoint(Point p) throws IOException {
95 | if (p == null) {
96 | Log.w(U.TAG,"StorageHelper:"+"trashPoint : null argument");
97 | return;
98 | }
99 | String h=mHeader+Point.NL;
100 | String buf = p.toCsv()+Point.NL;
101 | if ( U.fileExists(mPath, mMyTrashFile, "csv") == null) buf=h+buf;
102 | U.filePutContents(mPath, mMyTrashFile, buf, true);
103 | }
104 |
105 | public int getPointCount(String targetFile) throws IOException,U.DataException {
106 | targetFile=U.assureExtension(targetFile,"csv");
107 | String buf=U.fileGetContents(mPath, targetFile);
108 | String[] lines=splitCsv(buf);
109 | int l=lines.length;
110 | int expected=l-2;
111 | if (l == 1) expected=0;
112 | return expected;
113 | }
114 |
115 | public int checkPointCount(String targetFile, PointList pl) throws IOException,U.DataException {
116 | int expected=getPointCount(targetFile);
117 | checkWithListSize(expected,pl);
118 | return expected;
119 | }
120 |
121 | private String[] splitCsv(String buf) throws DataException {
122 | String[] lines=TextUtils.split(buf, Point.NL);
123 | int l=lines.length;
124 | if (l == 0) throw new U.DataException("The file has no header line");
125 | if ( ! lines[0].equals(mHeader)) throw new U.DataException("The file has wrong header line");
126 | return lines;
127 | }
128 |
129 | private void checkWithListSize(int expected, PointList pl) throws DataException {
130 | int maxCount=pl.getMax();
131 | if (expected > maxCount) { throw new U.DataException("No room!"
132 | +" Set max point count to at least "+expected); }
133 | }
134 |
135 | public U.Summary readPoints(PointList pl) throws IOException,U.DataException {
136 | U.Summary s=readPoints(pl, mMyFile, 0, "");
137 | pl.clearDirty();
138 | return s;
139 | }
140 |
141 | public U.Summary readPoints(PointList pl, String targetFile, int currentPointCount) throws IOException,U.DataException {
142 | return readPoints(pl, targetFile, currentPointCount, "");
143 | }
144 |
145 | public U.Summary readPoints(PointList pl, String targetFile, int currentPointCount, String convertFrom)
146 | throws IOException,U.DataException {
147 | if (null == U.fileExists(mPath, targetFile)) {
148 | return new U.Summary("loaded", 0, 0, targetFile);
149 | }
150 | String buf=U.fileGetContents(mPath, targetFile);
151 | if (convertFrom.equals("gpx")) {
152 | //buf=GpxHelper.removeGpxTail(buf);
153 | GpxHelper gh=new GpxHelper();
154 | buf=gh.gpx2csv(buf);
155 | }
156 |
157 | String[] lines=splitCsv(buf);
158 | int l=lines.length;
159 | if (l == 1) return new U.Summary("loaded", 0, 0, mMyFile);
160 | int expected=currentPointCount+l-2;// 2 for the header and ending NL
161 | checkWithListSize(expected,pl);
162 |
163 | String line;
164 | Point p;
165 | int i=1;
166 | int count=0;
167 | int maxCount=pl.getMax();
168 | for (; i < l; i+=1) {
169 | line=lines[i].trim();
170 | if (line.isEmpty()) continue;
171 | p=(new Point()).fromCsv(line);
172 | if (U.DEBUG) Log.d(U.TAG,"StorageHelper:"+"About to add point "+p.getId());
173 | if (p.getId() == pl.getNext()) { pl.addAsNext(p); }
174 | else { pl.addAndShiftNext(p); }
175 | count+=1;
176 | if (count >= maxCount) {
177 | if (U.DEBUG) Log.d(U.TAG,"StorageHelper:"+"Loaded first "+count+" points of "+(l-2));
178 | break;
179 | }
180 | }
181 | if (count > 0) pl.setDirty();
182 | return new U.Summary("loaded", l-2, count, targetFile);
183 | }
184 |
185 | public static String append2LatLonString(String unit, boolean isNewSeg, String lls) {
186 | StringBuilder buf;
187 | if (null == lls || lls.length() < 4) {
188 | return "[["+unit+"]]";
189 | }
190 | if (isNewSeg) unit="],["+unit;
191 | else unit=","+unit;
192 | String cutEnding=lls.substring(0, lls.length()-2);// minus "]]"
193 | buf=new StringBuilder(cutEnding).append(unit).append("]]");
194 | return buf.toString();
195 | }
196 |
197 | public String getMyDir() { return mPath; }
198 | }
199 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/TestHelper.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.util.List;
4 |
5 | import android.text.TextUtils;
6 | import android.widget.TextView;
7 |
8 | public class TestHelper {
9 | private static TestHelper sMe;
10 | private static TextView sOutputElement;
11 | private static String sOutput="";
12 | private int mCountAssert=0;
13 |
14 | public static TestHelper getInstance(TextView aView) {
15 | if(sMe == null) {
16 | sMe=new TestHelper();
17 | }
18 | sOutputElement=aView;
19 | return sMe;
20 | }
21 |
22 | public void print(String s) {
23 | sOutput=sOutput.concat(s);
24 | sOutputElement.setText(sOutput);
25 | }
26 |
27 | public void println(String s) {
28 | //sOutput=sOutput.concat("\n");
29 | print(s.concat("\n"));
30 | }
31 |
32 | public void printlnln(String s) {
33 | sOutput=sOutput.concat("\n");
34 | print(s.concat("\n"));
35 | }
36 |
37 | public static class TestFailure extends Exception {
38 |
39 | public TestFailure(String aMessage) { super(aMessage); }
40 | }
41 |
42 | public void assertTrue(boolean aStatement, String aMessage, String aMessageOk, String aExplanation) throws TestFailure {
43 | incCount();
44 | String out="";
45 | if (aStatement) {
46 | out="Passed "+mCountAssert;
47 | if ( aMessageOk != null && ! aMessageOk.isEmpty() ) out+=": "+aMessageOk;
48 | println(out);
49 | return;
50 | }
51 | out="Failed "+mCountAssert;
52 | if ( aExplanation != null && ! aExplanation.isEmpty() ) out+=": "+aExplanation;
53 | //out+=": "+aMessage;
54 | println(out);
55 | throw new TestFailure(aMessage);
56 | }
57 |
58 | public void assertTrue(boolean aStatement, String aMessage, String aMessageOk) throws TestFailure {
59 | assertTrue(aStatement, aMessage, aMessageOk, "");
60 | }
61 |
62 | public void assertTrue(boolean aStatement, String aMessage) throws TestFailure {
63 | assertTrue(aStatement, aMessage, "", "");
64 | }
65 |
66 | public void assertEquals(int aExpected,int aFound, String aMessage, String aMessageOk) throws TestFailure {
67 | String expl=aFound+" does not equal to the expected "+aExpected;
68 | assertTrue(aExpected == aFound, aMessage, aMessageOk, expl);
69 | }
70 |
71 | public void assertEquals(String aExpected,String aFound, String aMessage, String aMessageOk) throws TestFailure {
72 | String expl=aFound+" does not equal to the expected "+aExpected;
73 | assertTrue(aExpected.equals(aFound), aMessage, aMessageOk, expl);
74 | }
75 |
76 | public void assertEqualsList(List aExpected, List aFound, String aMessage, String aMessageOk) throws TestFailure {
77 | String e=TextUtils.join(", ", aExpected);
78 | String f=TextUtils.join(", ", aFound);
79 | String expl=f+" does not equal to the expected "+e;
80 | assertTrue(e.equals(f), aMessage, aMessageOk, expl);
81 | }
82 |
83 | public void assertContains(String aExpected,String aHaystack, String aMessage, String aMessageOk) throws TestFailure {
84 | String expl=aHaystack+" does not contain "+aExpected;
85 | assertTrue(aHaystack.indexOf(aExpected) >= 0, aMessage, aMessageOk, expl);
86 | }
87 |
88 | public void assertNotContains(String aExpected,String aHaystack, String aMessage, String aMessageOk) throws TestFailure {
89 | String expl=aHaystack+" still contains "+aExpected;
90 | assertTrue(aHaystack.indexOf(aExpected) < 0, aMessage, aMessageOk, expl);
91 | }
92 |
93 | public void csvLineDiff(String aExpected, String aObtained, String SEP) throws TestFailure {
94 | aExpected=aExpected.trim();
95 | String[] esa=TextUtils.split(aExpected, SEP);
96 | aObtained=aObtained.trim();
97 | String[] osa=TextUtils.split(aObtained, SEP);
98 | if (esa.length != osa.length) throw new TestFailure("Expected "+esa.length+" fields, got "+osa.length);
99 | String SAME="=";
100 | String DIFF="/";
101 | String ef,of;
102 | String[] rsa=new String[esa.length];
103 | for (int i=0; i < esa.length; i+=1) {
104 | ef=esa[i];
105 | of=osa[i];
106 | if (ef.isEmpty() && of.isEmpty()) { rsa[i]=""; }
107 | else if (ef.equals(of)) { rsa[i]=SAME; }
108 | else { rsa[i]=ef+DIFF+of; }
109 | }
110 | println(TextUtils.join(SEP,rsa));
111 | }
112 |
113 | private void incCount() { mCountAssert+=1; }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/TrackListener.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import android.content.Context;
4 | import android.location.Location;
5 | import android.location.LocationListener;
6 | import android.location.LocationManager;
7 | import android.os.Bundle;
8 | import android.util.Log;
9 |
10 | public class TrackListener implements LocationListener {
11 |
12 | private boolean mOn=false;
13 | private int mCounter=0;
14 | public String status=" new ";
15 | public long prevUpdateTime=0;
16 | public long updateTime=0;
17 | public long startUpdatesTime=0;
18 | private LocationManager mLocationManager=null;// same instance for startListening and stopListening !
19 | //private Model.LocationReceiver mLocationReceiver = new Model.LocationReceiver();
20 | private MyRegistry mRegistry = MyRegistry.getInstance();
21 | private TrackStorage mTrackStorage=null;//=Model.getInstance().getTrackStorage(); causes loop
22 | private TrackPointListener mListener=null;
23 |
24 | public TrackListener(TrackStorage aTrackStorage) {
25 | mTrackStorage=aTrackStorage;
26 | }
27 |
28 | public boolean isOn() { return mOn; }
29 | public void setOn() { mOn=true; }
30 | public void setOff() { mOn=false; }
31 |
32 | public void clearCounter() { mCounter=0; }
33 | public void incCounter() { mCounter+=1; }
34 | public int getCounter() { return mCounter; }
35 |
36 | public void startListening(Context ct) {
37 | long minTimeMs=1000* mRegistry.getInt("gpsMinDelayS");//U.minFixDelayS;
38 | float minDistanceM= mRegistry.getInt("gpsMinDistance");//0;
39 | try {
40 | mLocationManager = (LocationManager) ct.getSystemService(Context.LOCATION_SERVICE);
41 | mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTimeMs, minDistanceM,
42 | this);
43 | prevUpdateTime=updateTime=startUpdatesTime=U.getTimeStamp();
44 | //if (U.DEBUG) Log.d(U.TAG,"TrackListener:"+"started");
45 | }
46 | catch (SecurityException e) {
47 | Log.i(U.TAG, "TrackListener:"+"Security Exception"+e.getMessage());
48 | throw new U.RunException(e.getMessage());
49 | }
50 | }
51 |
52 | public void stopListening() {
53 | try {
54 | if (null == mLocationManager) return;
55 | mLocationManager.removeUpdates(this);
56 | mLocationManager = null;
57 | }
58 | catch (SecurityException e) {
59 | throw new U.RunException(e.getMessage());
60 | }
61 | }
62 |
63 | public void onLocationChanged(Location loc) {
64 | if (U.DEBUG) Log.i(U.TAG, "LocationReceiver:"+"got a location " + loc.toString());
65 | incCounter();
66 | prevUpdateTime=updateTime;
67 | updateTime = U.getTimeStamp();
68 | if (prevUpdateTime > 0) {
69 | long delay = updateTime - prevUpdateTime;
70 | if (delay - mRegistry.getInt("gpsMinDelayS") > mRegistry.getInt("gpsTimeoutS")) {
71 | mTrackStorage.saveNote("delay=" + Long.toString(delay) + "s", "");
72 | }
73 | }
74 | onPointavailable(loc);
75 | }
76 |
77 | public void onStatusChanged(String provider, int status, Bundle extras) {
78 | if ( ! provider.equals(LocationManager.GPS_PROVIDER)) return;
79 | if (U.DEBUG) Log.i(U.TAG, "got new GPS status:"+String.valueOf(status));
80 | }
81 |
82 | @Override
83 | public void onProviderEnabled(String provider) {}
84 |
85 | @Override
86 | public void onProviderDisabled(String provider) {}
87 |
88 | private void onPointavailable(Location loc) {
89 | Trackpoint p=new Trackpoint(loc);
90 | p=mTrackStorage.simplySave(p);// may set newSegment
91 | Model.getInstance().getJSbridge().consumeTrackpoint(p);
92 | if (null != mListener) mListener.onTrackpointAvailable(p);
93 | }
94 |
95 | public static interface TrackPointListener {
96 | public void onTrackpointAvailable(Trackpoint p);
97 | }
98 |
99 | public void attachListener(TrackPointListener l) { mListener=l; }
100 |
101 | public void removeListener(TrackPointListener l) {
102 | mListener=null;
103 | //if (mListener == null) return;
104 | //if (mListener != l) throw new U.RunException("Unregistering of unknown listener");
105 | //mListener=null;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/java/truewatcher/tower/Trackpoint.java:
--------------------------------------------------------------------------------
1 | package truewatcher.tower;
2 |
3 | import java.util.Set;
4 | import org.json.JSONArray;
5 | import android.location.Location;
6 | import android.text.TextUtils;
7 | import java.text.SimpleDateFormat;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Calendar;
11 | import java.util.Collections;
12 | import java.util.HashSet;
13 | import java.util.List;
14 |
15 | public class Trackpoint extends LatLon implements Cloneable {
16 | private int mId=0;
17 | private String mType="T";
18 | private static final Set TYPES = new HashSet(Arrays.asList(
19 | new String[] {"T","note"}
20 | ));
21 | public String alt="";
22 | public String range="";
23 | public String time=getDate();
24 | public String data="";
25 | private String mNewSegment="";
26 | public String comment="";
27 | // https://www.gpsvisualizer.com/tutorials/tracks.html
28 | public static final List FIELDS = Collections.unmodifiableList(Arrays.asList(
29 | new String[] {"type","new_track","time","lat","lon","alt","range","name","data"}
30 | ));
31 | public static final String SEP = ";";
32 | public static final String NL = "\n";
33 |
34 | public Trackpoint() { }
35 |
36 | public Trackpoint(String aType, String aComment, String aData) {
37 | if ( ! aType.equals("note")) throw new U.RunException("Trackpoint note of wrong type="+aType);
38 | setType(aType);
39 | this.comment=aComment;
40 | this.data=aData;
41 | }
42 |
43 | public Trackpoint(Location loc) {
44 | this.lat=String.valueOf(loc.getLatitude());
45 | this.lon=String.valueOf(loc.getLongitude());
46 | if (loc.hasAltitude()) this.alt=String.valueOf(loc.getAltitude());
47 | if (loc.hasAccuracy()) this.range=String.valueOf(loc.getAccuracy());
48 | }
49 |
50 | public Object clone() {
51 | try { return super.clone(); }
52 | catch ( CloneNotSupportedException e ) { return null; }
53 | }
54 |
55 | public void setId(int i) { mId=i; }
56 | public int getId() { return mId; }
57 |
58 | public void setType(String t) {
59 | if ( ! TYPES.contains(t) ) throw new U.RunException("Unhnown type="+t);
60 | mType=t;
61 | }
62 |
63 | public String getType() { return mType; }
64 |
65 | public void setNewSegment() { mNewSegment="1"; }
66 | public void setNewSegment(String s) { mNewSegment=s; }
67 | public boolean isNewSegment() { return ! mNewSegment.isEmpty(); }
68 |
69 | public static String getDate() {
70 | SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
71 | String fd = df.format(Calendar.getInstance().getTime());
72 | return fd;
73 | }
74 |
75 | public JSONArray makeJsonPresentation() {
76 | JSONArray ja=new JSONArray();
77 | if ( ! mType.equals("T")) return ja;
78 | ja.put(lat);
79 | ja.put(lon);
80 | return ja;//.toString();
81 | }
82 |
83 | public String toCsv() {
84 | List ls=new ArrayList();
85 | ls.add(ne(mType));
86 | ls.add(ne(mNewSegment));
87 | ls.add(ne(time));
88 | ls.add(ne(lat));
89 | ls.add(ne(lon));
90 | ls.add(ne(alt));
91 | ls.add(ne(range));
92 | ls.add(ne(comment));
93 | ls.add(ne(data));
94 | String s= TextUtils.join(Trackpoint.SEP, ls);
95 | return s;
96 | }
97 |
98 | public Trackpoint fromCsv(String s) throws U.DataException {
99 | s=s.trim();
100 | if (s.isEmpty()) return null;
101 | String[] ls=TextUtils.split(s, Trackpoint.SEP);
102 | if (ls.length != Trackpoint.FIELDS.size()) {
103 | throw new U.DataException("Source has "+ls.length+" fields, while "+Trackpoint.FIELDS.size()+" are required");
104 | }
105 | setType(ls[0]);
106 | if ( ! ls[1].isEmpty()) setNewSegment(ls[1]);
107 | time=ls[2];
108 | lat=ls[3];
109 | lon=ls[4];
110 | alt=ls[5];
111 | range=ls[6];
112 | comment=ls[7];
113 | data=ls[8];
114 | return this;
115 | }
116 |
117 | private String ne(String s) {
118 | if (s == null || s.isEmpty()) return "";
119 | if (s.contains(SEP)) throw new U.RunException("Misplaced separator");
120 | if (s.contains(NL)) throw new U.RunException("Misplaced NL");
121 | return s;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_build_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_chevron_left_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done_black_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_open_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_format_list_bulleted_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher3_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher3_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
13 |
18 |
23 |
28 |
33 |
38 |
43 |
47 |
51 |
55 |
59 |
64 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_map_white_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_refresh_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_target_variant_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_fragment.xml:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_add_point.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
12 |
13 |
17 |
18 |
22 |
23 |
27 |
28 |
33 |
34 |
39 |
40 |
41 |
42 |
46 |
47 |
54 |
55 |
61 |
62 |
63 |
64 |
68 |
69 |
75 |
76 |
80 |
81 |
85 |
86 |
90 |
91 |
97 |
98 |
102 |
103 |
109 |
110 |
111 |
115 |
116 |
120 |
121 |
125 |
126 |
130 |
131 |
137 |
138 |
142 |
143 |
149 |
150 |
151 |
155 |
156 |
160 |
161 |
165 |
166 |
171 |
172 |
176 |
177 |
182 |
183 |
188 |
189 |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_edit_point.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
19 |
20 |
24 |
25 |
31 |
32 |
38 |
39 |
45 |
46 |
50 |
51 |
52 |
53 |
58 |
59 |
63 |
64 |
68 |
69 |
73 |
74 |
80 |
81 |
86 |
87 |
93 |
94 |
98 |
99 |
100 |
101 |
105 |
106 |
110 |
111 |
117 |
118 |
124 |
125 |
126 |
127 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
18 |
19 |
23 |
24 |
28 |
29 |
35 |
36 |
41 |
42 |
47 |
48 |
53 |
54 |
59 |
60 |
65 |
66 |
71 |
72 |
73 |
74 |
78 |
79 |
84 |
85 |
91 |
92 |
97 |
98 |
99 |
104 |
105 |
110 |
111 |
116 |
117 |
121 |
122 |
126 |
127 |
132 |
133 |
137 |
138 |
143 |
144 |
145 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 |
17 |
22 |
23 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_track.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
17 |
18 |
23 |
24 |
29 |
30 |
31 |
32 |
36 |
37 |
41 |
42 |
48 |
49 |
53 |
54 |
58 |
59 |
63 |
64 |
68 |
69 |
70 |
71 |
75 |
76 |
80 |
81 |
85 |
86 |
90 |
91 |
92 |
93 |
94 |
95 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item_simple.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/add_point_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
11 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/edit_point_fragment.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
19 |
20 |
25 |
26 |
31 |
32 |
37 |
38 |
43 |
44 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/file_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/list_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
16 |
17 |
21 |
22 |
26 |
27 |
31 |
32 |
36 |
37 |
41 |
42 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_fragment.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
17 |
18 |
24 |
25 |
31 |
32 |
38 |
39 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/prefs_activity.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/track_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
19 |
20 |
26 |
27 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher3.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher3_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-hdpi/ic_launcher3.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher3_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-hdpi/ic_launcher3_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-mdpi/ic_launcher3.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher3_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-mdpi/ic_launcher3_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-xhdpi/ic_launcher3.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher3_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-xhdpi/ic_launcher3_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-xxhdpi/ic_launcher3.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher3_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-xxhdpi/ic_launcher3_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-xxxhdpi/ic_launcher3.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher3_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/app/src/main/res/mipmap-xxxhdpi/ic_launcher3_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/array.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - off
5 | - mylnikov.org
6 | - Yandex Locator
7 |
8 |
9 | - none
10 | - mylnikov
11 | - yandex
12 |
13 |
14 |
15 | - OpenStreetMap map
16 | - OpenTopoMap map
17 | - blank map
18 | - Google hybrid
19 | - Yandex hybrid
20 |
21 |
22 | - osm map
23 | - opentopo map
24 | - blank map
25 | - google hyb
26 | - yandex hyb
27 |
28 |
29 |
30 | - auto
31 | - light
32 | - dark
33 |
34 |
35 | - auto
36 | - light
37 | - dark
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher3_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #AAAAAA
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | tower
5 | Settings
6 | Cell
7 | GPS
8 | Add
9 | Point
10 | List
11 | Map
12 | Reload
13 | File
14 | Go
15 | OK
16 | Toggle protect
17 | Edit comment
18 | Delete
19 | Get coords
20 | By ID
21 | By ID, reverse
22 | By proximity
23 | Renumber from 1
24 | Delete ALL points
25 | As center
26 | New
27 | Open
28 | Load
29 | Export
30 | Export track
31 | View track
32 | Back
33 | Current map center
34 | New waypoint
35 | (comment)
36 | (add note)
37 | remove exported points
38 | Get
39 | Done
40 | Cell location service
41 | Map service
42 | Map zoom level
43 | Max number of points
44 | Save removed points to trash.csv
45 | Are you sure ?
46 | Ops, never mind
47 | Use as center
48 | Date:
49 | Accuracy:
50 | Altitude:
51 | 🔒
52 | ⏎
53 | select file
54 | file name
55 | API key for Yandex Maps
56 | API key for Yandex Locator
57 | This distribution has no API keys, so some services are not available
58 | Track
59 | Minimal track leg, m
60 | Minimal GPS fix interval, s
61 | Display current track
62 | Center map on a new trackpoint
63 | Use Tower folder
64 | Save to Android/media
65 | Color theme
66 | Working file:
67 | Start
68 | Start \nnew segment
69 | Stop
70 | Fit to map
71 | Delete last segment
72 | Export as GPX
73 | Count lengths
74 | While writing a track, do not minimize this app, or else it will stop after a few minutes.You may press POWER to turn the screen off
75 | Problem with location permission
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 | #109070
3 | #80CBC4
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/prefs.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
18 |
23 |
24 |
27 |
29 |
30 |
31 |
34 |
35 |
38 |
39 |
42 |
43 |
46 |
47 |
50 |
51 |
54 |
55 |
58 |
59 |
64 |
65 |
68 |
69 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | google()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:8.7.1'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | jcenter()
15 | google()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 |
2 | no Google Services dependencies, only necessary permissions (fine and coarse location, internet, foreground service)
3 | connects to several online map providers
4 | acceptable performance on slow GPRS-EDGE networks; may find coarse location by phone cell without GPS; may create waypoints with GPS without phone and internet connections
5 | displays and saves cell info ( MCC, MNC, LAC, CID ) and the signal strength ( RSRP/dBm )
6 | all waypoints are stored on a memory card and may be organized in any number of files (by regions etc.)
7 | waypoint lists may be exported or imported in the GPX format, compatible with many navigators and software
8 | finds location only by explicit user's command, so is very mild on the battery
9 | capable of writing tracks (by means of a foreground service) and exporting them in the GPX format
10 | tracks from other devices (GPX files) may be viewed along with your data
11 | NOT implemented: map offline caching, routing, editing tracks, photo and video attachments, serving cold beer :)
12 | for pure open distributions, that lack API keys and access to some services, there are options to enter user's own keys
13 |
14 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/01_waypoints_and_track_from_garmin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/01_waypoints_and_track_from_garmin.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/02_map_with_menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/02_map_with_menu.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/03_add_new_waypoint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/03_add_new_waypoint.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/04_waypoint_list_with_distances.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/04_waypoint_list_with_distances.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/05_edit_waypoint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/05_edit_waypoint.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/06_writing_track.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/06_writing_track.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/07_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/07_settings.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/08_importing_track.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/08_importing_track.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/09_export_part_of_waypoint_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/09_export_part_of_waypoint_list.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/10_settings_missing_API_key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/fastlane/metadata/android/en-US/images/phoneScreenshots/10_settings_missing_API_key.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | find location (cell or GPS), view online maps, store waypoints and tracks
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | android.nonFinalResIds=false
2 | android.nonTransitiveRClass=false
3 | android.useAndroidX=true
4 | android.enableJetifier=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TrueWatcher/tower/6699f23ed40de1991d5b2304d99e40747054d5e3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 29 22:31:57 MSK 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/import-summary.txt:
--------------------------------------------------------------------------------
1 | ECLIPSE ANDROID PROJECT IMPORT SUMMARY
2 | ======================================
3 |
4 | Ignored Files:
5 | --------------
6 | The following files were *not* copied into the new Gradle project; you
7 | should evaluate whether these are still needed in your project and if
8 | so manually move them:
9 |
10 | * ic_launcher-web.png
11 | * proguard-project.txt
12 |
13 | Moved Files:
14 | ------------
15 | Android Gradle projects use a different directory structure than ADT
16 | Eclipse projects. Here's how the projects were restructured:
17 |
18 | * AndroidManifest.xml => app\src\main\AndroidManifest.xml
19 | * assets\ => app\src\main\assets\
20 | * res\ => app\src\main\res\
21 | * src\ => app\src\main\java\
22 |
23 | Next Steps:
24 | -----------
25 | You can now build the project. The Gradle project needs network
26 | connectivity to download dependencies.
27 |
28 | Bugs:
29 | -----
30 | If for some reason your project does not build, and you determine that
31 | it is due to a bug or limitation of the Eclipse to Gradle importer,
32 | please file a bug at http://b.android.com with category
33 | Component-Tools.
34 |
35 | (This import summary is for your information only, and can be deleted
36 | after import once you are satisfied with the results.)
37 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------