Сравнить коммиты

...
174 Коммитов
Автор SHA1 Сообщение Дата
Anton Tananaev a3d43294ce Update project and tests 2020-06-10 21:14:19 -07:00
Anton Tananaev 330dc27f0f Update version number 2020-04-22 22:30:03 -07:00
Anton Tananaev f052be83c3 Update libraries 2020-04-22 22:29:27 -07:00
Anton Tananaev c77d4b477b Update localization files 2020-04-22 22:24:43 -07:00
Anton Tananaev eb7de9eb98 Fix numeric input type 2020-02-03 21:50:28 -08:00
Anton Tananaev 3a786b14b7 Option to disable buffering (fix #411) 2020-02-02 20:55:12 -08:00
Anton Tananaev 6bad9786ec Set default flavor 2020-01-26 22:33:40 -08:00
Anton Tananaev 259aaf2000 Update Play Store icon 2019-11-30 12:52:15 -08:00
Anton Tananaev aa00a33f5b Fix build issue 2019-11-24 21:32:05 -08:00
Anton Tananaev ab86076b9b Update version number 2019-11-24 11:01:57 -08:00
Anton Tananaev fed2a73999 Implement dark theme support 2019-11-24 11:00:32 -08:00
Anton Tananaev 42c210763c Update localization 2019-11-24 10:34:11 -08:00
Anton Tananaev faa05dea5a Update app version 2019-11-17 13:08:25 -08:00
Anton Tananaev 453edfa97e Temporary downgrade libraries 2019-11-17 12:56:15 -08:00
Anton Tananaev 4c6c79b6a2 Remove about screen 2019-11-11 23:51:04 -08:00
Anton Tananaev 93456bf7fb Target Android 10 2019-09-26 00:04:21 -07:00
Anton Tananaev a2ff7fcb38 Update version number 2019-05-04 14:25:00 -07:00
Anton Tananaev 7015f75edb Update project files 2019-05-04 14:23:37 -07:00
Anton Tananaev 636ceaf027 Keep wake lock for service 2019-05-04 14:13:12 -07:00
Anton Tananaev 350b800184 Update app version 2019-01-26 20:36:35 -08:00
Anton Tananaev 7ecba2cffd Update translations (fix #393) 2019-01-26 20:33:59 -08:00
Anton Tananaev 5d8f0dfb83 Update app version 2019-01-06 14:49:43 -08:00
Anton Tananaev f42ecf9f09 Use fused location provider 2019-01-06 14:48:47 -08:00
Anton Tananaev 556856ab5c Update copyright notice 2019-01-06 14:23:45 -08:00
Anton Tananaev f9f37ead08 Split position provider implementation 2019-01-06 14:22:19 -08:00
Anton Tananaev b3ee99140e Fix unit tests 2019-01-06 12:12:51 -08:00
Anton Tananaev 85f1e09308 Fix compilation issue 2019-01-06 12:03:38 -08:00
Anton Tananaev 20fd20f425 Migrate to AndroidX libraries 2019-01-06 11:56:36 -08:00
Anton Tananaev 1ae7b27683 Use app compat classes 2019-01-06 11:44:08 -08:00
Anton Tananaev f5b12b9e74 Update project files 2019-01-06 11:41:08 -08:00
Anton Tananaev d2f653034d Update app version 2018-11-06 20:58:48 +13:00
Anton Tananaev ab5aec9721 Improvements in permissions handling 2018-11-06 20:57:17 +13:00
Anton Tananaev 1b0559c98a Update version number 2018-10-18 16:05:48 +13:00
Anton Tananaev 2e0e7ea453 Fix merge issues 2018-10-18 15:44:58 +13:00
Anton Tananaev ac6d677ff4 Explicitly select location providers 2018-10-18 15:43:07 +13:00
Anton Tananaev cbfe90757d Disable advertising id collection 2018-09-22 08:51:41 +12:00
Anton Tananaev a9f4390ae4 Update app version 2018-09-15 08:54:32 +12:00
Anton Tananaev 8a938701c8 Improve service handling 2018-09-15 08:54:09 +12:00
Anton Tananaev 2319834c5b Handle runtime exceptions 2018-09-15 08:48:37 +12:00
Anton Tananaev 1b93619e64 Update app version 2018-09-13 12:08:05 +12:00
Anton Tananaev 2a36e1aa6d Remove Lost library 2018-09-12 13:55:30 +12:00
Anton Tananaev 64687b26c2 Merge pull request #357 from oliv3/bug_350
Gracefully handle package downgrade (fix #350)
2018-09-06 08:17:42 +12:00
Olivier Girondel dcb60ab75e Gracefully handle package downgrade (fix #350) 2018-09-04 14:30:09 +02:00
Anton Tananaev 0b4c4cccc6 Merge pull request #347 from oliv3/statusSeconds
Display seconds in the status activity
2018-08-28 08:40:57 +12:00
Olivier Girondel ef5bfff70a Display seconds in the status activity 2018-08-25 15:11:57 +02:00
Anton Tananaev 855efc7acb Merge pull request #345 from oliv3/distance
Remove DistanceCalculator class
2018-08-21 08:46:33 +12:00
Olivier Girondel 0ae47e65d4 Remove DistanceCalculator class 2018-08-20 15:57:01 +02:00
Anton Tananaev e8d5e5c99b Update robolectric library 2018-08-09 14:46:02 +12:00
Anton Tananaev c3980780d8 Update project and dependencies 2018-08-09 14:33:05 +12:00
Anton Tananaev 3c11216334 Check API version 2018-05-06 16:44:56 -07:00
Anton Tananaev bcde8388d6 Update database version 2018-05-01 10:01:57 +12:00
Anton Tananaev 7f18915514 Merge pull request #327 from oliv3/mock_locations
Report mock locations
2018-04-30 13:54:15 +12:00
Olivier Girondel 42ad669fca Tell if a location is a mock 2018-04-30 02:16:10 +02:00
Anton Tananaev 3e2b508ea7 Update version number 2018-03-19 22:23:05 +13:00
Anton Tananaev fd4d616812 Check permissions in service 2018-03-19 22:22:45 +13:00
Anton Tananaev 4f6571e572 Update app version number 2018-03-18 21:28:11 +13:00
Anton Tananaev d3025b437b Replace firebase old crash reporting 2018-03-18 21:27:27 +13:00
Anton Tananaev 1e861ef0ac Fix hidden app URL open issue (fix #321) 2018-02-19 09:49:50 +13:00
Anton Tananaev 43afc57114 Merge pull request #318 from garyvdm/online_status
Show online/offline in connection changed status messages
2018-02-15 23:03:55 +13:00
Gary van der Merwe d47783808f Show online/offline in connection changed status messages. 2018-02-15 12:03:09 +02:00
Anton Tananaev 3ef2272227 Merge pull request #320 from garyvdm/revert-301-vector
Revert "Use vector icons"
2018-02-15 22:24:54 +13:00
Gary van der Merwe 3625ee42db Revert "Use vector icons." 2018-02-15 11:04:38 +02:00
Anton Tananaev 385826c3c1 Merge pull request #301 from garyvdm/vector
Use vector icons
2018-02-14 09:42:29 +13:00
Gary van der Merwe 1426926c8e Use vector icons. 2018-02-13 18:33:01 +02:00
Anton Tananaev 4f680d33c9 Merge pull request #311 from Bubu/patch-1
Add google maven repo
2018-01-23 08:20:51 +13:00
Marcus Hoffmann 2dad912da5 Add google() repo
This is required to build the app outside of Android Studio.

Fixes:
 > Could not find com.android.tools.build:gradle:3.0.1.
     Searched in the following locations:
         https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.pom
         https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.jar
2018-01-22 20:15:20 +01:00
Anton Tananaev 290a7d80eb Update app version number 2018-01-19 17:43:43 +13:00
Anton Tananaev 44228feb2f Fix SOS functionality 2018-01-19 17:43:18 +13:00
Anton Tananaev d887bc4119 Update version number 2018-01-09 08:26:19 +13:00
Anton Tananaev c892a20104 Fix lost API connection logic 2018-01-09 08:25:57 +13:00
Anton Tananaev 2719b57f7c Use color code directly 2018-01-07 09:26:55 +13:00
Anton Tananaev cc6c8a23f6 Update project files 2018-01-07 09:23:14 +13:00
Anton Tananaev 1e82cbbbf6 Merge pull request #305 from Alpha200/fixAccuracy
Fix reading accuracy property
2017-12-30 10:40:06 +13:00
Daniel Sendzik 4b8fde36d7 Fix reading accuracy property 2017-12-29 19:34:14 +01:00
Anton Tananaev b8cd56439a Merge pull request #304 from garyvdm/restore_startWakefulForegroundService
Restore fix auto-start issue on Android O
2017-12-21 23:03:37 +13:00
Gary van der Merwe edb1f696df Restore fix auto-start issue on Android O.
Also rename startWakefulService to startWakefulForegroundService to highlight
that it's different to the SDK version.
2017-12-21 11:54:14 +02:00
Anton Tananaev 56d2f2ef53 Merge pull request #303 from garyvdm/cleanup
Remove unnecessary code
2017-12-21 09:52:43 +13:00
Gary van der Merwe 59e6a5ae65 Remove code that was originally included for supporting older SDK APIs that are now older than our minSdkVersion. 2017-12-20 14:17:51 +02:00
Anton Tananaev 319fab647b Merge pull request #302 from garyvdm/hidden_notification
For hidden flavor, make notificaion not open the app when clicked, use different icon
2017-12-16 09:47:04 +13:00
Gary van der Merwe 42429f3496 For hidden flavor, make notification open system settings activity. 2017-12-15 12:38:56 +02:00
Gary van der Merwe 841d3e581c For hidden flavor, make notificaion not open the app when clicked, use different icon.
This only apply to when the notificaion can't be hidden, i.e. SDK >= 25.
2017-12-15 09:09:25 +02:00
Anton Tananaev dac43ccbc1 Merge pull request #300 from garyvdm/notification
Get service notification to show. Tweek settings.
2017-12-14 13:53:22 +13:00
Gary van der Merwe 3c9ae512bd Get service notification to show. Tweek settings. 2017-12-13 08:11:11 +02:00
Anton Tananaev d3e600d81d Gracefully stop location updates 2017-11-23 00:03:17 +13:00
Anton Tananaev e0296a9c65 Use fused location provider 2017-11-22 23:59:52 +13:00
Anton Tananaev e51f590e19 Merge pull request #297 from Mantas0726/master
Update PositionProvider.java
2017-11-15 09:57:45 +13:00
Mantas0726 6e3812320c Update PositionProvider.java 2017-11-14 22:20:11 +02:00
Anton Tananaev c45c8ea797 Update version number 2017-09-25 15:08:10 +13:00
Anton Tananaev ed0aa53aa3 Fix null pointer crash 2017-09-25 15:04:19 +13:00
Anton Tananaev f88891be2b Add another hidden intent filter 2017-09-25 15:01:56 +13:00
Anton Tananaev 3f83684d02 Update version number 2017-09-20 05:23:51 +12:00
Anton Tananaev a9d772bfda Restart service after upgrade 2017-09-20 05:23:27 +12:00
Anton Tananaev 2bc3c78a91 Update version number 2017-09-12 23:22:33 +12:00
Anton Tananaev 8fab55552d Handle no network provider 2017-09-12 23:21:58 +12:00
Anton Tananaev 979963f6a2 Fix auto-start issue on Android O 2017-09-07 19:04:10 +12:00
Anton Tananaev 70d6dfec4e Fix issue with URL migration 2017-09-02 20:01:13 +12:00
Anton Tananaev 43e108de8c Fix number format exception 2017-09-02 08:40:56 +12:00
Anton Tananaev fedce0e26b Fix number format exception 2017-09-02 08:38:42 +12:00
Anton Tananaev e24d725feb Disable failing unit test 2017-09-01 11:20:15 +12:00
Anton Tananaev 3198bee2f2 Use POST to send data (fix #279) 2017-09-01 11:18:26 +12:00
Anton Tananaev 92e0fb3ad5 Remove unnecessary permissions 2017-09-01 10:41:24 +12:00
Anton Tananaev f7b0df1c41 Update version code 2017-09-01 10:36:49 +12:00
Anton Tananaev 8a26c7e5ba Update localization files 2017-09-01 10:34:47 +12:00
Anton Tananaev 27c98b910b Remove redundant colors and use icons 2017-09-01 09:51:57 +12:00
Anton Tananaev 360958d250 New hidden app icon 2017-09-01 08:55:26 +12:00
Anton Tananaev 4d8d15d615 Implement static shortcuts 2017-08-31 14:16:04 +12:00
Anton Tananaev 7c49bb78f3 Implement proper shortcuts 2017-08-31 13:48:52 +12:00
Anton Tananaev e87c3c7783 Report location accuracy 2017-08-31 11:42:15 +12:00
Anton Tananaev 5428530dbd Add Firebase analytics 2017-08-31 11:07:16 +12:00
Anton Tananaev 3d5682d1b1 Move color to common file 2017-08-31 10:14:05 +12:00
Anton Tananaev 8f91c0e0ea New hidden app icon 2017-08-30 23:05:24 +12:00
Anton Tananaev 965feb9b04 Remove old launcher icons 2017-08-30 23:00:05 +12:00
Anton Tananaev 8f8a5e3f55 Add new app icon 2017-08-30 22:48:47 +12:00
Anton Tananaev 77154a81af Use some new Android APIs 2017-08-30 15:34:45 +12:00
Anton Tananaev 8d94b8d011 Implement notification channel 2017-08-30 15:27:00 +12:00
Anton Tananaev ab37fd478a Make spacing more standard 2017-08-30 13:51:06 +12:00
Anton Tananaev 79662bad35 Remove redundant preferences 2017-08-30 13:45:56 +12:00
Anton Tananaev 90373ff8fb Fix app unit tests 2017-08-30 13:44:40 +12:00
Anton Tananaev f2e7ae7c21 Finish app modernization 2017-08-30 13:42:04 +12:00
Anton Tananaev e0c5340d8b Modernize and fix compilation 2017-08-30 13:25:18 +12:00
Anton Tananaev 90fffa9873 Update built tools to latest 2017-08-30 12:59:06 +12:00
Anton Tananaev db51adaf86 Add github templates 2017-08-21 22:44:12 +12:00
Anton Tananaev 7eb6876491 Merge pull request #269 from BrianBlade/basepath
Add serverpath preference
2017-06-15 09:42:27 +12:00
BrianBlade 5db3407e88 remove check if URL isEmpty; dont set 5055 as default port 2017-06-14 14:36:00 +02:00
BrianBlade ff4fe90c6d simplify URL validation 2017-06-14 13:02:03 +02:00
BrianBlade 62a2599765 rename setServerUrl to validateServerUrl and add port validation 2017-06-14 11:00:56 +02:00
BrianBlade 149eee530a some requested fixes 2017-06-14 08:40:40 +02:00
BrianBlade 6e398d12fe add ServerURL preference (replaces address, port, encryption) 2017-06-13 12:40:32 +02:00
Anton Tananaev 10e573017b Bump app version number 2017-05-16 20:50:50 +12:00
Anton Tananaev f5f8d41e41 Validate distance and angle 2017-05-16 20:42:32 +12:00
Anton Tananaev 839b180890 Add new error messages 2017-05-14 12:47:32 +12:00
Anton Tananaev e2a26297c9 Update app version number 2017-05-13 22:03:50 +12:00
Anton Tananaev 4d33d8ff9e Fix locale folder issue 2017-05-13 22:03:27 +12:00
Anton Tananaev 7e923954b8 Update and add localizations 2017-05-13 21:55:52 +12:00
Anton Tananaev f0b0650531 Update translation script 2017-05-13 21:54:31 +12:00
Anton Tananaev 3eb655040d Update gradle plugin version 2017-05-13 21:42:34 +12:00
Anton Tananaev 96a75f02a7 Check shortcut action for null 2017-04-23 12:00:58 +12:00
Anton Tananaev d632c7cdfa Fix issues with notification 2017-04-08 17:47:54 +12:00
Anton Tananaev 9bae6e554f Implement reporting by distance and angle 2017-04-08 17:36:12 +12:00
Anton Tananaev faf23e7926 Final update to badges 2017-03-29 23:02:39 +13:00
Anton Tananaev 54331ffe93 Adjust badge height 2017-03-29 22:51:38 +13:00
Anton Tananaev c19c17f091 Try SVG image in readme 2017-03-29 22:50:18 +13:00
Anton Tananaev 2bd5bf540f Testing embedded badges 2017-03-29 22:44:22 +13:00
Anton Tananaev 3d89cfe649 Merge pull request #260 from Poussinou/master
Add a button with link to fdroid in README.md
2017-03-29 22:18:46 +13:00
Poussinou e2e51b3048 Update README.md 2017-03-28 19:08:27 +02:00
Poussinou 43d9775aa0 add a button with link to fdroid 2017-03-27 16:21:33 +02:00
Anton Tananaev e5bddc5aa6 Update app version number 2017-03-17 00:57:49 +13:00
Anton Tananaev 3b1ba21a76 Relaxed host address matching 2017-03-17 00:56:46 +13:00
Anton Tananaev 26467740fa Update Android gradle plugin 2017-03-01 23:29:44 +13:00
Anton Tananaev a8d6f0b82d Use jcenter maven repository 2017-02-08 21:56:41 +13:00
Anton Tananaev 94acc71f8c Use jcenter maven repository 2017-01-18 23:56:42 +13:00
Anton Tananaev fe4e798eb8 Update app version number 2017-01-15 17:25:02 +13:00
Anton Tananaev 8b72fd5522 Update localization files 2017-01-15 17:24:14 +13:00
Anton Tananaev 4a8b66694c Implement alarm shortcut (fix #244) 2017-01-15 17:16:41 +13:00
Anton Tananaev c717da9c6a Increase wake lock timeout 2017-01-15 16:33:52 +13:00
Anton Tananaev 969cbc6cf4 Update developer tools and SDK 2017-01-15 16:25:57 +13:00
Anton Tananaev a04689350a Update readme file 2016-10-22 14:14:22 +13:00
Anton Tananaev 87e798b459 Update gitignore file 2016-10-10 23:22:12 +13:00
Anton Tananaev 5a99be4005 Update to version 3.12 2016-10-02 11:49:12 +13:00
Anton Tananaev 255598b7e8 Support longer time interval 2016-10-02 11:40:15 +13:00
Anton Tananaev 6c29113278 Update gradle and android plugin 2016-10-02 11:30:15 +13:00
Anton Tananaev a861b71d02 Upload full license file 2016-09-23 03:10:35 +12:00
Anton Tananaev 370e9778b4 Add license file to the project 2016-09-22 17:55:52 +12:00
Anton Tananaev efdaf69d07 Remove unused class import 2016-09-01 02:28:35 +12:00
Anton Tananaev 3dbdaeb555 Handle missing GPS provider 2016-09-01 02:15:05 +12:00
Anton Tananaev 806a31cd92 Avoid index out of bounds 2016-09-01 02:05:20 +12:00
Anton Tananaev fa4654c969 Avoid uninitialized provider type 2016-09-01 01:56:17 +12:00
Anton Tananaev 624eefc309 Increase keep alive for connections 2016-07-29 11:23:44 +12:00
Anton Tananaev 7ab99da14c Fix protocol formatter unit test 2016-07-24 12:50:42 +12:00
Anton Tananaev 532a1f4249 Merge pull request #211 from mwild1/master
Add support for posting locations over HTTPS
2016-07-24 12:46:39 +12:00
Matthew Wild c3986ac172 Add support for posting locations over HTTPS 2016-07-22 21:27:15 +01:00
Anton Tananaev fae01757b6 Add gradle wrapper for Windows 2016-07-07 00:30:09 +12:00
Anton Tananaev 4f1af00383 Update project gradle files 2016-06-19 15:23:30 +12:00
Anton Tananaev 4c4192394f Add Google Play link to readme 2016-05-08 13:48:55 +12:00
176 изменённых файлов: 3479 добавлений и 1264 удалений
+47
Просмотреть файл
@@ -0,0 +1,47 @@
# Contributing to Traccar
Please make sure you read this guide before commenting on any issue or creating a new issue or pull request.
Before asking anything, please search for an answer in:
- [Traccar documentation](https://www.traccar.org/documentation/)
- [Traccar forums](https://www.traccar.org/forums/)
- Relevant repositories (see below)
- [Google Search](https://www.google.com/)
GitHub issues should be used ONLY for feature requests, code discussions and bug reports. For general discussions please use [Traccar forums](https://www.traccar.org/forums/).
There are multiple Traccar projects. If you create a new issue you MUST do it in the relevant repository:
- [Traccar Server](https://github.com/tananaev/traccar/issues)
- [Traccar Web Interface](https://github.com/tananaev/traccar-web/issues)
- [Traccar Client for Android](https://github.com/tananaev/traccar-client-android/issues)
- [Traccar Client for iOS](https://github.com/tananaev/traccar-client-ios/issues)
- [Traccar Manager for Android](https://github.com/tananaev/traccar-manager-android/issues)
- [Traccar Manager for iOS](https://github.com/tananaev/traccar-manager-ios/issues)
If you are not sure where your issue belongs to, please use Traccar Server main repository.
If you want to discuss something that applies to both Android and iOS apps, please use Android repository.
## Bug Reports
Before creating a bug report make sure that you have tested latest official release with default configuration.
Only create a bug report issue if you are confident that there is a problem in Traccar software.
Provide as much details as possible, including log fragments, operating system and hardware information.
## Feature Requests
Before creating a feature request make sure that the feature or modification that you are requesting is not yet implemented.
Search reposiroty to ensure that there is no existing issues for your request. If there is, add a new comment on that issue.
Provide as much details as possible, including use case for your feature and any benefits that you can think of.
## Pull Requests
If you want to contribute some code to Traccar, it is recommended to discuss your solution with maintainers before starting any work.
Any code that you want to contribute must be of high quality and follow existing code patterns and styles.
+7
Просмотреть файл
@@ -0,0 +1,7 @@
<!--
Do you want to ask a question? Traccar official forum is the best place for getting support:
https://www.traccar.org/forums/
-->
+2
Просмотреть файл
@@ -3,3 +3,5 @@
local.properties local.properties
*.iml *.iml
build/ build/
.DS_Store
google-services.json
+202
Просмотреть файл
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+5 -5
Просмотреть файл
@@ -1,15 +1,15 @@
# [Traccar Client for Android](https://www.traccar.org/client) # [Traccar Client for Android](https://www.traccar.org/client)
## Contacts [![Get it on Google Play](http://www.tananaev.com/badges/google-play.svg)](https://play.google.com/store/apps/details?id=org.traccar.client) [![Get it on F-Droid](http://www.tananaev.com/badges/f-droid.svg)](https://f-droid.org/repository/browse/?fdid=org.traccar.client)
Author - Anton Tananaev ([anton.tananaev@gmail.com](mailto:anton.tananaev@gmail.com))
Website - [https://www.traccar.org/client](https://www.traccar.org/client)
## Overview ## Overview
Traccar Client is an Android GPS tracking application. It can work with Traccar open source server software. Traccar Client is an Android GPS tracking application. It can work with Traccar open source server software.
## Team
- Anton Tananaev ([anton@traccar.org](mailto:anton@traccar.org))
## License ## License
Apache License, Version 2.0 Apache License, Version 2.0
+44 -41
Просмотреть файл
@@ -1,60 +1,63 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 23 compileSdkVersion 29
buildToolsVersion '23.0.3'
defaultConfig { defaultConfig {
minSdkVersion 3 applicationId 'org.traccar.client'
targetSdkVersion 23 buildConfigField 'boolean', 'HIDDEN_APP', 'false'
versionCode 31 minSdkVersion 16
versionName '3.11' targetSdkVersion 29
} versionCode 64
versionName '6.3'
signingConfigs {
release {
storeFile file('../../android.keystore')
keyAlias 'tananaev'
storePassword ''
keyPassword ''
}
}
productFlavors {
regular {
applicationId "org.traccar.client"
buildConfigField "boolean", "HIDDEN_APP", "false"
}
hidden {
applicationId "org.traccar.client.hidden"
buildConfigField "boolean", "HIDDEN_APP", "true"
}
}
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.release
}
} }
lintOptions { lintOptions {
checkReleaseBuilds false checkReleaseBuilds false
} }
flavorDimensions 'default'
productFlavors {
regular {
isDefault.set(true)
}
google
hidden {
applicationId 'org.traccar.client.hidden'
buildConfigField 'boolean', 'HIDDEN_APP', 'true'
}
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12' implementation 'com.google.android.material:material:1.3.0-alpha01'
testCompile 'org.robolectric:robolectric:3.0' implementation 'androidx.preference:preference:1.1.1'
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.1'
googleImplementation 'com.google.firebase:firebase-core:17.4.3'
googleImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
googleImplementation 'com.google.firebase:firebase-analytics:17.4.3'
googleImplementation 'com.google.android.gms:play-services-location:17.0.0'
} }
task requestPasswords << { if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
android.signingConfigs.release.storePassword = new String(System.console().readPassword('\nStore password: ')) apply plugin: 'com.google.gms.google-services'
android.signingConfigs.release.keyPassword = new String(System.console().readPassword('Key password: ')) apply plugin: 'com.google.firebase.crashlytics'
task copyJson(type: Copy) {
from '../../traccar-shared/google-services'
into '.'
include 'traccar-client-android.json'
rename('traccar-client-android.json', 'google-services.json')
} }
tasks.whenTaskAdded { theTask -> afterEvaluate {
if (theTask.name.equals('packageRegularRelease') || theTask.name.equals('packageHiddenRelease')) { preBuild.dependsOn copyJson
theTask.dependsOn 'requestPasswords'
} }
} }
+13
Просмотреть файл
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name="org.traccar.client.GoogleMainApplication"
tools:replace="android:name"
tools:ignore="GoogleAppIndexingWarning">
</application>
</manifest>
+30
Просмотреть файл
@@ -0,0 +1,30 @@
/*
* Copyright 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import com.google.firebase.analytics.FirebaseAnalytics;
public class GoogleMainApplication extends MainApplication {
private FirebaseAnalytics firebaseAnalytics;
@Override
public void onCreate() {
super.onCreate();
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
}
}
+84
Просмотреть файл
@@ -0,0 +1,84 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnSuccessListener;
public class GooglePositionProvider extends PositionProvider {
private FusedLocationProviderClient fusedLocationClient;
public GooglePositionProvider(Context context, PositionListener listener) {
super(context, listener);
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
}
@SuppressLint("MissingPermission")
public void startUpdates() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setPriority(getPriority(preferences.getString(MainFragment.KEY_ACCURACY, "medium")));
locationRequest.setInterval(distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval);
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null);
}
public void stopUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
fusedLocationClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
}
});
}
private static int getPriority(String accuracy) {
switch (accuracy) {
case "high":
return LocationRequest.PRIORITY_HIGH_ACCURACY;
case "low":
return LocationRequest.PRIORITY_LOW_POWER;
default:
return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
}
}
private LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
for (Location location : locationResult.getLocations()) {
processLocation(location);
}
}
}
};
}
+26
Просмотреть файл
@@ -0,0 +1,26 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
public class PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) {
return new GooglePositionProvider(context, listener);
}
}
+11 -1
Просмотреть файл
@@ -6,7 +6,17 @@
<application <application
android:label="@string/hidden_app_name" android:label="@string/hidden_app_name"
tools:replace="label"> tools:replace="android:label">
<activity
android:name="org.traccar.client.MainActivity">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.traccar.org" android:pathPrefix="/hidden" />
</intent-filter>
</activity>
<receiver android:name="org.traccar.client.DialLaunchReceiver" > <receiver android:name="org.traccar.client.DialLaunchReceiver" >
<intent-filter> <intent-filter>
+26
Просмотреть файл
@@ -0,0 +1,26 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
public class PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) {
return new AndroidPositionProvider(context, listener);
}
}
+9
Просмотреть файл
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM17.25,12c0,0.23 -0.02,0.46 -0.05,0.68l1.48,1.16c0.13,0.11 0.17,0.3 0.08,0.45l-1.4,2.42c-0.09,0.15 -0.27,0.21 -0.43,0.15l-1.74,-0.7c-0.36,0.28 -0.76,0.51 -1.18,0.69l-0.26,1.85c-0.03,0.17 -0.18,0.3 -0.35,0.3h-2.8c-0.17,0 -0.32,-0.13 -0.35,-0.29l-0.26,-1.85c-0.43,-0.18 -0.82,-0.41 -1.18,-0.69l-1.74,0.7c-0.16,0.06 -0.34,0 -0.43,-0.15l-1.4,-2.42c-0.09,-0.15 -0.05,-0.34 0.08,-0.45l1.48,-1.16c-0.03,-0.23 -0.05,-0.46 -0.05,-0.69 0,-0.23 0.02,-0.46 0.05,-0.68l-1.48,-1.16c-0.13,-0.11 -0.17,-0.3 -0.08,-0.45l1.4,-2.42c0.09,-0.15 0.27,-0.21 0.43,-0.15l1.74,0.7c0.36,-0.28 0.76,-0.51 1.18,-0.69l0.26,-1.85c0.03,-0.17 0.18,-0.3 0.35,-0.3h2.8c0.17,0 0.32,0.13 0.35,0.29l0.26,1.85c0.43,0.18 0.82,0.41 1.18,0.69l1.74,-0.7c0.16,-0.06 0.34,0 0.43,0.15l1.4,2.42c0.09,0.15 0.05,0.34 -0.08,0.45l-1.48,1.16c0.03,0.23 0.05,0.46 0.05,0.69z"/>
</vector>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 5.5 KiB

После

Ширина:  |  Высота:  |  Размер: 3.3 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.3 KiB

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.0 KiB

После

Ширина:  |  Высота:  |  Размер: 4.7 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.5 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 14 KiB

После

Ширина:  |  Высота:  |  Размер: 7.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.7 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 20 KiB

После

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.4 KiB

+33 -8
Просмотреть файл
@@ -1,22 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.traccar.client"> package="org.traccar.client">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-feature android:name="android.hardware.location.network" />
<uses-feature android:name="android.hardware.location.gps" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/TraccarTheme"> android:theme="@style/AppTheme"
android:name=".MainApplication"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity" android:launchMode="singleTask" /> <meta-data
android:name="google_analytics_adid_collection_enabled"
android:value="false" />
<activity
android:name=".MainActivity"
android:launchMode="singleTask" />
<activity-alias <activity-alias
android:name=".Launcher" android:name=".Launcher"
@@ -25,27 +41,36 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias> </activity-alias>
<activity android:name=".StatusActivity"/> <activity android:name=".StatusActivity"/>
<activity android:name=".AboutActivity"/>
<activity <activity
android:name=".ShortcutActivity"> android:name=".ShortcutActivity"
android:label="@string/menu_shortcuts">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.CREATE_SHORTCUT" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".TrackingService" /> <service
android:name=".TrackingService"
android:foregroundServiceType="location" />
<service android:name=".TrackingService$HideNotificationService" /> <service
android:name=".TrackingService$HideNotificationService" />
<receiver android:name=".AutostartReceiver"> <receiver android:name=".AutostartReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver> </receiver>
</application> </application>
Двоичные данные
Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 26 KiB

-134
Просмотреть файл
@@ -1,134 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v4.content;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;
import android.util.SparseArray;
/**
* Helper for the common pattern of implementing a {@link BroadcastReceiver}
* that receives a device wakeup event and then passes the work off
* to a {@link android.app.Service}, while ensuring that the
* device does not go back to sleep during the transition.
*
* <p>This class takes care of creating and managing a partial wake lock
* for you; you must request the {@link android.Manifest.permission#WAKE_LOCK}
* permission to use it.</p>
*
* <h3>Example</h3>
*
* <p>A {@link WakefulBroadcastReceiver} uses the method
* {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
* to start the service that does the work. This method is comparable to
* {@link android.content.Context#startService startService()}, except that
* the {@link WakefulBroadcastReceiver} is holding a wake lock when the service
* starts. The intent that is passed with
* {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
* holds an extra identifying the wake lock.</p>
*
* {@sample development/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulReceiver.java complete}
*
* <p>The service (in this example, an {@link android.app.IntentService}) does
* some work. When it is finished, it releases the wake lock by calling
* {@link WakefulBroadcastReceiver#completeWakefulIntent
* completeWakefulIntent(intent)}. The intent it passes as a parameter
* is the same intent that the {@link WakefulBroadcastReceiver} originally
* passed in.</p>
*
* {@sample development/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulService.java complete}
*/
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
= new SparseArray<PowerManager.WakeLock>();
private static int mNextId = 1;
/**
* Do a {@link android.content.Context#startService(android.content.Intent)
* Context.startService}, but holding a wake lock while the service starts.
* This will modify the Intent to hold an extra identifying the wake lock;
* when the service receives it in {@link android.app.Service#onStartCommand
* Service.onStartCommand}, it should pass back the Intent it receives there to
* {@link #completeWakefulIntent(android.content.Intent)} in order to release
* the wake lock.
*
* @param context The Context in which it operate.
* @param intent The Intent with which to start the service, as per
* {@link android.content.Context#startService(android.content.Intent)
* Context.startService}.
*/
public static ComponentName startWakefulService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ComponentName comp = context.startService(intent);
if (comp == null) {
return null;
}
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"wake:" + comp.flattenToShortString());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
return comp;
}
}
/**
* Finish the execution from a previous {@link #startWakefulService}. Any wake lock
* that was being held will now be released.
*
* @param intent The Intent as originally generated by {@link #startWakefulService}.
* @return Returns true if the intent is associated with a wake lock that is
* now released; returns false if there was no wake lock specified for it.
*/
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
// We return true whether or not we actually found the wake lock
// the return code is defined to indicate whether the Intent contained
// an identifier for a wake lock that it was supposed to match.
// We just log a warning here if there is no wake lock found, which could
// happen for example if this function is called twice on the same
// intent or the process is killed and restarted before processing the intent.
Log.w("WakefulBroadcastReceive", "No active wake lock id #" + id);
return true;
}
}
}
-37
Просмотреть файл
@@ -1,37 +0,0 @@
/*
* Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.app.Activity;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.widget.TextView;
public class AboutActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
TextView title = (TextView) findViewById(R.id.title);
try {
title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName);
} catch (NameNotFoundException e) {
}
}
}
+110
Просмотреть файл
@@ -0,0 +1,110 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
public class AndroidPositionProvider extends PositionProvider implements LocationListener {
private LocationManager locationManager;
private String provider;
public AndroidPositionProvider(Context context, PositionListener listener) {
super(context, listener);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
provider = getProvider(preferences.getString(MainFragment.KEY_ACCURACY, "medium"));
}
@SuppressLint("MissingPermission")
public void startUpdates() {
try {
locationManager.requestLocationUpdates(
provider, distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval, 0, this);
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
public void stopUpdates() {
locationManager.removeUpdates(this);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
try {
Location location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else {
locationManager.requestSingleUpdate(provider, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}, Looper.myLooper());
}
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
private static String getProvider(String accuracy) {
switch (accuracy) {
case "high":
return LocationManager.GPS_PROVIDER;
case "low":
return LocationManager.PASSIVE_PROVIDER;
default:
return LocationManager.NETWORK_PROVIDER;
}
}
@Override
public void onLocationChanged(Location location) {
processLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
+3 -4
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2013 - 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,15 +19,14 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.WakefulBroadcastReceiver;
public class AutostartReceiver extends WakefulBroadcastReceiver { public class AutostartReceiver extends WakefulBroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.getBoolean(MainActivity.KEY_STATUS, false)) { if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
startWakefulService(context, new Intent(context, TrackingService.class)); startWakefulForegroundService(context, new Intent(context, TrackingService.class));
} }
} }
+14 -3
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.Date;
public class DatabaseHelper extends SQLiteOpenHelper { public class DatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1; public static final int DATABASE_VERSION = 3;
public static final String DATABASE_NAME = "traccar.db"; public static final String DATABASE_NAME = "traccar.db";
public interface DatabaseHandler<T> { public interface DatabaseHandler<T> {
@@ -79,7 +79,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"altitude REAL," + "altitude REAL," +
"speed REAL," + "speed REAL," +
"course REAL," + "course REAL," +
"battery REAL)"); "accuracy REAL," +
"battery REAL," +
"mock INTEGER)");
} }
@Override @Override
@@ -88,6 +90,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
onCreate(db); onCreate(db);
} }
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS position;");
onCreate(db);
}
public void insertPosition(Position position) { public void insertPosition(Position position) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("deviceId", position.getDeviceId()); values.put("deviceId", position.getDeviceId());
@@ -97,7 +104,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
values.put("altitude", position.getAltitude()); values.put("altitude", position.getAltitude());
values.put("speed", position.getSpeed()); values.put("speed", position.getSpeed());
values.put("course", position.getCourse()); values.put("course", position.getCourse());
values.put("accuracy", position.getAccuracy());
values.put("battery", position.getBattery()); values.put("battery", position.getBattery());
values.put("mock", position.getMock() ? 1 : 0);
db.insertOrThrow("position", null, values); db.insertOrThrow("position", null, values);
} }
@@ -129,7 +138,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude"))); position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude")));
position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed"))); position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed")));
position.setCourse(cursor.getDouble(cursor.getColumnIndex("course"))); position.setCourse(cursor.getDouble(cursor.getColumnIndex("course")));
position.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy")));
position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery"))); position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
position.setMock(cursor.getInt(cursor.getColumnIndex("mock")) > 0);
} else { } else {
return null; return null;
+1 -1
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+7 -251
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2016 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,261 +15,17 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import androidx.annotation.Nullable;
import android.preference.EditTextPreference; import androidx.appcompat.app.AppCompatActivity;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.TwoStatePreference;
import android.util.Patterns;
import android.view.Menu;
import android.view.MenuItem;
import java.util.HashSet; public class MainActivity extends AppCompatActivity {
import java.util.Random;
import java.util.Set;
@SuppressWarnings("deprecation")
public class MainActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
public static final String KEY_DEVICE = "id";
public static final String KEY_ADDRESS = "address";
public static final String KEY_PORT = "port";
public static final String KEY_INTERVAL = "interval";
public static final String KEY_PROVIDER = "provider";
public static final String KEY_STATUS = "status";
private static final int PERMISSIONS_REQUEST_LOCATION = 2;
private SharedPreferences sharedPreferences;
private AlarmManager alarmManager;
private PendingIntent alarmIntent;
@Override @Override
public void onCreate(Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
if (BuildConfig.HIDDEN_APP) { getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new MainFragment()).commit();
removeLauncherIcon();
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
addPreferencesFromResource(R.xml.preferences);
initPreferences();
findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return newValue != null && !newValue.equals("");
}
});
findPreference(KEY_ADDRESS).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
return newValue != null && Patterns.DOMAIN_NAME.matcher((String) newValue).matches();
} else {
return newValue != null && !((String) newValue).isEmpty();
}
}
});
findPreference(KEY_PORT).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
int value = Integer.parseInt((String) newValue);
if (value > 0 && value <= 65536) {
return true;
}
} catch (NumberFormatException e) {
}
}
return false;
}
});
findPreference(KEY_INTERVAL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
Integer.parseInt((String) newValue);
return true;
} catch (NumberFormatException e) {
}
}
return false;
}
});
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmIntent = PendingIntent.getBroadcast(this, 0, new Intent(this, AutostartReceiver.class), 0);
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
}
}
private void removeLauncherIcon() {
String className = MainActivity.class.getCanonicalName().replace(".MainActivity", ".Launcher");
ComponentName componentName = new ComponentName(getPackageName(), className);
PackageManager packageManager = getPackageManager();
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting(
componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.hidden_alert));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
private void addShortcuts(boolean start, int name) {
Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
shortcutIntent.setComponent(new ComponentName(getPackageName(), ShortcutActivity.class.getCanonicalName()));
shortcutIntent.putExtra(ShortcutActivity.EXTRA_ACTION, start);
Intent installShortCutIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
installShortCutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
installShortCutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(name));
installShortCutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher));
sendBroadcast(installShortCutIntent);
}
private boolean hasPermission(String permission) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
return true;
}
return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
@Override
protected void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
private void setPreferencesEnabled(boolean enabled) {
findPreference(KEY_DEVICE).setEnabled(enabled);
findPreference(KEY_ADDRESS).setEnabled(enabled);
findPreference(KEY_PORT).setEnabled(enabled);
findPreference(KEY_INTERVAL).setEnabled(enabled);
findPreference(KEY_PROVIDER).setEnabled(enabled);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_STATUS)) {
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
} else {
stopTrackingService();
}
} else if (key.equals(KEY_DEVICE)) {
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.status) {
startActivity(new Intent(this, StatusActivity.class));
return true;
} else if (item.getItemId() == R.id.shortcuts) {
addShortcuts(true, R.string.shortcut_start);
addShortcuts(false, R.string.shortcut_stop);
return true;
} else if (item.getItemId() == R.id.about) {
startActivity(new Intent(this, AboutActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
private void initPreferences() {
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
if (!sharedPreferences.contains(KEY_DEVICE)) {
String id = String.valueOf(new Random().nextInt(900000) + 100000);
sharedPreferences.edit().putString(KEY_DEVICE, id).commit();
((EditTextPreference) findPreference(KEY_DEVICE)).setText(id);
}
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
private void startTrackingService(boolean checkPermission, boolean permission) {
if (checkPermission) {
Set<String> missingPermissions = new HashSet<>();
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (!hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
missingPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
if (missingPermissions.isEmpty()) {
permission = true;
} else {
requestPermissions(missingPermissions.toArray(new String[missingPermissions.size()]), PERMISSIONS_REQUEST_LOCATION);
return;
}
}
if (permission) {
setPreferencesEnabled(false);
startService(new Intent(this, TrackingService.class));
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
15000, 15000, alarmIntent);
} else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).commit();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TwoStatePreference preference = (TwoStatePreference) findPreference(KEY_STATUS);
preference.setChecked(false);
} else {
CheckBoxPreference preference = (CheckBoxPreference) findPreference(KEY_STATUS);
preference.setChecked(false);
}
}
}
private void stopTrackingService() {
alarmManager.cancel(alarmIntent);
stopService(new Intent(this, TrackingService.class));
setPreferencesEnabled(true);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
startTrackingService(false, grantResults[0] == PackageManager.PERMISSION_GRANTED &&
(permissions.length < 2 || grantResults[1] == PackageManager.PERMISSION_GRANTED));
} }
} }
+73
Просмотреть файл
@@ -0,0 +1,73 @@
/*
* Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
public class MainApplication extends Application {
public static final String PRIMARY_CHANNEL = "default";
@Override
public void onCreate() {
super.onCreate();
System.setProperty("http.keepAliveDuration", String.valueOf(30 * 60 * 1000));
migrateLegacyPreferences(PreferenceManager.getDefaultSharedPreferences(this));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerChannel();
}
}
@TargetApi(Build.VERSION_CODES.O)
private void registerChannel() {
NotificationChannel channel = new NotificationChannel(
PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_MIN);
channel.setLightColor(Color.GREEN);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
}
private void migrateLegacyPreferences(SharedPreferences preferences) {
String port = preferences.getString("port", null);
if (port != null) {
String host = preferences.getString("address", getString(R.string.settings_url_default_value));
String scheme = preferences.getBoolean("secure", false) ? "https" : "http";
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme).encodedAuthority(host + ":" + port).build();
SharedPreferences.Editor editor = preferences.edit();
editor.putString(MainFragment.KEY_URL, builder.toString());
editor.remove("port");
editor.remove("address");
editor.remove("secure");
editor.apply();
}
}
}
+311
Просмотреть файл
@@ -0,0 +1,311 @@
/*
* Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.webkit.URLUtil;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.EditTextPreference;
import androidx.preference.EditTextPreferenceDialogFragmentCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.TwoStatePreference;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class MainFragment extends PreferenceFragmentCompat implements OnSharedPreferenceChangeListener {
private static final String TAG = MainFragment.class.getSimpleName();
private static final int ALARM_MANAGER_INTERVAL = 15000;
public static final String KEY_DEVICE = "id";
public static final String KEY_URL = "url";
public static final String KEY_INTERVAL = "interval";
public static final String KEY_DISTANCE = "distance";
public static final String KEY_ANGLE = "angle";
public static final String KEY_ACCURACY = "accuracy";
public static final String KEY_STATUS = "status";
public static final String KEY_BUFFER = "buffer";
private static final int PERMISSIONS_REQUEST_LOCATION = 2;
private SharedPreferences sharedPreferences;
private AlarmManager alarmManager;
private PendingIntent alarmIntent;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
removeLauncherIcon();
}
setHasOptionsMenu(true);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
addPreferencesFromResource(R.xml.preferences);
initPreferences();
findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return newValue != null && !newValue.equals("");
}
});
findPreference(KEY_URL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return (newValue != null) && validateServerURL(newValue.toString());
}
});
findPreference(KEY_INTERVAL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
int value = Integer.parseInt((String) newValue);
return value > 0;
} catch (NumberFormatException e) {
Log.w(TAG, e);
}
}
return false;
}
});
Preference.OnPreferenceChangeListener numberValidationListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
int value = Integer.parseInt((String) newValue);
return value >= 0;
} catch (NumberFormatException e) {
Log.w(TAG, e);
}
}
return false;
}
};
findPreference(KEY_DISTANCE).setOnPreferenceChangeListener(numberValidationListener);
findPreference(KEY_ANGLE).setOnPreferenceChangeListener(numberValidationListener);
alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
alarmIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(getActivity(), AutostartReceiver.class), 0);
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
}
}
public static class NumericEditTextPreferenceDialogFragment extends EditTextPreferenceDialogFragmentCompat {
public static NumericEditTextPreferenceDialogFragment newInstance(String key) {
final NumericEditTextPreferenceDialogFragment fragment = new NumericEditTextPreferenceDialogFragment();
final Bundle bundle = new Bundle();
bundle.putString(ARG_KEY, key);
fragment.setArguments(bundle);
return fragment;
}
@Override
protected void onBindDialogView(View view) {
EditText editText = view.findViewById(android.R.id.edit);
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
super.onBindDialogView(view);
}
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
if (Arrays.asList(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.getKey())) {
final EditTextPreferenceDialogFragmentCompat f = NumericEditTextPreferenceDialogFragment.newInstance(preference.getKey());
f.setTargetFragment(this, 0);
f.show(getFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG");
} else {
super.onDisplayPreferenceDialog(preference);
}
}
private void removeLauncherIcon() {
String className = MainActivity.class.getCanonicalName().replace(".MainActivity", ".Launcher");
ComponentName componentName = new ComponentName(getActivity().getPackageName(), className);
PackageManager packageManager = getActivity().getPackageManager();
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting(
componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.hidden_alert));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
@Override
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
private void setPreferencesEnabled(boolean enabled) {
findPreference(KEY_DEVICE).setEnabled(enabled);
findPreference(KEY_URL).setEnabled(enabled);
findPreference(KEY_INTERVAL).setEnabled(enabled);
findPreference(KEY_DISTANCE).setEnabled(enabled);
findPreference(KEY_ANGLE).setEnabled(enabled);
findPreference(KEY_ACCURACY).setEnabled(enabled);
findPreference(KEY_BUFFER).setEnabled(enabled);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_STATUS)) {
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
} else {
stopTrackingService();
}
} else if (key.equals(KEY_DEVICE)) {
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.status) {
startActivity(new Intent(getActivity(), StatusActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
private void initPreferences() {
PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false);
if (!sharedPreferences.contains(KEY_DEVICE)) {
String id = String.valueOf(new Random().nextInt(900000) + 100000);
sharedPreferences.edit().putString(KEY_DEVICE, id).apply();
((EditTextPreference) findPreference(KEY_DEVICE)).setText(id);
}
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
private void startTrackingService(boolean checkPermission, boolean permission) {
if (checkPermission) {
Set<String> requiredPermissions = new HashSet<>();
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
permission = requiredPermissions.isEmpty();
if (!permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(requiredPermissions.toArray(new String[requiredPermissions.size()]), PERMISSIONS_REQUEST_LOCATION);
}
return;
}
}
if (permission) {
setPreferencesEnabled(false);
ContextCompat.startForegroundService(getContext(), new Intent(getActivity(), TrackingService.class));
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
ALARM_MANAGER_INTERVAL, ALARM_MANAGER_INTERVAL, alarmIntent);
} else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply();
TwoStatePreference preference = findPreference(KEY_STATUS);
preference.setChecked(false);
}
}
private void stopTrackingService() {
alarmManager.cancel(alarmIntent);
getActivity().stopService(new Intent(getActivity(), TrackingService.class));
setPreferencesEnabled(true);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
boolean granted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
granted = false;
break;
}
}
startTrackingService(false, granted);
}
}
private boolean validateServerURL(String userUrl) {
int port = Uri.parse(userUrl).getPort();
if (URLUtil.isValidUrl(userUrl) && (port == -1 || (port > 0 && port <= 65535))
&& (URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))) {
return true;
}
Toast.makeText(getActivity(), R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show();
return false;
}
}
-117
Просмотреть файл
@@ -1,117 +0,0 @@
/*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
public class MixedPositionProvider extends PositionProvider implements LocationListener, GpsStatus.Listener {
private static int FIX_TIMEOUT = 30 * 1000;
private LocationListener backupListener;
private long lastFixTime;
public MixedPositionProvider(Context context, PositionListener listener) {
super(context, listener);
}
public void startUpdates() {
lastFixTime = System.currentTimeMillis();
locationManager.addGpsStatusListener(this);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, period, 0, this);
}
public void stopUpdates() {
locationManager.removeUpdates(this);
locationManager.removeGpsStatusListener(this);
stopBackupProvider();
}
private void startBackupProvider() {
Log.i(TAG, "backup provider start");
if (backupListener == null) {
backupListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, "backup provider location");
updateLocation(location);
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
@Override
public void onProviderEnabled(String s) {
}
@Override
public void onProviderDisabled(String s) {
}
};
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, period, 0, backupListener);
}
}
private void stopBackupProvider() {
Log.i(TAG, "backup provider stop");
if (backupListener != null) {
locationManager.removeUpdates(backupListener);
backupListener = null;
}
}
@Override
public void onLocationChanged(Location location) {
Log.i(TAG, "provider location");
stopBackupProvider();
lastFixTime = System.currentTimeMillis();
updateLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
Log.i(TAG, "provider enabled");
stopBackupProvider();
}
@Override
public void onProviderDisabled(String provider) {
Log.i(TAG, "provider disabled");
startBackupProvider();
}
@Override
public void onGpsStatusChanged(int event) {
if (backupListener == null && System.currentTimeMillis() - (lastFixTime + period) > FIX_TIMEOUT) {
startBackupProvider();
}
}
}
+1 -1
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+101 -19
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.traccar.client; package org.traccar.client;
import android.location.Location; import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import java.util.Date; import java.util.Date;
@@ -32,43 +34,123 @@ public class Position {
altitude = location.getAltitude(); altitude = location.getAltitude();
speed = location.getSpeed() * 1.943844; // speed in knots speed = location.getSpeed() * 1.943844; // speed in knots
course = location.getBearing(); course = location.getBearing();
if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
accuracy = location.getAccuracy();
}
this.battery = battery; this.battery = battery;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
this.mock = location.isFromMockProvider();
}
} }
private long id; private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; } public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
private String deviceId; private String deviceId;
public String getDeviceId() { return deviceId; }
public void setDeviceId(String deviceId) { this.deviceId = deviceId; } public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
private Date time; private Date time;
public Date getTime() { return time; }
public void setTime(Date time) { this.time = time; } public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
private double latitude; private double latitude;
public double getLatitude() { return latitude; }
public void setLatitude(double latitude) { this.latitude = latitude; } public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
private double longitude; private double longitude;
public double getLongitude() { return longitude; }
public void setLongitude(double longitude) { this.longitude = longitude; } public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
private double altitude; private double altitude;
public double getAltitude() { return altitude; }
public void setAltitude(double altitude) { this.altitude = altitude; } public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
this.altitude = altitude;
}
private double speed; private double speed;
public double getSpeed() { return speed; }
public void setSpeed(double speed) { this.speed = speed; } public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
private double course; private double course;
public double getCourse() { return course; }
public void setCourse(double course) { this.course = course; } public double getCourse() {
return course;
}
public void setCourse(double course) {
this.course = course;
}
private double accuracy;
public double getAccuracy() {
return accuracy;
}
public void setAccuracy(double accuracy) {
this.accuracy = accuracy;
}
private double battery; private double battery;
public double getBattery() { return battery; }
public void setBattery(double battery) { this.battery = battery; } public double getBattery() {
return battery;
}
public void setBattery(double battery) {
this.battery = battery;
}
private boolean mock;
public boolean getMock() {
return mock;
}
public void setMock(boolean mock) {
this.mock = mock;
}
} }
+30 -28
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,75 +15,77 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.location.Location; import android.location.Location;
import android.location.LocationManager;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
public abstract class PositionProvider { public abstract class PositionProvider {
protected static final String TAG = PositionProvider.class.getSimpleName(); private static final String TAG = PositionProvider.class.getSimpleName();
protected static final int MINIMUM_INTERVAL = 1000;
public interface PositionListener { public interface PositionListener {
void onPositionUpdate(Position position); void onPositionUpdate(Position position);
void onPositionError(Throwable error);
} }
private final PositionListener listener; protected final PositionListener listener;
private final Context context; protected final Context context;
private final SharedPreferences preferences; protected SharedPreferences preferences;
protected final LocationManager locationManager;
private String deviceId; protected String deviceId;
protected String type; protected long interval;
protected final long period; protected double distance;
protected double angle;
private long lastUpdateTime; protected Location lastLocation;
public PositionProvider(Context context, PositionListener listener) { public PositionProvider(Context context, PositionListener listener) {
this.context = context; this.context = context;
this.listener = listener; this.listener = listener;
preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
deviceId = preferences.getString(MainActivity.KEY_DEVICE, null); deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined");
period = Integer.parseInt(preferences.getString(MainActivity.KEY_INTERVAL, null)) * 1000; interval = Long.parseLong(preferences.getString(MainFragment.KEY_INTERVAL, "600")) * 1000;
distance = Integer.parseInt(preferences.getString(MainFragment.KEY_DISTANCE, "0"));
type = preferences.getString(MainActivity.KEY_PROVIDER, null); angle = Integer.parseInt(preferences.getString(MainFragment.KEY_ANGLE, "0"));
} }
public abstract void startUpdates(); public abstract void startUpdates();
public abstract void stopUpdates(); public abstract void stopUpdates();
protected void updateLocation(Location location) { public abstract void requestSingleLocation();
if (location != null && location.getTime() - lastUpdateTime >= period) {
protected void processLocation(Location location) {
if (location != null && (lastLocation == null
|| location.getTime() - lastLocation.getTime() >= interval
|| distance > 0 && location.distanceTo(lastLocation) >= distance
|| angle > 0 && Math.abs(location.getBearing() - lastLocation.getBearing()) >= angle)) {
Log.i(TAG, "location new"); Log.i(TAG, "location new");
lastUpdateTime = location.getTime(); lastLocation = location;
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel())); listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else { } else {
Log.i(TAG, location != null ? "location old" : "location nil"); Log.i(TAG, location != null ? "location ignored" : "location nil");
} }
} }
@TargetApi(Build.VERSION_CODES.ECLAIR) protected static double getBatteryLevel(Context context) {
private double getBatteryLevel() {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.ECLAIR) {
Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batteryIntent != null) {
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
return (level * 100.0) / scale; return (level * 100.0) / scale;
} else {
return 0;
} }
return 0;
} }
} }
+16 -6
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,15 +15,17 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.location.Location;
import android.net.Uri; import android.net.Uri;
public class ProtocolFormatter { public class ProtocolFormatter {
public static String formatRequest(String address, int port, Position position) { public static String formatRequest(String url, Position position) {
return formatRequest(url, position, null);
}
Uri.Builder builder = new Uri.Builder(); public static String formatRequest(String url, Position position, String alarm) {
builder.scheme("http").encodedAuthority(address + ':' + port) Uri serverUrl = Uri.parse(url);
Uri.Builder builder = serverUrl.buildUpon()
.appendQueryParameter("id", position.getDeviceId()) .appendQueryParameter("id", position.getDeviceId())
.appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000)) .appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000))
.appendQueryParameter("lat", String.valueOf(position.getLatitude())) .appendQueryParameter("lat", String.valueOf(position.getLatitude()))
@@ -31,9 +33,17 @@ public class ProtocolFormatter {
.appendQueryParameter("speed", String.valueOf(position.getSpeed())) .appendQueryParameter("speed", String.valueOf(position.getSpeed()))
.appendQueryParameter("bearing", String.valueOf(position.getCourse())) .appendQueryParameter("bearing", String.valueOf(position.getCourse()))
.appendQueryParameter("altitude", String.valueOf(position.getAltitude())) .appendQueryParameter("altitude", String.valueOf(position.getAltitude()))
.appendQueryParameter("accuracy", String.valueOf(position.getAccuracy()))
.appendQueryParameter("batt", String.valueOf(position.getBattery())); .appendQueryParameter("batt", String.valueOf(position.getBattery()));
if (position.getMock()) {
builder.appendQueryParameter("mock", String.valueOf(position.getMock()));
}
if (alarm != null) {
builder.appendQueryParameter("alarm", alarm);
}
return builder.build().toString(); return builder.build().toString();
} }
} }
+4 -2
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.traccar.client; package org.traccar.client;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -56,6 +57,7 @@ public class RequestManager {
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(TIMEOUT); connection.setReadTimeout(TIMEOUT);
connection.setConnectTimeout(TIMEOUT); connection.setConnectTimeout(TIMEOUT);
connection.setRequestMethod("POST");
connection.connect(); connection.connect();
inputStream = connection.getInputStream(); inputStream = connection.getInputStream();
while (inputStream.read() != -1); while (inputStream.read() != -1);
@@ -68,7 +70,7 @@ public class RequestManager {
inputStream.close(); inputStream.close();
} }
} catch (IOException secondError) { } catch (IOException secondError) {
return false; Log.w(RequestManager.class.getSimpleName(), secondError);
} }
} }
} }
+122 -14
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,42 +15,150 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
public class ShortcutActivity extends Activity { import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
public static final String EXTRA_ACTION = "shortcutAction"; public class ShortcutActivity extends AppCompatActivity {
public static final String EXTRA_ACTION = "action";
public static final String ACTION_START = "start";
public static final String ACTION_STOP = "stop";
public static final String ACTION_SOS = "sos";
private static final String ALARM_SOS = "sos";
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
checkShortcutAction(getIntent()); if (!executeAction(getIntent())) {
setContentView(R.layout.list);
final String[] items = new String[] {
getString(R.string.shortcut_start),
getString(R.string.shortcut_stop),
getString(R.string.shortcut_sos)
};
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(new ArrayAdapter<>(
this, android.R.layout.simple_list_item_1, items));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START);
break;
case 1:
setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP);
break;
case 2:
setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS);
break;
}
finish();
}
});
}
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
checkShortcutAction(intent); executeAction(intent);
} }
private void checkShortcutAction(Intent intent) { private void setShortcutResult(String label, @DrawableRes int iconResId, String action) {
if (intent.hasExtra(EXTRA_ACTION)) { Intent intent = new Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity.class);
boolean start = intent.getBooleanExtra(EXTRA_ACTION, false); intent.putExtra(EXTRA_ACTION, action);
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainActivity.KEY_STATUS, start).commit(); ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, action)
if (start) { .setShortLabel(label)
startService(new Intent(this, TrackingService.class)); .setIcon(IconCompat.createWithResource(this, iconResId))
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show(); .setIntent(intent)
.build();
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut));
}
@SuppressWarnings("MissingPermission")
private void sendAlarm() {
PositionProviderFactory.create(this, new PositionProvider.PositionListener() {
@Override
public void onPositionUpdate(Position position) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ShortcutActivity.this);
String request = ProtocolFormatter.formatRequest(
preferences.getString(MainFragment.KEY_URL, null), position, ALARM_SOS);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override
public void onComplete(boolean success) {
if (success) {
Toast.makeText(ShortcutActivity.this, R.string.status_send_success, Toast.LENGTH_SHORT).show();
} else { } else {
Toast.makeText(ShortcutActivity.this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onPositionError(Throwable error) {
Toast.makeText(ShortcutActivity.this, error.getMessage(), Toast.LENGTH_LONG).show();
}
}).requestSingleLocation();
}
private boolean executeAction(Intent intent) {
String action;
if (intent.hasExtra("shortcutAction")) {
action = intent.getBooleanExtra("shortcutAction", false)
? ACTION_START : ACTION_STOP;
} else {
action = intent.getStringExtra(EXTRA_ACTION);
}
if (action != null) {
switch (action) {
case ACTION_START:
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainFragment.KEY_STATUS, true).apply();
ContextCompat.startForegroundService(this, new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show();
break;
case ACTION_STOP:
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainFragment.KEY_STATUS, false).apply();
stopService(new Intent(this, TrackingService.class)); stopService(new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show();
break;
case ACTION_SOS:
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
sendAlarm();
} else {
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
break;
} }
finish(); finish();
} }
return action != null;
} }
} }
-58
Просмотреть файл
@@ -1,58 +0,0 @@
/*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
public class SimplePositionProvider extends PositionProvider implements LocationListener {
public SimplePositionProvider(Context context, PositionListener listener) {
super(context, listener);
if (!type.equals(LocationManager.NETWORK_PROVIDER)) {
type = LocationManager.GPS_PROVIDER;
}
}
public void startUpdates() {
locationManager.requestLocationUpdates(type, period, 0, this);
}
public void stopUpdates() {
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(Location location) {
updateLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
+11 -9
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2013 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,19 +21,20 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Set; import java.util.Set;
import android.app.ListActivity;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView;
public class StatusActivity extends ListActivity { public class StatusActivity extends AppCompatActivity {
private static final int LIMIT = 20; private static final int LIMIT = 20;
private static final LinkedList<String> messages = new LinkedList<String>(); private static final LinkedList<String> messages = new LinkedList<>();
private static final Set<ArrayAdapter<String>> adapters = new HashSet<ArrayAdapter<String>>(); private static final Set<ArrayAdapter<String>> adapters = new HashSet<>();
private static void notifyAdapters() { private static void notifyAdapters() {
for (ArrayAdapter<String> adapter : adapters) { for (ArrayAdapter<String> adapter : adapters) {
@@ -42,7 +43,7 @@ public class StatusActivity extends ListActivity {
} }
public static void addMessage(String message) { public static void addMessage(String message) {
DateFormat format = DateFormat.getTimeInstance(DateFormat.SHORT); DateFormat format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
message = format.format(new Date()) + " - " + message; message = format.format(new Date()) + " - " + message;
messages.add(message); messages.add(message);
while (messages.size() > LIMIT) { while (messages.size() > LIMIT) {
@@ -61,9 +62,10 @@ public class StatusActivity extends ListActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.status); setContentView(R.layout.list);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages); adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages);
setListAdapter(adapter); ListView listView = findViewById(android.R.id.list);
listView.setAdapter(adapter);
adapters.add(adapter); adapters.add(adapter);
} }
+23 -43
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,9 +17,7 @@ package org.traccar.client;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
@@ -27,7 +25,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private static final String TAG = TrackingController.class.getSimpleName(); private static final String TAG = TrackingController.class.getSimpleName();
private static final int RETRY_DELAY = 30 * 1000; private static final int RETRY_DELAY = 30 * 1000;
private static final int WAKE_LOCK_TIMEOUT = 60 * 1000; private static final int WAKE_LOCK_TIMEOUT = 120 * 1000;
private boolean isOnline; private boolean isOnline;
private boolean isWaiting; private boolean isWaiting;
@@ -36,47 +34,24 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private Handler handler; private Handler handler;
private SharedPreferences preferences; private SharedPreferences preferences;
private String address; private String url;
private int port; private boolean buffer;
private PositionProvider positionProvider; private PositionProvider positionProvider;
private DatabaseHelper databaseHelper; private DatabaseHelper databaseHelper;
private NetworkManager networkManager; private NetworkManager networkManager;
private PowerManager.WakeLock wakeLock;
private void lock() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
wakeLock.acquire();
} else {
wakeLock.acquire(WAKE_LOCK_TIMEOUT);
}
}
private void unlock() {
if (wakeLock.isHeld()) {
wakeLock.release();
}
}
public TrackingController(Context context) { public TrackingController(Context context) {
this.context = context; this.context = context;
handler = new Handler(); handler = new Handler();
preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getString(MainActivity.KEY_PROVIDER, null).equals("mixed")) { positionProvider = PositionProviderFactory.create(context, this);
positionProvider = new MixedPositionProvider(context, this);
} else {
positionProvider = new SimplePositionProvider(context, this);
}
databaseHelper = new DatabaseHelper(context); databaseHelper = new DatabaseHelper(context);
networkManager = new NetworkManager(context, this); networkManager = new NetworkManager(context, this);
isOnline = networkManager.isOnline(); isOnline = networkManager.isOnline();
address = preferences.getString(MainActivity.KEY_ADDRESS, null); url = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value));
port = Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null)); buffer = preferences.getBoolean(MainFragment.KEY_BUFFER, true);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
} }
public void start() { public void start() {
@@ -105,13 +80,22 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
public void onPositionUpdate(Position position) { public void onPositionUpdate(Position position) {
StatusActivity.addMessage(context.getString(R.string.status_location_update)); StatusActivity.addMessage(context.getString(R.string.status_location_update));
if (position != null) { if (position != null) {
if (buffer) {
write(position); write(position);
} else {
send(position);
}
} }
} }
@Override
public void onPositionError(Throwable error) {
}
@Override @Override
public void onNetworkUpdate(boolean isOnline) { public void onNetworkUpdate(boolean isOnline) {
StatusActivity.addMessage(context.getString(R.string.status_connectivity_change)); int message = isOnline ? R.string.status_network_online : R.string.status_network_offline;
StatusActivity.addMessage(context.getString(message));
if (!this.isOnline && isOnline) { if (!this.isOnline && isOnline) {
read(); read();
} }
@@ -139,7 +123,6 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private void write(Position position) { private void write(Position position) {
log("write", position); log("write", position);
lock();
databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() { databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() {
@Override @Override
public void onComplete(boolean success, Void result) { public void onComplete(boolean success, Void result) {
@@ -149,20 +132,18 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
isWaiting = false; isWaiting = false;
} }
} }
unlock();
} }
}); });
} }
private void read() { private void read() {
log("read", null); log("read", null);
lock();
databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() { databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() {
@Override @Override
public void onComplete(boolean success, Position result) { public void onComplete(boolean success, Position result) {
if (success) { if (success) {
if (result != null) { if (result != null) {
if (result.getDeviceId().equals(preferences.getString(MainActivity.KEY_DEVICE, null))) { if (result.getDeviceId().equals(preferences.getString(MainFragment.KEY_DEVICE, null))) {
send(result); send(result);
} else { } else {
delete(result); delete(result);
@@ -173,14 +154,12 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
} else { } else {
retry(); retry();
} }
unlock();
} }
}); });
} }
private void delete(Position position) { private void delete(Position position) {
log("delete", position); log("delete", position);
lock();
databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() { databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() {
@Override @Override
public void onComplete(boolean success, Void result) { public void onComplete(boolean success, Void result) {
@@ -189,25 +168,26 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
} else { } else {
retry(); retry();
} }
unlock();
} }
}); });
} }
private void send(final Position position) { private void send(final Position position) {
log("send", position); log("send", position);
lock(); String request = ProtocolFormatter.formatRequest(url, position);
String request = ProtocolFormatter.formatRequest(address, port, position);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() { RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override @Override
public void onComplete(boolean success) { public void onComplete(boolean success) {
if (success) { if (success) {
if (buffer) {
delete(position); delete(position);
}
} else { } else {
StatusActivity.addMessage(context.getString(R.string.status_send_fail)); StatusActivity.addMessage(context.getString(R.string.status_send_fail));
if (buffer) {
retry(); retry();
} }
unlock(); }
} }
}); });
} }
+41 -40
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,52 +15,49 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager; import androidx.core.app.NotificationCompat;
import android.util.Log; import androidx.core.content.ContextCompat;
import java.lang.reflect.InvocationTargetException; import android.os.PowerManager;
import java.lang.reflect.Method; import android.util.Log;
public class TrackingService extends Service { public class TrackingService extends Service {
private static final String TAG = TrackingService.class.getSimpleName(); private static final String TAG = TrackingService.class.getSimpleName();
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private PowerManager.WakeLock wakeLock;
private TrackingController trackingController; private TrackingController trackingController;
@SuppressWarnings("deprecation")
private static Notification createNotification(Context context) { private static Notification createNotification(Context context) {
Intent notificationIntent = new Intent(context, MainActivity.class); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); .setSmallIcon(R.drawable.ic_stat_notify)
.setPriority(NotificationCompat.PRIORITY_LOW)
Notification notification = new Notification(android.R.drawable.stat_notify_sync_noanim, null, 0); .setCategory(NotificationCompat.CATEGORY_SERVICE);
try { Intent intent;
Method method = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class); if (!BuildConfig.HIDDEN_APP) {
try { intent = new Intent(context, MainActivity.class);
method.invoke(notification, context, context.getString(R.string.app_name), context.getString(R.string.settings_status_on_summary), pendingIntent); builder
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { .setContentTitle(context.getString(R.string.settings_status_on_summary))
Log.w(TAG, e); .setTicker(context.getString(R.string.settings_status_on_summary))
.setColor(ContextCompat.getColor(context, R.color.primary_dark));
} else {
intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
} }
} catch (SecurityException | NoSuchMethodException e) { builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
Log.w(TAG, e); return builder.build();
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
notification.priority = Notification.PRIORITY_MIN;
}
return notification;
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
public static class HideNotificationService extends Service { public static class HideNotificationService extends Service {
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
@@ -80,17 +77,26 @@ public class TrackingService extends Service {
} }
} }
@SuppressLint("WakelockTimeout")
@Override @Override
public void onCreate() { public void onCreate() {
Log.i(TAG, "service create"); Log.i(TAG, "service create");
StatusActivity.addMessage(getString(R.string.status_service_create)); StatusActivity.addMessage(getString(R.string.status_service_create));
startForeground(NOTIFICATION_ID, createNotification(this));
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
wakeLock.acquire();
trackingController = new TrackingController(this); trackingController = new TrackingController(this);
trackingController.start(); trackingController.start();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
startForeground(NOTIFICATION_ID, createNotification(this)); ContextCompat.startForegroundService(this, new Intent(this, HideNotificationService.class));
startService(new Intent(this, HideNotificationService.class));
} }
} }
@@ -99,18 +105,12 @@ public class TrackingService extends Service {
return null; return null;
} }
@SuppressWarnings("deprecation")
@Override
public void onStart(Intent intent, int startId) {
if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
}
@TargetApi(Build.VERSION_CODES.ECLAIR) @TargetApi(Build.VERSION_CODES.ECLAIR)
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId); if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
return START_STICKY; return START_STICKY;
} }
@@ -119,10 +119,11 @@ public class TrackingService extends Service {
Log.i(TAG, "service destroy"); Log.i(TAG, "service destroy");
StatusActivity.addMessage(getString(R.string.status_service_destroy)); StatusActivity.addMessage(getString(R.string.status_service_destroy));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
stopForeground(true); stopForeground(true);
}
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
if (trackingController != null) { if (trackingController != null) {
trackingController.stop(); trackingController.stop();
} }
+62
Просмотреть файл
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import androidx.core.content.ContextCompat;
import android.util.SparseArray;
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks = new SparseArray<>();
private static int mNextId = 1;
public static void startWakefulForegroundService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ContextCompat.startForegroundService(context, intent);
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WakefulBroadcastReceiver.class.getSimpleName());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
}
}
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
return true;
}
}
}
Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.6 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 2.4 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 4.8 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 7.2 KiB

+10
Просмотреть файл
@@ -0,0 +1,10 @@
<vector
android:width="24dp"
android:height="24dp"
android:viewportHeight="72.0"
android:viewportWidth="72.0"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#FF000000"
android:pathData="M36,0A36,36 0,0 0,0 36A36,36 0,0 0,36 72A36,36 0,0 0,72 36A36,36 0,0 0,36 0zM50.51,14.66C53.41,16.89 55.93,19.68 57.88,23.05C59.83,26.42 60.99,30 61.46,33.63L55.09,34.55C54.72,31.74 53.87,28.85 52.36,26.24C50.85,23.63 48.78,21.45 46.53,19.72L50.51,14.66zM42.82,24.85C44.4,26.08 45.77,27.58 46.84,29.43C47.91,31.27 48.52,33.21 48.79,35.2L42.42,36.12C42.25,34.95 41.95,33.7 41.32,32.61C40.69,31.53 39.77,30.64 38.84,29.91L42.82,24.85zM18.38,31.13L24.76,42.17L31.13,53.21A12.75,12.75 37.68,0 1,18.38 53.21A12.75,12.75 37.68,0 1,12.01 42.17A12.75,12.75 37.68,0 1,18.38 31.13zM32.96,34.21A3.19,3.19 60,0 1,35.8 35.8A3.19,3.19 60,0 1,34.63 40.15A3.19,3.19 60,0 1,30.28 38.99A3.19,3.19 60,0 1,31.45 34.63A3.19,3.19 60,0 1,32.96 34.21z"/>
</vector>
-56
Просмотреть файл
@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="@dimen/about_margin"
android:layout_marginStart="@dimen/about_margin"
android:layout_marginTop="@dimen/about_margin"
android:src="@drawable/logo"
android:contentDescription="@string/app_logo" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="@dimen/about_margin"
android:layout_toRightOf="@+id/logo"
android:layout_toEndOf="@+id/logo"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/about_spacing"
android:text="@string/app_name"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/about_spacing"
android:text="@string/about_description" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/about_spacing"
android:text="@string/about_license" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/about_spacing"
android:text="@string/about_web"
android:autoLink="web" />
</LinearLayout>
</RelativeLayout>
+6
Просмотреть файл
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
-5
Просмотреть файл
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
+4 -12
Просмотреть файл
@@ -1,19 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/status" android:id="@+id/status"
android:title="@string/menu_status" android:title="@string/menu_status"
android:showAsAction="always" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/shortcuts"
android:title="@string/menu_shortcuts"
android:showAsAction="never" />
<item
android:id="@+id/about"
android:title="@string/menu_about"
android:showAsAction="never" />
</menu> </menu>
+4 -2
Просмотреть файл
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/clear" android:id="@+id/clear"
android:title="@string/menu_clear" android:title="@string/menu_clear"
android:showAsAction="always" /> app:showAsAction="always" />
</menu> </menu>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/primary"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/primary"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_sos_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_sos_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_start_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_start_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_stop_foreground"/>
</adaptive-icon>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/light"/>
<foreground android:drawable="@mipmap/ic_stop_foreground"/>
</adaptive-icon>
Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 930 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 943 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.5 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.3 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.3 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.0 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 13 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.8 KiB

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше