Сравнить коммиты
| Автор | SHA1 | Дата | |
|---|---|---|---|
|
|
8b985526dd | ||
|
|
5e8ea134e0 | ||
|
|
ec85edd56f | ||
|
|
c5ab10919e | ||
|
|
3e8ba9a603 | ||
|
|
826d638ea3 | ||
|
|
1b687bd075 | ||
|
|
42d5584d8d | ||
|
|
ff88682e40 | ||
|
|
307e692459 | ||
|
|
b7ec644ecf | ||
|
|
842baa2aec | ||
|
|
26bc92cf8e | ||
|
|
6dadf5e7c3 | ||
|
|
39e9c171c4 | ||
|
|
bcb5985cef | ||
|
|
eb868056bf | ||
|
|
94e094e648 | ||
|
|
25c0434e4c | ||
|
|
3afcfa17d0 | ||
|
|
02263d5fd9 | ||
|
|
a884b2f3d7 | ||
|
|
6910790fa7 | ||
|
|
6cab1a0b36 | ||
|
|
edd4f670fe | ||
|
|
298d7becb5 | ||
|
|
9a2e3f9126 | ||
|
|
d3f7ee0c8b | ||
|
|
8ae486dbc8 | ||
|
|
02295f5fcf | ||
|
|
958eb77c51 | ||
|
|
0a3a8381a5 | ||
|
|
6009dba342 | ||
|
|
59085f668c | ||
|
|
1cb5326e07 | ||
|
|
28befc6180 | ||
|
|
e83ef80fa3 | ||
|
|
b26a1bd133 | ||
|
|
0bbee716a6 | ||
|
|
52e3b839ae | ||
|
|
cfe04eb0a6 | ||
|
|
c243912c55 | ||
|
|
914a157afc | ||
|
|
454ce6be21 | ||
|
|
e8a1e015d4 | ||
|
|
ceb15c1378 | ||
|
|
d25c745013 | ||
|
|
c24df5b246 | ||
|
|
283d0467b4 | ||
|
|
83bf1daa84 | ||
|
|
96d61c4c66 | ||
|
|
3978a3d8d6 | ||
|
|
78018c3038 | ||
|
|
19b7c4da21 | ||
|
|
a3d43294ce | ||
|
|
330dc27f0f | ||
|
|
f052be83c3 | ||
|
|
c77d4b477b | ||
|
|
eb7de9eb98 | ||
|
|
3a786b14b7 | ||
|
|
6bad9786ec | ||
|
|
259aaf2000 | ||
|
|
aa00a33f5b | ||
|
|
ab86076b9b | ||
|
|
fed2a73999 | ||
|
|
42c210763c | ||
|
|
faa05dea5a | ||
|
|
453edfa97e | ||
|
|
4c6c79b6a2 | ||
|
|
93456bf7fb | ||
|
|
a2ff7fcb38 | ||
|
|
7015f75edb | ||
|
|
636ceaf027 | ||
|
|
350b800184 | ||
|
|
7ecba2cffd | ||
|
|
5d8f0dfb83 | ||
|
|
f42ecf9f09 | ||
|
|
556856ab5c | ||
|
|
f9f37ead08 | ||
|
|
b3ee99140e | ||
|
|
85f1e09308 | ||
|
|
20fd20f425 | ||
|
|
1ae7b27683 | ||
|
|
f5b12b9e74 | ||
|
|
d2f653034d | ||
|
|
ab5aec9721 | ||
|
|
1b0559c98a | ||
|
|
2e0e7ea453 | ||
|
|
ac6d677ff4 | ||
|
|
cbfe90757d | ||
|
|
a9f4390ae4 | ||
|
|
8a938701c8 | ||
|
|
2319834c5b | ||
|
|
1b93619e64 | ||
|
|
2a36e1aa6d | ||
|
|
64687b26c2 | ||
|
|
dcb60ab75e | ||
|
|
0b4c4cccc6 | ||
|
|
ef5bfff70a | ||
|
|
855efc7acb | ||
|
|
0ae47e65d4 | ||
|
|
e8d5e5c99b | ||
|
|
c3980780d8 | ||
|
|
3c11216334 | ||
|
|
bcde8388d6 | ||
|
|
7f18915514 | ||
|
|
42ad669fca | ||
|
|
3e2b508ea7 | ||
|
|
fd4d616812 | ||
|
|
4f6571e572 | ||
|
|
d3025b437b | ||
|
|
1e861ef0ac | ||
|
|
43afc57114 | ||
|
|
d47783808f | ||
|
|
3ef2272227 | ||
|
|
3625ee42db | ||
|
|
385826c3c1 | ||
|
|
1426926c8e | ||
|
|
4f680d33c9 | ||
|
|
2dad912da5 | ||
|
|
290a7d80eb | ||
|
|
44228feb2f | ||
|
|
d887bc4119 | ||
|
|
c892a20104 | ||
|
|
2719b57f7c | ||
|
|
cc6c8a23f6 | ||
|
|
1e82cbbbf6 | ||
|
|
4b8fde36d7 | ||
|
|
b8cd56439a | ||
|
|
edb1f696df | ||
|
|
56d2f2ef53 | ||
|
|
59e6a5ae65 | ||
|
|
319fab647b | ||
|
|
42429f3496 | ||
|
|
841d3e581c | ||
|
|
dac43ccbc1 | ||
|
|
3c9ae512bd | ||
|
|
d3e600d81d | ||
|
|
e0296a9c65 | ||
|
|
e51f590e19 | ||
|
|
6e3812320c | ||
|
|
c45c8ea797 | ||
|
|
ed0aa53aa3 | ||
|
|
f88891be2b | ||
|
|
3f83684d02 | ||
|
|
a9d772bfda | ||
|
|
2bc3c78a91 | ||
|
|
8fab55552d | ||
|
|
979963f6a2 | ||
|
|
70d6dfec4e | ||
|
|
43e108de8c | ||
|
|
fedce0e26b | ||
|
|
e24d725feb | ||
|
|
3198bee2f2 | ||
|
|
92e0fb3ad5 | ||
|
|
f7b0df1c41 | ||
|
|
8a26c7e5ba | ||
|
|
27c98b910b | ||
|
|
360958d250 | ||
|
|
4d8d15d615 | ||
|
|
7c49bb78f3 | ||
|
|
e87c3c7783 | ||
|
|
5428530dbd | ||
|
|
3d5682d1b1 | ||
|
|
8f91c0e0ea | ||
|
|
965feb9b04 | ||
|
|
8f8a5e3f55 | ||
|
|
77154a81af | ||
|
|
8d94b8d011 | ||
|
|
ab37fd478a | ||
|
|
79662bad35 | ||
|
|
90373ff8fb | ||
|
|
f2e7ae7c21 | ||
|
|
e0c5340d8b | ||
|
|
90fffa9873 | ||
|
|
db51adaf86 | ||
|
|
7eb6876491 | ||
|
|
5db3407e88 | ||
|
|
ff4fe90c6d | ||
|
|
62a2599765 | ||
|
|
149eee530a | ||
|
|
6e398d12fe | ||
|
|
10e573017b | ||
|
|
f5f8d41e41 | ||
|
|
839b180890 | ||
|
|
e2a26297c9 | ||
|
|
4d33d8ff9e | ||
|
|
7e923954b8 | ||
|
|
f0b0650531 | ||
|
|
3eb655040d | ||
|
|
96a75f02a7 | ||
|
|
d632c7cdfa | ||
|
|
9bae6e554f | ||
|
|
faf23e7926 | ||
|
|
54331ffe93 | ||
|
|
c19c17f091 | ||
|
|
2bd5bf540f | ||
|
|
3d89cfe649 | ||
|
|
e2e51b3048 | ||
|
|
43d9775aa0 |
@@ -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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
custom: https://www.traccar.org/donate/
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -4,3 +4,4 @@ local.properties
|
|||||||
*.iml
|
*.iml
|
||||||
build/
|
build/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
google-services.json
|
||||||
|
|||||||
@@ -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/store/apps/details?id=org.traccar.client)
|
[](https://play.google.com/store/apps/details?id=org.traccar.client) [](https://f-droid.org/repository/browse/?fdid=org.traccar.client)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,84 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 31
|
||||||
buildToolsVersion '25.0.2'
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 3
|
applicationId 'org.traccar.client'
|
||||||
targetSdkVersion 25
|
buildConfigField 'boolean', 'HIDDEN_APP', 'false'
|
||||||
versionCode 34
|
minSdkVersion 16
|
||||||
versionName '4.1'
|
targetSdkVersion 31
|
||||||
|
versionCode 77
|
||||||
|
versionName '6.16'
|
||||||
|
multiDexEnabled true
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/DEPENDENCIES'
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions 'default'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
regular {
|
regular {
|
||||||
applicationId "org.traccar.client"
|
isDefault = true
|
||||||
buildConfigField "boolean", "HIDDEN_APP", "false"
|
|
||||||
}
|
}
|
||||||
|
google
|
||||||
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.5.0-alpha05'
|
||||||
testCompile 'org.robolectric:robolectric:3.0'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
|
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||||
|
implementation 'androidx.test:core-ktx:1.4.0'
|
||||||
|
implementation 'androidx.core:core-ktx:1.7.0'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'com.github.judemanutd:autostarter:1.1.0'
|
||||||
|
implementation('dev.doubledot.doki:library:0.0.1@aar') {
|
||||||
|
transitive = true
|
||||||
|
}
|
||||||
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||||
|
googleImplementation platform('com.google.firebase:firebase-bom:28.2.1')
|
||||||
|
googleImplementation 'com.google.firebase:firebase-core'
|
||||||
|
googleImplementation 'com.google.firebase:firebase-analytics'
|
||||||
|
googleImplementation 'com.google.firebase:firebase-crashlytics'
|
||||||
|
googleImplementation 'com.google.android.gms:play-services-location:18.0.0'
|
||||||
|
googleImplementation 'com.google.android.play:core:1.10.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
apply plugin: 'com.google.firebase.crashlytics'
|
||||||
|
|
||||||
|
task copyJson(type: Copy) {
|
||||||
|
from '../../environment/google-services'
|
||||||
|
into '.'
|
||||||
|
include 'traccar-client-android.json'
|
||||||
|
rename('traccar-client-android.json', 'google-services.json')
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
preBuild.dependsOn copyJson
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name="org.traccar.client.GoogleMainApplication"
|
||||||
|
tools:replace="android:name"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 - 2021 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.app.Activity
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.play.core.review.ReviewInfo
|
||||||
|
import com.google.android.play.core.review.ReviewManagerFactory
|
||||||
|
import com.google.android.play.core.tasks.Task
|
||||||
|
import com.google.firebase.analytics.FirebaseAnalytics
|
||||||
|
|
||||||
|
class GoogleMainApplication : MainApplication() {
|
||||||
|
|
||||||
|
private var firebaseAnalytics: FirebaseAnalytics? = null
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
firebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
||||||
|
val filter = IntentFilter()
|
||||||
|
filter.addAction(TrackingService.ACTION_STARTED)
|
||||||
|
filter.addAction(TrackingService.ACTION_STOPPED)
|
||||||
|
registerReceiver(ServiceReceiver(), filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||||
|
override fun handleRatingFlow(activity: Activity) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
val ratingShown = preferences.getBoolean(KEY_RATING_SHOWN, false)
|
||||||
|
val totalDuration = preferences.getLong(ServiceReceiver.KEY_DURATION, 0)
|
||||||
|
if (!ratingShown && totalDuration > RATING_THRESHOLD) {
|
||||||
|
val reviewManager = ReviewManagerFactory.create(activity)
|
||||||
|
reviewManager.requestReviewFlow().addOnCompleteListener { infoTask: Task<ReviewInfo?> ->
|
||||||
|
if (infoTask.isSuccessful) {
|
||||||
|
val flow = reviewManager.launchReviewFlow(activity, infoTask.result)
|
||||||
|
flow.addOnCompleteListener { preferences.edit().putBoolean(KEY_RATING_SHOWN, true).apply() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_RATING_SHOWN = "ratingShown"
|
||||||
|
private const val RATING_THRESHOLD = -24 * 60 * 60 * 1000L
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 - 2021 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.os.Looper
|
||||||
|
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
|
||||||
|
|
||||||
|
class GooglePositionProvider(context: Context, listener: PositionListener) : PositionProvider(context, listener) {
|
||||||
|
|
||||||
|
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION", "MissingPermission")
|
||||||
|
override fun startUpdates() {
|
||||||
|
val locationRequest = LocationRequest()
|
||||||
|
locationRequest.priority = getPriority(preferences.getString(MainFragment.KEY_ACCURACY,"medium"))
|
||||||
|
locationRequest.interval = if (distance > 0 || angle > 0) MINIMUM_INTERVAL else interval
|
||||||
|
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopUpdates() {
|
||||||
|
fusedLocationClient.removeLocationUpdates(locationCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override fun requestSingleLocation() {
|
||||||
|
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
|
||||||
|
if (location != null) {
|
||||||
|
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val locationCallback: LocationCallback = object : LocationCallback() {
|
||||||
|
override fun onLocationResult(locationResult: LocationResult) {
|
||||||
|
for (location in locationResult.locations) {
|
||||||
|
processLocation(location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPriority(accuracy: String?): Int {
|
||||||
|
return when (accuracy) {
|
||||||
|
"high" -> LocationRequest.PRIORITY_HIGH_ACCURACY
|
||||||
|
"low" -> LocationRequest.PRIORITY_LOW_POWER
|
||||||
|
else -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 - 2021 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
|
||||||
|
import org.traccar.client.PositionProvider.PositionListener
|
||||||
|
|
||||||
|
object PositionProviderFactory {
|
||||||
|
|
||||||
|
fun create(context: Context, listener: PositionListener): PositionProvider {
|
||||||
|
return GooglePositionProvider(context, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 - 2021 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.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
|
||||||
|
class ServiceReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (TrackingService.ACTION_STARTED == intent.action) {
|
||||||
|
startTime = System.currentTimeMillis()
|
||||||
|
} else if (startTime > 0) {
|
||||||
|
updateTime(context, System.currentTimeMillis() - startTime)
|
||||||
|
startTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTime(context: Context, duration: Long) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val totalDuration = preferences.getLong(KEY_DURATION, 0)
|
||||||
|
preferences.edit().putLong(KEY_DURATION, totalDuration + duration).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KEY_DURATION = "serviceTime"
|
||||||
|
private var startTime: Long = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,22 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="@string/hidden_app_name"
|
android:label="@string/hidden_app_name"
|
||||||
tools:replace="label">
|
tools:replace="android:label">
|
||||||
|
|
||||||
<receiver android:name="org.traccar.client.DialLaunchReceiver" >
|
<activity
|
||||||
|
android:name="org.traccar.client.MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<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"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
|
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 - 2021 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
|
||||||
|
import org.traccar.client.PositionProvider.PositionListener
|
||||||
|
|
||||||
|
object PositionProviderFactory {
|
||||||
|
|
||||||
|
fun create(context: Context, listener: PositionListener): PositionProvider {
|
||||||
|
return AndroidPositionProvider(context, listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
До Ширина: | Высота: | Размер: 5.5 KiB После Ширина: | Высота: | Размер: 3.3 KiB |
|
После Ширина: | Высота: | Размер: 2.0 KiB |
|
До Ширина: | Высота: | Размер: 3.3 KiB После Ширина: | Высота: | Размер: 2.1 KiB |
|
После Ширина: | Высота: | Размер: 1.2 KiB |
|
До Ширина: | Высота: | Размер: 8.0 KiB После Ширина: | Высота: | Размер: 4.7 KiB |
|
После Ширина: | Высота: | Размер: 2.5 KiB |
|
До Ширина: | Высота: | Размер: 14 KiB После Ширина: | Высота: | Размер: 7.6 KiB |
|
После Ширина: | Высота: | Размер: 4.7 KiB |
|
До Ширина: | Высота: | Размер: 20 KiB После Ширина: | Высота: | Размер: 11 KiB |
|
После Ширина: | Высота: | Размер: 4.4 KiB |
@@ -1,52 +1,88 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.traccar.client">
|
package="org.traccar.client">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
|
||||||
|
<uses-permission android:name="oppo.permission.OPPO_COMPONENT_SAFE" />
|
||||||
|
<uses-permission android:name="com.huawei.permission.external_app_settings.USE_COMPONENT" />
|
||||||
|
|
||||||
|
<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"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning">
|
||||||
|
|
||||||
<activity android:name=".MainActivity" android:launchMode="singleTask" />
|
<meta-data
|
||||||
|
android:name="google_analytics_adid_collection_enabled"
|
||||||
|
android:value="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask" />
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".Launcher"
|
android:name=".Launcher"
|
||||||
|
android:exported="true"
|
||||||
android:targetActivity=".MainActivity">
|
android:targetActivity=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcuts" />
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity android:name=".StatusActivity"/>
|
<activity
|
||||||
|
android:name=".StatusActivity"
|
||||||
<activity android:name=".AboutActivity"/>
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ShortcutActivity">
|
android:name=".ShortcutActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/menu_shortcuts">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<action android:name="android.intent.action.CREATE_SHORTCUT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service android:name=".TrackingService" />
|
<service
|
||||||
|
android:name=".TrackingService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="location" />
|
||||||
|
|
||||||
<service android:name=".TrackingService$HideNotificationService" />
|
<service
|
||||||
|
android:name=".TrackingService$HideNotificationService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver android:name=".AutostartReceiver">
|
<receiver
|
||||||
|
android:name=".AutostartReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
|
После Ширина: | Высота: | Размер: 26 KiB |
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 Anton Tananaev (anton.tananaev@gmail.com)
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.traccar.client;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
public class AboutActivity extends Activity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.about);
|
|
||||||
|
|
||||||
TextView title = (TextView) findViewById(R.id.title);
|
|
||||||
try {
|
|
||||||
title.setText(title.getText() + " " + getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName);
|
|
||||||
} catch (NameNotFoundException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 - 2021 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
|
||||||
|
|
||||||
|
class AndroidPositionProvider(context: Context, listener: PositionListener) : PositionProvider(context, listener), LocationListener {
|
||||||
|
|
||||||
|
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
private val provider = getProvider(preferences.getString(MainFragment.KEY_ACCURACY, "medium"))
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override fun startUpdates() {
|
||||||
|
try {
|
||||||
|
locationManager.requestLocationUpdates(
|
||||||
|
provider, if (distance > 0 || angle > 0) MINIMUM_INTERVAL else interval, 0f, this)
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
listener.onPositionError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopUpdates() {
|
||||||
|
locationManager.removeUpdates(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION", "MissingPermission")
|
||||||
|
override fun requestSingleLocation() {
|
||||||
|
try {
|
||||||
|
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
|
||||||
|
if (location != null) {
|
||||||
|
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
|
||||||
|
} else {
|
||||||
|
locationManager.requestSingleUpdate(provider, object : LocationListener {
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
|
||||||
|
override fun onProviderEnabled(provider: String) {}
|
||||||
|
override fun onProviderDisabled(provider: String) {}
|
||||||
|
}, Looper.myLooper())
|
||||||
|
}
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
listener.onPositionError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
processLocation(location)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
|
||||||
|
override fun onProviderEnabled(provider: String) {}
|
||||||
|
override fun onProviderDisabled(provider: String) {}
|
||||||
|
|
||||||
|
private fun getProvider(accuracy: String?): String {
|
||||||
|
return when (accuracy) {
|
||||||
|
"high" -> LocationManager.GPS_PROVIDER
|
||||||
|
"low" -> LocationManager.PASSIVE_PROVIDER
|
||||||
|
else -> LocationManager.NETWORK_PROVIDER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 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.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.content.WakefulBroadcastReceiver;
|
|
||||||
|
|
||||||
public class AutostartReceiver extends WakefulBroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
if (sharedPreferences.getBoolean(MainActivity.KEY_STATUS, false)) {
|
|
||||||
startWakefulService(context, new Intent(context, TrackingService.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 - 2021 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
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
|
||||||
|
class AutostartReceiver : WakefulBroadcastReceiver() {
|
||||||
|
|
||||||
|
@Suppress("UnsafeProtectedBroadcastReceiver")
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
|
||||||
|
startWakefulForegroundService(context, Intent(context, TrackingService::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.ActivityNotFoundException
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.judemanutd.autostarter.AutoStartPermissionHelper
|
||||||
|
|
||||||
|
class BatteryOptimizationHelper {
|
||||||
|
|
||||||
|
private fun showDialog(context: Context, onSuccess: () -> Unit) {
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
builder.setMessage(context.getString(R.string.request_exception))
|
||||||
|
builder.setPositiveButton(android.R.string.ok) { _, _ -> onSuccess() }
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestVendorException(context: Context) {
|
||||||
|
val vendorIntentList = listOf(
|
||||||
|
Intent().setComponent(ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.appcontrol.activity.StartupAppControlActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")),
|
||||||
|
Intent().setComponent(ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.htc.pitroad", "com.htc.pitroad.landingpage.activity.LandingPageActivity")),
|
||||||
|
Intent().setComponent(ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.MainActivity")),
|
||||||
|
)
|
||||||
|
for (vendorIntent in vendorIntentList) {
|
||||||
|
if (context.packageManager.resolveActivity(vendorIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
|
||||||
|
try {
|
||||||
|
context.startActivity(vendorIntent)
|
||||||
|
return
|
||||||
|
} catch (e: Exception) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestException(context: Context): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
if (!sharedPreferences.getBoolean(KEY_EXCEPTION_REQUESTED, false)) {
|
||||||
|
sharedPreferences.edit().putBoolean(KEY_EXCEPTION_REQUESTED, true).apply()
|
||||||
|
val powerManager = context.getSystemService(PowerManager::class.java)
|
||||||
|
if (!powerManager.isIgnoringBatteryOptimizations(context.packageName)) {
|
||||||
|
showDialog(context) {
|
||||||
|
try {
|
||||||
|
context.startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
requestVendorException(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if (!sharedPreferences.getBoolean(KEY_AUTOSTART_REQUESTED, false)) {
|
||||||
|
sharedPreferences.edit().putBoolean(KEY_AUTOSTART_REQUESTED, true).apply()
|
||||||
|
try {
|
||||||
|
if (AutoStartPermissionHelper.getInstance().getAutoStartPermission(context)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_EXCEPTION_REQUESTED = "exceptionRequested"
|
||||||
|
private const val KEY_AUTOSTART_REQUESTED = "autostartRequested"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,169 +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.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.SQLException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
|
||||||
|
|
||||||
public static final int DATABASE_VERSION = 1;
|
|
||||||
public static final String DATABASE_NAME = "traccar.db";
|
|
||||||
|
|
||||||
public interface DatabaseHandler<T> {
|
|
||||||
void onComplete(boolean success, T result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static abstract class DatabaseAsyncTask<T> extends AsyncTask<Void, Void, T> {
|
|
||||||
|
|
||||||
private DatabaseHandler<T> handler;
|
|
||||||
private RuntimeException error;
|
|
||||||
|
|
||||||
public DatabaseAsyncTask(DatabaseHandler<T> handler) {
|
|
||||||
this.handler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected T doInBackground(Void... params) {
|
|
||||||
try {
|
|
||||||
return executeMethod();
|
|
||||||
} catch (RuntimeException error) {
|
|
||||||
this.error = error;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract T executeMethod();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(T result) {
|
|
||||||
handler.onComplete(error == null, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SQLiteDatabase db;
|
|
||||||
|
|
||||||
public DatabaseHelper(Context context) {
|
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
|
||||||
db = getWritableDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(SQLiteDatabase db) {
|
|
||||||
db.execSQL("CREATE TABLE position (" +
|
|
||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
|
||||||
"deviceId TEXT," +
|
|
||||||
"time INTEGER," +
|
|
||||||
"latitude REAL," +
|
|
||||||
"longitude REAL," +
|
|
||||||
"altitude REAL," +
|
|
||||||
"speed REAL," +
|
|
||||||
"course REAL," +
|
|
||||||
"battery REAL)");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS position;");
|
|
||||||
onCreate(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertPosition(Position position) {
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put("deviceId", position.getDeviceId());
|
|
||||||
values.put("time", position.getTime().getTime());
|
|
||||||
values.put("latitude", position.getLatitude());
|
|
||||||
values.put("longitude", position.getLongitude());
|
|
||||||
values.put("altitude", position.getAltitude());
|
|
||||||
values.put("speed", position.getSpeed());
|
|
||||||
values.put("course", position.getCourse());
|
|
||||||
values.put("battery", position.getBattery());
|
|
||||||
|
|
||||||
db.insertOrThrow("position", null, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertPositionAsync(final Position position, DatabaseHandler<Void> handler) {
|
|
||||||
new DatabaseAsyncTask<Void>(handler) {
|
|
||||||
@Override
|
|
||||||
protected Void executeMethod() {
|
|
||||||
insertPosition(position);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Position selectPosition() {
|
|
||||||
Position position = new Position();
|
|
||||||
|
|
||||||
Cursor cursor = db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null);
|
|
||||||
try {
|
|
||||||
if (cursor.getCount() > 0) {
|
|
||||||
|
|
||||||
cursor.moveToFirst();
|
|
||||||
|
|
||||||
position.setId(cursor.getLong(cursor.getColumnIndex("id")));
|
|
||||||
position.setDeviceId(cursor.getString(cursor.getColumnIndex("deviceId")));
|
|
||||||
position.setTime(new Date(cursor.getLong(cursor.getColumnIndex("time"))));
|
|
||||||
position.setLatitude(cursor.getDouble(cursor.getColumnIndex("latitude")));
|
|
||||||
position.setLongitude(cursor.getDouble(cursor.getColumnIndex("longitude")));
|
|
||||||
position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude")));
|
|
||||||
position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed")));
|
|
||||||
position.setCourse(cursor.getDouble(cursor.getColumnIndex("course")));
|
|
||||||
position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectPositionAsync(DatabaseHandler<Position> handler) {
|
|
||||||
new DatabaseAsyncTask<Position>(handler) {
|
|
||||||
@Override
|
|
||||||
protected Position executeMethod() {
|
|
||||||
return selectPosition();
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePosition(long id) {
|
|
||||||
if (db.delete("position", "id = ?", new String[] { String.valueOf(id) }) != 1) {
|
|
||||||
throw new SQLException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deletePositionAsync(final long id, DatabaseHandler<Void> handler) {
|
|
||||||
new DatabaseAsyncTask<Void>(handler) {
|
|
||||||
@Override
|
|
||||||
protected Void executeMethod() {
|
|
||||||
deletePosition(id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 - 2021 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.
|
||||||
|
*/
|
||||||
|
@file:Suppress("DEPRECATION", "StaticFieldLeak")
|
||||||
|
package org.traccar.client
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.SQLException
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import java.sql.Date
|
||||||
|
|
||||||
|
class DatabaseHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||||
|
|
||||||
|
interface DatabaseHandler<T> {
|
||||||
|
fun onComplete(success: Boolean, result: T)
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class DatabaseAsyncTask<T>(val handler: DatabaseHandler<T?>) : AsyncTask<Unit, Unit, T?>() {
|
||||||
|
|
||||||
|
private var error: RuntimeException? = null
|
||||||
|
|
||||||
|
override fun doInBackground(vararg params: Unit): T? {
|
||||||
|
return try {
|
||||||
|
executeMethod()
|
||||||
|
} catch (error: RuntimeException) {
|
||||||
|
this.error = error
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun executeMethod(): T
|
||||||
|
|
||||||
|
override fun onPostExecute(result: T?) {
|
||||||
|
handler.onComplete(error == null, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val db: SQLiteDatabase = writableDatabase
|
||||||
|
|
||||||
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
|
db.execSQL(
|
||||||
|
"CREATE TABLE position (" +
|
||||||
|
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||||
|
"deviceId TEXT," +
|
||||||
|
"time INTEGER," +
|
||||||
|
"latitude REAL," +
|
||||||
|
"longitude REAL," +
|
||||||
|
"altitude REAL," +
|
||||||
|
"speed REAL," +
|
||||||
|
"course REAL," +
|
||||||
|
"accuracy REAL," +
|
||||||
|
"battery REAL," +
|
||||||
|
"mock INTEGER)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS position;")
|
||||||
|
onCreate(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS position;")
|
||||||
|
onCreate(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertPosition(position: Position) {
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put("deviceId", position.deviceId)
|
||||||
|
values.put("time", position.time.time)
|
||||||
|
values.put("latitude", position.latitude)
|
||||||
|
values.put("longitude", position.longitude)
|
||||||
|
values.put("altitude", position.altitude)
|
||||||
|
values.put("speed", position.speed)
|
||||||
|
values.put("course", position.course)
|
||||||
|
values.put("accuracy", position.accuracy)
|
||||||
|
values.put("battery", position.battery)
|
||||||
|
values.put("mock", if (position.mock) 1 else 0)
|
||||||
|
db.insertOrThrow("position", null, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertPositionAsync(position: Position, handler: DatabaseHandler<Unit?>) {
|
||||||
|
object : DatabaseAsyncTask<Unit>(handler) {
|
||||||
|
override fun executeMethod() {
|
||||||
|
insertPosition(position)
|
||||||
|
}
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectPosition(): Position? {
|
||||||
|
db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null).use { cursor ->
|
||||||
|
if (cursor.count > 0) {
|
||||||
|
cursor.moveToFirst()
|
||||||
|
return Position(
|
||||||
|
id = cursor.getLong(cursor.getColumnIndex("id")),
|
||||||
|
deviceId = cursor.getString(cursor.getColumnIndex("deviceId")),
|
||||||
|
time = Date(cursor.getLong(cursor.getColumnIndex("time"))),
|
||||||
|
latitude = cursor.getDouble(cursor.getColumnIndex("latitude")),
|
||||||
|
longitude = cursor.getDouble(cursor.getColumnIndex("longitude")),
|
||||||
|
altitude = cursor.getDouble(cursor.getColumnIndex("altitude")),
|
||||||
|
speed = cursor.getDouble(cursor.getColumnIndex("speed")),
|
||||||
|
course = cursor.getDouble(cursor.getColumnIndex("course")),
|
||||||
|
accuracy = cursor.getDouble(cursor.getColumnIndex("accuracy")),
|
||||||
|
battery = cursor.getDouble(cursor.getColumnIndex("battery")),
|
||||||
|
mock = cursor.getInt(cursor.getColumnIndex("mock")) > 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectPositionAsync(handler: DatabaseHandler<Position?>) {
|
||||||
|
object : DatabaseAsyncTask<Position?>(handler) {
|
||||||
|
override fun executeMethod(): Position? {
|
||||||
|
return selectPosition()
|
||||||
|
}
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deletePosition(id: Long) {
|
||||||
|
if (db.delete("position", "id = ?", arrayOf(id.toString())) != 1) {
|
||||||
|
throw SQLException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deletePositionAsync(id: Long, handler: DatabaseHandler<Unit?>) {
|
||||||
|
object : DatabaseAsyncTask<Unit>(handler) {
|
||||||
|
override fun executeMethod() {
|
||||||
|
deletePosition(id)
|
||||||
|
}
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DATABASE_VERSION = 3
|
||||||
|
const val DATABASE_NAME = "traccar.db"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,37 +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.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
public class DialLaunchReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
private static final String LAUNCHER_NUMBER = "8722227"; // TRACCAR
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
|
|
||||||
if (phoneNumber.equals(LAUNCHER_NUMBER)) {
|
|
||||||
setResultData(null);
|
|
||||||
Intent appIntent = new Intent(context, MainActivity.class);
|
|
||||||
appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
context.startActivity(appIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 - 2021 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.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
class DialLaunchReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
|
||||||
|
if (phoneNumber == LAUNCHER_NUMBER) {
|
||||||
|
resultData = null
|
||||||
|
val appIntent = Intent(context, MainActivity::class.java)
|
||||||
|
appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
context.startActivity(appIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val LAUNCHER_NUMBER = "8722227" // TRACCAR
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.traccar.client;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.AlarmManager;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.CheckBoxPreference;
|
|
||||||
import android.preference.EditTextPreference;
|
|
||||||
import android.preference.Preference;
|
|
||||||
import android.preference.PreferenceActivity;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.preference.TwoStatePreference;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
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
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (BuildConfig.HIDDEN_APP) {
|
|
||||||
removeLauncherIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
addPreferencesFromResource(R.xml.preferences);
|
|
||||||
initPreferences();
|
|
||||||
|
|
||||||
findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
return newValue != null && !newValue.equals("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
findPreference(KEY_ADDRESS).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
|
||||||
return newValue != null && Patterns.DOMAIN_NAME.matcher((String) newValue).matches();
|
|
||||||
} else {
|
|
||||||
return newValue != null && !((String) newValue).isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
findPreference(KEY_PORT).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
|
||||||
if (newValue != null) {
|
|
||||||
try {
|
|
||||||
int value = Integer.parseInt((String) newValue);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016 Anton Tananaev (anton.tananaev@gmail.com)
|
* Copyright 2017 - 2021 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.
|
||||||
@@ -13,16 +13,16 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.traccar.client;
|
package org.traccar.client
|
||||||
|
|
||||||
import android.app.Application;
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.os.Bundle
|
||||||
|
|
||||||
public class MainApplication extends Application {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@Override
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
public void onCreate() {
|
super.onCreate(savedInstanceState)
|
||||||
super.onCreate();
|
setContentView(R.layout.main)
|
||||||
System.setProperty("http.keepAliveDuration", String.valueOf(30 * 60 * 1000));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2021 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 androidx.multidex.MultiDexApplication
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.Notification
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
|
import android.app.Activity
|
||||||
|
|
||||||
|
open class MainApplication : MultiDexApplication() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
System.setProperty("http.keepAliveDuration", (30 * 60 * 1000).toString())
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
registerChannel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
private fun registerChannel() {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
channel.lightColor = Color.GREEN
|
||||||
|
channel.lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||||
|
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun handleRatingFlow(activity: Activity) {}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PRIMARY_CHANNEL = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 - 2021 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.annotation.SuppressLint
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.InputType
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
import androidx.preference.EditTextPreferenceDialogFragmentCompat
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.preference.TwoStatePreference
|
||||||
|
import dev.doubledot.doki.ui.DokiActivity
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
|
class MainFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var alarmManager: AlarmManager
|
||||||
|
private lateinit var alarmIntent: PendingIntent
|
||||||
|
private var requestingPermissions: Boolean = false
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
|
removeLauncherIcon()
|
||||||
|
}
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||||
|
initPreferences()
|
||||||
|
|
||||||
|
findPreference<Preference>(KEY_DEVICE)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
newValue != null && newValue != ""
|
||||||
|
}
|
||||||
|
findPreference<Preference>(KEY_URL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
newValue != null && validateServerURL(newValue.toString())
|
||||||
|
}
|
||||||
|
findPreference<Preference>(KEY_INTERVAL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
try {
|
||||||
|
newValue != null && (newValue as String).toInt() > 0
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val numberValidationListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
try {
|
||||||
|
newValue != null && (newValue as String).toInt() >= 0
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findPreference<Preference>(KEY_DISTANCE)?.onPreferenceChangeListener = numberValidationListener
|
||||||
|
findPreference<Preference>(KEY_ANGLE)?.onPreferenceChangeListener = numberValidationListener
|
||||||
|
|
||||||
|
alarmManager = requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
val originalIntent = Intent(activity, AutostartReceiver::class.java)
|
||||||
|
originalIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
|
||||||
|
|
||||||
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
alarmIntent = PendingIntent.getBroadcast(activity, 0, originalIntent, flags)
|
||||||
|
|
||||||
|
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
|
||||||
|
startTrackingService(checkPermission = true, initialPermission = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NumericEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
|
||||||
|
|
||||||
|
override fun onBindDialogView(view: View) {
|
||||||
|
val editText = view.findViewById<EditText>(android.R.id.edit)
|
||||||
|
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
super.onBindDialogView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(key: String?): NumericEditTextPreferenceDialogFragment {
|
||||||
|
val fragment = NumericEditTextPreferenceDialogFragment()
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString(ARG_KEY, key)
|
||||||
|
fragment.arguments = bundle
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||||
|
if (listOf(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.key)) {
|
||||||
|
val f: EditTextPreferenceDialogFragmentCompat =
|
||||||
|
NumericEditTextPreferenceDialogFragment.newInstance(preference.key)
|
||||||
|
f.setTargetFragment(this, 0)
|
||||||
|
f.show(requireFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG")
|
||||||
|
} else {
|
||||||
|
super.onDisplayPreferenceDialog(preference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeLauncherIcon() {
|
||||||
|
val className = MainActivity::class.java.canonicalName!!.replace(".MainActivity", ".Launcher")
|
||||||
|
val componentName = ComponentName(requireActivity().packageName, className)
|
||||||
|
val packageManager = requireActivity().packageManager
|
||||||
|
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
|
||||||
|
packageManager.setComponentEnabledSetting(
|
||||||
|
componentName,
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||||
|
PackageManager.DONT_KILL_APP
|
||||||
|
)
|
||||||
|
val builder = AlertDialog.Builder(requireActivity())
|
||||||
|
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 fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
if (requestingPermissions) {
|
||||||
|
requestingPermissions = BatteryOptimizationHelper().requestException(requireContext())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPreferencesEnabled(enabled: Boolean) {
|
||||||
|
findPreference<Preference>(KEY_DEVICE)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_URL)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_INTERVAL)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_DISTANCE)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_ANGLE)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_ACCURACY)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_BUFFER)?.isEnabled = enabled
|
||||||
|
findPreference<Preference>(KEY_WAKELOCK)?.isEnabled = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||||
|
if (key == KEY_STATUS) {
|
||||||
|
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
|
||||||
|
startTrackingService(checkPermission = true, initialPermission = false)
|
||||||
|
} else {
|
||||||
|
stopTrackingService()
|
||||||
|
}
|
||||||
|
(requireActivity().application as MainApplication).handleRatingFlow(requireActivity())
|
||||||
|
} else if (key == KEY_DEVICE) {
|
||||||
|
findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.main, menu)
|
||||||
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.status) {
|
||||||
|
startActivity(Intent(activity, StatusActivity::class.java))
|
||||||
|
return true
|
||||||
|
} else if (item.itemId == R.id.info) {
|
||||||
|
DokiActivity.start(requireContext())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initPreferences() {
|
||||||
|
PreferenceManager.setDefaultValues(activity, R.xml.preferences, false)
|
||||||
|
if (!sharedPreferences.contains(KEY_DEVICE)) {
|
||||||
|
val id = (Random().nextInt(900000) + 100000).toString()
|
||||||
|
sharedPreferences.edit().putString(KEY_DEVICE, id).apply()
|
||||||
|
findPreference<EditTextPreference>(KEY_DEVICE)?.text = id
|
||||||
|
}
|
||||||
|
findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBackgroundLocationDialog(context: Context, onSuccess: () -> Unit) {
|
||||||
|
val builder = AlertDialog.Builder(context)
|
||||||
|
val option = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
context.packageManager.backgroundPermissionOptionLabel
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.request_background_option)
|
||||||
|
}
|
||||||
|
builder.setMessage(context.getString(R.string.request_background, option))
|
||||||
|
builder.setPositiveButton(android.R.string.ok) { _, _ -> onSuccess() }
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startTrackingService(checkPermission: Boolean, initialPermission: Boolean) {
|
||||||
|
var permission = initialPermission
|
||||||
|
if (checkPermission) {
|
||||||
|
val requiredPermissions: MutableSet<String> = HashSet()
|
||||||
|
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
|
}
|
||||||
|
permission = requiredPermissions.isEmpty()
|
||||||
|
if (!permission) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
requestPermissions(requiredPermissions.toTypedArray(), PERMISSIONS_REQUEST_LOCATION)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (permission) {
|
||||||
|
setPreferencesEnabled(false)
|
||||||
|
ContextCompat.startForegroundService(requireContext(), Intent(activity, TrackingService::class.java))
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
alarmManager.setInexactRepeating(
|
||||||
|
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||||
|
ALARM_MANAGER_INTERVAL.toLong(), ALARM_MANAGER_INTERVAL.toLong(), alarmIntent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
|
&& ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestingPermissions = true
|
||||||
|
showBackgroundLocationDialog(requireContext()) {
|
||||||
|
requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), PERMISSIONS_REQUEST_BACKGROUND_LOCATION)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requestingPermissions = BatteryOptimizationHelper().requestException(requireContext())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply()
|
||||||
|
val preference = findPreference<TwoStatePreference>(KEY_STATUS)
|
||||||
|
preference?.isChecked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopTrackingService() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
alarmManager.cancel(alarmIntent)
|
||||||
|
}
|
||||||
|
requireActivity().stopService(Intent(activity, TrackingService::class.java))
|
||||||
|
setPreferencesEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
|
||||||
|
var granted = true
|
||||||
|
for (result in grantResults) {
|
||||||
|
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
granted = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startTrackingService(false, granted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateServerURL(userUrl: String): Boolean {
|
||||||
|
val port = Uri.parse(userUrl).port
|
||||||
|
if (
|
||||||
|
URLUtil.isValidUrl(userUrl) &&
|
||||||
|
(port == -1 || port in 1..65535) &&
|
||||||
|
(URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Toast.makeText(activity, R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = MainFragment::class.java.simpleName
|
||||||
|
private const val ALARM_MANAGER_INTERVAL = 15000
|
||||||
|
const val KEY_DEVICE = "id"
|
||||||
|
const val KEY_URL = "url"
|
||||||
|
const val KEY_INTERVAL = "interval"
|
||||||
|
const val KEY_DISTANCE = "distance"
|
||||||
|
const val KEY_ANGLE = "angle"
|
||||||
|
const val KEY_ACCURACY = "accuracy"
|
||||||
|
const val KEY_STATUS = "status"
|
||||||
|
const val KEY_BUFFER = "buffer"
|
||||||
|
const val KEY_WAKELOCK = "wakelock"
|
||||||
|
private const val PERMISSIONS_REQUEST_LOCATION = 2
|
||||||
|
private const val PERMISSIONS_REQUEST_BACKGROUND_LOCATION = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,68 +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.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class NetworkManager extends BroadcastReceiver {
|
|
||||||
|
|
||||||
private static final String TAG = NetworkManager.class.getSimpleName();
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private NetworkHandler handler;
|
|
||||||
private ConnectivityManager connectivityManager;
|
|
||||||
|
|
||||||
public NetworkManager(Context context, NetworkHandler handler) {
|
|
||||||
this.context = context;
|
|
||||||
this.handler = handler;
|
|
||||||
connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface NetworkHandler {
|
|
||||||
void onNetworkUpdate(boolean isOnline);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOnline() {
|
|
||||||
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
|
|
||||||
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
|
||||||
context.registerReceiver(this, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
context.unregisterReceiver(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION) && handler != null) {
|
|
||||||
boolean isOnline = isOnline();
|
|
||||||
Log.i(TAG, "network " + (isOnline ? "on" : "off"));
|
|
||||||
handler.onNetworkUpdate(isOnline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 - 2021 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.
|
||||||
|
*/
|
||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
package org.traccar.client
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
class NetworkManager(private val context: Context, private val handler: NetworkHandler?) : BroadcastReceiver() {
|
||||||
|
|
||||||
|
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
|
||||||
|
interface NetworkHandler {
|
||||||
|
fun onNetworkUpdate(isOnline: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isOnline: Boolean
|
||||||
|
get() {
|
||||||
|
val activeNetwork = connectivityManager.activeNetworkInfo
|
||||||
|
return activeNetwork != null && activeNetwork.isConnectedOrConnecting
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
val filter = IntentFilter()
|
||||||
|
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||||
|
context.registerReceiver(this, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
context.unregisterReceiver(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION && handler != null) {
|
||||||
|
val isOnline = isOnline
|
||||||
|
Log.i(TAG, "network " + if (isOnline) "on" else "off")
|
||||||
|
handler.onNetworkUpdate(isOnline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = NetworkManager::class.java.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,74 +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.location.Location;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class Position {
|
|
||||||
|
|
||||||
public Position() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Position(String deviceId, Location location, double battery) {
|
|
||||||
this.deviceId = deviceId;
|
|
||||||
time = new Date(location.getTime());
|
|
||||||
latitude = location.getLatitude();
|
|
||||||
longitude = location.getLongitude();
|
|
||||||
altitude = location.getAltitude();
|
|
||||||
speed = location.getSpeed() * 1.943844; // speed in knots
|
|
||||||
course = location.getBearing();
|
|
||||||
this.battery = battery;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long id;
|
|
||||||
public long getId() { return id; }
|
|
||||||
public void setId(long id) { this.id = id; }
|
|
||||||
|
|
||||||
private String deviceId;
|
|
||||||
public String getDeviceId() { return deviceId; }
|
|
||||||
public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
|
|
||||||
|
|
||||||
private Date time;
|
|
||||||
public Date getTime() { return time; }
|
|
||||||
public void setTime(Date time) { this.time = time; }
|
|
||||||
|
|
||||||
private double latitude;
|
|
||||||
public double getLatitude() { return latitude; }
|
|
||||||
public void setLatitude(double latitude) { this.latitude = latitude; }
|
|
||||||
|
|
||||||
private double longitude;
|
|
||||||
public double getLongitude() { return longitude; }
|
|
||||||
public void setLongitude(double longitude) { this.longitude = longitude; }
|
|
||||||
|
|
||||||
private double altitude;
|
|
||||||
public double getAltitude() { return altitude; }
|
|
||||||
public void setAltitude(double altitude) { this.altitude = altitude; }
|
|
||||||
|
|
||||||
private double speed;
|
|
||||||
public double getSpeed() { return speed; }
|
|
||||||
public void setSpeed(double speed) { this.speed = speed; }
|
|
||||||
|
|
||||||
private double course;
|
|
||||||
public double getCourse() { return course; }
|
|
||||||
public void setCourse(double course) { this.course = course; }
|
|
||||||
|
|
||||||
private double battery;
|
|
||||||
public double getBattery() { return battery; }
|
|
||||||
public void setBattery(double battery) { this.battery = battery; }
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 - 2021 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.location.Location
|
||||||
|
import android.location.LocationManager
|
||||||
|
import android.os.Build
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class Position(
|
||||||
|
val id: Long = 0,
|
||||||
|
val deviceId: String,
|
||||||
|
val time: Date,
|
||||||
|
val latitude: Double = 0.0,
|
||||||
|
val longitude: Double = 0.0,
|
||||||
|
val altitude: Double = 0.0,
|
||||||
|
val speed: Double = 0.0,
|
||||||
|
val course: Double = 0.0,
|
||||||
|
val accuracy: Double = 0.0,
|
||||||
|
val battery: Double = 0.0,
|
||||||
|
val mock: Boolean = false,
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(deviceId: String, location: Location, battery: Double) : this(
|
||||||
|
deviceId = deviceId,
|
||||||
|
time = Date(location.time),
|
||||||
|
latitude = location.latitude,
|
||||||
|
longitude = location.longitude,
|
||||||
|
altitude = location.altitude,
|
||||||
|
speed = location.speed * 1.943844, // speed in knots
|
||||||
|
course = location.bearing.toDouble(),
|
||||||
|
accuracy = if (location.provider != null && location.provider != LocationManager.GPS_PROVIDER) location.accuracy.toDouble() else 0.0,
|
||||||
|
battery = battery,
|
||||||
|
mock = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) location.isFromMockProvider else false,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2013 - 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.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.BatteryManager;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public abstract class PositionProvider {
|
|
||||||
|
|
||||||
protected static final String TAG = PositionProvider.class.getSimpleName();
|
|
||||||
|
|
||||||
public interface PositionListener {
|
|
||||||
void onPositionUpdate(Position position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final PositionListener listener;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final SharedPreferences preferences;
|
|
||||||
protected final LocationManager locationManager;
|
|
||||||
|
|
||||||
private String deviceId;
|
|
||||||
protected String type;
|
|
||||||
protected final long period;
|
|
||||||
|
|
||||||
private long lastUpdateTime;
|
|
||||||
|
|
||||||
public PositionProvider(Context context, PositionListener listener) {
|
|
||||||
this.context = context;
|
|
||||||
this.listener = listener;
|
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
|
|
||||||
deviceId = preferences.getString(MainActivity.KEY_DEVICE, null);
|
|
||||||
period = Long.parseLong(preferences.getString(MainActivity.KEY_INTERVAL, null)) * 1000;
|
|
||||||
|
|
||||||
type = preferences.getString(MainActivity.KEY_PROVIDER, "gps");
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void startUpdates();
|
|
||||||
|
|
||||||
public abstract void stopUpdates();
|
|
||||||
|
|
||||||
protected void updateLocation(Location location) {
|
|
||||||
if (location != null && location.getTime() - lastUpdateTime >= period) {
|
|
||||||
Log.i(TAG, "location new");
|
|
||||||
lastUpdateTime = location.getTime();
|
|
||||||
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, location != null ? "location old" : "location nil");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.ECLAIR)
|
|
||||||
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));
|
|
||||||
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
|
|
||||||
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
|
|
||||||
return (level * 100.0) / scale;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2013 - 2021 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
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.location.Location
|
||||||
|
import android.os.BatteryManager
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
abstract class PositionProvider(
|
||||||
|
protected val context: Context,
|
||||||
|
protected val listener: PositionListener,
|
||||||
|
) {
|
||||||
|
|
||||||
|
interface PositionListener {
|
||||||
|
fun onPositionUpdate(position: Position)
|
||||||
|
fun onPositionError(error: Throwable)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected var preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
protected var deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined")!!
|
||||||
|
protected var interval = preferences.getString(MainFragment.KEY_INTERVAL, "600")!!.toLong() * 1000
|
||||||
|
protected var distance: Double = preferences.getString(MainFragment.KEY_DISTANCE, "0")!!.toInt().toDouble()
|
||||||
|
protected var angle: Double = preferences.getString(MainFragment.KEY_ANGLE, "0")!!.toInt().toDouble()
|
||||||
|
private var lastLocation: Location? = null
|
||||||
|
|
||||||
|
abstract fun startUpdates()
|
||||||
|
abstract fun stopUpdates()
|
||||||
|
abstract fun requestSingleLocation()
|
||||||
|
|
||||||
|
protected fun processLocation(location: Location?) {
|
||||||
|
if (location != null &&
|
||||||
|
(lastLocation == null || location.time - lastLocation!!.time >= interval || distance > 0
|
||||||
|
&& location.distanceTo(lastLocation) >= distance || angle > 0
|
||||||
|
&& abs(location.bearing - lastLocation!!.bearing) >= angle)
|
||||||
|
) {
|
||||||
|
Log.i(TAG, "location new")
|
||||||
|
lastLocation = location
|
||||||
|
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, if (location != null) "location ignored" else "location nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getBatteryLevel(context: Context): Double {
|
||||||
|
val batteryIntent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||||
|
if (batteryIntent != null) {
|
||||||
|
val level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
|
||||||
|
val scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1)
|
||||||
|
return level * 100.0 / scale
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = PositionProvider::class.java.simpleName
|
||||||
|
const val MINIMUM_INTERVAL: Long = 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 - 2016 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.net.Uri;
|
|
||||||
|
|
||||||
public class ProtocolFormatter {
|
|
||||||
|
|
||||||
public static String formatRequest(String address, int port, boolean secure, Position position) {
|
|
||||||
return formatRequest(address, port, secure, position, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatRequest(String address, int port, boolean secure, Position position, String alarm) {
|
|
||||||
|
|
||||||
Uri.Builder builder = new Uri.Builder();
|
|
||||||
builder.scheme(secure ? "https" : "http").encodedAuthority(address + ':' + port)
|
|
||||||
.appendQueryParameter("id", position.getDeviceId())
|
|
||||||
.appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000))
|
|
||||||
.appendQueryParameter("lat", String.valueOf(position.getLatitude()))
|
|
||||||
.appendQueryParameter("lon", String.valueOf(position.getLongitude()))
|
|
||||||
.appendQueryParameter("speed", String.valueOf(position.getSpeed()))
|
|
||||||
.appendQueryParameter("bearing", String.valueOf(position.getCourse()))
|
|
||||||
.appendQueryParameter("altitude", String.valueOf(position.getAltitude()))
|
|
||||||
.appendQueryParameter("batt", String.valueOf(position.getBattery()));
|
|
||||||
|
|
||||||
if (alarm != null) {
|
|
||||||
builder.appendQueryParameter("alarm", alarm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 - 2021 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.net.Uri
|
||||||
|
|
||||||
|
object ProtocolFormatter {
|
||||||
|
|
||||||
|
fun formatRequest(url: String, position: Position, alarm: String? = null): String {
|
||||||
|
val serverUrl = Uri.parse(url)
|
||||||
|
val builder = serverUrl.buildUpon()
|
||||||
|
.appendQueryParameter("id", position.deviceId)
|
||||||
|
.appendQueryParameter("timestamp", (position.time.time / 1000).toString())
|
||||||
|
.appendQueryParameter("lat", position.latitude.toString())
|
||||||
|
.appendQueryParameter("lon", position.longitude.toString())
|
||||||
|
.appendQueryParameter("speed", position.speed.toString())
|
||||||
|
.appendQueryParameter("bearing", position.course.toString())
|
||||||
|
.appendQueryParameter("altitude", position.altitude.toString())
|
||||||
|
.appendQueryParameter("accuracy", position.accuracy.toString())
|
||||||
|
.appendQueryParameter("batt", position.battery.toString())
|
||||||
|
if (position.mock) {
|
||||||
|
builder.appendQueryParameter("mock", position.mock.toString())
|
||||||
|
}
|
||||||
|
if (alarm != null) {
|
||||||
|
builder.appendQueryParameter("alarm", alarm)
|
||||||
|
}
|
||||||
|
return builder.build().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +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.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
public class RequestManager {
|
|
||||||
|
|
||||||
private static final int TIMEOUT = 15 * 1000;
|
|
||||||
|
|
||||||
public interface RequestHandler {
|
|
||||||
void onComplete(boolean success);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RequestAsyncTask extends AsyncTask<String, Void, Boolean> {
|
|
||||||
|
|
||||||
private RequestHandler handler;
|
|
||||||
|
|
||||||
public RequestAsyncTask(RequestHandler handler) {
|
|
||||||
this.handler = handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(String... request) {
|
|
||||||
return sendRequest(request[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean result) {
|
|
||||||
handler.onComplete(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean sendRequest(String request) {
|
|
||||||
InputStream inputStream = null;
|
|
||||||
try {
|
|
||||||
URL url = new URL(request);
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setReadTimeout(TIMEOUT);
|
|
||||||
connection.setConnectTimeout(TIMEOUT);
|
|
||||||
connection.connect();
|
|
||||||
inputStream = connection.getInputStream();
|
|
||||||
while (inputStream.read() != -1);
|
|
||||||
return true;
|
|
||||||
} catch (IOException error) {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (inputStream != null) {
|
|
||||||
inputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException secondError) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void sendRequestAsync(String request, RequestHandler handler) {
|
|
||||||
RequestAsyncTask task = new RequestAsyncTask(handler);
|
|
||||||
task.execute(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 - 2021 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.
|
||||||
|
*/
|
||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
package org.traccar.client
|
||||||
|
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
object RequestManager {
|
||||||
|
|
||||||
|
private const val TIMEOUT = 15 * 1000
|
||||||
|
|
||||||
|
fun sendRequest(request: String?): Boolean {
|
||||||
|
var inputStream: InputStream? = null
|
||||||
|
return try {
|
||||||
|
val url = URL(request)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.readTimeout = TIMEOUT
|
||||||
|
connection.connectTimeout = TIMEOUT
|
||||||
|
connection.requestMethod = "POST"
|
||||||
|
connection.connect()
|
||||||
|
inputStream = connection.inputStream
|
||||||
|
while (inputStream.read() != -1) {}
|
||||||
|
true
|
||||||
|
} catch (error: IOException) {
|
||||||
|
false
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream?.close()
|
||||||
|
} catch (secondError: IOException) {
|
||||||
|
Log.w(RequestManager::class.java.simpleName, secondError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendRequestAsync(request: String, handler: RequestHandler) {
|
||||||
|
RequestAsyncTask(handler).execute(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestHandler {
|
||||||
|
fun onComplete(success: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RequestAsyncTask(private val handler: RequestHandler) : AsyncTask<String, Unit, Boolean>() {
|
||||||
|
|
||||||
|
override fun doInBackground(vararg request: String): Boolean {
|
||||||
|
return sendRequest(request[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(result: Boolean) {
|
||||||
|
handler.onComplete(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 - 2017 Anton Tananaev (anton.tananaev@gmail.com)
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.traccar.client;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
public class ShortcutActivity extends Activity {
|
|
||||||
|
|
||||||
public static final String EXTRA_ACTION = "action";
|
|
||||||
public static final String EXTRA_ACTION_START = "start";
|
|
||||||
public static final String EXTRA_ACTION_STOP = "stop";
|
|
||||||
public static final String EXTRA_ACTION_SOS = "sos";
|
|
||||||
|
|
||||||
private static final String ALARM_SOS = "sos";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
checkShortcutAction(getIntent());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNewIntent(Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
checkShortcutAction(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("MissingPermission")
|
|
||||||
private void sendAlarm() {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
|
|
||||||
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
|
|
||||||
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(
|
|
||||||
preferences.getString(MainActivity.KEY_ADDRESS, null),
|
|
||||||
Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null)),
|
|
||||||
preferences.getBoolean(MainActivity.KEY_SECURE, false),
|
|
||||||
position, ALARM_SOS);
|
|
||||||
|
|
||||||
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(boolean success) {
|
|
||||||
if (success) {
|
|
||||||
Toast.makeText(ShortcutActivity.this, R.string.status_send_success, Toast.LENGTH_SHORT).show();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(ShortcutActivity.this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkShortcutAction(Intent intent) {
|
|
||||||
String action;
|
|
||||||
if (intent.hasExtra("shortcutAction")) {
|
|
||||||
action = intent.getBooleanExtra("shortcutAction", false)
|
|
||||||
? EXTRA_ACTION_START : EXTRA_ACTION_STOP;
|
|
||||||
} else {
|
|
||||||
action = intent.getStringExtra(EXTRA_ACTION);
|
|
||||||
}
|
|
||||||
switch (action) {
|
|
||||||
case EXTRA_ACTION_START:
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
.edit().putBoolean(MainActivity.KEY_STATUS, true).commit();
|
|
||||||
startService(new Intent(this, TrackingService.class));
|
|
||||||
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
case EXTRA_ACTION_STOP:
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
.edit().putBoolean(MainActivity.KEY_STATUS, false).commit();
|
|
||||||
stopService(new Intent(this, TrackingService.class));
|
|
||||||
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
case EXTRA_ACTION_SOS:
|
|
||||||
sendAlarm();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2021 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.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ListView
|
||||||
|
import android.widget.Toast
|
||||||
|
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
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import org.traccar.client.PositionProvider.PositionListener
|
||||||
|
import org.traccar.client.ProtocolFormatter.formatRequest
|
||||||
|
import org.traccar.client.RequestManager.RequestHandler
|
||||||
|
import org.traccar.client.RequestManager.sendRequestAsync
|
||||||
|
|
||||||
|
class ShortcutActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (!executeAction(intent)) {
|
||||||
|
setContentView(R.layout.list)
|
||||||
|
val items = arrayOf(
|
||||||
|
getString(R.string.shortcut_start),
|
||||||
|
getString(R.string.shortcut_stop),
|
||||||
|
getString(R.string.shortcut_sos)
|
||||||
|
)
|
||||||
|
val listView = findViewById<ListView>(android.R.id.list)
|
||||||
|
listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)
|
||||||
|
listView.onItemClickListener = OnItemClickListener { _, _, position, _ ->
|
||||||
|
when (position) {
|
||||||
|
0 -> setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START)
|
||||||
|
1 -> setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP)
|
||||||
|
2 -> setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
executeAction(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setShortcutResult(label: String, @DrawableRes iconResId: Int, action: String) {
|
||||||
|
val intent = Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity::class.java)
|
||||||
|
intent.putExtra(EXTRA_ACTION, action)
|
||||||
|
val shortcut = ShortcutInfoCompat.Builder(this, action)
|
||||||
|
.setShortLabel(label)
|
||||||
|
.setIcon(IconCompat.createWithResource(this, iconResId))
|
||||||
|
.setIntent(intent)
|
||||||
|
.build()
|
||||||
|
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendAlarm() {
|
||||||
|
PositionProviderFactory.create(this, object : PositionListener {
|
||||||
|
override fun onPositionUpdate(position: Position) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(this@ShortcutActivity)
|
||||||
|
val request = formatRequest(preferences.getString(MainFragment.KEY_URL, null)!!, position, ALARM_SOS)
|
||||||
|
sendRequestAsync(request, object : RequestHandler {
|
||||||
|
override fun onComplete(success: Boolean) {
|
||||||
|
if (success) {
|
||||||
|
Toast.makeText(this@ShortcutActivity, R.string.status_send_success, Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this@ShortcutActivity, R.string.status_send_fail, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositionError(error: Throwable) {
|
||||||
|
Toast.makeText(this@ShortcutActivity, error.message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}).requestSingleLocation()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun executeAction(intent: Intent): Boolean {
|
||||||
|
val action: String? = if (intent.hasExtra("shortcutAction")) {
|
||||||
|
if (intent.getBooleanExtra("shortcutAction", false)) ACTION_START else ACTION_STOP
|
||||||
|
} else {
|
||||||
|
intent.getStringExtra(EXTRA_ACTION)
|
||||||
|
}
|
||||||
|
if (action != null) {
|
||||||
|
when (action) {
|
||||||
|
ACTION_START -> {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, true).apply()
|
||||||
|
ContextCompat.startForegroundService(this, Intent(this, TrackingService::class.java))
|
||||||
|
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
ACTION_STOP -> {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, false).apply()
|
||||||
|
stopService(Intent(this, TrackingService::class.java))
|
||||||
|
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
ACTION_SOS -> {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
sendAlarm()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
return action != null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_ACTION = "action"
|
||||||
|
const val ACTION_START = "start"
|
||||||
|
const val ACTION_STOP = "stop"
|
||||||
|
const val ACTION_SOS = "sos"
|
||||||
|
private const val ALARM_SOS = "sos"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 - 2013 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.text.DateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import android.app.ListActivity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
|
|
||||||
public class StatusActivity extends ListActivity {
|
|
||||||
|
|
||||||
private static final int LIMIT = 20;
|
|
||||||
|
|
||||||
private static final LinkedList<String> messages = new LinkedList<String>();
|
|
||||||
private static final Set<ArrayAdapter<String>> adapters = new HashSet<ArrayAdapter<String>>();
|
|
||||||
|
|
||||||
private static void notifyAdapters() {
|
|
||||||
for (ArrayAdapter<String> adapter : adapters) {
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void addMessage(String message) {
|
|
||||||
DateFormat format = DateFormat.getTimeInstance(DateFormat.SHORT);
|
|
||||||
message = format.format(new Date()) + " - " + message;
|
|
||||||
messages.add(message);
|
|
||||||
while (messages.size() > LIMIT) {
|
|
||||||
messages.removeFirst();
|
|
||||||
}
|
|
||||||
notifyAdapters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearMessages() {
|
|
||||||
messages.clear();
|
|
||||||
notifyAdapters();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ArrayAdapter<String> adapter;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.status);
|
|
||||||
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages);
|
|
||||||
setListAdapter(adapter);
|
|
||||||
adapters.add(adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
adapters.remove(adapter);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
MenuInflater inflater = getMenuInflater();
|
|
||||||
inflater.inflate(R.menu.status, menu);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.clear) {
|
|
||||||
clearMessages();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 - 2021 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 androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.ListView
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class StatusActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var adapter: ArrayAdapter<String>? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.list)
|
||||||
|
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages)
|
||||||
|
val listView = findViewById<ListView>(android.R.id.list)
|
||||||
|
listView.adapter = adapter
|
||||||
|
adapter?.let { adapters.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
adapters.remove(adapter)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
val inflater = menuInflater
|
||||||
|
inflater.inflate(R.menu.status, menu)
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.clear) {
|
||||||
|
clearMessages()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val LIMIT = 20
|
||||||
|
private val messages = LinkedList<String>()
|
||||||
|
private val adapters: MutableSet<ArrayAdapter<String>> = HashSet()
|
||||||
|
|
||||||
|
private fun notifyAdapters() {
|
||||||
|
for (adapter in adapters) {
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addMessage(originalMessage: String) {
|
||||||
|
var message = originalMessage
|
||||||
|
val format = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
||||||
|
message = format.format(Date()) + " - " + message
|
||||||
|
messages.add(message)
|
||||||
|
while (messages.size > LIMIT) {
|
||||||
|
messages.removeFirst()
|
||||||
|
}
|
||||||
|
notifyAdapters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearMessages() {
|
||||||
|
messages.clear()
|
||||||
|
notifyAdapters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,229 +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.content.SharedPreferences;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class TrackingController implements PositionProvider.PositionListener, NetworkManager.NetworkHandler {
|
|
||||||
|
|
||||||
private static final String TAG = TrackingController.class.getSimpleName();
|
|
||||||
private static final int RETRY_DELAY = 30 * 1000;
|
|
||||||
private static final int WAKE_LOCK_TIMEOUT = 120 * 1000;
|
|
||||||
|
|
||||||
private boolean isOnline;
|
|
||||||
private boolean isWaiting;
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private Handler handler;
|
|
||||||
private SharedPreferences preferences;
|
|
||||||
|
|
||||||
private String address;
|
|
||||||
private int port;
|
|
||||||
private boolean secure;
|
|
||||||
|
|
||||||
private PositionProvider positionProvider;
|
|
||||||
private DatabaseHelper databaseHelper;
|
|
||||||
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) {
|
|
||||||
this.context = context;
|
|
||||||
handler = new Handler();
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
if (preferences.getString(MainActivity.KEY_PROVIDER, "gps").equals("mixed")) {
|
|
||||||
positionProvider = new MixedPositionProvider(context, this);
|
|
||||||
} else {
|
|
||||||
positionProvider = new SimplePositionProvider(context, this);
|
|
||||||
}
|
|
||||||
databaseHelper = new DatabaseHelper(context);
|
|
||||||
networkManager = new NetworkManager(context, this);
|
|
||||||
isOnline = networkManager.isOnline();
|
|
||||||
|
|
||||||
address = preferences.getString(MainActivity.KEY_ADDRESS, null);
|
|
||||||
port = Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null));
|
|
||||||
secure = preferences.getBoolean(MainActivity.KEY_SECURE, false);
|
|
||||||
|
|
||||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
if (isOnline) {
|
|
||||||
read();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
positionProvider.startUpdates();
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
networkManager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
networkManager.stop();
|
|
||||||
try {
|
|
||||||
positionProvider.stopUpdates();
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
handler.removeCallbacksAndMessages(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPositionUpdate(Position position) {
|
|
||||||
StatusActivity.addMessage(context.getString(R.string.status_location_update));
|
|
||||||
if (position != null) {
|
|
||||||
write(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNetworkUpdate(boolean isOnline) {
|
|
||||||
StatusActivity.addMessage(context.getString(R.string.status_connectivity_change));
|
|
||||||
if (!this.isOnline && isOnline) {
|
|
||||||
read();
|
|
||||||
}
|
|
||||||
this.isOnline = isOnline;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// State transition examples:
|
|
||||||
//
|
|
||||||
// write -> read -> send -> delete -> read
|
|
||||||
//
|
|
||||||
// read -> send -> retry -> read -> send
|
|
||||||
//
|
|
||||||
|
|
||||||
private void log(String action, Position position) {
|
|
||||||
if (position != null) {
|
|
||||||
action += " (" +
|
|
||||||
"id:" + position.getId() +
|
|
||||||
" time:" + position.getTime().getTime() / 1000 +
|
|
||||||
" lat:" + position.getLatitude() +
|
|
||||||
" lon:" + position.getLongitude() + ")";
|
|
||||||
}
|
|
||||||
Log.d(TAG, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void write(Position position) {
|
|
||||||
log("write", position);
|
|
||||||
lock();
|
|
||||||
databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(boolean success, Void result) {
|
|
||||||
if (success) {
|
|
||||||
if (isOnline && isWaiting) {
|
|
||||||
read();
|
|
||||||
isWaiting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void read() {
|
|
||||||
log("read", null);
|
|
||||||
lock();
|
|
||||||
databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(boolean success, Position result) {
|
|
||||||
if (success) {
|
|
||||||
if (result != null) {
|
|
||||||
if (result.getDeviceId().equals(preferences.getString(MainActivity.KEY_DEVICE, null))) {
|
|
||||||
send(result);
|
|
||||||
} else {
|
|
||||||
delete(result);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isWaiting = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
retry();
|
|
||||||
}
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void delete(Position position) {
|
|
||||||
log("delete", position);
|
|
||||||
lock();
|
|
||||||
databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(boolean success, Void result) {
|
|
||||||
if (success) {
|
|
||||||
read();
|
|
||||||
} else {
|
|
||||||
retry();
|
|
||||||
}
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void send(final Position position) {
|
|
||||||
log("send", position);
|
|
||||||
lock();
|
|
||||||
String request = ProtocolFormatter.formatRequest(address, port, secure, position);
|
|
||||||
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(boolean success) {
|
|
||||||
if (success) {
|
|
||||||
delete(position);
|
|
||||||
} else {
|
|
||||||
StatusActivity.addMessage(context.getString(R.string.status_send_fail));
|
|
||||||
retry();
|
|
||||||
}
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void retry() {
|
|
||||||
log("retry", null);
|
|
||||||
handler.postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (isOnline) {
|
|
||||||
read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, RETRY_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 - 2021 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
|
||||||
|
import org.traccar.client.ProtocolFormatter.formatRequest
|
||||||
|
import org.traccar.client.RequestManager.sendRequestAsync
|
||||||
|
import org.traccar.client.PositionProvider.PositionListener
|
||||||
|
import org.traccar.client.NetworkManager.NetworkHandler
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import android.util.Log
|
||||||
|
import org.traccar.client.DatabaseHelper.DatabaseHandler
|
||||||
|
import org.traccar.client.RequestManager.RequestHandler
|
||||||
|
|
||||||
|
class TrackingController(private val context: Context) : PositionListener, NetworkHandler {
|
||||||
|
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
private val positionProvider = PositionProviderFactory.create(context, this)
|
||||||
|
private val databaseHelper = DatabaseHelper(context)
|
||||||
|
private val networkManager = NetworkManager(context, this)
|
||||||
|
|
||||||
|
private val url: String = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value))!!
|
||||||
|
private val buffer: Boolean = preferences.getBoolean(MainFragment.KEY_BUFFER, true)
|
||||||
|
|
||||||
|
private var isOnline = networkManager.isOnline
|
||||||
|
private var isWaiting = false
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (isOnline) {
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
positionProvider.startUpdates()
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
networkManager.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
networkManager.stop()
|
||||||
|
try {
|
||||||
|
positionProvider.stopUpdates()
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.w(TAG, e)
|
||||||
|
}
|
||||||
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositionUpdate(position: Position) {
|
||||||
|
StatusActivity.addMessage(context.getString(R.string.status_location_update))
|
||||||
|
if (buffer) {
|
||||||
|
write(position)
|
||||||
|
} else {
|
||||||
|
send(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositionError(error: Throwable) {}
|
||||||
|
override fun onNetworkUpdate(isOnline: Boolean) {
|
||||||
|
val message = if (isOnline) R.string.status_network_online else R.string.status_network_offline
|
||||||
|
StatusActivity.addMessage(context.getString(message))
|
||||||
|
if (!this.isOnline && isOnline) {
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
this.isOnline = isOnline
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// State transition examples:
|
||||||
|
//
|
||||||
|
// write -> read -> send -> delete -> read
|
||||||
|
//
|
||||||
|
// read -> send -> retry -> read -> send
|
||||||
|
//
|
||||||
|
|
||||||
|
private fun log(action: String, position: Position?) {
|
||||||
|
var formattedAction: String = action
|
||||||
|
if (position != null) {
|
||||||
|
formattedAction +=
|
||||||
|
" (id:" + position.id +
|
||||||
|
" time:" + position.time.time / 1000 +
|
||||||
|
" lat:" + position.latitude +
|
||||||
|
" lon:" + position.longitude + ")"
|
||||||
|
}
|
||||||
|
Log.d(TAG, formattedAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun write(position: Position) {
|
||||||
|
log("write", position)
|
||||||
|
databaseHelper.insertPositionAsync(position, object : DatabaseHandler<Unit?> {
|
||||||
|
override fun onComplete(success: Boolean, result: Unit?) {
|
||||||
|
if (success) {
|
||||||
|
if (isOnline && isWaiting) {
|
||||||
|
read()
|
||||||
|
isWaiting = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun read() {
|
||||||
|
log("read", null)
|
||||||
|
databaseHelper.selectPositionAsync(object : DatabaseHandler<Position?> {
|
||||||
|
override fun onComplete(success: Boolean, result: Position?) {
|
||||||
|
if (success) {
|
||||||
|
if (result != null) {
|
||||||
|
if (result.deviceId == preferences.getString(MainFragment.KEY_DEVICE, null)) {
|
||||||
|
send(result)
|
||||||
|
} else {
|
||||||
|
delete(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isWaiting = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
retry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun delete(position: Position) {
|
||||||
|
log("delete", position)
|
||||||
|
databaseHelper.deletePositionAsync(position.id, object : DatabaseHandler<Unit?> {
|
||||||
|
override fun onComplete(success: Boolean, result: Unit?) {
|
||||||
|
if (success) {
|
||||||
|
read()
|
||||||
|
} else {
|
||||||
|
retry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun send(position: Position) {
|
||||||
|
log("send", position)
|
||||||
|
val request = formatRequest(url, position)
|
||||||
|
sendRequestAsync(request, object : RequestHandler {
|
||||||
|
override fun onComplete(success: Boolean) {
|
||||||
|
if (success) {
|
||||||
|
if (buffer) {
|
||||||
|
delete(position)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StatusActivity.addMessage(context.getString(R.string.status_send_fail))
|
||||||
|
if (buffer) {
|
||||||
|
retry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun retry() {
|
||||||
|
log("retry", null)
|
||||||
|
handler.postDelayed({
|
||||||
|
if (isOnline) {
|
||||||
|
read()
|
||||||
|
}
|
||||||
|
}, RETRY_DELAY.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = TrackingController::class.java.simpleName
|
||||||
|
private const val RETRY_DELAY = 30 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012 - 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.annotation.TargetApi;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class TrackingService extends Service {
|
|
||||||
|
|
||||||
private static final String TAG = TrackingService.class.getSimpleName();
|
|
||||||
private static final int NOTIFICATION_ID = 1;
|
|
||||||
|
|
||||||
private TrackingController trackingController;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private static Notification createNotification(Context context) {
|
|
||||||
Intent notificationIntent = new Intent(context, MainActivity.class);
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
|
||||||
|
|
||||||
Notification notification = new Notification(android.R.drawable.stat_notify_sync_noanim, null, 0);
|
|
||||||
try {
|
|
||||||
Method method = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
|
|
||||||
try {
|
|
||||||
method.invoke(notification, context, context.getString(R.string.app_name), context.getString(R.string.settings_status_on_summary), pendingIntent);
|
|
||||||
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
} catch (SecurityException | NoSuchMethodException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
startForeground(NOTIFICATION_ID, createNotification(this));
|
|
||||||
stopForeground(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
stopSelfResult(startId);
|
|
||||||
return START_NOT_STICKY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
Log.i(TAG, "service create");
|
|
||||||
StatusActivity.addMessage(getString(R.string.status_service_create));
|
|
||||||
|
|
||||||
trackingController = new TrackingController(this);
|
|
||||||
trackingController.start();
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
|
|
||||||
startForeground(NOTIFICATION_ID, createNotification(this));
|
|
||||||
startService(new Intent(this, HideNotificationService.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
|
||||||
public void onStart(Intent intent, int startId) {
|
|
||||||
if (intent != null) {
|
|
||||||
AutostartReceiver.completeWakefulIntent(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.ECLAIR)
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
onStart(intent, startId);
|
|
||||||
return START_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
Log.i(TAG, "service destroy");
|
|
||||||
StatusActivity.addMessage(getString(R.string.status_service_destroy));
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) {
|
|
||||||
stopForeground(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trackingController != null) {
|
|
||||||
trackingController.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 - 2021 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.annotation.SuppressLint
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
|
import android.os.PowerManager.WakeLock
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
|
||||||
|
class TrackingService : Service() {
|
||||||
|
|
||||||
|
private var wakeLock: WakeLock? = null
|
||||||
|
private var trackingController: TrackingController? = null
|
||||||
|
|
||||||
|
class HideNotificationService : Service() {
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
startForeground(NOTIFICATION_ID, createNotification(this))
|
||||||
|
stopForeground(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
stopSelfResult(startId)
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WakelockTimeout")
|
||||||
|
override fun onCreate() {
|
||||||
|
startForeground(NOTIFICATION_ID, createNotification(this))
|
||||||
|
Log.i(TAG, "service create")
|
||||||
|
sendBroadcast(Intent(ACTION_STARTED))
|
||||||
|
StatusActivity.addMessage(getString(R.string.status_service_create))
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(MainFragment.KEY_WAKELOCK, true)) {
|
||||||
|
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
|
||||||
|
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name)
|
||||||
|
wakeLock?.acquire()
|
||||||
|
}
|
||||||
|
trackingController = TrackingController(this)
|
||||||
|
trackingController?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
ContextCompat.startForegroundService(this, Intent(this, HideNotificationService::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.ECLAIR)
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
WakefulBroadcastReceiver.completeWakefulIntent(intent)
|
||||||
|
return START_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
stopForeground(true)
|
||||||
|
Log.i(TAG, "service destroy")
|
||||||
|
sendBroadcast(Intent(ACTION_STOPPED))
|
||||||
|
StatusActivity.addMessage(getString(R.string.status_service_destroy))
|
||||||
|
if (wakeLock?.isHeld == true) {
|
||||||
|
wakeLock?.release()
|
||||||
|
}
|
||||||
|
trackingController?.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
const val ACTION_STARTED = "org.traccar.action.SERVICE_STARTED"
|
||||||
|
const val ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED"
|
||||||
|
private val TAG = TrackingService::class.java.simpleName
|
||||||
|
private const val NOTIFICATION_ID = 1
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
|
private fun createNotification(context: Context): Notification {
|
||||||
|
val builder = NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_notify)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
val intent: Intent
|
||||||
|
if (!BuildConfig.HIDDEN_APP) {
|
||||||
|
intent = Intent(context, MainActivity::class.java)
|
||||||
|
builder
|
||||||
|
.setContentTitle(context.getString(R.string.settings_status_on_summary))
|
||||||
|
.setTicker(context.getString(R.string.settings_status_on_summary))
|
||||||
|
.color = ContextCompat.getColor(context, R.color.primary_dark)
|
||||||
|
} else {
|
||||||
|
intent = Intent(Settings.ACTION_SETTINGS)
|
||||||
|
}
|
||||||
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
} else {
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
}
|
||||||
|
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, flags))
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 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.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.SparseArray
|
||||||
|
import android.os.PowerManager.WakeLock
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import android.os.PowerManager
|
||||||
|
|
||||||
|
abstract class WakefulBroadcastReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid"
|
||||||
|
private val activeWakeLocks = SparseArray<WakeLock>()
|
||||||
|
private var nextId = 1
|
||||||
|
|
||||||
|
fun startWakefulForegroundService(context: Context, intent: Intent) {
|
||||||
|
synchronized(activeWakeLocks) {
|
||||||
|
val id = nextId
|
||||||
|
nextId += 1
|
||||||
|
if (nextId <= 0) {
|
||||||
|
nextId = 1
|
||||||
|
}
|
||||||
|
intent.putExtra(EXTRA_WAKE_LOCK_ID, id)
|
||||||
|
ContextCompat.startForegroundService(context, intent)
|
||||||
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
val wakeLock = powerManager.newWakeLock(
|
||||||
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
WakefulBroadcastReceiver::class.java.simpleName
|
||||||
|
)
|
||||||
|
wakeLock.setReferenceCounted(false)
|
||||||
|
wakeLock.acquire((60 * 1000).toLong())
|
||||||
|
activeWakeLocks.put(id, wakeLock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun completeWakefulIntent(intent: Intent?): Boolean {
|
||||||
|
val id = intent?.getIntExtra(EXTRA_WAKE_LOCK_ID, 0) ?: 0
|
||||||
|
if (id == 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
synchronized(activeWakeLocks) {
|
||||||
|
val wakeLock = activeWakeLocks[id]
|
||||||
|
if (wakeLock != null) {
|
||||||
|
wakeLock.release()
|
||||||
|
activeWakeLocks.remove(id)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
До Ширина: | Высота: | Размер: 3.6 KiB |
|
До Ширина: | Высота: | Размер: 2.4 KiB |
|
До Ширина: | Высота: | Размер: 4.8 KiB |
|
До Ширина: | Высота: | Размер: 7.2 KiB |
@@ -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>
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent" >
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/logo"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginLeft="@dimen/about_margin"
|
|
||||||
android:layout_marginStart="@dimen/about_margin"
|
|
||||||
android:layout_marginTop="@dimen/about_margin"
|
|
||||||
android:src="@drawable/logo"
|
|
||||||
android:contentDescription="@string/app_logo" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:layout_margin="@dimen/about_margin"
|
|
||||||
android:layout_toRightOf="@+id/logo"
|
|
||||||
android:layout_toEndOf="@+id/logo"
|
|
||||||
android:orientation="vertical" >
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/about_spacing"
|
|
||||||
android:text="@string/app_name"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/about_spacing"
|
|
||||||
android:text="@string/about_description" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/about_spacing"
|
|
||||||
android:text="@string/about_license" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="@dimen/about_spacing"
|
|
||||||
android:text="@string/about_web"
|
|
||||||
android:autoLink="web" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
@@ -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" />
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:name="org.traccar.client.MainFragment" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -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" />
|
|
||||||
@@ -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
|
<item
|
||||||
android:id="@+id/shortcuts"
|
android:id="@+id/info"
|
||||||
android:title="@string/menu_shortcuts"
|
android:title="@string/menu_info"
|
||||||
android:showAsAction="never" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/about"
|
|
||||||
android:title="@string/menu_about"
|
|
||||||
android:showAsAction="never" />
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_sos_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_sos_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_start_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_start_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_stop_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/light"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_stop_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
После Ширина: | Высота: | Размер: 4.2 KiB |
|
После Ширина: | Высота: | Размер: 3.0 KiB |
|
После Ширина: | Высота: | Размер: 3.1 KiB |
|
После Ширина: | Высота: | Размер: 1.5 KiB |
|
После Ширина: | Высота: | Размер: 3.2 KiB |
|
После Ширина: | Высота: | Размер: 1.7 KiB |
|
После Ширина: | Высота: | Размер: 3.1 KiB |
|
После Ширина: | Высота: | Размер: 1.6 KiB |
|
После Ширина: | Высота: | Размер: 2.5 KiB |
|
После Ширина: | Высота: | Размер: 1.7 KiB |
|
После Ширина: | Высота: | Размер: 1.9 KiB |
|
После Ширина: | Высота: | Размер: 930 B |
|
После Ширина: | Высота: | Размер: 2.0 KiB |
|
После Ширина: | Высота: | Размер: 1.0 KiB |
|
После Ширина: | Высота: | Размер: 2.0 KiB |
|
После Ширина: | Высота: | Размер: 943 B |