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

...
133 Коммитов
Автор SHA1 Сообщение Дата
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
Anton Tananaev 3d602aaf16 Update app version to 3.11 2016-04-29 10:33:43 +12:00
Anton Tananaev acf935dcfd Fix crash on second app start 2016-04-29 10:26:05 +12:00
Anton Tananaev bb49b11bf4 Disable lint check for release builds 2016-04-27 22:33:31 +12:00
Anton Tananaev b72b7dfa6f Update all language translations 2016-04-27 22:26:07 +12:00
Anton Tananaev c4d4e9ff40 Update all app project files 2016-04-27 22:20:45 +12:00
Anton Tananaev bcfa577e91 Set up Alarm to wake service (fix #175) 2016-04-27 22:08:16 +12:00
Anton Tananaev ac1ca8df2f Limit number of location updates (fix #156) 2016-04-27 22:07:15 +12:00
Anton Tananaev e0dfc8fe66 Update git ignore file rules 2016-04-27 22:06:07 +12:00
Anton Tananaev 7dc7f32110 Fix issue with shortcuts on hidden app 2016-04-23 15:08:17 +12:00
Anton Tananaev 6bffc7bbc1 Add transifex localization script 2016-04-10 00:57:20 +12:00
Anton Tananaev aaa6092501 Add Lao localization to the app 2016-04-10 00:55:18 +12:00
Anton Tananaev 8cd3601a4f Move string array to values xml 2016-04-09 23:59:58 +12:00
Anton Tananaev 088adcaa24 Release version 3.9 of the app 2016-02-20 17:20:44 +13:00
Anton Tananaev 0365837e4d Use full name in manifest file 2016-02-04 23:21:36 +11:00
Anton Tananaev f952e16c00 Application version 3.8 release 2016-01-30 10:31:05 +13:00
Anton Tananaev b7fb6445ef Implement application shortcuts (fix #161) 2016-01-30 10:17:30 +13:00
Anton Tananaev bc3f47e296 Remove position if deviceId is different 2016-01-29 22:11:32 +13:00
Anton Tananaev c60f749a67 Catch missing permissions exception 2016-01-29 22:05:47 +13:00
Anton Tananaev 821f24ce2b Implement app settings validation 2016-01-29 22:01:02 +13:00
Anton Tananaev 62c713e97a Update launcher icon and logo image 2016-01-29 20:24:37 +13:00
Anton Tananaev ab587d465e Merge pull request #173 from DrumSergio/patch-1
Minimal fault on translation
2015-12-31 08:10:51 +13:00
DrumSergio f82e89409d minimal fault on translation 2015-12-30 17:38:27 +01:00
Anton Tananaev 7e6d4f5822 Add Italian app localization file 2015-12-13 11:27:18 +13:00
Anton Tananaev b8a099189e Fix hidden app component name 2015-11-03 10:49:48 +13:00
Anton Tananaev e961df85d5 Use different application ids 2015-10-19 10:19:39 +13:00
160 изменённых файлов: 3215 добавлений и 1260 удалений
+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/
-->
+3 -3
Просмотреть файл
@@ -1,7 +1,7 @@
.gradle .gradle
.idea/ .idea/
local.properties local.properties
traccar-client-android.iml *.iml
app/app.iml
build/ build/
app/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
+30 -31
Просмотреть файл
@@ -1,54 +1,53 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 23 compileSdkVersion 27
buildToolsVersion '23.0.1' buildToolsVersion '27.0.3'
defaultConfig { defaultConfig {
minSdkVersion 3 applicationId "org.traccar.client"
targetSdkVersion 23 buildConfigField "boolean", "HIDDEN_APP", "false"
versionCode 27 minSdkVersion 15
versionName '3.7' targetSdkVersion 27
versionCode 45
versionName '5.8'
} }
signingConfigs { lintOptions {
release { checkReleaseBuilds false
storeFile file('../../android.keystore')
keyAlias 'tananaev'
storePassword ''
keyPassword ''
}
} }
flavorDimensions "default"
productFlavors { productFlavors {
regular { regular
buildConfigField "boolean", "HIDDEN_APP", "false" google
}
hidden { hidden {
applicationId "org.traccar.client.hidden"
buildConfigField "boolean", "HIDDEN_APP", "true" buildConfigField "boolean", "HIDDEN_APP", "true"
} }
} }
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.release
}
}
} }
dependencies { dependencies {
compile 'com.android.support:design:27.0.2'
compile 'com.mapzen.android:lost:3.0.4'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0' testCompile 'org.robolectric:robolectric:3.6.1'
googleCompile 'com.google.firebase:firebase-core:11.8.0'
googleCompile 'com.google.firebase:firebase-crash:11.8.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: '))
}
tasks.whenTaskAdded { theTask -> task copyJson(type: Copy) {
if (theTask.name.equals('packageRegularRelease') || theTask.name.equals('packageHiddenRelease')) { from '../../traccar-shared/google-services'
theTask.dependsOn 'requestPasswords' into '.'
include 'traccar-client-android.json'
rename('traccar-client-android.json', 'google-services.json')
}
afterEvaluate {
preBuild.dependsOn copyJson
} }
} }
+12
Просмотреть файл
@@ -0,0 +1,12 @@
<?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">
</application>
</manifest>
+30
Просмотреть файл
@@ -0,0 +1,30 @@
/*
* Copyright 2017 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 com.google.firebase.analytics.FirebaseAnalytics;
public class GoogleMainApplication extends MainApplication {
private FirebaseAnalytics firebaseAnalytics;
@Override
public void onCreate() {
super.onCreate();
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
}
}
+12 -2
Просмотреть файл
@@ -6,9 +6,19 @@
<application <application
android:label="@string/hidden_app_name" android:label="@string/hidden_app_name"
tools:replace="label"> tools:replace="android:label">
<receiver android:name=".DialLaunchReceiver" > <activity
android:name=".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" >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" /> <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter> </intent-filter>
+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/white"/>
<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/white"/>
<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

+23 -3
Просмотреть файл
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="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" />
@@ -8,14 +9,18 @@
<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="com.android.launcher.permission.INSTALL_SHORTCUT" />
<application <application
android:allowBackup="true" android:allowBackup="true"
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">
<activity android:name=".MainActivity" android:launchMode="singleTask" /> <activity
android:name=".MainActivity"
android:launchMode="singleTask" />
<activity-alias <activity-alias
android:name=".Launcher" android:name=".Launcher"
@@ -24,12 +29,24 @@
<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 android:name=".AboutActivity"/>
<activity
android:name=".ShortcutActivity"
android:label="@string/menu_shortcuts">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.CREATE_SHORTCUT" />
</intent-filter>
</activity>
<service android:name=".TrackingService" /> <service android:name=".TrackingService" />
<service android:name=".TrackingService$HideNotificationService" /> <service android:name=".TrackingService$HideNotificationService" />
@@ -38,6 +55,9 @@
<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>
Двоичные данные
Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 48 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;
}
}
}
+6 -4
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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,22 +15,24 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.app.Activity;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
public class AboutActivity extends Activity { public class AboutActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.about); setContentView(R.layout.about);
TextView title = (TextView) findViewById(R.id.title); TextView title = findViewById(R.id.title);
try { try {
title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName); title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName);
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
Log.w(AboutActivity.class.getSimpleName(), e);
} }
} }
+3 -4
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2013 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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));
} }
} }
+4 -1
Просмотреть файл
@@ -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 = 2;
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,6 +79,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"altitude REAL," + "altitude REAL," +
"speed REAL," + "speed REAL," +
"course REAL," + "course REAL," +
"accuracy REAL," +
"battery REAL)"); "battery REAL)");
} }
@@ -97,6 +98,7 @@ 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());
db.insertOrThrow("position", null, values); db.insertOrThrow("position", null, values);
@@ -129,6 +131,7 @@ 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")));
} else { } else {
+33
Просмотреть файл
@@ -0,0 +1,33 @@
/*
* 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;
public final class DistanceCalculator {
private static final double EQUATORIAL_EARTH_RADIUS = 6378.1370;
private static final double DEG_TO_RAD = Math.PI / 180;
public static double distance(double lat1, double lon1, double lat2, double lon2) {
double dlong = (lon2 - lon1) * DEG_TO_RAD;
double dlat = (lat2 - lat1) * DEG_TO_RAD;
double a = Math.pow(Math.sin(dlat / 2), 2)
+ Math.cos(lat1 * DEG_TO_RAD) * Math.cos(lat2 * DEG_TO_RAD) * Math.pow(Math.sin(dlong / 2), 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
double d = EQUATORIAL_EARTH_RADIUS * c;
return d * 1000;
}
}
+7 -181
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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,191 +15,17 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.support.annotation.Nullable;
import android.preference.EditTextPreference; import android.support.v7.app.AppCompatActivity;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.TwoStatePreference;
import android.telephony.TelephonyManager;
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;
@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) { getFragmentManager().beginTransaction().replace(android.R.id.content, new MainFragment()).commit();
removeLauncherIcon();
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
addPreferencesFromResource(R.xml.preferences);
initPreferences();
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
}
}
private void removeLauncherIcon() {
ComponentName componentName = new ComponentName(getPackageName(), getPackageName() + ".Launcher");
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 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) {
PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceScreen.findPreference(KEY_DEVICE).setEnabled(enabled);
preferenceScreen.findPreference(KEY_ADDRESS).setEnabled(enabled);
preferenceScreen.findPreference(KEY_PORT).setEnabled(enabled);
preferenceScreen.findPreference(KEY_INTERVAL).setEnabled(enabled);
preferenceScreen.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.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<String>();
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));
} 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() {
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.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.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();
}
}
}
+285
Просмотреть файл
@@ -0,0 +1,285 @@
/*
* Copyright 2012 - 2017 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.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
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.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.TwoStatePreference;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.webkit.URLUtil;
import android.widget.Toast;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class MainFragment extends PreferenceFragment 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";
private static final int PERMISSIONS_REQUEST_LOCATION = 2;
private SharedPreferences sharedPreferences;
private AlarmManager alarmManager;
private PendingIntent alarmIntent;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (BuildConfig.HIDDEN_APP) {
removeLauncherIcon();
}
setHasOptionsMenu(true);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
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);
}
}
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();
}
}
private boolean hasPermission(String permission) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
return true;
}
return ContextCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED;
}
@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);
}
@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;
} else if (item.getItemId() == R.id.about) {
startActivity(new Intent(getActivity(), AboutActivity.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> 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 {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(missingPermissions.toArray(new String[missingPermissions.size()]),
PERMISSIONS_REQUEST_LOCATION);
}
return;
}
}
if (permission) {
setPreferencesEnabled(false);
ContextCompat.startForegroundService(getActivity(), 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 = (TwoStatePreference) 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();
}
}
}
+87 -19
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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.location.Location; import android.location.Location;
import android.location.LocationManager;
import java.util.Date; import java.util.Date;
@@ -32,43 +33,110 @@ 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;
} }
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;
}
} }
+80 -32
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2013 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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,21 +15,26 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.annotation.TargetApi; import android.annotation.SuppressLint;
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 { import com.mapzen.android.lost.api.LocationListener;
import com.mapzen.android.lost.api.LocationRequest;
import com.mapzen.android.lost.api.LocationServices;
import com.mapzen.android.lost.api.LostApiClient;
protected static final String TAG = PositionProvider.class.getSimpleName(); public class PositionProvider implements LostApiClient.ConnectionCallbacks, LocationListener {
private static final String TAG = PositionProvider.class.getSimpleName();
private static final int MINIMUM_INTERVAL = 1000;
public interface PositionListener { public interface PositionListener {
void onPositionUpdate(Position position); void onPositionUpdate(Position position);
@@ -38,52 +43,95 @@ public abstract class PositionProvider {
private final PositionListener listener; private final PositionListener listener;
private final Context context; private final Context context;
private final SharedPreferences preferences; private SharedPreferences preferences;
protected final LocationManager locationManager; private LostApiClient apiClient;
private String deviceId; private String deviceId;
protected String type; private long interval;
protected final long period; private double distance;
private double angle;
private long lastUpdateTime; private Location lastLocation;
private boolean started;
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 void startUpdates() {
started = true;
apiClient = new LostApiClient.Builder(context).addConnectionCallbacks(this).build();
apiClient.connect();
}
public abstract void stopUpdates(); private int getPriority(String accuracy) {
switch (accuracy) {
protected void updateLocation(Location location) { case "high":
if (location != null && location.getTime() != lastUpdateTime) { return LocationRequest.PRIORITY_HIGH_ACCURACY;
Log.i(TAG, "location new"); case "low":
lastUpdateTime = location.getTime(); return LocationRequest.PRIORITY_LOW_POWER;
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel())); default:
} else { return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
Log.i(TAG, location != null ? "location old" : "location nil");
} }
} }
@TargetApi(Build.VERSION_CODES.ECLAIR) @SuppressLint("MissingPermission")
private double getBatteryLevel() { @Override
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.ECLAIR) { public void onConnected() {
Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); if (started) {
LocationRequest request = LocationRequest.create()
.setPriority(getPriority(preferences.getString(MainFragment.KEY_ACCURACY, "medium")))
.setInterval(distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval);
LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, request, this);
} else {
apiClient.disconnect();
}
}
@Override
public void onLocationChanged(Location location) {
if (location != null && (lastLocation == null
|| location.getTime() - lastLocation.getTime() >= interval
|| distance > 0 && DistanceCalculator.distance(location.getLatitude(), location.getLongitude(), lastLocation.getLatitude(), lastLocation.getLongitude()) >= distance
|| angle > 0 && Math.abs(location.getBearing() - lastLocation.getBearing()) >= angle)) {
Log.i(TAG, "location new");
lastLocation = location;
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else {
Log.i(TAG, location != null ? "location ignored" : "location nil");
}
}
@Override
public void onConnectionSuspended() {
Log.i(TAG, "lost client suspended");
}
public void stopUpdates() {
if (apiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, this);
apiClient.disconnect();
}
started = false;
}
public static double getBatteryLevel(Context context) {
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;
} }
} }
+12 -6
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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,13 @@ 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 (alarm != null) {
builder.appendQueryParameter("alarm", alarm);
}
return builder.build().toString(); return builder.build().toString();
} }
} }
+3 -1
Просмотреть файл
@@ -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);
} }
} }
} }
+179
Просмотреть файл
@@ -0,0 +1,179 @@
/*
* Copyright 2016 - 2017 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.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.DrawableRes;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.pm.ShortcutInfoCompat;
import android.support.v4.content.pm.ShortcutManagerCompat;
import android.support.v4.graphics.drawable.IconCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.mapzen.android.lost.api.LocationServices;
import com.mapzen.android.lost.api.LostApiClient;
public class ShortcutActivity extends AppCompatActivity implements LostApiClient.ConnectionCallbacks {
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";
private LostApiClient apiClient;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
executeAction(intent);
}
private void setShortcutResult(String label, @DrawableRes int iconResId, String action) {
Intent intent = new Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity.class);
intent.putExtra(EXTRA_ACTION, action);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, action)
.setShortLabel(label)
.setIcon(IconCompat.createWithResource(this, iconResId))
.setIntent(intent)
.build();
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut));
}
private void sendAlarm() {
apiClient = new LostApiClient.Builder(this).addConnectionCallbacks(this).build();
apiClient.connect();
}
@SuppressWarnings("MissingPermission")
@Override
public void onConnected() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
Location location = LocationServices.FusedLocationApi.getLastLocation(apiClient);
if (location != null) {
Position position = new Position(
preferences.getString(MainFragment.KEY_DEVICE, null),
location, PositionProvider.getBatteryLevel(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 {
Toast.makeText(ShortcutActivity.this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
}
});
} else {
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
apiClient.disconnect();
}
@Override
public void onConnectionSuspended() {
}
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));
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show();
break;
case ACTION_SOS:
sendAlarm();
break;
}
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) {
}
}
+10 -8
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2013 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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 android.support.v7.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) {
@@ -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 -21
Просмотреть файл
@@ -17,7 +17,6 @@ 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.os.PowerManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@@ -27,16 +26,16 @@ 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;
private Context context; private Context context;
private Handler handler; private Handler handler;
private SharedPreferences preferences;
private String address; private String url;
private int port;
private PositionProvider positionProvider; private PositionProvider positionProvider;
private DatabaseHelper databaseHelper; private DatabaseHelper databaseHelper;
@@ -45,11 +44,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private PowerManager.WakeLock wakeLock; private PowerManager.WakeLock wakeLock;
private void lock() { private void lock() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { wakeLock.acquire(WAKE_LOCK_TIMEOUT);
wakeLock.acquire();
} else {
wakeLock.acquire(WAKE_LOCK_TIMEOUT);
}
} }
private void unlock() { private void unlock() {
@@ -61,18 +56,13 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
public TrackingController(Context context) { public TrackingController(Context context) {
this.context = context; this.context = context;
handler = new Handler(); handler = new Handler();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getString(MainActivity.KEY_PROVIDER, null).equals("mixed")) { positionProvider = new PositionProvider(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));
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
@@ -82,13 +72,21 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
if (isOnline) { if (isOnline) {
read(); read();
} }
positionProvider.startUpdates(); try {
positionProvider.startUpdates();
} catch (SecurityException e) {
Log.w(TAG, e);
}
networkManager.start(); networkManager.start();
} }
public void stop() { public void stop() {
networkManager.stop(); networkManager.stop();
positionProvider.stopUpdates(); try {
positionProvider.stopUpdates();
} catch (SecurityException e) {
Log.w(TAG, e);
}
handler.removeCallbacksAndMessages(null); handler.removeCallbacksAndMessages(null);
} }
@@ -153,7 +151,11 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
public void onComplete(boolean success, Position result) { public void onComplete(boolean success, Position result) {
if (success) { if (success) {
if (result != null) { if (result != null) {
send(result); if (result.getDeviceId().equals(preferences.getString(MainFragment.KEY_DEVICE, null))) {
send(result);
} else {
delete(result);
}
} else { } else {
isWaiting = true; isWaiting = true;
} }
@@ -184,7 +186,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private void send(final Position position) { private void send(final Position position) {
log("send", position); log("send", position);
lock(); lock();
String request = ProtocolFormatter.formatRequest(address, port, position); String request = ProtocolFormatter.formatRequest(url, 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) {
+24 -42
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
* *
* 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.
@@ -23,12 +23,10 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log; import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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();
@@ -36,31 +34,25 @@ public class TrackingService extends Service {
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(Notification.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));
} catch (SecurityException | NoSuchMethodException e) { } else {
Log.w(TAG, e); intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
} }
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { return builder.build();
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) {
@@ -88,10 +80,8 @@ public class TrackingService extends Service {
trackingController = new TrackingController(this); trackingController = new TrackingController(this);
trackingController.start(); trackingController.start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { startForeground(NOTIFICATION_ID, createNotification(this));
startForeground(NOTIFICATION_ID, createNotification(this)); ContextCompat.startForegroundService(this, new Intent(this, HideNotificationService.class));
startService(new Intent(this, HideNotificationService.class));
}
} }
@Override @Override
@@ -99,18 +89,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,9 +103,7 @@ 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 (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 android.support.v4.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;
}
}
}
+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>
+28 -47
Просмотреть файл
@@ -1,56 +1,37 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" > android:layout_height="fill_parent"
android:layout_toRightOf="@+id/logo"
android:layout_toEndOf="@+id/logo"
android:padding="@dimen/spacing_normal"
android:orientation="vertical" >
<ImageView <TextView
android:id="@+id/logo" android:id="@+id/title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_marginBottom="@dimen/spacing_normal"
android:layout_alignParentStart="true" android:text="@string/app_name"
android:layout_alignParentTop="true" android:textAppearance="?android:attr/textAppearanceLarge" />
android:layout_marginLeft="@dimen/about_margin"
android:layout_marginStart="@dimen/about_margin"
android:layout_marginTop="@dimen/about_margin"
android:src="@mipmap/ic_launcher"
android:contentDescription="@string/app_logo" />
<LinearLayout <TextView
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:layout_height="wrap_content"
android:layout_margin="@dimen/about_margin" android:layout_marginBottom="@dimen/spacing_normal"
android:layout_toRightOf="@+id/logo" android:text="@string/about_description" />
android:layout_toEndOf="@+id/logo"
android:orientation="vertical" >
<TextView <TextView
android:id="@+id/title" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginBottom="@dimen/spacing_normal"
android:layout_marginBottom="@dimen/about_spacing" android:text="@string/about_license" />
android:text="@string/app_name"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/about_spacing" android:text="@string/about_web"
android:text="@string/about_description" /> android:autoLink="web" />
<TextView </LinearLayout>
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" />
+5 -3
Просмотреть файл
@@ -1,14 +1,16 @@
<?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 <item
android:id="@+id/about" android:id="@+id/about"
android:title="@string/menu_about" android:title="@string/menu_about"
android:showAsAction="ifRoom" /> app: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/white"/>
<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/white"/>
<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/white"/>
<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/white"/>
<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/white"/>
<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/white"/>
<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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

+40
Просмотреть файл
@@ -0,0 +1,40 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">عميل Traccar</string>
<string name="app_logo">Traccar</string>
<string name="settings_id_title">معرف الجهاز</string>
<string name="settings_url_title">رابط الخادم</string>
<string name="settings_url_summary">رابط خادم التتبع</string>
<string name="settings_interval_title">تردد</string>
<string name="settings_interval_summary">فترة التقرير بالثواني</string>
<string name="settings_distance_title">المسافة</string>
<string name="settings_distance_summary">مسافة التقرير بالميتر</string>
<string name="settings_angle_title">الزاوية</string>
<string name="settings_angle_summary">زاوية التقرير بالدرجات</string>
<string name="settings_status_title">حالة الخدمة</string>
<string name="settings_status_off">بدء</string>
<string name="settings_status_on">إيقاف</string>
<string name="settings_status_off_summary">الخدمة توقفت</string>
<string name="settings_status_on_summary">الخدمة تعمل</string>
<string name="settings_provider_title">موفر الموقع</string>
<string name="settings_provider_summary">مصدر بيانات الموقع</string>
<string name="settings_provider_gps">موفر نظام تحديد الموقع</string>
<string name="settings_provider_network">موفر الشبكة</string>
<string name="settings_provider_mixed">موفر المختلط</string>
<string name="settings_foreground_title">الخدمة الأمامية</string>
<string name="settings_foreground_summary">زيادة أولوية الخدمة</string>
<string name="menu_status">الحالة</string>
<string name="menu_about">حول</string>
<string name="menu_shortcuts">إضافة اختصارات</string>
<string name="menu_clear">مسح</string>
<string name="about_description">محدد الموقع في الوقت الفعلي لأجهزة Android. متوافق مع خادم Traccar ونظم تعقب أخرى </string>
<string name="about_license">هذا التطبيق مجاني ومفتوح المصدر، الكود المصدري مرخص تحت رخصة Apache الإصدار 2.0 و متاح على GitHub.</string>
<string name="about_web">لمزيد من المعلومات زر \nwww.traccar.org/client</string>
<string name="status_send_success">تم الإرسال بنجاح</string>
<string name="status_send_fail">فشل الإرسال</string>
<string name="status_location_update">تحديث الموقع</string>
<string name="status_connectivity_change">تغير الارتباط</string>
<string name="hidden_app_name">إعدادات الجهاز</string>
<string name="hidden_alert">تم إخفاء التطبيق. لفتحه مجددا رجاءا اتصل بـ8722227 (TRACCAR).</string>
<string name="error_msg_invalid_url">أرجو إدخال رابط صحيح http:// أو https://</string>
</resources>
+37
Просмотреть файл
@@ -0,0 +1,37 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Traccar клиент</string>
<string name="app_logo">Traccar</string>
<string name="settings_id_title">Идентификатор</string>
<string name="settings_interval_title">Честота</string>
<string name="settings_interval_summary">Докладван интервал в сек.</string>
<string name="settings_distance_title">Разстояние</string>
<string name="settings_distance_summary">Докладвано разстояние в метри</string>
<string name="settings_angle_title">Ъгъл</string>
<string name="settings_angle_summary">Докладван ъгъл в градуси</string>
<string name="settings_status_title">Състояние на услугата</string>
<string name="settings_status_off">Старт</string>
<string name="settings_status_on">Стоп</string>
<string name="settings_status_off_summary">Услугата е спряна</string>
<string name="settings_status_on_summary">Услугата работи</string>
<string name="settings_provider_title">Доставчик на местоположение</string>
<string name="settings_provider_summary">Източник на данни за местоположението</string>
<string name="settings_provider_gps">GPS</string>
<string name="settings_provider_network">Мрежи</string>
<string name="settings_provider_mixed">Смесен Доставчик</string>
<string name="settings_foreground_title">Foreground service</string>
<string name="settings_foreground_summary">Увеличаване на приоритета на услугата</string>
<string name="menu_status">Състояние</string>
<string name="menu_about">Относно</string>
<string name="menu_shortcuts">Добави пряк път</string>
<string name="menu_clear">Изтрий</string>
<string name="about_description">Real time GPS tracker for Android devices. Compatible with Traccar Server and other tracking systems.</string>
<string name="about_license">This application is free and open source, source code is licensed under Apache License Version 2.0 and available on GitHub.</string>
<string name="about_web">For more information visit\nwww.traccar.org/client</string>
<string name="status_send_success">Успешно изпращане</string>
<string name="status_send_fail">Грешка в изпращането</string>
<string name="status_location_update">Местоположението е обновено</string>
<string name="status_connectivity_change">Промяна в свързването</string>
<string name="hidden_app_name">Настройки</string>
<string name="hidden_alert">Приложението е скрито. За да го отворите отново, моля наберете 8722227 (TRACCAR).</string>
</resources>
+40
Просмотреть файл
@@ -0,0 +1,40 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Traccar ক্লায়েন্ট</string>
<string name="app_logo">Traccar</string>
<string name="settings_id_title">ডিভাইস সনাক্তকারী</string>
<string name="settings_url_title">সার্ভার URL</string>
<string name="settings_url_summary">ট্র্যাকিং সার্ভার URL</string>
<string name="settings_interval_title">ফ্রিকোয়েন্সি</string>
<string name="settings_interval_summary">রিপোর্টিং বিরতি - সেকেন্ডে</string>
<string name="settings_distance_title">দূরত্ব</string>
<string name="settings_distance_summary">রিপোর্টিং বিরতি - মিটারে</string>
<string name="settings_angle_title">অ্যাঙ্গেল</string>
<string name="settings_angle_summary">রিপোর্টিং অ্যাঙ্গেল - ডিগ্রিতে</string>
<string name="settings_status_title">সার্ভিসের অবস্থা</string>
<string name="settings_status_off">চালু</string>
<string name="settings_status_on">বন্ধ</string>
<string name="settings_status_off_summary">সার্ভিস বন্ধ হয়েছে</string>
<string name="settings_status_on_summary">সার্ভিস চলমান</string>
<string name="settings_provider_title">লোকেশন প্রদানকারী</string>
<string name="settings_provider_summary">লোকেশন ডেটা উৎস</string>
<string name="settings_provider_gps">জিপিএস প্রদানকারী</string>
<string name="settings_provider_network">নেটওয়ার্ক প্রদানকারী</string>
<string name="settings_provider_mixed">মিশ্র প্রদানকারী</string>
<string name="settings_foreground_title">ফোরগ্রাউন্ড সার্ভিস</string>
<string name="settings_foreground_summary">সার্ভিসের পূর্ববর্তিতা বৃদ্ধি</string>
<string name="menu_status">অবস্থা</string>
<string name="menu_about">সম্পর্কে / সম্বন্ধে</string>
<string name="menu_shortcuts">শর্টকাট যুক্ত করা</string>
<string name="menu_clear">পরিষ্কার</string>
<string name="about_description">অ্যান্ড্রয়েড ডিভাইসের জন্য রিয়েল টাইম GPS ট্র্যাকার। Traccar সার্ভার এবং অন্যান্য ট্র্যাকিং সিস্টেমগুলির সাথে সামঞ্জস্যপূর্ণ।</string>
<string name="about_license">এই অ্যাপ্লিকেশনটি বিনামূল্যে প্রদানকৃত এবং ওপেন সোর্স, সোর্স কোডটি Apache License Version 2.0 এর অধীনে লাইসেন্স করা এবং GitHub এ রক্ষিত।</string>
<string name="about_web">আরও তথ্যের জন্য www.traccar.org/client দেখুন</string>
<string name="status_send_success">সফলভাবে পাঠান হয়েছে</string>
<string name="status_send_fail">পাঠান ব্যর্থ হয়েছে</string>
<string name="status_location_update">অবস্থান আপডেট</string>
<string name="status_connectivity_change">সংযোগ পরিবর্তন হয়েছে</string>
<string name="hidden_app_name">ডিভাইস সেটিংস</string>
<string name="hidden_alert">অ্যাপ্লিকেশনটি লুকানো আছে। আবার খুলতে ডায়াল করুন 8722227 (TRACCAR)।</string>
<string name="error_msg_invalid_url">দয়া করে একটি ভ্যালিড http:// বা https:// URL লিখুন</string>
</resources>

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