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

..
103 Коммитов
Автор SHA1 Сообщение Дата
Anton Tananaev 4f6571e572 Update app version number 2018-03-18 21:28:11 +13:00
Anton Tananaev d3025b437b Replace firebase old crash reporting 2018-03-18 21:27:27 +13:00
Anton Tananaev 1e861ef0ac Fix hidden app URL open issue (fix #321) 2018-02-19 09:49:50 +13:00
Anton Tananaev 43afc57114 Merge pull request #318 from garyvdm/online_status
Show online/offline in connection changed status messages
2018-02-15 23:03:55 +13:00
Gary van der Merwe d47783808f Show online/offline in connection changed status messages. 2018-02-15 12:03:09 +02:00
Anton Tananaev 3ef2272227 Merge pull request #320 from garyvdm/revert-301-vector
Revert "Use vector icons"
2018-02-15 22:24:54 +13:00
Gary van der Merwe 3625ee42db Revert "Use vector icons." 2018-02-15 11:04:38 +02:00
Anton Tananaev 385826c3c1 Merge pull request #301 from garyvdm/vector
Use vector icons
2018-02-14 09:42:29 +13:00
Gary van der Merwe 1426926c8e Use vector icons. 2018-02-13 18:33:01 +02:00
Anton Tananaev 4f680d33c9 Merge pull request #311 from Bubu/patch-1
Add google maven repo
2018-01-23 08:20:51 +13:00
Marcus Hoffmann 2dad912da5 Add google() repo
This is required to build the app outside of Android Studio.

Fixes:
 > Could not find com.android.tools.build:gradle:3.0.1.
     Searched in the following locations:
         https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.pom
         https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.jar
2018-01-22 20:15:20 +01:00
Anton Tananaev 290a7d80eb Update app version number 2018-01-19 17:43:43 +13:00
Anton Tananaev 44228feb2f Fix SOS functionality 2018-01-19 17:43:18 +13:00
Anton Tananaev d887bc4119 Update version number 2018-01-09 08:26:19 +13:00
Anton Tananaev c892a20104 Fix lost API connection logic 2018-01-09 08:25:57 +13:00
Anton Tananaev 2719b57f7c Use color code directly 2018-01-07 09:26:55 +13:00
Anton Tananaev cc6c8a23f6 Update project files 2018-01-07 09:23:14 +13:00
Anton Tananaev 1e82cbbbf6 Merge pull request #305 from Alpha200/fixAccuracy
Fix reading accuracy property
2017-12-30 10:40:06 +13:00
Daniel Sendzik 4b8fde36d7 Fix reading accuracy property 2017-12-29 19:34:14 +01:00
Anton Tananaev b8cd56439a Merge pull request #304 from garyvdm/restore_startWakefulForegroundService
Restore fix auto-start issue on Android O
2017-12-21 23:03:37 +13:00
Gary van der Merwe edb1f696df Restore fix auto-start issue on Android O.
Also rename startWakefulService to startWakefulForegroundService to highlight
that it's different to the SDK version.
2017-12-21 11:54:14 +02:00
Anton Tananaev 56d2f2ef53 Merge pull request #303 from garyvdm/cleanup
Remove unnecessary code
2017-12-21 09:52:43 +13:00
Gary van der Merwe 59e6a5ae65 Remove code that was originally included for supporting older SDK APIs that are now older than our minSdkVersion. 2017-12-20 14:17:51 +02:00
Anton Tananaev 319fab647b Merge pull request #302 from garyvdm/hidden_notification
For hidden flavor, make notificaion not open the app when clicked, use different icon
2017-12-16 09:47:04 +13:00
Gary van der Merwe 42429f3496 For hidden flavor, make notification open system settings activity. 2017-12-15 12:38:56 +02:00
Gary van der Merwe 841d3e581c For hidden flavor, make notificaion not open the app when clicked, use different icon.
This only apply to when the notificaion can't be hidden, i.e. SDK >= 25.
2017-12-15 09:09:25 +02:00
Anton Tananaev dac43ccbc1 Merge pull request #300 from garyvdm/notification
Get service notification to show. Tweek settings.
2017-12-14 13:53:22 +13:00
Gary van der Merwe 3c9ae512bd Get service notification to show. Tweek settings. 2017-12-13 08:11:11 +02:00
Anton Tananaev d3e600d81d Gracefully stop location updates 2017-11-23 00:03:17 +13:00
Anton Tananaev e0296a9c65 Use fused location provider 2017-11-22 23:59:52 +13:00
Anton Tananaev e51f590e19 Merge pull request #297 from Mantas0726/master
Update PositionProvider.java
2017-11-15 09:57:45 +13:00
Mantas0726 6e3812320c Update PositionProvider.java 2017-11-14 22:20:11 +02:00
Anton Tananaev c45c8ea797 Update version number 2017-09-25 15:08:10 +13:00
Anton Tananaev ed0aa53aa3 Fix null pointer crash 2017-09-25 15:04:19 +13:00
Anton Tananaev f88891be2b Add another hidden intent filter 2017-09-25 15:01:56 +13:00
Anton Tananaev 3f83684d02 Update version number 2017-09-20 05:23:51 +12:00
Anton Tananaev a9d772bfda Restart service after upgrade 2017-09-20 05:23:27 +12:00
Anton Tananaev 2bc3c78a91 Update version number 2017-09-12 23:22:33 +12:00
Anton Tananaev 8fab55552d Handle no network provider 2017-09-12 23:21:58 +12:00
Anton Tananaev 979963f6a2 Fix auto-start issue on Android O 2017-09-07 19:04:10 +12:00
Anton Tananaev 70d6dfec4e Fix issue with URL migration 2017-09-02 20:01:13 +12:00
Anton Tananaev 43e108de8c Fix number format exception 2017-09-02 08:40:56 +12:00
Anton Tananaev fedce0e26b Fix number format exception 2017-09-02 08:38:42 +12:00
Anton Tananaev e24d725feb Disable failing unit test 2017-09-01 11:20:15 +12:00
Anton Tananaev 3198bee2f2 Use POST to send data (fix #279) 2017-09-01 11:18:26 +12:00
Anton Tananaev 92e0fb3ad5 Remove unnecessary permissions 2017-09-01 10:41:24 +12:00
Anton Tananaev f7b0df1c41 Update version code 2017-09-01 10:36:49 +12:00
Anton Tananaev 8a26c7e5ba Update localization files 2017-09-01 10:34:47 +12:00
Anton Tananaev 27c98b910b Remove redundant colors and use icons 2017-09-01 09:51:57 +12:00
Anton Tananaev 360958d250 New hidden app icon 2017-09-01 08:55:26 +12:00
Anton Tananaev 4d8d15d615 Implement static shortcuts 2017-08-31 14:16:04 +12:00
Anton Tananaev 7c49bb78f3 Implement proper shortcuts 2017-08-31 13:48:52 +12:00
Anton Tananaev e87c3c7783 Report location accuracy 2017-08-31 11:42:15 +12:00
Anton Tananaev 5428530dbd Add Firebase analytics 2017-08-31 11:07:16 +12:00
Anton Tananaev 3d5682d1b1 Move color to common file 2017-08-31 10:14:05 +12:00
Anton Tananaev 8f91c0e0ea New hidden app icon 2017-08-30 23:05:24 +12:00
Anton Tananaev 965feb9b04 Remove old launcher icons 2017-08-30 23:00:05 +12:00
Anton Tananaev 8f8a5e3f55 Add new app icon 2017-08-30 22:48:47 +12:00
Anton Tananaev 77154a81af Use some new Android APIs 2017-08-30 15:34:45 +12:00
Anton Tananaev 8d94b8d011 Implement notification channel 2017-08-30 15:27:00 +12:00
Anton Tananaev ab37fd478a Make spacing more standard 2017-08-30 13:51:06 +12:00
Anton Tananaev 79662bad35 Remove redundant preferences 2017-08-30 13:45:56 +12:00
Anton Tananaev 90373ff8fb Fix app unit tests 2017-08-30 13:44:40 +12:00
Anton Tananaev f2e7ae7c21 Finish app modernization 2017-08-30 13:42:04 +12:00
Anton Tananaev e0c5340d8b Modernize and fix compilation 2017-08-30 13:25:18 +12:00
Anton Tananaev 90fffa9873 Update built tools to latest 2017-08-30 12:59:06 +12:00
Anton Tananaev db51adaf86 Add github templates 2017-08-21 22:44:12 +12:00
Anton Tananaev 7eb6876491 Merge pull request #269 from BrianBlade/basepath
Add serverpath preference
2017-06-15 09:42:27 +12:00
BrianBlade 5db3407e88 remove check if URL isEmpty; dont set 5055 as default port 2017-06-14 14:36:00 +02:00
BrianBlade ff4fe90c6d simplify URL validation 2017-06-14 13:02:03 +02:00
BrianBlade 62a2599765 rename setServerUrl to validateServerUrl and add port validation 2017-06-14 11:00:56 +02:00
BrianBlade 149eee530a some requested fixes 2017-06-14 08:40:40 +02:00
BrianBlade 6e398d12fe add ServerURL preference (replaces address, port, encryption) 2017-06-13 12:40:32 +02:00
Anton Tananaev 10e573017b Bump app version number 2017-05-16 20:50:50 +12:00
Anton Tananaev f5f8d41e41 Validate distance and angle 2017-05-16 20:42:32 +12:00
Anton Tananaev 839b180890 Add new error messages 2017-05-14 12:47:32 +12:00
Anton Tananaev e2a26297c9 Update app version number 2017-05-13 22:03:50 +12:00
Anton Tananaev 4d33d8ff9e Fix locale folder issue 2017-05-13 22:03:27 +12:00
Anton Tananaev 7e923954b8 Update and add localizations 2017-05-13 21:55:52 +12:00
Anton Tananaev f0b0650531 Update translation script 2017-05-13 21:54:31 +12:00
Anton Tananaev 3eb655040d Update gradle plugin version 2017-05-13 21:42:34 +12:00
Anton Tananaev 96a75f02a7 Check shortcut action for null 2017-04-23 12:00:58 +12:00
Anton Tananaev d632c7cdfa Fix issues with notification 2017-04-08 17:47:54 +12:00
Anton Tananaev 9bae6e554f Implement reporting by distance and angle 2017-04-08 17:36:12 +12:00
Anton Tananaev faf23e7926 Final update to badges 2017-03-29 23:02:39 +13:00
Anton Tananaev 54331ffe93 Adjust badge height 2017-03-29 22:51:38 +13:00
Anton Tananaev c19c17f091 Try SVG image in readme 2017-03-29 22:50:18 +13:00
Anton Tananaev 2bd5bf540f Testing embedded badges 2017-03-29 22:44:22 +13:00
Anton Tananaev 3d89cfe649 Merge pull request #260 from Poussinou/master
Add a button with link to fdroid in README.md
2017-03-29 22:18:46 +13:00
Poussinou e2e51b3048 Update README.md 2017-03-28 19:08:27 +02:00
Poussinou 43d9775aa0 add a button with link to fdroid 2017-03-27 16:21:33 +02:00
Anton Tananaev e5bddc5aa6 Update app version number 2017-03-17 00:57:49 +13:00
Anton Tananaev 3b1ba21a76 Relaxed host address matching 2017-03-17 00:56:46 +13:00
Anton Tananaev 26467740fa Update Android gradle plugin 2017-03-01 23:29:44 +13:00
Anton Tananaev a8d6f0b82d Use jcenter maven repository 2017-02-08 21:56:41 +13:00
Anton Tananaev 94acc71f8c Use jcenter maven repository 2017-01-18 23:56:42 +13:00
Anton Tananaev fe4e798eb8 Update app version number 2017-01-15 17:25:02 +13:00
Anton Tananaev 8b72fd5522 Update localization files 2017-01-15 17:24:14 +13:00
Anton Tananaev 4a8b66694c Implement alarm shortcut (fix #244) 2017-01-15 17:16:41 +13:00
Anton Tananaev c717da9c6a Increase wake lock timeout 2017-01-15 16:33:52 +13:00
Anton Tananaev 969cbc6cf4 Update developer tools and SDK 2017-01-15 16:25:57 +13:00
Anton Tananaev a04689350a Update readme file 2016-10-22 14:14:22 +13:00
Anton Tananaev 87e798b459 Update gitignore file 2016-10-10 23:22:12 +13:00
162 изменённых файлов: 2501 добавлений и 1101 удалений
+47
Просмотреть файл
@@ -0,0 +1,47 @@
# Contributing to Traccar
Please make sure you read this guide before commenting on any issue or creating a new issue or pull request.
Before asking anything, please search for an answer in:
- [Traccar documentation](https://www.traccar.org/documentation/)
- [Traccar forums](https://www.traccar.org/forums/)
- Relevant repositories (see below)
- [Google Search](https://www.google.com/)
GitHub issues should be used ONLY for feature requests, code discussions and bug reports. For general discussions please use [Traccar forums](https://www.traccar.org/forums/).
There are multiple Traccar projects. If you create a new issue you MUST do it in the relevant repository:
- [Traccar Server](https://github.com/tananaev/traccar/issues)
- [Traccar Web Interface](https://github.com/tananaev/traccar-web/issues)
- [Traccar Client for Android](https://github.com/tananaev/traccar-client-android/issues)
- [Traccar Client for iOS](https://github.com/tananaev/traccar-client-ios/issues)
- [Traccar Manager for Android](https://github.com/tananaev/traccar-manager-android/issues)
- [Traccar Manager for iOS](https://github.com/tananaev/traccar-manager-ios/issues)
If you are not sure where your issue belongs to, please use Traccar Server main repository.
If you want to discuss something that applies to both Android and iOS apps, please use Android repository.
## Bug Reports
Before creating a bug report make sure that you have tested latest official release with default configuration.
Only create a bug report issue if you are confident that there is a problem in Traccar software.
Provide as much details as possible, including log fragments, operating system and hardware information.
## Feature Requests
Before creating a feature request make sure that the feature or modification that you are requesting is not yet implemented.
Search reposiroty to ensure that there is no existing issues for your request. If there is, add a new comment on that issue.
Provide as much details as possible, including use case for your feature and any benefits that you can think of.
## Pull Requests
If you want to contribute some code to Traccar, it is recommended to discuss your solution with maintainers before starting any work.
Any code that you want to contribute must be of high quality and follow existing code patterns and styles.
+7
Просмотреть файл
@@ -0,0 +1,7 @@
<!--
Do you want to ask a question? Traccar official forum is the best place for getting support:
https://www.traccar.org/forums/
-->
+2
Просмотреть файл
@@ -3,3 +3,5 @@
local.properties
*.iml
build/
.DS_Store
google-services.json
+5 -7
Просмотреть файл
@@ -1,17 +1,15 @@
# [Traccar Client for Android](https://www.traccar.org/client)
[![](https://play.google.com/intl/en_us/badges/images/badge_new.png)](https://play.google.com/store/apps/details?id=org.traccar.client)
## Contacts
Author - Anton Tananaev ([anton.tananaev@gmail.com](mailto:anton.tananaev@gmail.com))
Website - [https://www.traccar.org/client](https://www.traccar.org/client)
[![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)
## Overview
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
Apache License, Version 2.0
+32 -18
Просмотреть файл
@@ -1,40 +1,54 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion '23.0.3'
compileSdkVersion 27
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 3
targetSdkVersion 24
versionCode 32
versionName '3.12'
applicationId "org.traccar.client"
buildConfigField "boolean", "HIDDEN_APP", "false"
minSdkVersion 15
targetSdkVersion 27
versionCode 46
versionName '5.9'
}
lintOptions {
checkReleaseBuilds false
}
flavorDimensions "default"
productFlavors {
regular {
applicationId "org.traccar.client"
buildConfigField "boolean", "HIDDEN_APP", "false"
}
regular
google
hidden {
applicationId "org.traccar.client.hidden"
buildConfigField "boolean", "HIDDEN_APP", "true"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
}
}
dependencies {
compile 'com.android.support:design:27.1.0'
compile 'com.mapzen.android:lost:3.0.4'
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.crashlytics.sdk.android:crashlytics:2.9.1'
}
if (getGradle().getStartParameter().getTaskRequests().toString().contains("Google")) {
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
task copyJson(type: Copy) {
from '../../traccar-shared/google-services'
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);
}
}
+11 -1
Просмотреть файл
@@ -6,7 +6,17 @@
<application
android:label="@string/hidden_app_name"
tools:replace="label">
tools:replace="android:label">
<activity
android:name="org.traccar.client.MainActivity">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.traccar.org" android:pathPrefix="/hidden" />
</intent-filter>
</activity>
<receiver android:name="org.traccar.client.DialLaunchReceiver" >
<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

+15 -4
Просмотреть файл
@@ -1,5 +1,6 @@
<?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">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
@@ -14,10 +15,12 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
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
android:name=".Launcher"
@@ -26,6 +29,9 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity-alias>
<activity android:name=".StatusActivity"/>
@@ -33,9 +39,11 @@
<activity android:name=".AboutActivity"/>
<activity
android:name=".ShortcutActivity">
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>
@@ -47,6 +55,9 @@
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
</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");
* you may not use this file except in compliance with the License.
@@ -15,22 +15,24 @@
*/
package org.traccar.client;
import android.app.Activity;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
public class AboutActivity extends Activity {
public class AboutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.about);
TextView title = (TextView) findViewById(R.id.title);
TextView title = findViewById(R.id.title);
try {
title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName);
} 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");
* 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.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.WakefulBroadcastReceiver;
public class AutostartReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.getBoolean(MainActivity.KEY_STATUS, false)) {
startWakefulService(context, new Intent(context, TrackingService.class));
if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
startWakefulForegroundService(context, new Intent(context, TrackingService.class));
}
}
+4 -1
Просмотреть файл
@@ -27,7 +27,7 @@ import java.util.Date;
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 interface DatabaseHandler<T> {
@@ -79,6 +79,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"altitude REAL," +
"speed REAL," +
"course REAL," +
"accuracy REAL," +
"battery REAL)");
}
@@ -97,6 +98,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
values.put("altitude", position.getAltitude());
values.put("speed", position.getSpeed());
values.put("course", position.getCourse());
values.put("accuracy", position.getAccuracy());
values.put("battery", position.getBattery());
db.insertOrThrow("position", null, values);
@@ -129,6 +131,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude")));
position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed")));
position.setCourse(cursor.getDouble(cursor.getColumnIndex("course")));
position.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy")));
position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
} 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 -262
Просмотреть файл
@@ -1,5 +1,5 @@
/*
* Copyright 2012 - 2016 Anton Tananaev (anton.tananaev@gmail.com)
* 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.
@@ -15,272 +15,17 @@
*/
package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.TwoStatePreference;
import android.util.Patterns;
import android.view.Menu;
import android.view.MenuItem;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import java.util.HashSet;
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_SECURE = "secure";
public static final String KEY_INTERVAL = "interval";
public static final String KEY_PROVIDER = "provider";
public static final String KEY_STATUS = "status";
private static final int PERMISSIONS_REQUEST_LOCATION = 2;
private SharedPreferences sharedPreferences;
private AlarmManager alarmManager;
private PendingIntent alarmIntent;
public class MainActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (BuildConfig.HIDDEN_APP) {
removeLauncherIcon();
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
addPreferencesFromResource(R.xml.preferences);
initPreferences();
findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return newValue != null && !newValue.equals("");
}
});
findPreference(KEY_ADDRESS).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
return newValue != null && Patterns.DOMAIN_NAME.matcher((String) newValue).matches();
} else {
return newValue != null && !((String) newValue).isEmpty();
}
}
});
findPreference(KEY_PORT).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
int value = Integer.parseInt((String) newValue);
if (value > 0 && value <= 65536) {
return true;
}
} catch (NumberFormatException e) {
}
}
return false;
}
});
findPreference(KEY_INTERVAL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
Integer.parseInt((String) newValue);
return true;
} catch (NumberFormatException e) {
}
}
return false;
}
});
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmIntent = PendingIntent.getBroadcast(this, 0, new Intent(this, AutostartReceiver.class), 0);
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
}
}
private void removeLauncherIcon() {
String className = MainActivity.class.getCanonicalName().replace(".MainActivity", ".Launcher");
ComponentName componentName = new ComponentName(getPackageName(), className);
PackageManager packageManager = getPackageManager();
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting(
componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.hidden_alert));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
private void addShortcuts(boolean start, int name) {
Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
shortcutIntent.setComponent(new ComponentName(getPackageName(), ShortcutActivity.class.getCanonicalName()));
shortcutIntent.putExtra(ShortcutActivity.EXTRA_ACTION, start);
Intent installShortCutIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
installShortCutIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
installShortCutIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(name));
installShortCutIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher));
sendBroadcast(installShortCutIntent);
}
private boolean hasPermission(String permission) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
return true;
}
return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
@Override
protected void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
private void setPreferencesEnabled(boolean enabled) {
findPreference(KEY_DEVICE).setEnabled(enabled);
findPreference(KEY_ADDRESS).setEnabled(enabled);
findPreference(KEY_PORT).setEnabled(enabled);
findPreference(KEY_SECURE).setEnabled(enabled);
findPreference(KEY_INTERVAL).setEnabled(enabled);
findPreference(KEY_PROVIDER).setEnabled(enabled);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_STATUS)) {
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
} else {
stopTrackingService();
}
} else if (key.equals(KEY_DEVICE)) {
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.status) {
startActivity(new Intent(this, StatusActivity.class));
return true;
} else if (item.getItemId() == R.id.shortcuts) {
addShortcuts(true, R.string.shortcut_start);
addShortcuts(false, R.string.shortcut_stop);
return true;
} else if (item.getItemId() == R.id.about) {
startActivity(new Intent(this, AboutActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
private void initPreferences() {
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
if (!sharedPreferences.contains(KEY_DEVICE)) {
String id = String.valueOf(new Random().nextInt(900000) + 100000);
sharedPreferences.edit().putString(KEY_DEVICE, id).commit();
((EditTextPreference) findPreference(KEY_DEVICE)).setText(id);
}
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
private void startTrackingService(boolean checkPermission, boolean permission) {
if (checkPermission) {
Set<String> missingPermissions = new HashSet<>();
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (!hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
missingPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
if (missingPermissions.isEmpty()) {
permission = true;
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(missingPermissions.toArray(new String[missingPermissions.size()]),
PERMISSIONS_REQUEST_LOCATION);
}
return;
}
}
if (permission) {
setPreferencesEnabled(false);
startService(new Intent(this, TrackingService.class));
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
15000, 15000, alarmIntent);
} else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).commit();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TwoStatePreference preference = (TwoStatePreference) findPreference(KEY_STATUS);
preference.setChecked(false);
} else {
CheckBoxPreference preference = (CheckBoxPreference) findPreference(KEY_STATUS);
preference.setChecked(false);
}
}
}
private void stopTrackingService() {
alarmManager.cancel(alarmIntent);
stopService(new Intent(this, TrackingService.class));
setPreferencesEnabled(true);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
boolean granted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
granted = false;
break;
}
}
startTrackingService(false, granted);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().replace(android.R.id.content, new MainFragment()).commit();
}
}
+46 -1
Просмотреть файл
@@ -1,5 +1,5 @@
/*
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
* 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.
@@ -15,14 +15,59 @@
*/
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;
}
}
-122
Просмотреть файл
@@ -1,122 +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;
@SuppressWarnings("MissingPermission")
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);
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, period, 0, this);
} catch (IllegalArgumentException e) {
Log.w(TAG, e);
}
}
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");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.traccar.client;
import android.location.Location;
import android.location.LocationManager;
import java.util.Date;
@@ -32,43 +33,110 @@ public class Position {
altitude = location.getAltitude();
speed = location.getSpeed() * 1.943844; // speed in knots
course = location.getBearing();
if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
accuracy = location.getAccuracy();
}
this.battery = battery;
}
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;
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;
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;
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;
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;
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;
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;
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;
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");
* you may not use this file except in compliance with the License.
@@ -15,21 +15,26 @@
*/
package org.traccar.client;
import android.annotation.TargetApi;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.os.BatteryManager;
import android.os.Build;
import android.preference.PreferenceManager;
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 {
void onPositionUpdate(Position position);
@@ -38,52 +43,95 @@ public abstract class PositionProvider {
private final PositionListener listener;
private final Context context;
private final SharedPreferences preferences;
protected final LocationManager locationManager;
private SharedPreferences preferences;
private LostApiClient apiClient;
private String deviceId;
protected String type;
protected final long period;
private long interval;
private double distance;
private double angle;
private long lastUpdateTime;
private Location lastLocation;
private boolean started;
public PositionProvider(Context context, PositionListener listener) {
this.context = context;
this.listener = listener;
preferences = PreferenceManager.getDefaultSharedPreferences(context);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
deviceId = preferences.getString(MainActivity.KEY_DEVICE, null);
period = Long.parseLong(preferences.getString(MainActivity.KEY_INTERVAL, null)) * 1000;
type = preferences.getString(MainActivity.KEY_PROVIDER, "gps");
deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined");
interval = Long.parseLong(preferences.getString(MainFragment.KEY_INTERVAL, "600")) * 1000;
distance = Integer.parseInt(preferences.getString(MainFragment.KEY_DISTANCE, "0"));
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();
protected void updateLocation(Location location) {
if (location != null && location.getTime() - lastUpdateTime >= period) {
Log.i(TAG, "location new");
lastUpdateTime = location.getTime();
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel()));
} else {
Log.i(TAG, location != null ? "location old" : "location nil");
private int getPriority(String accuracy) {
switch (accuracy) {
case "high":
return LocationRequest.PRIORITY_HIGH_ACCURACY;
case "low":
return LocationRequest.PRIORITY_LOW_POWER;
default:
return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
}
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
private double getBatteryLevel() {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.ECLAIR) {
Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
@SuppressLint("MissingPermission")
@Override
public void onConnected() {
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 scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
return (level * 100.0) / scale;
} else {
return 0;
}
return 0;
}
}
+11 -4
Просмотреть файл
@@ -19,10 +19,13 @@ import android.net.Uri;
public class ProtocolFormatter {
public static String formatRequest(String address, int port, boolean secure, Position position) {
public static String formatRequest(String url, Position position) {
return formatRequest(url, position, null);
}
Uri.Builder builder = new Uri.Builder();
builder.scheme(secure ? "https" : "http").encodedAuthority(address + ':' + port)
public static String formatRequest(String url, Position position, String alarm) {
Uri serverUrl = Uri.parse(url);
Uri.Builder builder = serverUrl.buildUpon()
.appendQueryParameter("id", position.getDeviceId())
.appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000))
.appendQueryParameter("lat", String.valueOf(position.getLatitude()))
@@ -30,9 +33,13 @@ public class ProtocolFormatter {
.appendQueryParameter("speed", String.valueOf(position.getSpeed()))
.appendQueryParameter("bearing", String.valueOf(position.getCourse()))
.appendQueryParameter("altitude", String.valueOf(position.getAltitude()))
.appendQueryParameter("accuracy", String.valueOf(position.getAccuracy()))
.appendQueryParameter("batt", String.valueOf(position.getBattery()));
if (alarm != null) {
builder.appendQueryParameter("alarm", alarm);
}
return builder.build().toString();
}
}
+3 -1
Просмотреть файл
@@ -16,6 +16,7 @@
package org.traccar.client;
import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
@@ -56,6 +57,7 @@ public class RequestManager {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(TIMEOUT);
connection.setConnectTimeout(TIMEOUT);
connection.setRequestMethod("POST");
connection.connect();
inputStream = connection.getInputStream();
while (inputStream.read() != -1);
@@ -68,7 +70,7 @@ public class RequestManager {
inputStream.close();
}
} catch (IOException secondError) {
return false;
Log.w(RequestManager.class.getSimpleName(), secondError);
}
}
}
+140 -17
Просмотреть файл
@@ -1,5 +1,5 @@
/*
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
* 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.
@@ -15,42 +15,165 @@
*/
package org.traccar.client;
import android.app.Activity;
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;
public class ShortcutActivity extends Activity {
import com.mapzen.android.lost.api.LocationServices;
import com.mapzen.android.lost.api.LostApiClient;
public static final String EXTRA_ACTION = "shortcutAction";
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);
checkShortcutAction(getIntent());
if (!executeAction(getIntent())) {
setContentView(R.layout.list);
final String[] items = new String[] {
getString(R.string.shortcut_start),
getString(R.string.shortcut_stop),
getString(R.string.shortcut_sos)
};
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(new ArrayAdapter<>(
this, android.R.layout.simple_list_item_1, items));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START);
break;
case 1:
setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP);
break;
case 2:
setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS);
break;
}
finish();
}
});
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
checkShortcutAction(intent);
executeAction(intent);
}
private void checkShortcutAction(Intent intent) {
if (intent.hasExtra(EXTRA_ACTION)) {
boolean start = intent.getBooleanExtra(EXTRA_ACTION, false);
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainActivity.KEY_STATUS, start).commit();
if (start) {
startService(new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show();
} else {
stopService(new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show();
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;
}
}
-64
Просмотреть файл
@@ -1,64 +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;
import android.util.Log;
@SuppressWarnings("MissingPermission")
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() {
try {
locationManager.requestLocationUpdates(type, period, 0, this);
} catch (IllegalArgumentException e) {
Log.w(TAG, e);
}
}
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");
* 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.Set;
import android.app.ListActivity;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
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 LinkedList<String> messages = new LinkedList<String>();
private static final Set<ArrayAdapter<String>> adapters = new HashSet<ArrayAdapter<String>>();
private static final LinkedList<String> messages = new LinkedList<>();
private static final Set<ArrayAdapter<String>> adapters = new HashSet<>();
private static void notifyAdapters() {
for (ArrayAdapter<String> adapter : adapters) {
@@ -61,9 +62,10 @@ public class StatusActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.status);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages);
setListAdapter(adapter);
setContentView(R.layout.list);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages);
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(adapter);
adapters.add(adapter);
}
+9 -21
Просмотреть файл
@@ -17,7 +17,6 @@ package org.traccar.client;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.preference.PreferenceManager;
@@ -27,7 +26,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private static final String TAG = TrackingController.class.getSimpleName();
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 isWaiting;
@@ -36,9 +35,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private Handler handler;
private SharedPreferences preferences;
private String address;
private int port;
private boolean secure;
private String url;
private PositionProvider positionProvider;
private DatabaseHelper databaseHelper;
@@ -47,11 +44,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private PowerManager.WakeLock wakeLock;
private void lock() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
wakeLock.acquire();
} else {
wakeLock.acquire(WAKE_LOCK_TIMEOUT);
}
wakeLock.acquire(WAKE_LOCK_TIMEOUT);
}
private void unlock() {
@@ -64,18 +57,12 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
this.context = context;
handler = new Handler();
preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getString(MainActivity.KEY_PROVIDER, "gps").equals("mixed")) {
positionProvider = new MixedPositionProvider(context, this);
} else {
positionProvider = new SimplePositionProvider(context, this);
}
positionProvider = new PositionProvider(context, this);
databaseHelper = new DatabaseHelper(context);
networkManager = new NetworkManager(context, this);
isOnline = networkManager.isOnline();
address = preferences.getString(MainActivity.KEY_ADDRESS, null);
port = Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null));
secure = preferences.getBoolean(MainActivity.KEY_SECURE, false);
url = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value));
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
@@ -113,7 +100,8 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
@Override
public void onNetworkUpdate(boolean isOnline) {
StatusActivity.addMessage(context.getString(R.string.status_connectivity_change));
int message = isOnline ? R.string.status_network_online : R.string.status_network_offline;
StatusActivity.addMessage(context.getString(message));
if (!this.isOnline && isOnline) {
read();
}
@@ -164,7 +152,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
public void onComplete(boolean success, Position result) {
if (success) {
if (result != null) {
if (result.getDeviceId().equals(preferences.getString(MainActivity.KEY_DEVICE, null))) {
if (result.getDeviceId().equals(preferences.getString(MainFragment.KEY_DEVICE, null))) {
send(result);
} else {
delete(result);
@@ -199,7 +187,7 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private void send(final Position position) {
log("send", position);
lock();
String request = ProtocolFormatter.formatRequest(address, port, secure, position);
String request = ProtocolFormatter.formatRequest(url, position);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override
public void onComplete(boolean success) {
+24 -41
Просмотреть файл
@@ -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");
* you may not use this file except in compliance with the License.
@@ -23,11 +23,10 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TrackingService extends Service {
private static final String TAG = TrackingService.class.getSimpleName();
@@ -35,31 +34,25 @@ public class TrackingService extends Service {
private TrackingController trackingController;
@SuppressWarnings("deprecation")
private static Notification createNotification(Context context) {
Intent notificationIntent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
Notification notification = new Notification(android.R.drawable.stat_notify_sync_noanim, null, 0);
try {
Method method = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
try {
method.invoke(notification, context, context.getString(R.string.app_name), context.getString(R.string.settings_status_on_summary), pendingIntent);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
Log.w(TAG, e);
}
} catch (SecurityException | NoSuchMethodException e) {
Log.w(TAG, e);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
.setSmallIcon(R.drawable.ic_stat_notify)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(Notification.CATEGORY_SERVICE);
Intent intent;
if (!BuildConfig.HIDDEN_APP) {
intent = new Intent(context, MainActivity.class);
builder
.setContentTitle(context.getString(R.string.settings_status_on_summary))
.setTicker(context.getString(R.string.settings_status_on_summary))
.setColor(ContextCompat.getColor(context, R.color.primary_dark));
} else {
intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
notification.priority = Notification.PRIORITY_MIN;
}
return notification;
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
return builder.build();
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
public static class HideNotificationService extends Service {
@Override
public IBinder onBind(Intent intent) {
@@ -87,10 +80,8 @@ public class TrackingService extends Service {
trackingController = new TrackingController(this);
trackingController.start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
startForeground(NOTIFICATION_ID, createNotification(this));
startService(new Intent(this, HideNotificationService.class));
}
startForeground(NOTIFICATION_ID, createNotification(this));
ContextCompat.startForegroundService(this, new Intent(this, HideNotificationService.class));
}
@Override
@@ -98,18 +89,12 @@ public class TrackingService extends Service {
return null;
}
@SuppressWarnings("deprecation")
@Override
public void onStart(Intent intent, int startId) {
if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
return START_STICKY;
}
@@ -118,9 +103,7 @@ public class TrackingService extends Service {
Log.i(TAG, "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) {
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;
}
}
}
Двоичный файл не отображается.

До

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

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

До

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

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

До

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

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

До

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

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

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