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

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

Fixes:
 > Could not find com.android.tools.build:gradle:3.0.1.
     Searched in the following locations:
         https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.pom
         https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.jar
2018-01-22 20:15:20 +01:00
Anton Tananaev 290a7d80eb Update app version number 2018-01-19 17:43:43 +13:00
Anton Tananaev 44228feb2f Fix SOS functionality 2018-01-19 17:43:18 +13:00
Anton Tananaev d887bc4119 Update version number 2018-01-09 08:26:19 +13:00
Anton Tananaev c892a20104 Fix lost API connection logic 2018-01-09 08:25:57 +13:00
Anton Tananaev 2719b57f7c Use color code directly 2018-01-07 09:26:55 +13:00
Anton Tananaev cc6c8a23f6 Update project files 2018-01-07 09:23:14 +13:00
Anton Tananaev 1e82cbbbf6 Merge pull request #305 from Alpha200/fixAccuracy
Fix reading accuracy property
2017-12-30 10:40:06 +13:00
Daniel Sendzik 4b8fde36d7 Fix reading accuracy property 2017-12-29 19:34:14 +01:00
Anton Tananaev b8cd56439a Merge pull request #304 from garyvdm/restore_startWakefulForegroundService
Restore fix auto-start issue on Android O
2017-12-21 23:03:37 +13:00
Gary van der Merwe edb1f696df Restore fix auto-start issue on Android O.
Also rename startWakefulService to startWakefulForegroundService to highlight
that it's different to the SDK version.
2017-12-21 11:54:14 +02:00
Anton Tananaev 56d2f2ef53 Merge pull request #303 from garyvdm/cleanup
Remove unnecessary code
2017-12-21 09:52:43 +13:00
Gary van der Merwe 59e6a5ae65 Remove code that was originally included for supporting older SDK APIs that are now older than our minSdkVersion. 2017-12-20 14:17:51 +02:00
Anton Tananaev 319fab647b Merge pull request #302 from garyvdm/hidden_notification
For hidden flavor, make notificaion not open the app when clicked, use different icon
2017-12-16 09:47:04 +13:00
Gary van der Merwe 42429f3496 For hidden flavor, make notification open system settings activity. 2017-12-15 12:38:56 +02:00
Gary van der Merwe 841d3e581c For hidden flavor, make notificaion not open the app when clicked, use different icon.
This only apply to when the notificaion can't be hidden, i.e. SDK >= 25.
2017-12-15 09:09:25 +02:00
Anton Tananaev dac43ccbc1 Merge pull request #300 from garyvdm/notification
Get service notification to show. Tweek settings.
2017-12-14 13:53:22 +13:00
Gary van der Merwe 3c9ae512bd Get service notification to show. Tweek settings. 2017-12-13 08:11:11 +02:00
Anton Tananaev d3e600d81d Gracefully stop location updates 2017-11-23 00:03:17 +13:00
Anton Tananaev e0296a9c65 Use fused location provider 2017-11-22 23:59:52 +13:00
Anton Tananaev e51f590e19 Merge pull request #297 from Mantas0726/master
Update PositionProvider.java
2017-11-15 09:57:45 +13:00
Mantas0726 6e3812320c Update PositionProvider.java 2017-11-14 22:20:11 +02:00
Anton Tananaev c45c8ea797 Update version number 2017-09-25 15:08:10 +13:00
Anton Tananaev ed0aa53aa3 Fix null pointer crash 2017-09-25 15:04:19 +13:00
Anton Tananaev f88891be2b Add another hidden intent filter 2017-09-25 15:01:56 +13:00
Anton Tananaev 3f83684d02 Update version number 2017-09-20 05:23:51 +12:00
Anton Tananaev a9d772bfda Restart service after upgrade 2017-09-20 05:23:27 +12:00
Anton Tananaev 2bc3c78a91 Update version number 2017-09-12 23:22:33 +12:00
Anton Tananaev 8fab55552d Handle no network provider 2017-09-12 23:21:58 +12:00
Anton Tananaev 979963f6a2 Fix auto-start issue on Android O 2017-09-07 19:04:10 +12:00
Anton Tananaev 70d6dfec4e Fix issue with URL migration 2017-09-02 20:01:13 +12:00
Anton Tananaev 43e108de8c Fix number format exception 2017-09-02 08:40:56 +12:00
Anton Tananaev fedce0e26b Fix number format exception 2017-09-02 08:38:42 +12:00
Anton Tananaev e24d725feb Disable failing unit test 2017-09-01 11:20:15 +12:00
Anton Tananaev 3198bee2f2 Use POST to send data (fix #279) 2017-09-01 11:18:26 +12:00
Anton Tananaev 92e0fb3ad5 Remove unnecessary permissions 2017-09-01 10:41:24 +12:00
Anton Tananaev f7b0df1c41 Update version code 2017-09-01 10:36:49 +12:00
Anton Tananaev 8a26c7e5ba Update localization files 2017-09-01 10:34:47 +12:00
Anton Tananaev 27c98b910b Remove redundant colors and use icons 2017-09-01 09:51:57 +12:00
Anton Tananaev 360958d250 New hidden app icon 2017-09-01 08:55:26 +12:00
Anton Tananaev 4d8d15d615 Implement static shortcuts 2017-08-31 14:16:04 +12:00
Anton Tananaev 7c49bb78f3 Implement proper shortcuts 2017-08-31 13:48:52 +12:00
Anton Tananaev e87c3c7783 Report location accuracy 2017-08-31 11:42:15 +12:00
Anton Tananaev 5428530dbd Add Firebase analytics 2017-08-31 11:07:16 +12:00
Anton Tananaev 3d5682d1b1 Move color to common file 2017-08-31 10:14:05 +12:00
Anton Tananaev 8f91c0e0ea New hidden app icon 2017-08-30 23:05:24 +12:00
Anton Tananaev 965feb9b04 Remove old launcher icons 2017-08-30 23:00:05 +12:00
Anton Tananaev 8f8a5e3f55 Add new app icon 2017-08-30 22:48:47 +12:00
Anton Tananaev 77154a81af Use some new Android APIs 2017-08-30 15:34:45 +12:00
Anton Tananaev 8d94b8d011 Implement notification channel 2017-08-30 15:27:00 +12:00
Anton Tananaev ab37fd478a Make spacing more standard 2017-08-30 13:51:06 +12:00
Anton Tananaev 79662bad35 Remove redundant preferences 2017-08-30 13:45:56 +12:00
Anton Tananaev 90373ff8fb Fix app unit tests 2017-08-30 13:44:40 +12:00
Anton Tananaev f2e7ae7c21 Finish app modernization 2017-08-30 13:42:04 +12:00
Anton Tananaev e0c5340d8b Modernize and fix compilation 2017-08-30 13:25:18 +12:00
Anton Tananaev 90fffa9873 Update built tools to latest 2017-08-30 12:59:06 +12:00
Anton Tananaev db51adaf86 Add github templates 2017-08-21 22:44:12 +12:00
Anton Tananaev 7eb6876491 Merge pull request #269 from BrianBlade/basepath
Add serverpath preference
2017-06-15 09:42:27 +12:00
BrianBlade 5db3407e88 remove check if URL isEmpty; dont set 5055 as default port 2017-06-14 14:36:00 +02:00
BrianBlade ff4fe90c6d simplify URL validation 2017-06-14 13:02:03 +02:00
BrianBlade 62a2599765 rename setServerUrl to validateServerUrl and add port validation 2017-06-14 11:00:56 +02:00
BrianBlade 149eee530a some requested fixes 2017-06-14 08:40:40 +02:00
BrianBlade 6e398d12fe add ServerURL preference (replaces address, port, encryption) 2017-06-13 12:40:32 +02:00
Anton Tananaev 10e573017b Bump app version number 2017-05-16 20:50:50 +12:00
Anton Tananaev f5f8d41e41 Validate distance and angle 2017-05-16 20:42:32 +12:00
Anton Tananaev 839b180890 Add new error messages 2017-05-14 12:47:32 +12:00
Anton Tananaev e2a26297c9 Update app version number 2017-05-13 22:03:50 +12:00
Anton Tananaev 4d33d8ff9e Fix locale folder issue 2017-05-13 22:03:27 +12:00
Anton Tananaev 7e923954b8 Update and add localizations 2017-05-13 21:55:52 +12:00
Anton Tananaev f0b0650531 Update translation script 2017-05-13 21:54:31 +12:00
Anton Tananaev 3eb655040d Update gradle plugin version 2017-05-13 21:42:34 +12:00
Anton Tananaev 96a75f02a7 Check shortcut action for null 2017-04-23 12:00:58 +12:00
Anton Tananaev d632c7cdfa Fix issues with notification 2017-04-08 17:47:54 +12:00
Anton Tananaev 9bae6e554f Implement reporting by distance and angle 2017-04-08 17:36:12 +12:00
Anton Tananaev faf23e7926 Final update to badges 2017-03-29 23:02:39 +13:00
Anton Tananaev 54331ffe93 Adjust badge height 2017-03-29 22:51:38 +13:00
Anton Tananaev c19c17f091 Try SVG image in readme 2017-03-29 22:50:18 +13:00
Anton Tananaev 2bd5bf540f Testing embedded badges 2017-03-29 22:44:22 +13:00
Anton Tananaev 3d89cfe649 Merge pull request #260 from Poussinou/master
Add a button with link to fdroid in README.md
2017-03-29 22:18:46 +13:00
Poussinou e2e51b3048 Update README.md 2017-03-28 19:08:27 +02:00
Poussinou 43d9775aa0 add a button with link to fdroid 2017-03-27 16:21:33 +02:00
172 изменённых файлов: 2537 добавлений и 1733 удалений
+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/
-->
+1
Просмотреть файл
@@ -4,3 +4,4 @@ local.properties
*.iml *.iml
build/ build/
.DS_Store .DS_Store
google-services.json
+1 -1
Просмотреть файл
@@ -1,6 +1,6 @@
# [Traccar Client for Android](https://www.traccar.org/client) # [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) [![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 ## Overview
+35 -16
Просмотреть файл
@@ -1,40 +1,59 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 25 compileSdkVersion 28
buildToolsVersion '25.0.2'
defaultConfig { defaultConfig {
minSdkVersion 3 applicationId "org.traccar.client"
targetSdkVersion 25 buildConfigField "boolean", "HIDDEN_APP", "false"
versionCode 34 minSdkVersion 15
versionName '4.1' targetSdkVersion 28
versionCode 56
versionName '5.19'
} }
lintOptions { lintOptions {
checkReleaseBuilds false checkReleaseBuilds false
} }
flavorDimensions "default"
productFlavors { productFlavors {
regular { regular
applicationId "org.traccar.client" google
buildConfigField "boolean", "HIDDEN_APP", "false"
}
hidden { hidden {
applicationId "org.traccar.client.hidden" applicationId "org.traccar.client.hidden"
buildConfigField "boolean", "HIDDEN_APP", "true" buildConfigField "boolean", "HIDDEN_APP", "true"
} }
} }
buildTypes { testOptions {
release { unitTests {
minifyEnabled false includeAndroidResources = true
proguardFiles getDefaultProguardFile('proguard-android.txt')
} }
} }
} }
dependencies { dependencies {
testCompile 'junit:junit:4.12' implementation 'com.google.android.material:material:1.1.0-alpha06'
testCompile 'org.robolectric:robolectric:3.0' testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.1'
googleImplementation 'com.google.firebase:firebase-core:16.0.8'
googleImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.0'
googleImplementation "com.google.android.gms:play-services-location:16.0.0"
}
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@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import com.google.firebase.analytics.FirebaseAnalytics;
public class GoogleMainApplication extends MainApplication {
private FirebaseAnalytics firebaseAnalytics;
@Override
public void onCreate() {
super.onCreate();
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
}
}
+84
Просмотреть файл
@@ -0,0 +1,84 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnSuccessListener;
public class GooglePositionProvider extends PositionProvider {
private FusedLocationProviderClient fusedLocationClient;
public GooglePositionProvider(Context context, PositionListener listener) {
super(context, listener);
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
}
@SuppressLint("MissingPermission")
public void startUpdates() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setPriority(getPriority(preferences.getString(MainFragment.KEY_ACCURACY, "medium")));
locationRequest.setInterval(distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval);
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null);
}
public void stopUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
fusedLocationClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
}
});
}
private static int getPriority(String accuracy) {
switch (accuracy) {
case "high":
return LocationRequest.PRIORITY_HIGH_ACCURACY;
case "low":
return LocationRequest.PRIORITY_LOW_POWER;
default:
return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
}
}
private LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
for (Location location : locationResult.getLocations()) {
processLocation(location);
}
}
}
};
}
+26
Просмотреть файл
@@ -0,0 +1,26 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
public class PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) {
return new GooglePositionProvider(context, listener);
}
}
+11 -1
Просмотреть файл
@@ -6,7 +6,17 @@
<application <application
android:label="@string/hidden_app_name" android:label="@string/hidden_app_name"
tools:replace="label"> tools:replace="android:label">
<activity
android:name="org.traccar.client.MainActivity">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="www.traccar.org" android:pathPrefix="/hidden" />
</intent-filter>
</activity>
<receiver android:name="org.traccar.client.DialLaunchReceiver" > <receiver android:name="org.traccar.client.DialLaunchReceiver" >
<intent-filter> <intent-filter>
+26
Просмотреть файл
@@ -0,0 +1,26 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
public class PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) {
return new AndroidPositionProvider(context, listener);
}
}
+9
Просмотреть файл
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM19,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.11,0 2,-0.9 2,-2L21,5c0,-1.1 -0.89,-2 -2,-2zM17.25,12c0,0.23 -0.02,0.46 -0.05,0.68l1.48,1.16c0.13,0.11 0.17,0.3 0.08,0.45l-1.4,2.42c-0.09,0.15 -0.27,0.21 -0.43,0.15l-1.74,-0.7c-0.36,0.28 -0.76,0.51 -1.18,0.69l-0.26,1.85c-0.03,0.17 -0.18,0.3 -0.35,0.3h-2.8c-0.17,0 -0.32,-0.13 -0.35,-0.29l-0.26,-1.85c-0.43,-0.18 -0.82,-0.41 -1.18,-0.69l-1.74,0.7c-0.16,0.06 -0.34,0 -0.43,-0.15l-1.4,-2.42c-0.09,-0.15 -0.05,-0.34 0.08,-0.45l1.48,-1.16c-0.03,-0.23 -0.05,-0.46 -0.05,-0.69 0,-0.23 0.02,-0.46 0.05,-0.68l-1.48,-1.16c-0.13,-0.11 -0.17,-0.3 -0.08,-0.45l1.4,-2.42c0.09,-0.15 0.27,-0.21 0.43,-0.15l1.74,0.7c0.36,-0.28 0.76,-0.51 1.18,-0.69l0.26,-1.85c0.03,-0.17 0.18,-0.3 0.35,-0.3h2.8c0.17,0 0.32,0.13 0.35,0.29l0.26,1.85c0.43,0.18 0.82,0.41 1.18,0.69l1.74,-0.7c0.16,-0.06 0.34,0 0.43,0.15l1.4,2.42c0.09,0.15 0.05,0.34 -0.08,0.45l-1.48,1.16c0.03,0.23 0.05,0.46 0.05,0.69z"/>
</vector>
+5
Просмотреть файл
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/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

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

После

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

-134
Просмотреть файл
@@ -1,134 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v4.content;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;
import android.util.SparseArray;
/**
* Helper for the common pattern of implementing a {@link BroadcastReceiver}
* that receives a device wakeup event and then passes the work off
* to a {@link android.app.Service}, while ensuring that the
* device does not go back to sleep during the transition.
*
* <p>This class takes care of creating and managing a partial wake lock
* for you; you must request the {@link android.Manifest.permission#WAKE_LOCK}
* permission to use it.</p>
*
* <h3>Example</h3>
*
* <p>A {@link WakefulBroadcastReceiver} uses the method
* {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
* to start the service that does the work. This method is comparable to
* {@link android.content.Context#startService startService()}, except that
* the {@link WakefulBroadcastReceiver} is holding a wake lock when the service
* starts. The intent that is passed with
* {@link WakefulBroadcastReceiver#startWakefulService startWakefulService()}
* holds an extra identifying the wake lock.</p>
*
* {@sample development/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulReceiver.java complete}
*
* <p>The service (in this example, an {@link android.app.IntentService}) does
* some work. When it is finished, it releases the wake lock by calling
* {@link WakefulBroadcastReceiver#completeWakefulIntent
* completeWakefulIntent(intent)}. The intent it passes as a parameter
* is the same intent that the {@link WakefulBroadcastReceiver} originally
* passed in.</p>
*
* {@sample development/samples/Support4Demos/src/com/example/android/supportv4/content/SimpleWakefulService.java complete}
*/
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
= new SparseArray<PowerManager.WakeLock>();
private static int mNextId = 1;
/**
* Do a {@link android.content.Context#startService(android.content.Intent)
* Context.startService}, but holding a wake lock while the service starts.
* This will modify the Intent to hold an extra identifying the wake lock;
* when the service receives it in {@link android.app.Service#onStartCommand
* Service.onStartCommand}, it should pass back the Intent it receives there to
* {@link #completeWakefulIntent(android.content.Intent)} in order to release
* the wake lock.
*
* @param context The Context in which it operate.
* @param intent The Intent with which to start the service, as per
* {@link android.content.Context#startService(android.content.Intent)
* Context.startService}.
*/
public static ComponentName startWakefulService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ComponentName comp = context.startService(intent);
if (comp == null) {
return null;
}
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"wake:" + comp.flattenToShortString());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
return comp;
}
}
/**
* Finish the execution from a previous {@link #startWakefulService}. Any wake lock
* that was being held will now be released.
*
* @param intent The Intent as originally generated by {@link #startWakefulService}.
* @return Returns true if the intent is associated with a wake lock that is
* now released; returns false if there was no wake lock specified for it.
*/
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
// We return true whether or not we actually found the wake lock
// the return code is defined to indicate whether the Intent contained
// an identifier for a wake lock that it was supposed to match.
// We just log a warning here if there is no wake lock found, which could
// happen for example if this function is called twice on the same
// intent or the process is killed and restarted before processing the intent.
Log.w("WakefulBroadcastReceive", "No active wake lock id #" + id);
return true;
}
}
}
+6 -4
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,22 +15,24 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.app.Activity;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView; import android.widget.TextView;
public class AboutActivity extends Activity { public class AboutActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.about); setContentView(R.layout.about);
TextView title = (TextView) findViewById(R.id.title); TextView title = findViewById(R.id.title);
try { try {
title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName); title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName);
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
Log.w(AboutActivity.class.getSimpleName(), e);
} }
} }
+110
Просмотреть файл
@@ -0,0 +1,110 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
public class AndroidPositionProvider extends PositionProvider implements LocationListener {
private LocationManager locationManager;
private String provider;
public AndroidPositionProvider(Context context, PositionListener listener) {
super(context, listener);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
provider = getProvider(preferences.getString(MainFragment.KEY_ACCURACY, "medium"));
}
@SuppressLint("MissingPermission")
public void startUpdates() {
try {
locationManager.requestLocationUpdates(
provider, distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval, 0, this);
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
public void stopUpdates() {
locationManager.removeUpdates(this);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
try {
Location location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else {
locationManager.requestSingleUpdate(provider, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}, Looper.myLooper());
}
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
private static String getProvider(String accuracy) {
switch (accuracy) {
case "high":
return LocationManager.GPS_PROVIDER;
case "low":
return LocationManager.PASSIVE_PROVIDER;
default:
return LocationManager.NETWORK_PROVIDER;
}
}
@Override
public void onLocationChanged(Location location) {
processLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
+3 -4
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2013 - 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,15 +19,14 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.content.WakefulBroadcastReceiver;
public class AutostartReceiver extends WakefulBroadcastReceiver { public class AutostartReceiver extends WakefulBroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedPreferences.getBoolean(MainActivity.KEY_STATUS, false)) { if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
startWakefulService(context, new Intent(context, TrackingService.class)); startWakefulForegroundService(context, new Intent(context, TrackingService.class));
} }
} }
+14 -3
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@ import java.util.Date;
public class DatabaseHelper extends SQLiteOpenHelper { public class DatabaseHelper extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1; public static final int DATABASE_VERSION = 3;
public static final String DATABASE_NAME = "traccar.db"; public static final String DATABASE_NAME = "traccar.db";
public interface DatabaseHandler<T> { public interface DatabaseHandler<T> {
@@ -79,7 +79,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
"altitude REAL," + "altitude REAL," +
"speed REAL," + "speed REAL," +
"course REAL," + "course REAL," +
"battery REAL)"); "accuracy REAL," +
"battery REAL," +
"mock INTEGER)");
} }
@Override @Override
@@ -88,6 +90,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
onCreate(db); onCreate(db);
} }
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS position;");
onCreate(db);
}
public void insertPosition(Position position) { public void insertPosition(Position position) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("deviceId", position.getDeviceId()); values.put("deviceId", position.getDeviceId());
@@ -97,7 +104,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
values.put("altitude", position.getAltitude()); values.put("altitude", position.getAltitude());
values.put("speed", position.getSpeed()); values.put("speed", position.getSpeed());
values.put("course", position.getCourse()); values.put("course", position.getCourse());
values.put("accuracy", position.getAccuracy());
values.put("battery", position.getBattery()); values.put("battery", position.getBattery());
values.put("mock", position.getMock() ? 1 : 0);
db.insertOrThrow("position", null, values); db.insertOrThrow("position", null, values);
} }
@@ -129,7 +138,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude"))); position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude")));
position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed"))); position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed")));
position.setCourse(cursor.getDouble(cursor.getColumnIndex("course"))); position.setCourse(cursor.getDouble(cursor.getColumnIndex("course")));
position.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy")));
position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery"))); position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
position.setMock(cursor.getInt(cursor.getColumnIndex("mock")) > 0);
} else { } else {
return null; return null;
+1 -1
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
+7 -264
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2017 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,274 +15,17 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import androidx.annotation.Nullable;
import android.preference.EditTextPreference; import androidx.appcompat.app.AppCompatActivity;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.TwoStatePreference;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import java.util.HashSet; public class MainActivity extends AppCompatActivity {
import java.util.Random;
import java.util.Set;
@SuppressWarnings("deprecation")
public class MainActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
private static final String TAG = MainActivity.class.getSimpleName();
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;
@Override @Override
public void onCreate(Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
if (BuildConfig.HIDDEN_APP) { getFragmentManager().beginTransaction().replace(android.R.id.content, new MainFragment()).commit();
removeLauncherIcon();
}
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
addPreferencesFromResource(R.xml.preferences);
initPreferences();
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);
return value > 0 && value <= 65536;
} catch (NumberFormatException e) {
Log.w(TAG, e);
}
}
return false;
}
});
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;
}
});
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(String action, int name) {
Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
shortcutIntent.setComponent(new ComponentName(getPackageName(), ShortcutActivity.class.getCanonicalName()));
shortcutIntent.putExtra(ShortcutActivity.EXTRA_ACTION, action);
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(ShortcutActivity.EXTRA_ACTION_START, R.string.shortcut_start);
addShortcuts(ShortcutActivity.EXTRA_ACTION_STOP, R.string.shortcut_stop);
addShortcuts(ShortcutActivity.EXTRA_ACTION_SOS, R.string.shortcut_sos);
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);
} }
} }
+46 -1
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,14 +15,59 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.annotation.TargetApi;
import android.app.Application; 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 class MainApplication extends Application {
public static final String PRIMARY_CHANNEL = "default";
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
System.setProperty("http.keepAliveDuration", String.valueOf(30 * 60 * 1000)); 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();
}
} }
} }
+267
Просмотреть файл
@@ -0,0 +1,267 @@
/*
* Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.TwoStatePreference;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AlertDialog;
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.Random;
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();
}
}
@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) {
if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
permission = true;
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 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();
}
}
}
+1 -1
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
-58
Просмотреть файл
@@ -1,58 +0,0 @@
/*
* 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 java.util.regex.Pattern;
public class Patterns {
private static final String IP_ADDRESS_STRING =
"((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ "|[1-9][0-9]|[0-9]))";
private static final String UCS_CHAR = "[" +
"\u00A0-\uD7FF" +
"\uF900-\uFDCF" +
"\uFDF0-\uFFEF" +
"\uD800\uDC00-\uD83F\uDFFD" +
"\uD840\uDC00-\uD87F\uDFFD" +
"\uD880\uDC00-\uD8BF\uDFFD" +
"\uD8C0\uDC00-\uD8FF\uDFFD" +
"\uD900\uDC00-\uD93F\uDFFD" +
"\uD940\uDC00-\uD97F\uDFFD" +
"\uD980\uDC00-\uD9BF\uDFFD" +
"\uD9C0\uDC00-\uD9FF\uDFFD" +
"\uDA00\uDC00-\uDA3F\uDFFD" +
"\uDA40\uDC00-\uDA7F\uDFFD" +
"\uDA80\uDC00-\uDABF\uDFFD" +
"\uDAC0\uDC00-\uDAFF\uDFFD" +
"\uDB00\uDC00-\uDB3F\uDFFD" +
"\uDB44\uDC00-\uDB7F\uDFFD" +
"&&[^\u00A0[\u2000-\u200A]\u2028\u2029\u202F\u3000]]";
private static final String LABEL_CHAR = "a-zA-Z0-9" + UCS_CHAR;
private static final String IRI_LABEL =
"[" + LABEL_CHAR + "](?:[" + LABEL_CHAR + "\\-]{0,61}[" + LABEL_CHAR + "]){0,1}";
private static final String RELAXED_DOMAIN_NAME =
"(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS_STRING + ")";
public static final Pattern DOMAIN_NAME = Pattern.compile(RELAXED_DOMAIN_NAME);
}
+101 -19
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.traccar.client; package org.traccar.client;
import android.location.Location; import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import java.util.Date; import java.util.Date;
@@ -32,43 +34,123 @@ public class Position {
altitude = location.getAltitude(); altitude = location.getAltitude();
speed = location.getSpeed() * 1.943844; // speed in knots speed = location.getSpeed() * 1.943844; // speed in knots
course = location.getBearing(); course = location.getBearing();
if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
accuracy = location.getAccuracy();
}
this.battery = battery; this.battery = battery;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
this.mock = location.isFromMockProvider();
}
} }
private long id; private long id;
public long getId() { return id; }
public void setId(long id) { this.id = id; } public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
private String deviceId; private String deviceId;
public String getDeviceId() { return deviceId; }
public void setDeviceId(String deviceId) { this.deviceId = deviceId; } public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
private Date time; private Date time;
public Date getTime() { return time; }
public void setTime(Date time) { this.time = time; } public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
private double latitude; private double latitude;
public double getLatitude() { return latitude; }
public void setLatitude(double latitude) { this.latitude = latitude; } public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
private double longitude; private double longitude;
public double getLongitude() { return longitude; }
public void setLongitude(double longitude) { this.longitude = longitude; } public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
private double altitude; private double altitude;
public double getAltitude() { return altitude; }
public void setAltitude(double altitude) { this.altitude = altitude; } public double getAltitude() {
return altitude;
}
public void setAltitude(double altitude) {
this.altitude = altitude;
}
private double speed; private double speed;
public double getSpeed() { return speed; }
public void setSpeed(double speed) { this.speed = speed; } public double getSpeed() {
return speed;
}
public void setSpeed(double speed) {
this.speed = speed;
}
private double course; private double course;
public double getCourse() { return course; }
public void setCourse(double course) { this.course = course; } public double getCourse() {
return course;
}
public void setCourse(double course) {
this.course = course;
}
private double accuracy;
public double getAccuracy() {
return accuracy;
}
public void setAccuracy(double accuracy) {
this.accuracy = accuracy;
}
private double battery; private double battery;
public double getBattery() { return battery; }
public void setBattery(double battery) { this.battery = battery; } public double getBattery() {
return battery;
}
public void setBattery(double battery) {
this.battery = battery;
}
private boolean mock;
public boolean getMock() {
return mock;
}
public void setMock(boolean mock) {
this.mock = mock;
}
} }
+29 -27
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,75 +15,77 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.location.Location; import android.location.Location;
import android.location.LocationManager;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
public abstract class PositionProvider { public abstract class PositionProvider {
protected static final String TAG = PositionProvider.class.getSimpleName(); private static final String TAG = PositionProvider.class.getSimpleName();
protected static final int MINIMUM_INTERVAL = 1000;
public interface PositionListener { public interface PositionListener {
void onPositionUpdate(Position position); void onPositionUpdate(Position position);
void onPositionError(Throwable error);
} }
private final PositionListener listener; protected final PositionListener listener;
private final Context context; protected final Context context;
private final SharedPreferences preferences; protected SharedPreferences preferences;
protected final LocationManager locationManager;
private String deviceId; protected String deviceId;
protected String type; protected long interval;
protected final long period; protected double distance;
protected double angle;
private long lastUpdateTime; protected Location lastLocation;
public PositionProvider(Context context, PositionListener listener) { public PositionProvider(Context context, PositionListener listener) {
this.context = context; this.context = context;
this.listener = listener; this.listener = listener;
preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
deviceId = preferences.getString(MainActivity.KEY_DEVICE, null); deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined");
period = Long.parseLong(preferences.getString(MainActivity.KEY_INTERVAL, null)) * 1000; interval = Long.parseLong(preferences.getString(MainFragment.KEY_INTERVAL, "600")) * 1000;
distance = Integer.parseInt(preferences.getString(MainFragment.KEY_DISTANCE, "0"));
type = preferences.getString(MainActivity.KEY_PROVIDER, "gps"); angle = Integer.parseInt(preferences.getString(MainFragment.KEY_ANGLE, "0"));
} }
public abstract void startUpdates(); public abstract void startUpdates();
public abstract void stopUpdates(); public abstract void stopUpdates();
protected void updateLocation(Location location) { public abstract void requestSingleLocation();
if (location != null && location.getTime() - lastUpdateTime >= period) {
protected void processLocation(Location location) {
if (location != null && (lastLocation == null
|| location.getTime() - lastLocation.getTime() >= interval
|| distance > 0 && location.distanceTo(lastLocation) >= distance
|| angle > 0 && Math.abs(location.getBearing() - lastLocation.getBearing()) >= angle)) {
Log.i(TAG, "location new"); Log.i(TAG, "location new");
lastUpdateTime = location.getTime(); lastLocation = location;
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context))); listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else { } else {
Log.i(TAG, location != null ? "location old" : "location nil"); Log.i(TAG, location != null ? "location ignored" : "location nil");
} }
} }
@TargetApi(Build.VERSION_CODES.ECLAIR) protected static double getBatteryLevel(Context context) {
public static double getBatteryLevel(Context context) {
if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.ECLAIR) {
Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batteryIntent != null) {
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
return (level * 100.0) / scale; return (level * 100.0) / scale;
} else {
return 0;
} }
return 0;
} }
} }
+11 -8
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2016 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,14 +19,13 @@ import android.net.Uri;
public class ProtocolFormatter { 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(address, port, secure, position, null); return formatRequest(url, position, null);
} }
public static String formatRequest(String address, int port, boolean secure, Position position, String alarm) { public static String formatRequest(String url, Position position, String alarm) {
Uri serverUrl = Uri.parse(url);
Uri.Builder builder = new Uri.Builder(); Uri.Builder builder = serverUrl.buildUpon()
builder.scheme(secure ? "https" : "http").encodedAuthority(address + ':' + port)
.appendQueryParameter("id", position.getDeviceId()) .appendQueryParameter("id", position.getDeviceId())
.appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000)) .appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000))
.appendQueryParameter("lat", String.valueOf(position.getLatitude())) .appendQueryParameter("lat", String.valueOf(position.getLatitude()))
@@ -34,13 +33,17 @@ public class ProtocolFormatter {
.appendQueryParameter("speed", String.valueOf(position.getSpeed())) .appendQueryParameter("speed", String.valueOf(position.getSpeed()))
.appendQueryParameter("bearing", String.valueOf(position.getCourse())) .appendQueryParameter("bearing", String.valueOf(position.getCourse()))
.appendQueryParameter("altitude", String.valueOf(position.getAltitude())) .appendQueryParameter("altitude", String.valueOf(position.getAltitude()))
.appendQueryParameter("accuracy", String.valueOf(position.getAccuracy()))
.appendQueryParameter("batt", String.valueOf(position.getBattery())); .appendQueryParameter("batt", String.valueOf(position.getBattery()));
if (position.getMock()) {
builder.appendQueryParameter("mock", String.valueOf(position.getMock()));
}
if (alarm != null) { if (alarm != null) {
builder.appendQueryParameter("alarm", alarm); builder.appendQueryParameter("alarm", alarm);
} }
return builder.build().toString(); return builder.build().toString();
} }
} }
+4 -2
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.traccar.client; package org.traccar.client;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -56,6 +57,7 @@ public class RequestManager {
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(TIMEOUT); connection.setReadTimeout(TIMEOUT);
connection.setConnectTimeout(TIMEOUT); connection.setConnectTimeout(TIMEOUT);
connection.setRequestMethod("POST");
connection.connect(); connection.connect();
inputStream = connection.getInputStream(); inputStream = connection.getInputStream();
while (inputStream.read() != -1); while (inputStream.read() != -1);
@@ -68,7 +70,7 @@ public class RequestManager {
inputStream.close(); inputStream.close();
} }
} catch (IOException secondError) { } catch (IOException secondError) {
return false; Log.w(RequestManager.class.getSimpleName(), secondError);
} }
} }
} }
+90 -42
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016 - 2017 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,59 +15,97 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.location.Location; import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
public class ShortcutActivity extends Activity { import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
public class ShortcutActivity extends AppCompatActivity {
public static final String EXTRA_ACTION = "action"; public static final String EXTRA_ACTION = "action";
public static final String EXTRA_ACTION_START = "start"; public static final String ACTION_START = "start";
public static final String EXTRA_ACTION_STOP = "stop"; public static final String ACTION_STOP = "stop";
public static final String EXTRA_ACTION_SOS = "sos"; public static final String ACTION_SOS = "sos";
private static final String ALARM_SOS = "sos"; private static final String ALARM_SOS = "sos";
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
checkShortcutAction(getIntent()); if (!executeAction(getIntent())) {
setContentView(R.layout.list);
final String[] items = new String[] {
getString(R.string.shortcut_start),
getString(R.string.shortcut_stop),
getString(R.string.shortcut_sos)
};
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(new ArrayAdapter<>(
this, android.R.layout.simple_list_item_1, items));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START);
break;
case 1:
setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP);
break;
case 2:
setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS);
break;
}
finish();
}
});
}
} }
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
checkShortcutAction(intent); executeAction(intent);
}
private void 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));
} }
@SuppressWarnings("MissingPermission") @SuppressWarnings("MissingPermission")
private void sendAlarm() { private void sendAlarm() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); PositionProviderFactory.create(this, new PositionProvider.PositionListener() {
@Override
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); public void onPositionUpdate(Position position) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ShortcutActivity.this);
Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location == null) {
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
if (location != null) {
Position position = new Position(
preferences.getString(MainActivity.KEY_DEVICE, null),
location, PositionProvider.getBatteryLevel(this));
String request = ProtocolFormatter.formatRequest( String request = ProtocolFormatter.formatRequest(
preferences.getString(MainActivity.KEY_ADDRESS, null), preferences.getString(MainFragment.KEY_URL, null), position, ALARM_SOS);
Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null)),
preferences.getBoolean(MainActivity.KEY_SECURE, false),
position, ALARM_SOS);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() { RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override @Override
@@ -79,38 +117,48 @@ public class ShortcutActivity extends Activity {
} }
} }
}); });
} else {
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
} }
private void checkShortcutAction(Intent intent) { @Override
public void onPositionError(Throwable error) {
Toast.makeText(ShortcutActivity.this, error.getMessage(), Toast.LENGTH_LONG).show();
}
}).requestSingleLocation();
}
private boolean executeAction(Intent intent) {
String action; String action;
if (intent.hasExtra("shortcutAction")) { if (intent.hasExtra("shortcutAction")) {
action = intent.getBooleanExtra("shortcutAction", false) action = intent.getBooleanExtra("shortcutAction", false)
? EXTRA_ACTION_START : EXTRA_ACTION_STOP; ? ACTION_START : ACTION_STOP;
} else { } else {
action = intent.getStringExtra(EXTRA_ACTION); action = intent.getStringExtra(EXTRA_ACTION);
} }
if (action != null) {
switch (action) { switch (action) {
case EXTRA_ACTION_START: case ACTION_START:
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainActivity.KEY_STATUS, true).commit(); .edit().putBoolean(MainFragment.KEY_STATUS, true).apply();
startService(new Intent(this, TrackingService.class)); ContextCompat.startForegroundService(this, new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show();
break; break;
case EXTRA_ACTION_STOP: case ACTION_STOP:
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainActivity.KEY_STATUS, false).commit(); .edit().putBoolean(MainFragment.KEY_STATUS, false).apply();
stopService(new Intent(this, TrackingService.class)); stopService(new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show();
break; break;
case EXTRA_ACTION_SOS: case ACTION_SOS:
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
sendAlarm(); sendAlarm();
} else {
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
break; break;
} }
finish(); 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) {
}
}
+11 -9
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2013 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,19 +21,20 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Set; import java.util.Set;
import android.app.ListActivity;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ListView;
public class StatusActivity extends ListActivity { public class StatusActivity extends AppCompatActivity {
private static final int LIMIT = 20; private static final int LIMIT = 20;
private static final LinkedList<String> messages = new LinkedList<String>(); private static final LinkedList<String> messages = new LinkedList<>();
private static final Set<ArrayAdapter<String>> adapters = new HashSet<ArrayAdapter<String>>(); private static final Set<ArrayAdapter<String>> adapters = new HashSet<>();
private static void notifyAdapters() { private static void notifyAdapters() {
for (ArrayAdapter<String> adapter : adapters) { for (ArrayAdapter<String> adapter : adapters) {
@@ -42,7 +43,7 @@ public class StatusActivity extends ListActivity {
} }
public static void addMessage(String message) { public static void addMessage(String message) {
DateFormat format = DateFormat.getTimeInstance(DateFormat.SHORT); DateFormat format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
message = format.format(new Date()) + " - " + message; message = format.format(new Date()) + " - " + message;
messages.add(message); messages.add(message);
while (messages.size() > LIMIT) { while (messages.size() > LIMIT) {
@@ -61,9 +62,10 @@ public class StatusActivity extends ListActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.status); setContentView(R.layout.list);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages); adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages);
setListAdapter(adapter); ListView listView = findViewById(android.R.id.list);
listView.setAdapter(adapter);
adapters.add(adapter); adapters.add(adapter);
} }
+12 -44
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,9 +17,7 @@ package org.traccar.client;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
@@ -36,49 +34,22 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private Handler handler; private Handler handler;
private SharedPreferences preferences; private SharedPreferences preferences;
private String address; private String url;
private int port;
private boolean secure;
private PositionProvider positionProvider; private PositionProvider positionProvider;
private DatabaseHelper databaseHelper; private DatabaseHelper databaseHelper;
private NetworkManager networkManager; private NetworkManager networkManager;
private PowerManager.WakeLock wakeLock;
private void lock() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
wakeLock.acquire();
} else {
wakeLock.acquire(WAKE_LOCK_TIMEOUT);
}
}
private void unlock() {
if (wakeLock.isHeld()) {
wakeLock.release();
}
}
public TrackingController(Context context) { public TrackingController(Context context) {
this.context = context; this.context = context;
handler = new Handler(); handler = new Handler();
preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (preferences.getString(MainActivity.KEY_PROVIDER, "gps").equals("mixed")) { positionProvider = PositionProviderFactory.create(context, this);
positionProvider = new MixedPositionProvider(context, this);
} else {
positionProvider = new SimplePositionProvider(context, this);
}
databaseHelper = new DatabaseHelper(context); databaseHelper = new DatabaseHelper(context);
networkManager = new NetworkManager(context, this); networkManager = new NetworkManager(context, this);
isOnline = networkManager.isOnline(); isOnline = networkManager.isOnline();
address = preferences.getString(MainActivity.KEY_ADDRESS, null); url = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value));
port = Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null));
secure = preferences.getBoolean(MainActivity.KEY_SECURE, false);
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
} }
public void start() { public void start() {
@@ -111,9 +82,14 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
} }
} }
@Override
public void onPositionError(Throwable error) {
}
@Override @Override
public void onNetworkUpdate(boolean isOnline) { public void onNetworkUpdate(boolean isOnline) {
StatusActivity.addMessage(context.getString(R.string.status_connectivity_change)); int message = isOnline ? R.string.status_network_online : R.string.status_network_offline;
StatusActivity.addMessage(context.getString(message));
if (!this.isOnline && isOnline) { if (!this.isOnline && isOnline) {
read(); read();
} }
@@ -141,7 +117,6 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
private void write(Position position) { private void write(Position position) {
log("write", position); log("write", position);
lock();
databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() { databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() {
@Override @Override
public void onComplete(boolean success, Void result) { public void onComplete(boolean success, Void result) {
@@ -151,20 +126,18 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
isWaiting = false; isWaiting = false;
} }
} }
unlock();
} }
}); });
} }
private void read() { private void read() {
log("read", null); log("read", null);
lock();
databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() { databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() {
@Override @Override
public void onComplete(boolean success, Position result) { public void onComplete(boolean success, Position result) {
if (success) { if (success) {
if (result != null) { if (result != null) {
if (result.getDeviceId().equals(preferences.getString(MainActivity.KEY_DEVICE, null))) { if (result.getDeviceId().equals(preferences.getString(MainFragment.KEY_DEVICE, null))) {
send(result); send(result);
} else { } else {
delete(result); delete(result);
@@ -175,14 +148,12 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
} else { } else {
retry(); retry();
} }
unlock();
} }
}); });
} }
private void delete(Position position) { private void delete(Position position) {
log("delete", position); log("delete", position);
lock();
databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() { databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() {
@Override @Override
public void onComplete(boolean success, Void result) { public void onComplete(boolean success, Void result) {
@@ -191,15 +162,13 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
} else { } else {
retry(); retry();
} }
unlock();
} }
}); });
} }
private void send(final Position position) { private void send(final Position position) {
log("send", position); log("send", position);
lock(); String request = ProtocolFormatter.formatRequest(url, position);
String request = ProtocolFormatter.formatRequest(address, port, secure, position);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() { RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override @Override
public void onComplete(boolean success) { public void onComplete(boolean success) {
@@ -209,7 +178,6 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
StatusActivity.addMessage(context.getString(R.string.status_send_fail)); StatusActivity.addMessage(context.getString(R.string.status_send_fail));
retry(); retry();
} }
unlock();
} }
}); });
} }
+41 -39
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2015 Anton Tananaev (anton.tananaev@gmail.com) * Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -15,51 +15,49 @@
*/ */
package org.traccar.client; package org.traccar.client;
import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import java.lang.reflect.InvocationTargetException; import android.os.PowerManager;
import java.lang.reflect.Method; import android.util.Log;
public class TrackingService extends Service { public class TrackingService extends Service {
private static final String TAG = TrackingService.class.getSimpleName(); private static final String TAG = TrackingService.class.getSimpleName();
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private PowerManager.WakeLock wakeLock;
private TrackingController trackingController; private TrackingController trackingController;
@SuppressWarnings("deprecation")
private static Notification createNotification(Context context) { private static Notification createNotification(Context context) {
Intent notificationIntent = new Intent(context, MainActivity.class); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); .setSmallIcon(R.drawable.ic_stat_notify)
.setPriority(NotificationCompat.PRIORITY_LOW)
Notification notification = new Notification(android.R.drawable.stat_notify_sync_noanim, null, 0); .setCategory(NotificationCompat.CATEGORY_SERVICE);
try { Intent intent;
Method method = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class); if (!BuildConfig.HIDDEN_APP) {
try { intent = new Intent(context, MainActivity.class);
method.invoke(notification, context, context.getString(R.string.app_name), context.getString(R.string.settings_status_on_summary), pendingIntent); builder
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { .setContentTitle(context.getString(R.string.settings_status_on_summary))
Log.w(TAG, e); .setTicker(context.getString(R.string.settings_status_on_summary))
.setColor(ContextCompat.getColor(context, R.color.primary_dark));
} else {
intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
} }
} catch (SecurityException | NoSuchMethodException e) { builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
Log.w(TAG, e); return builder.build();
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
notification.priority = Notification.PRIORITY_MIN;
}
return notification;
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
public static class HideNotificationService extends Service { public static class HideNotificationService extends Service {
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
@@ -79,17 +77,26 @@ public class TrackingService extends Service {
} }
} }
@SuppressLint("WakelockTimeout")
@Override @Override
public void onCreate() { public void onCreate() {
Log.i(TAG, "service create"); Log.i(TAG, "service create");
StatusActivity.addMessage(getString(R.string.status_service_create)); StatusActivity.addMessage(getString(R.string.status_service_create));
startForeground(NOTIFICATION_ID, createNotification(this));
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
wakeLock.acquire();
trackingController = new TrackingController(this); trackingController = new TrackingController(this);
trackingController.start(); trackingController.start();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
startForeground(NOTIFICATION_ID, createNotification(this)); ContextCompat.startForegroundService(this, new Intent(this, HideNotificationService.class));
startService(new Intent(this, HideNotificationService.class));
} }
} }
@@ -98,18 +105,12 @@ public class TrackingService extends Service {
return null; return null;
} }
@SuppressWarnings("deprecation")
@Override
public void onStart(Intent intent, int startId) {
if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
}
@TargetApi(Build.VERSION_CODES.ECLAIR) @TargetApi(Build.VERSION_CODES.ECLAIR)
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId); if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
return START_STICKY; return START_STICKY;
} }
@@ -118,10 +119,11 @@ public class TrackingService extends Service {
Log.i(TAG, "service destroy"); Log.i(TAG, "service destroy");
StatusActivity.addMessage(getString(R.string.status_service_destroy)); StatusActivity.addMessage(getString(R.string.status_service_destroy));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
stopForeground(true); stopForeground(true);
}
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
if (trackingController != null) { if (trackingController != null) {
trackingController.stop(); trackingController.stop();
} }
+62
Просмотреть файл
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import androidx.core.content.ContextCompat;
import android.util.SparseArray;
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks = new SparseArray<>();
private static int mNextId = 1;
public static void startWakefulForegroundService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ContextCompat.startForegroundService(context, intent);
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WakefulBroadcastReceiver.class.getSimpleName());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
}
}
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
return true;
}
}
}
Двоичный файл не отображается.

До

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

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

До

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

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

До

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

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

До

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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

После

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

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