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

..
58 Коммитов
Автор SHA1 Сообщение Дата
Anton Tananaev 826d638ea3 Update version number 2021-10-11 23:26:51 -07:00
Anton Tananaev 1b687bd075 Handle security exception 2021-10-11 23:26:28 -07:00
Anton Tananaev 42d5584d8d Fix hidden app 2021-10-09 17:59:58 -07:00
Anton Tananaev ff88682e40 Update app version 2021-10-09 17:41:18 -07:00
Anton Tananaev 307e692459 Integrate DKMA info 2021-10-09 17:40:27 -07:00
Anton Tananaev b7ec644ecf Improve app autostart 2021-10-09 17:40:27 -07:00
Anton Tananaev 842baa2aec Merge pull request #427 from scfarley/master
Accept user CA's for connections via HTTPS proxy
2021-09-12 18:25:32 -07:00
Sean Farley 26bc92cf8e Accept user CA's for connections via HTTPS proxy
Permit Traccar client to communicate with the Traccar server via an
HTTPS proxy that uses a certificate generated from a user (non-system)
CA.
2021-09-12 21:02:20 -04:00
Anton Tananaev 6dadf5e7c3 Update project files 2021-08-29 22:13:52 -07:00
Anton Tananaev 39e9c171c4 Handle battery optimization exception 2021-08-29 18:17:31 -07:00
Anton Tananaev bcb5985cef Alarm foreground priority 2021-08-29 15:55:52 -07:00
Anton Tananaev eb868056bf Change alarm flag 2021-07-31 07:37:47 -10:00
Anton Tananaev 94e094e648 Update app version 2021-07-24 11:24:26 -07:00
Anton Tananaev 25c0434e4c Fix database management 2021-07-24 11:23:58 -07:00
Anton Tananaev 3afcfa17d0 Update version number 2021-07-20 23:12:26 -07:00
Anton Tananaev 02263d5fd9 Fix crash 2021-07-20 23:12:04 -07:00
Anton Tananaev a884b2f3d7 Reorder dependencies 2021-07-19 20:26:42 -07:00
Anton Tananaev 6910790fa7 Specify location services version 2021-07-19 20:17:53 -07:00
Anton Tananaev 6cab1a0b36 Update Firebase 2021-07-19 20:14:15 -07:00
Anton Tananaev edd4f670fe Remove unused 2021-07-19 20:03:06 -07:00
Anton Tananaev 298d7becb5 Update localization 2021-07-17 22:02:57 -07:00
Anton Tananaev 9a2e3f9126 Update app version 2021-07-17 21:57:48 -07:00
Anton Tananaev d3f7ee0c8b Convert project to Kotlin 2021-07-17 19:14:15 -07:00
Anton Tananaev 8ae486dbc8 Rename .java to .kt 2021-07-17 19:14:15 -07:00
Anton Tananaev 02295f5fcf Convert project to Kotlin 2021-07-17 19:11:29 -07:00
Anton Tananaev 958eb77c51 Rename .java to .kt 2021-07-17 19:11:29 -07:00
Anton Tananaev 0a3a8381a5 Add Kotlin support 2021-07-17 16:56:49 -07:00
Anton Tananaev 6009dba342 Update project 2021-07-17 16:51:10 -07:00
Anton Tananaev 59085f668c Remove disclosure 2020-12-16 20:50:40 -08:00
Anton Tananaev 1cb5326e07 Update app version 2020-12-14 21:21:04 -08:00
Anton Tananaev 28befc6180 Remove background location 2020-12-14 21:20:41 -08:00
Anton Tananaev e83ef80fa3 Add target SDK 2020-12-13 22:24:53 -08:00
Anton Tananaev b26a1bd133 Update app version 2020-12-13 22:21:24 -08:00
Anton Tananaev 0bbee716a6 Add background location disclosure 2020-12-13 22:00:44 -08:00
Anton Tananaev 52e3b839ae Update dependencies 2020-12-13 21:45:35 -08:00
Anton Tananaev cfe04eb0a6 Update tools 2020-12-13 21:38:17 -08:00
Anton Tananaev c243912c55 Update app version 2020-10-08 22:33:09 -07:00
Anton Tananaev 914a157afc Implement rating flow 2020-10-08 22:32:43 -07:00
Anton Tananaev 454ce6be21 Disable jetifier 2020-09-28 21:36:25 -07:00
Anton Tananaev e8a1e015d4 Enable multidex 2020-09-28 21:21:16 -07:00
Anton Tananaev ceb15c1378 Update version number 2020-09-28 21:07:42 -07:00
Anton Tananaev d25c745013 Update crashlytics 2020-09-28 21:07:17 -07:00
Anton Tananaev c24df5b246 Update notification priority 2020-09-20 11:16:41 -07:00
Anton Tananaev 283d0467b4 Add option to disable wakelock 2020-09-20 11:08:10 -07:00
Anton Tananaev 83bf1daa84 Use different method 2020-09-20 11:01:03 -07:00
Anton Tananaev 96d61c4c66 Update libraries 2020-09-20 10:50:21 -07:00
Anton Tananaev 3978a3d8d6 Update path 2020-09-06 15:05:41 -07:00
Anton Tananaev 78018c3038 Update templates 2020-07-05 11:05:41 -07:00
Anton Tananaev 19b7c4da21 Minor project file update 2020-06-19 22:28:02 -07:00
Anton Tananaev a3d43294ce Update project and tests 2020-06-10 21:14:19 -07:00
Anton Tananaev 330dc27f0f Update version number 2020-04-22 22:30:03 -07:00
Anton Tananaev f052be83c3 Update libraries 2020-04-22 22:29:27 -07:00
Anton Tananaev c77d4b477b Update localization files 2020-04-22 22:24:43 -07:00
Anton Tananaev eb7de9eb98 Fix numeric input type 2020-02-03 21:50:28 -08:00
Anton Tananaev 3a786b14b7 Option to disable buffering (fix #411) 2020-02-02 20:55:12 -08:00
Anton Tananaev 6bad9786ec Set default flavor 2020-01-26 22:33:40 -08:00
Anton Tananaev 259aaf2000 Update Play Store icon 2019-11-30 12:52:15 -08:00
Anton Tananaev aa00a33f5b Fix build issue 2019-11-24 21:32:05 -08:00
83 изменённых файлов: 2342 добавлений и 2109 удалений
+1
Просмотреть файл
@@ -0,0 +1 @@
custom: https://www.traccar.org/donate/
-7
Просмотреть файл
@@ -1,7 +0,0 @@
<!--
Do you want to ask a question? Traccar official forum is the best place for getting support:
https://www.traccar.org/forums/
-->
+38
Просмотреть файл
@@ -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.
+20
Просмотреть файл
@@ -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.
+44 -20
Просмотреть файл
@@ -1,28 +1,41 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 29 compileSdkVersion 31
defaultConfig { defaultConfig {
applicationId "org.traccar.client" applicationId 'org.traccar.client'
buildConfigField "boolean", "HIDDEN_APP", "false" buildConfigField 'boolean', 'HIDDEN_APP', 'false'
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 31
versionCode 61 versionCode 75
versionName '6.0' versionName '6.14'
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" flavorDimensions 'default'
productFlavors { productFlavors {
regular regular {
isDefault = true
}
google google
hidden { hidden {
applicationId "org.traccar.client.hidden" applicationId 'org.traccar.client.hidden'
buildConfigField "boolean", "HIDDEN_APP", "true" buildConfigField 'boolean', 'HIDDEN_APP', 'true'
} }
} }
@@ -34,21 +47,32 @@ android {
} }
dependencies { dependencies {
implementation 'com.google.android.material:material:1.1.0-alpha10' implementation 'com.google.android.material:material:1.5.0-alpha04'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.multidex:multidex:2.0.1'
testImplementation 'junit:junit:4.12' implementation 'androidx.preference:preference-ktx:1.1.1'
testImplementation 'org.robolectric:robolectric:4.1' implementation 'androidx.test:core-ktx:1.4.0'
googleImplementation 'com.google.firebase:firebase-core:17.2.0' implementation 'androidx.core:core-ktx:1.6.0'
googleImplementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
googleImplementation "com.google.android.gms:play-services-location:17.0.0" 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")) { if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric' apply plugin: 'com.google.firebase.crashlytics'
task copyJson(type: Copy) { task copyJson(type: Copy) {
from '../../traccar-shared/google-services' from '../../environment/google-services'
into '.' into '.'
include 'traccar-client-android.json' include 'traccar-client-android.json'
rename('traccar-client-android.json', 'google-services.json') rename('traccar-client-android.json', 'google-services.json')
+61
Просмотреть файл
@@ -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
}
}
-84
Просмотреть файл
@@ -1,84 +0,0 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnSuccessListener;
public class GooglePositionProvider extends PositionProvider {
private FusedLocationProviderClient fusedLocationClient;
public GooglePositionProvider(Context context, PositionListener listener) {
super(context, listener);
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
}
@SuppressLint("MissingPermission")
public void startUpdates() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setPriority(getPriority(preferences.getString(MainFragment.KEY_ACCURACY, "medium")));
locationRequest.setInterval(distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval);
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null);
}
public void stopUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
fusedLocationClient.getLastLocation().addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
}
});
}
private static int getPriority(String accuracy) {
switch (accuracy) {
case "high":
return LocationRequest.PRIORITY_HIGH_ACCURACY;
case "low":
return LocationRequest.PRIORITY_LOW_POWER;
default:
return LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
}
}
private LocationCallback locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
if (locationResult != null) {
for (Location location : locationResult.getLocations()) {
processLocation(location);
}
}
}
};
}
+66
Просмотреть файл
@@ -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
}
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Anton Tananaev (anton@traccar.org) * Copyright 2019 - 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,14 +13,14 @@
* 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.content.Context; import android.content.Context
import org.traccar.client.PositionProvider.PositionListener
public class PositionProviderFactory { object PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { fun create(context: Context, listener: PositionListener): PositionProvider {
return new AndroidPositionProvider(context, listener); return GooglePositionProvider(context, listener)
} }
} }
+44
Просмотреть файл
@@ -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
}
}
+5 -2
Просмотреть файл
@@ -9,7 +9,8 @@
tools:replace="android:label"> tools:replace="android:label">
<activity <activity
android:name="org.traccar.client.MainActivity"> android:name="org.traccar.client.MainActivity"
android:exported="true">
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -18,7 +19,9 @@
</intent-filter> </intent-filter>
</activity> </activity>
<receiver android:name="org.traccar.client.DialLaunchReceiver" > <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>
@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Anton Tananaev (anton@traccar.org) * Copyright 2019 - 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,14 +13,14 @@
* 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.content.Context; import android.content.Context
import org.traccar.client.PositionProvider.PositionListener
public class PositionProviderFactory { object PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { fun create(context: Context, listener: PositionListener): PositionProvider {
return new GooglePositionProvider(context, listener); return AndroidPositionProvider(context, listener)
} }
} }
+15 -4
Просмотреть файл
@@ -6,7 +6,6 @@
<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" />
@@ -14,6 +13,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <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.network" />
<uses-feature android:name="android.hardware.location.gps" /> <uses-feature android:name="android.hardware.location.gps" />
@@ -32,10 +34,12 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask" /> 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" />
@@ -46,10 +50,13 @@
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
</activity-alias> </activity-alias>
<activity android:name=".StatusActivity"/> <activity
android:name=".StatusActivity"
android:exported="false" />
<activity <activity
android:name=".ShortcutActivity" android:name=".ShortcutActivity"
android:exported="true"
android:label="@string/menu_shortcuts"> android:label="@string/menu_shortcuts">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -59,12 +66,16 @@
<service <service
android:name=".TrackingService" android:name=".TrackingService"
android:exported="false"
android:foregroundServiceType="location" /> android:foregroundServiceType="location" />
<service <service
android:name=".TrackingService$HideNotificationService" /> 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>
Двоичные данные
Двоичный файл не отображается.

До

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

После

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

-110
Просмотреть файл
@@ -1,110 +0,0 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
public class AndroidPositionProvider extends PositionProvider implements LocationListener {
private LocationManager locationManager;
private String provider;
public AndroidPositionProvider(Context context, PositionListener listener) {
super(context, listener);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
provider = getProvider(preferences.getString(MainFragment.KEY_ACCURACY, "medium"));
}
@SuppressLint("MissingPermission")
public void startUpdates() {
try {
locationManager.requestLocationUpdates(
provider, distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval, 0, this);
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
public void stopUpdates() {
locationManager.removeUpdates(this);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
try {
Location location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else {
locationManager.requestSingleUpdate(provider, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}, Looper.myLooper());
}
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
private static String getProvider(String accuracy) {
switch (accuracy) {
case "high":
return LocationManager.GPS_PROVIDER;
case "low":
return LocationManager.PASSIVE_PROVIDER;
default:
return LocationManager.NETWORK_PROVIDER;
}
}
@Override
public void onLocationChanged(Location location) {
processLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
+83
Просмотреть файл
@@ -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,5 +1,5 @@
/* /*
* Copyright 2013 - 2017 Anton Tananaev (anton@traccar.org) * Copyright 2013 - 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,20 +13,19 @@
* 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.content.Context; import android.content.Context
import android.content.Intent; import android.content.Intent
import android.content.SharedPreferences; import androidx.preference.PreferenceManager
import android.preference.PreferenceManager;
public class AutostartReceiver extends WakefulBroadcastReceiver { class AutostartReceiver : WakefulBroadcastReceiver() {
@Override @Suppress("UnsafeProtectedBroadcastReceiver")
public void onReceive(Context context, Intent intent) { override fun onReceive(context: Context, intent: Intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) { if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
startWakefulForegroundService(context, new Intent(context, TrackingService.class)); startWakefulForegroundService(context, Intent(context, TrackingService::class.java))
} }
} }
+98
Просмотреть файл
@@ -0,0 +1,98 @@
/*
* 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) {
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)
}
}
}
} else if (!sharedPreferences.getBoolean(KEY_AUTOSTART_REQUESTED, false)) {
sharedPreferences.edit().putBoolean(KEY_AUTOSTART_REQUESTED, true).apply()
try {
AutoStartPermissionHelper.getInstance().getAutoStartPermission(context)
} catch (e: SecurityException) {
}
}
}
}
companion object {
private const val KEY_EXCEPTION_REQUESTED = "exceptionRequested"
private const val KEY_AUTOSTART_REQUESTED = "autostartRequested"
}
}
-180
Просмотреть файл
@@ -1,180 +0,0 @@
/*
* Copyright 2015 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.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 = 3;
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," +
"accuracy REAL," +
"battery REAL," +
"mock INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS position;");
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS position;");
onCreate(db);
}
public void insertPosition(Position position) {
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("accuracy", position.getAccuracy());
values.put("battery", position.getBattery());
values.put("mock", position.getMock() ? 1 : 0);
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.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy")));
position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery")));
position.setMock(cursor.getInt(cursor.getColumnIndex("mock")) > 0);
} 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();
}
}
+154
Просмотреть файл
@@ -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"
}
}
-37
Просмотреть файл
@@ -1,37 +0,0 @@
/*
* Copyright 2015 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;
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);
}
}
}
+38
Просмотреть файл
@@ -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
}
}
-32
Просмотреть файл
@@ -1,32 +0,0 @@
/*
* Copyright 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new MainFragment()).commit();
}
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Anton Tananaev (anton@traccar.org) * 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,14 +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.content.Context; import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
public class PositionProviderFactory { class MainActivity : AppCompatActivity() {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { override fun onCreate(savedInstanceState: Bundle?) {
return new AndroidPositionProvider(context, listener); super.onCreate(savedInstanceState)
setContentView(R.layout.main)
} }
} }
-73
Просмотреть файл
@@ -1,73 +0,0 @@
/*
* Copyright 2016 - 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
public class MainApplication extends Application {
public static final String PRIMARY_CHANNEL = "default";
@Override
public void onCreate() {
super.onCreate();
System.setProperty("http.keepAliveDuration", String.valueOf(30 * 60 * 1000));
migrateLegacyPreferences(PreferenceManager.getDefaultSharedPreferences(this));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerChannel();
}
}
@TargetApi(Build.VERSION_CODES.O)
private void registerChannel() {
NotificationChannel channel = new NotificationChannel(
PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_MIN);
channel.setLightColor(Color.GREEN);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
}
private void migrateLegacyPreferences(SharedPreferences preferences) {
String port = preferences.getString("port", null);
if (port != null) {
String host = preferences.getString("address", getString(R.string.settings_url_default_value));
String scheme = preferences.getBoolean("secure", false) ? "https" : "http";
Uri.Builder builder = new Uri.Builder();
builder.scheme(scheme).encodedAuthority(host + ":" + port).build();
SharedPreferences.Editor editor = preferences.edit();
editor.putString(MainFragment.KEY_URL, builder.toString());
editor.remove("port");
editor.remove("address");
editor.remove("secure");
editor.apply();
}
}
}
+53
Просмотреть файл
@@ -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"
}
}
-274
Просмотреть файл
@@ -1,274 +0,0 @@
/*
* Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.EditTextPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.TwoStatePreference;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.webkit.URLUtil;
import android.widget.Toast;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class MainFragment extends PreferenceFragmentCompat implements OnSharedPreferenceChangeListener {
private static final String TAG = MainFragment.class.getSimpleName();
private static final int ALARM_MANAGER_INTERVAL = 15000;
public static final String KEY_DEVICE = "id";
public static final String KEY_URL = "url";
public static final String KEY_INTERVAL = "interval";
public static final String KEY_DISTANCE = "distance";
public static final String KEY_ANGLE = "angle";
public static final String KEY_ACCURACY = "accuracy";
public static final String KEY_STATUS = "status";
private static final int PERMISSIONS_REQUEST_LOCATION = 2;
private SharedPreferences sharedPreferences;
private AlarmManager alarmManager;
private PendingIntent alarmIntent;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
removeLauncherIcon();
}
setHasOptionsMenu(true);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
addPreferencesFromResource(R.xml.preferences);
initPreferences();
findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return newValue != null && !newValue.equals("");
}
});
findPreference(KEY_URL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return (newValue != null) && validateServerURL(newValue.toString());
}
});
findPreference(KEY_INTERVAL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
int value = Integer.parseInt((String) newValue);
return value > 0;
} catch (NumberFormatException e) {
Log.w(TAG, e);
}
}
return false;
}
});
Preference.OnPreferenceChangeListener numberValidationListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
try {
int value = Integer.parseInt((String) newValue);
return value >= 0;
} catch (NumberFormatException e) {
Log.w(TAG, e);
}
}
return false;
}
};
findPreference(KEY_DISTANCE).setOnPreferenceChangeListener(numberValidationListener);
findPreference(KEY_ANGLE).setOnPreferenceChangeListener(numberValidationListener);
alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
alarmIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(getActivity(), AutostartReceiver.class), 0);
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
}
}
private void removeLauncherIcon() {
String className = MainActivity.class.getCanonicalName().replace(".MainActivity", ".Launcher");
ComponentName componentName = new ComponentName(getActivity().getPackageName(), className);
PackageManager packageManager = getActivity().getPackageManager();
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting(
componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.hidden_alert));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
@Override
public void onResume() {
super.onResume();
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
private void setPreferencesEnabled(boolean enabled) {
findPreference(KEY_DEVICE).setEnabled(enabled);
findPreference(KEY_URL).setEnabled(enabled);
findPreference(KEY_INTERVAL).setEnabled(enabled);
findPreference(KEY_DISTANCE).setEnabled(enabled);
findPreference(KEY_ANGLE).setEnabled(enabled);
findPreference(KEY_ACCURACY).setEnabled(enabled);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_STATUS)) {
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false);
} else {
stopTrackingService();
}
} else if (key.equals(KEY_DEVICE)) {
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.status) {
startActivity(new Intent(getActivity(), StatusActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
private void initPreferences() {
PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false);
if (!sharedPreferences.contains(KEY_DEVICE)) {
String id = String.valueOf(new Random().nextInt(900000) + 100000);
sharedPreferences.edit().putString(KEY_DEVICE, id).apply();
((EditTextPreference) findPreference(KEY_DEVICE)).setText(id);
}
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null));
}
private void startTrackingService(boolean checkPermission, boolean permission) {
if (checkPermission) {
Set<String> requiredPermissions = new HashSet<>();
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
}
permission = requiredPermissions.isEmpty();
if (!permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(requiredPermissions.toArray(new String[requiredPermissions.size()]), PERMISSIONS_REQUEST_LOCATION);
}
return;
}
}
if (permission) {
setPreferencesEnabled(false);
ContextCompat.startForegroundService(getContext(), new Intent(getActivity(), TrackingService.class));
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
ALARM_MANAGER_INTERVAL, ALARM_MANAGER_INTERVAL, alarmIntent);
} else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply();
TwoStatePreference preference = findPreference(KEY_STATUS);
preference.setChecked(false);
}
}
private void stopTrackingService() {
alarmManager.cancel(alarmIntent);
getActivity().stopService(new Intent(getActivity(), TrackingService.class));
setPreferencesEnabled(true);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
boolean granted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
granted = false;
break;
}
}
startTrackingService(false, granted);
}
}
private boolean validateServerURL(String userUrl) {
int port = Uri.parse(userUrl).getPort();
if (URLUtil.isValidUrl(userUrl) && (port == -1 || (port > 0 && port <= 65535))
&& (URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))) {
return true;
}
Toast.makeText(getActivity(), R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show();
return false;
}
}
+297
Просмотреть файл
@@ -0,0 +1,297 @@
/*
* 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
@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_MUTABLE
} else {
0
}
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 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 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))
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
ALARM_MANAGER_INTERVAL.toLong(), ALARM_MANAGER_INTERVAL.toLong(), alarmIntent
)
BatteryOptimizationHelper().requestException(requireContext())
} else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply()
val preference = findPreference<TwoStatePreference>(KEY_STATUS)
preference?.isChecked = false
}
}
private fun stopTrackingService() {
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
}
}
-68
Просмотреть файл
@@ -1,68 +0,0 @@
/*
* Copyright 2015 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 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);
}
}
}
+62
Просмотреть файл
@@ -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
}
}
-156
Просмотреть файл
@@ -1,156 +0,0 @@
/*
* Copyright 2015 - 2018 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.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();
if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
accuracy = location.getAccuracy();
}
this.battery = battery;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
this.mock = location.isFromMockProvider();
}
}
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 accuracy;
public double getAccuracy() {
return accuracy;
}
public void setAccuracy(double accuracy) {
this.accuracy = accuracy;
}
private double battery;
public double getBattery() {
return battery;
}
public void setBattery(double battery) {
this.battery = battery;
}
private boolean mock;
public boolean getMock() {
return mock;
}
public void setMock(boolean mock) {
this.mock = mock;
}
}
+49
Просмотреть файл
@@ -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,
)
}
-91
Просмотреть файл
@@ -1,91 +0,0 @@
/*
* Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.BatteryManager;
import android.preference.PreferenceManager;
import android.util.Log;
public abstract class PositionProvider {
private static final String TAG = PositionProvider.class.getSimpleName();
protected static final int MINIMUM_INTERVAL = 1000;
public interface PositionListener {
void onPositionUpdate(Position position);
void onPositionError(Throwable error);
}
protected final PositionListener listener;
protected final Context context;
protected SharedPreferences preferences;
protected String deviceId;
protected long interval;
protected double distance;
protected double angle;
protected Location lastLocation;
public PositionProvider(Context context, PositionListener listener) {
this.context = context;
this.listener = listener;
preferences = PreferenceManager.getDefaultSharedPreferences(context);
deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined");
interval = Long.parseLong(preferences.getString(MainFragment.KEY_INTERVAL, "600")) * 1000;
distance = Integer.parseInt(preferences.getString(MainFragment.KEY_DISTANCE, "0"));
angle = Integer.parseInt(preferences.getString(MainFragment.KEY_ANGLE, "0"));
}
public abstract void startUpdates();
public abstract void stopUpdates();
public abstract void requestSingleLocation();
protected void processLocation(Location location) {
if (location != null && (lastLocation == null
|| location.getTime() - lastLocation.getTime() >= interval
|| distance > 0 && location.distanceTo(lastLocation) >= distance
|| angle > 0 && Math.abs(location.getBearing() - lastLocation.getBearing()) >= angle)) {
Log.i(TAG, "location new");
lastLocation = location;
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else {
Log.i(TAG, location != null ? "location ignored" : "location nil");
}
}
protected static double getBatteryLevel(Context context) {
Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
if (batteryIntent != null) {
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
return (level * 100.0) / scale;
}
return 0;
}
}
+78
Просмотреть файл
@@ -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
}
}
-49
Просмотреть файл
@@ -1,49 +0,0 @@
/*
* Copyright 2012 - 2016 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;
public class ProtocolFormatter {
public static String formatRequest(String url, Position position) {
return formatRequest(url, position, null);
}
public static String formatRequest(String url, Position position, String alarm) {
Uri serverUrl = Uri.parse(url);
Uri.Builder builder = serverUrl.buildUpon()
.appendQueryParameter("id", position.getDeviceId())
.appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000))
.appendQueryParameter("lat", String.valueOf(position.getLatitude()))
.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("accuracy", String.valueOf(position.getAccuracy()))
.appendQueryParameter("batt", String.valueOf(position.getBattery()));
if (position.getMock()) {
builder.appendQueryParameter("mock", String.valueOf(position.getMock()));
}
if (alarm != null) {
builder.appendQueryParameter("alarm", alarm);
}
return builder.build().toString();
}
}
+42
Просмотреть файл
@@ -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()
}
}
-83
Просмотреть файл
@@ -1,83 +0,0 @@
/*
* Copyright 2015 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.os.AsyncTask;
import android.util.Log;
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.setRequestMethod("POST");
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) {
Log.w(RequestManager.class.getSimpleName(), secondError);
}
}
}
public static void sendRequestAsync(String request, RequestHandler handler) {
RequestAsyncTask task = new RequestAsyncTask(handler);
task.execute(request);
}
}
+71
Просмотреть файл
@@ -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)
}
}
}
-164
Просмотреть файл
@@ -1,164 +0,0 @@
/*
* Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;
public class ShortcutActivity extends AppCompatActivity {
public static final String EXTRA_ACTION = "action";
public static final String ACTION_START = "start";
public static final String ACTION_STOP = "stop";
public static final String ACTION_SOS = "sos";
private static final String ALARM_SOS = "sos";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!executeAction(getIntent())) {
setContentView(R.layout.list);
final String[] items = new String[] {
getString(R.string.shortcut_start),
getString(R.string.shortcut_stop),
getString(R.string.shortcut_sos)
};
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(new ArrayAdapter<>(
this, android.R.layout.simple_list_item_1, items));
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START);
break;
case 1:
setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP);
break;
case 2:
setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS);
break;
}
finish();
}
});
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
executeAction(intent);
}
private void setShortcutResult(String label, @DrawableRes int iconResId, String action) {
Intent intent = new Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity.class);
intent.putExtra(EXTRA_ACTION, action);
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, action)
.setShortLabel(label)
.setIcon(IconCompat.createWithResource(this, iconResId))
.setIntent(intent)
.build();
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut));
}
@SuppressWarnings("MissingPermission")
private void sendAlarm() {
PositionProviderFactory.create(this, new PositionProvider.PositionListener() {
@Override
public void onPositionUpdate(Position position) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ShortcutActivity.this);
String request = ProtocolFormatter.formatRequest(
preferences.getString(MainFragment.KEY_URL, null), position, ALARM_SOS);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override
public void onComplete(boolean success) {
if (success) {
Toast.makeText(ShortcutActivity.this, R.string.status_send_success, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(ShortcutActivity.this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onPositionError(Throwable error) {
Toast.makeText(ShortcutActivity.this, error.getMessage(), Toast.LENGTH_LONG).show();
}
}).requestSingleLocation();
}
private boolean executeAction(Intent intent) {
String action;
if (intent.hasExtra("shortcutAction")) {
action = intent.getBooleanExtra("shortcutAction", false)
? ACTION_START : ACTION_STOP;
} else {
action = intent.getStringExtra(EXTRA_ACTION);
}
if (action != null) {
switch (action) {
case ACTION_START:
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainFragment.KEY_STATUS, true).apply();
ContextCompat.startForegroundService(this, new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show();
break;
case ACTION_STOP:
PreferenceManager.getDefaultSharedPreferences(this)
.edit().putBoolean(MainFragment.KEY_STATUS, false).apply();
stopService(new Intent(this, TrackingService.class));
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show();
break;
case ACTION_SOS:
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
sendAlarm();
} else {
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show();
}
break;
}
finish();
}
return action != null;
}
}
+138
Просмотреть файл
@@ -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"
}
}
-94
Просмотреть файл
@@ -1,94 +0,0 @@
/*
* Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class StatusActivity extends AppCompatActivity {
private static final int LIMIT = 20;
private static final LinkedList<String> messages = new LinkedList<>();
private static final Set<ArrayAdapter<String>> adapters = new HashSet<>();
private static void notifyAdapters() {
for (ArrayAdapter<String> adapter : adapters) {
adapter.notifyDataSetChanged();
}
}
public static void addMessage(String message) {
DateFormat format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
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.list);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages);
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(adapter);
adapters.add(adapter);
}
@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);
}
}
+86
Просмотреть файл
@@ -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()
}
}
}
-197
Просмотреть файл
@@ -1,197 +0,0 @@
/*
* Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
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 url;
private PositionProvider positionProvider;
private DatabaseHelper databaseHelper;
private NetworkManager networkManager;
public TrackingController(Context context) {
this.context = context;
handler = new Handler();
preferences = PreferenceManager.getDefaultSharedPreferences(context);
positionProvider = PositionProviderFactory.create(context, this);
databaseHelper = new DatabaseHelper(context);
networkManager = new NetworkManager(context, this);
isOnline = networkManager.isOnline();
url = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value));
}
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 onPositionError(Throwable error) {
}
@Override
public void onNetworkUpdate(boolean isOnline) {
int message = isOnline ? R.string.status_network_online : 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 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);
databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() {
@Override
public void onComplete(boolean success, Void result) {
if (success) {
if (isOnline && isWaiting) {
read();
isWaiting = false;
}
}
}
});
}
private void read() {
log("read", null);
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(MainFragment.KEY_DEVICE, null))) {
send(result);
} else {
delete(result);
}
} else {
isWaiting = true;
}
} else {
retry();
}
}
});
}
private void delete(Position position) {
log("delete", position);
databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() {
@Override
public void onComplete(boolean success, Void result) {
if (success) {
read();
} else {
retry();
}
}
});
}
private void send(final Position position) {
log("send", position);
String request = ProtocolFormatter.formatRequest(url, 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();
}
}
});
}
private void retry() {
log("retry", null);
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (isOnline) {
read();
}
}
}, RETRY_DELAY);
}
}
+186
Просмотреть файл
@@ -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
}
}
-132
Просмотреть файл
@@ -1,132 +0,0 @@
/*
* Copyright 2012 - 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.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 androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import android.os.PowerManager;
import android.util.Log;
public class TrackingService extends Service {
private static final String TAG = TrackingService.class.getSimpleName();
private static final int NOTIFICATION_ID = 1;
private PowerManager.WakeLock wakeLock;
private TrackingController trackingController;
private static Notification createNotification(Context context) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
.setSmallIcon(R.drawable.ic_stat_notify)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_SERVICE);
Intent intent;
if (!BuildConfig.HIDDEN_APP) {
intent = new Intent(context, MainActivity.class);
builder
.setContentTitle(context.getString(R.string.settings_status_on_summary))
.setTicker(context.getString(R.string.settings_status_on_summary))
.setColor(ContextCompat.getColor(context, R.color.primary_dark));
} else {
intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
}
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
return builder.build();
}
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;
}
}
@SuppressLint("WakelockTimeout")
@Override
public void onCreate() {
Log.i(TAG, "service create");
StatusActivity.addMessage(getString(R.string.status_service_create));
startForeground(NOTIFICATION_ID, createNotification(this));
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
wakeLock.acquire();
trackingController = new TrackingController(this);
trackingController.start();
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(this, new Intent(this, HideNotificationService.class));
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
AutostartReceiver.completeWakefulIntent(intent);
}
return START_STICKY;
}
@Override
public void onDestroy() {
Log.i(TAG, "service destroy");
StatusActivity.addMessage(getString(R.string.status_service_destroy));
stopForeground(true);
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
if (trackingController != null) {
trackingController.stop();
}
}
}
+134
Просмотреть файл
@@ -0,0 +1,134 @@
/*
* 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() {
Log.i(TAG, "service create")
sendBroadcast(Intent(ACTION_STARTED))
StatusActivity.addMessage(getString(R.string.status_service_create))
startForeground(NOTIFICATION_ID, createNotification(this))
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() {
Log.i(TAG, "service destroy")
sendBroadcast(Intent(ACTION_STOPPED))
StatusActivity.addMessage(getString(R.string.status_service_destroy))
stopForeground(true)
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()
}
}
}
-62
Просмотреть файл
@@ -1,62 +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 org.traccar.client;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import androidx.core.content.ContextCompat;
import android.util.SparseArray;
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks = new SparseArray<>();
private static int mNextId = 1;
public static void startWakefulForegroundService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ContextCompat.startForegroundService(context, intent);
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WakefulBroadcastReceiver.class.getSimpleName());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
}
}
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
return true;
}
}
}
+70
Просмотреть файл
@@ -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
}
}
}
}
+13
Просмотреть файл
@@ -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>
+5
Просмотреть файл
@@ -8,4 +8,9 @@
android:title="@string/menu_status" android:title="@string/menu_status"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/info"
android:title="@string/menu_info"
app:showAsAction="ifRoom" />
</menu> </menu>
+46
Просмотреть файл
@@ -0,0 +1,46 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Traccar-kliënt</string>
<string name="app_logo">Traccar</string>
<string name="shortcut_start">Begin diens</string>
<string name="shortcut_stop">Stop diens</string>
<string name="shortcut_sos">Stuur SOS</string>
<string name="channel_default">Primêre Kanaal</string>
<string name="settings_id_title">Toestel-identifiseerder</string>
<string name="settings_url_title">Bediener-URL</string>
<string name="settings_url_summary">Volg bediener-URL</string>
<string name="settings_interval_title">Frekwensie</string>
<string name="settings_interval_summary">Rapporteer interval in sekondes</string>
<string name="settings_distance_title">Afstand</string>
<string name="settings_distance_summary">Rapporteer afstand in meter</string>
<string name="settings_angle_title">Hoek</string>
<string name="settings_angle_summary">Rapporteringshoek in grade</string>
<string name="settings_status_title">Diensstatus</string>
<string name="settings_status_off">Begin</string>
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Diens gestop</string>
<string name="settings_status_on_summary">Diens loop</string>
<string name="settings_buffer">Vanlyn buffer</string>
<string name="settings_buffer_off_summary">Buffer af</string>
<string name="settings_buffer_on_summary">Buffer aan</string>
<string name="settings_accuracy_title">Ligging akkuraatheid</string>
<string name="settings_accuracy_summary">Verlangde ligging akkuraatheid</string>
<string name="settings_accuracy_high">Hoog</string>
<string name="settings_accuracy_medium">Medium</string>
<string name="settings_accuracy_low">Laag</string>
<string name="settings_foreground_title">Voorgrond diens</string>
<string name="settings_foreground_summary">Verhoog diensprioriteit</string>
<string name="menu_status">Status</string>
<string name="menu_shortcuts">Voeg kortpaaie by</string>
<string name="menu_clear">Skoonmaak</string>
<string name="status_service_create">Diens het begin</string>
<string name="status_service_destroy">Diens het gestop</string>
<string name="status_send_success">Stuur suksesvol</string>
<string name="status_send_fail">Stuur misluk</string>
<string name="status_location_update">Ligging opdateer</string>
<string name="status_network_online">Netwerk aanlyn</string>
<string name="status_network_offline">Netwerk vanlyn</string>
<string name="hidden_app_name">Toestelinstellings</string>
<string name="hidden_alert">Die toep is versteek. Skakel 8722227 (TRACCAR) om dit weer oop te maak.</string>
<string name="error_msg_invalid_url">Voer \'n geldige http: // of https: // URL in</string>
</resources>
+8 -2
Просмотреть файл
@@ -20,9 +20,15 @@
<string name="settings_status_on">إيقاف</string> <string name="settings_status_on">إيقاف</string>
<string name="settings_status_off_summary">الخدمة توقفت</string> <string name="settings_status_off_summary">الخدمة توقفت</string>
<string name="settings_status_on_summary">الخدمة تعمل</string> <string name="settings_status_on_summary">الخدمة تعمل</string>
<string name="settings_buffer">التخزين المؤقت دون اتصال</string>
<string name="settings_buffer_off_summary">التخزين المؤقت غير مفعل</string>
<string name="settings_buffer_on_summary">التخزين المؤقت مفعل</string>
<string name="settings_wakelock">قفل الإيقاظ</string>
<string name="settings_wakelock_off_summary">قفل الإيقاظ غير مفعل</string>
<string name="settings_wakelock_on_summary">قفل الإيقاظ مفعل</string>
<string name="settings_accuracy_title">دقة الموقع</string> <string name="settings_accuracy_title">دقة الموقع</string>
<string name="settings_accuracy_summary">دقة الموقع المطلوبة</string> <string name="settings_accuracy_summary">دقة الموقع المطلوبة</string>
<string name="settings_accuracy_high">عالي</string> <string name="settings_accuracy_high">مرتفع</string>
<string name="settings_accuracy_medium">متوسط</string> <string name="settings_accuracy_medium">متوسط</string>
<string name="settings_accuracy_low">منخفض</string> <string name="settings_accuracy_low">منخفض</string>
<string name="settings_foreground_title">الخدمة الأمامية</string> <string name="settings_foreground_title">الخدمة الأمامية</string>
@@ -39,5 +45,5 @@
<string name="status_network_offline">غير متصل بالشبكة</string> <string name="status_network_offline">غير متصل بالشبكة</string>
<string name="hidden_app_name">إعدادات الجهاز</string> <string name="hidden_app_name">إعدادات الجهاز</string>
<string name="hidden_alert">تم إخفاء التطبيق. لفتحه مجددا رجاءا اتصل بـ8722227 (TRACCAR).</string> <string name="hidden_alert">تم إخفاء التطبيق. لفتحه مجددا رجاءا اتصل بـ8722227 (TRACCAR).</string>
<string name="error_msg_invalid_url">أرجو إدخال رابط صحيح http:// أو https://</string> <string name="error_msg_invalid_url">الرجاء إدخال رابط صحيح http:// أو https://</string>
</resources> </resources>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Zastaveno</string> <string name="settings_status_off_summary">Zastaveno</string>
<string name="settings_status_on_summary">Spuštěno</string> <string name="settings_status_on_summary">Spuštěno</string>
<string name="settings_buffer">Ukládání do vyrovnávací paměti</string>
<string name="settings_buffer_off_summary">Vyrovnávací pamět vypnuta</string>
<string name="settings_buffer_on_summary">Vyrovnávací pamět zapnuta</string>
<string name="settings_wakelock">Zámek probuzení</string>
<string name="settings_wakelock_off_summary">Zámek probuzení vypnut</string>
<string name="settings_wakelock_on_summary">Zámek probuzení zapnut</string>
<string name="settings_accuracy_title">Přesnost umístění</string> <string name="settings_accuracy_title">Přesnost umístění</string>
<string name="settings_accuracy_summary">Požadovaná přesnost umístění</string> <string name="settings_accuracy_summary">Požadovaná přesnost umístění</string>
<string name="settings_accuracy_high">Vysoká</string> <string name="settings_accuracy_high">Vysoká</string>
+13
Просмотреть файл
@@ -20,6 +20,17 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Tjenesten stoppet</string> <string name="settings_status_off_summary">Tjenesten stoppet</string>
<string name="settings_status_on_summary">Tjenesten er aktiv</string> <string name="settings_status_on_summary">Tjenesten er aktiv</string>
<string name="settings_buffer">Offline buffering</string>
<string name="settings_buffer_off_summary">buffering off</string>
<string name="settings_buffer_on_summary">buffering on</string>
<string name="settings_wakelock">Vågelås</string>
<string name="settings_wakelock_off_summary">Vågelås off</string>
<string name="settings_wakelock_on_summary">Vågelås on</string>
<string name="settings_accuracy_title">Placeringsnøjagtighed</string>
<string name="settings_accuracy_summary">Ønsket placeringsnøjagtighed</string>
<string name="settings_accuracy_high">Høj</string>
<string name="settings_accuracy_medium">Mellem</string>
<string name="settings_accuracy_low">Lav</string>
<string name="settings_foreground_title">Forgrunds tjeneste</string> <string name="settings_foreground_title">Forgrunds tjeneste</string>
<string name="settings_foreground_summary">Øg tjeneste prioritet</string> <string name="settings_foreground_summary">Øg tjeneste prioritet</string>
<string name="menu_status">Status</string> <string name="menu_status">Status</string>
@@ -30,6 +41,8 @@
<string name="status_send_success">Send succesfuld</string> <string name="status_send_success">Send succesfuld</string>
<string name="status_send_fail">Send fejlede</string> <string name="status_send_fail">Send fejlede</string>
<string name="status_location_update">Placering opdateret</string> <string name="status_location_update">Placering opdateret</string>
<string name="status_network_online">Netværk online</string>
<string name="status_network_offline">Netværk offline</string>
<string name="hidden_app_name">Enheds indstillinger</string> <string name="hidden_app_name">Enheds indstillinger</string>
<string name="hidden_alert">Appen er skjult. For at åbne appen igen skal du ringe til 8722227 (TRACCAR)</string> <string name="hidden_alert">Appen er skjult. For at åbne appen igen skal du ringe til 8722227 (TRACCAR)</string>
<string name="error_msg_invalid_url">Indtast gyldig http:// eller https:// adresse</string> <string name="error_msg_invalid_url">Indtast gyldig http:// eller https:// adresse</string>
+3
Просмотреть файл
@@ -20,6 +20,9 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Dienst inaktiv</string> <string name="settings_status_off_summary">Dienst inaktiv</string>
<string name="settings_status_on_summary">Dienst aktiv</string> <string name="settings_status_on_summary">Dienst aktiv</string>
<string name="settings_buffer">Offline puffern</string>
<string name="settings_buffer_off_summary">Puffern aus</string>
<string name="settings_buffer_on_summary">Puffern ein</string>
<string name="settings_accuracy_title">Positionsgenauigkeit</string> <string name="settings_accuracy_title">Positionsgenauigkeit</string>
<string name="settings_accuracy_summary">Erwünschte Positionsgenauigkeit</string> <string name="settings_accuracy_summary">Erwünschte Positionsgenauigkeit</string>
<string name="settings_accuracy_high">Hoch</string> <string name="settings_accuracy_high">Hoch</string>
+16 -10
Просмотреть файл
@@ -1,14 +1,14 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources> <resources>
<string name="app_name">πελάτης Traccar</string> <string name="app_name">Εφαρμογή Traccar</string>
<string name="app_logo">Traccar</string> <string name="app_logo">Traccar</string>
<string name="shortcut_start">Εκκίνηση υπηρεσίας</string> <string name="shortcut_start">Εκκίνηση υπηρεσίας</string>
<string name="shortcut_stop">Διακοπή υπηρεσίας</string> <string name="shortcut_stop">Διακοπή υπηρεσίας</string>
<string name="shortcut_sos">Αποστολή SOS</string> <string name="shortcut_sos">Αποστολή SOS</string>
<string name="channel_default">Κύριο Κανάλι</string> <string name="channel_default">Κύριο κανάλι</string>
<string name="settings_id_title">Αναγνωριστικό συσκευής</string> <string name="settings_id_title">Αναγνωριστικό συσκευής</string>
<string name="settings_url_title">Διεύθυνση εξυπηρετητή</string> <string name="settings_url_title">Διεύθυνση διακομιστή</string>
<string name="settings_url_summary">Διεύθυνση εξυπηρετητή παρακολούθησης</string> <string name="settings_url_summary">Διεύθυνση διακομιστή παρακολούθησης</string>
<string name="settings_interval_title">Συχνότητα</string> <string name="settings_interval_title">Συχνότητα</string>
<string name="settings_interval_summary">Διάστημα αναφορών σε δευτερόλεπτα</string> <string name="settings_interval_summary">Διάστημα αναφορών σε δευτερόλεπτα</string>
<string name="settings_distance_title">Απόσταση</string> <string name="settings_distance_title">Απόσταση</string>
@@ -20,20 +20,26 @@
<string name="settings_status_on">Παύση</string> <string name="settings_status_on">Παύση</string>
<string name="settings_status_off_summary">Η υπηρεσία σταμάτησε</string> <string name="settings_status_off_summary">Η υπηρεσία σταμάτησε</string>
<string name="settings_status_on_summary">Η υπηρεσία εκτελείται</string> <string name="settings_status_on_summary">Η υπηρεσία εκτελείται</string>
<string name="settings_buffer">Εκτός σύνδεσης ενδιάμεση αποθήκευση</string>
<string name="settings_buffer_off_summary">Ενδιάμεση αποθήκευση απενεργοποιημένη</string>
<string name="settings_buffer_on_summary">Ενδιάμεση αποθήκευση ενεργοποιημένη</string>
<string name="settings_wakelock">Κλείδωμα αφύπνισης</string>
<string name="settings_wakelock_off_summary">Κλείδωμα αφύπνισης απενεργοποιημένο</string>
<string name="settings_wakelock_on_summary">Κλείδωμα αφύπνισης ενεργοποιημένο</string>
<string name="settings_accuracy_title">Ακρίβεια θέσης</string> <string name="settings_accuracy_title">Ακρίβεια θέσης</string>
<string name="settings_accuracy_summary">Επιθυμητή ακρίβεια θέσης</string> <string name="settings_accuracy_summary">Επιθυμητή ακρίβεια θέσης</string>
<string name="settings_accuracy_high">Υψηλό</string> <string name="settings_accuracy_high">Υψηλή</string>
<string name="settings_accuracy_medium">Μέσο</string> <string name="settings_accuracy_medium">Μέση</string>
<string name="settings_accuracy_low">Χαμηλό</string> <string name="settings_accuracy_low">Χαμηλή</string>
<string name="settings_foreground_title">Υπηρεσία προσκηνίου</string> <string name="settings_foreground_title">Υπηρεσία προσκηνίου</string>
<string name="settings_foreground_summary">Αύξηση προτεραιότητας της υπηρεσίας</string> <string name="settings_foreground_summary">Αύξηση προτεραιότητας της υπηρεσίας</string>
<string name="menu_status">Κατάσταση</string> <string name="menu_status">Κατάσταση</string>
<string name="menu_shortcuts">Προσθήκη συντόμευσης</string> <string name="menu_shortcuts">Προσθήκη συντόμευσης</string>
<string name="menu_clear">Καθαρισμός</string> <string name="menu_clear">Καθαρισμός</string>
<string name="status_service_create">Η Υπηρεσία εκκινήθηκε</string> <string name="status_service_create">Η υπηρεσία εκκινήθηκε</string>
<string name="status_service_destroy">Η Υπηρεσία διακόπηκε</string> <string name="status_service_destroy">Η υπηρεσία διακόπηκε</string>
<string name="status_send_success">Απεστάλη επιτυχώς</string> <string name="status_send_success">Απεστάλη επιτυχώς</string>
<string name="status_send_fail">Αποτυχημένη αποστολή</string> <string name="status_send_fail">Η αποστολή απέτυχε</string>
<string name="status_location_update">Ενημέρωση τοποθεσίας</string> <string name="status_location_update">Ενημέρωση τοποθεσίας</string>
<string name="status_network_online">Ενεργό δίκτυο</string> <string name="status_network_online">Ενεργό δίκτυο</string>
<string name="status_network_offline">Ανενεργό δίκτυο</string> <string name="status_network_offline">Ανενεργό δίκτυο</string>
+13 -7
Просмотреть файл
@@ -16,15 +16,21 @@
<string name="settings_angle_title">Ángulo</string> <string name="settings_angle_title">Ángulo</string>
<string name="settings_angle_summary">Ángulo para reportes en grados</string> <string name="settings_angle_summary">Ángulo para reportes en grados</string>
<string name="settings_status_title">Estado del servicio</string> <string name="settings_status_title">Estado del servicio</string>
<string name="settings_status_off">Activado</string> <string name="settings_status_off">Activar</string>
<string name="settings_status_on">Desactivado</string> <string name="settings_status_on">Desactivar</string>
<string name="settings_status_off_summary">Servicio detenido</string> <string name="settings_status_off_summary">Servicio detenido</string>
<string name="settings_status_on_summary">Servicio en ejecución</string> <string name="settings_status_on_summary">Servicio en ejecución</string>
<string name="settings_accuracy_title">Precisión</string> <string name="settings_buffer">Búfer fuera de línea</string>
<string name="settings_accuracy_summary">Precisión deseada</string> <string name="settings_buffer_off_summary">Búfer activado</string>
<string name="settings_accuracy_high">Alto</string> <string name="settings_buffer_on_summary">Búfer desactivado</string>
<string name="settings_accuracy_medium">Medio</string> <string name="settings_wakelock">Estado despierto forzado</string>
<string name="settings_accuracy_low">Bajo</string> <string name="settings_wakelock_off_summary">Estado despierto forzado activado</string>
<string name="settings_wakelock_on_summary">Estado despierto forzado desactivado</string>
<string name="settings_accuracy_title">Precisión de posición</string>
<string name="settings_accuracy_summary">Precisión de posición deseada</string>
<string name="settings_accuracy_high">Alta</string>
<string name="settings_accuracy_medium">Media</string>
<string name="settings_accuracy_low">Baja</string>
<string name="settings_foreground_title">Servicio en primer plano</string> <string name="settings_foreground_title">Servicio en primer plano</string>
<string name="settings_foreground_summary">Aumentar prioridad del servicio</string> <string name="settings_foreground_summary">Aumentar prioridad del servicio</string>
<string name="menu_status">Estado</string> <string name="menu_status">Estado</string>
+3
Просмотреть файл
@@ -20,6 +20,9 @@
<string name="settings_status_on">Pysäytä</string> <string name="settings_status_on">Pysäytä</string>
<string name="settings_status_off_summary">Palvelu pysäytetty</string> <string name="settings_status_off_summary">Palvelu pysäytetty</string>
<string name="settings_status_on_summary">Palvelu käynnissä</string> <string name="settings_status_on_summary">Palvelu käynnissä</string>
<string name="settings_buffer">Offline-puskurointi</string>
<string name="settings_buffer_off_summary">Puskurointi pois päältä</string>
<string name="settings_buffer_on_summary">Puskurointi päälle</string>
<string name="settings_accuracy_title">Sijainnin tarkkuus</string> <string name="settings_accuracy_title">Sijainnin tarkkuus</string>
<string name="settings_accuracy_summary">Haluttu sijainnin tarkkuus</string> <string name="settings_accuracy_summary">Haluttu sijainnin tarkkuus</string>
<string name="settings_accuracy_high">Korkea</string> <string name="settings_accuracy_high">Korkea</string>
+11
Просмотреть файл
@@ -4,6 +4,8 @@
<string name="app_logo">Traccar</string> <string name="app_logo">Traccar</string>
<string name="shortcut_start">Pokreni servis</string> <string name="shortcut_start">Pokreni servis</string>
<string name="shortcut_stop">Zaustavi servis</string> <string name="shortcut_stop">Zaustavi servis</string>
<string name="shortcut_sos">Pošalji SOS</string>
<string name="channel_default">Primarni kanal</string>
<string name="settings_id_title">Identifikator uređaja</string> <string name="settings_id_title">Identifikator uređaja</string>
<string name="settings_url_title">Adresa poslužitelja</string> <string name="settings_url_title">Adresa poslužitelja</string>
<string name="settings_url_summary">Adresa poslužitelja za praćenje</string> <string name="settings_url_summary">Adresa poslužitelja za praćenje</string>
@@ -18,7 +20,14 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Servis zaustavljen</string> <string name="settings_status_off_summary">Servis zaustavljen</string>
<string name="settings_status_on_summary">Servis pokrenut</string> <string name="settings_status_on_summary">Servis pokrenut</string>
<string name="settings_buffer">Offline memoriranje</string>
<string name="settings_buffer_off_summary">Predmemoriranje isključeno</string>
<string name="settings_buffer_on_summary">Predmemoriranje uključeno</string>
<string name="settings_wakelock">Gašenje ekrana pri neaktivnosti</string>
<string name="settings_wakelock_off_summary">Gasi ekran pri neaktivnosti</string>
<string name="settings_wakelock_on_summary">Ne gasi ekran pri neaktivnosti</string>
<string name="settings_accuracy_title">Točnost lokacije</string> <string name="settings_accuracy_title">Točnost lokacije</string>
<string name="settings_accuracy_summary">Željena preciznost lokacije</string>
<string name="settings_accuracy_high">Visoki</string> <string name="settings_accuracy_high">Visoki</string>
<string name="settings_accuracy_medium">Srednji</string> <string name="settings_accuracy_medium">Srednji</string>
<string name="settings_accuracy_low">Niski</string> <string name="settings_accuracy_low">Niski</string>
@@ -32,6 +41,8 @@
<string name="status_send_success">Slanje uspješno</string> <string name="status_send_success">Slanje uspješno</string>
<string name="status_send_fail">Greška pri slanju</string> <string name="status_send_fail">Greška pri slanju</string>
<string name="status_location_update">Osvežavanje lokacije</string> <string name="status_location_update">Osvežavanje lokacije</string>
<string name="status_network_online">Mrežna dostupnost</string>
<string name="status_network_offline">Mrežna nedostupnost</string>
<string name="hidden_app_name">Podešavanje uređaja</string> <string name="hidden_app_name">Podešavanje uređaja</string>
<string name="hidden_alert">Aplikacija je skrivena. Za ponovno otvaranje molim nazovite 8722227 (TRACCAR).</string> <string name="hidden_alert">Aplikacija je skrivena. Za ponovno otvaranje molim nazovite 8722227 (TRACCAR).</string>
<string name="error_msg_invalid_url">Molimo unesite ispravnu http:// ili https:// adresu</string> <string name="error_msg_invalid_url">Molimo unesite ispravnu http:// ili https:// adresu</string>
+43
Просмотреть файл
@@ -0,0 +1,43 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name"> Traccar לאנדרויד</string>
<string name="app_logo">Traccar</string>
<string name="shortcut_start">הפעל שירות </string>
<string name="shortcut_stop">עצור תהליך</string>
<string name="shortcut_sos">שלח הודעת חירום</string>
<string name="channel_default">ערוץ ראשי</string>
<string name="settings_id_title">מזהה מכשיר</string>
<string name="settings_url_title">כתובת שרת</string>
<string name="settings_url_summary">כתובת שרת איתור</string>
<string name="settings_interval_title">תדירות</string>
<string name="settings_interval_summary">זמן דיווח בשניות </string>
<string name="settings_distance_title">מרחק</string>
<string name="settings_distance_summary">דיווח מרחק במטרים</string>
<string name="settings_angle_title">זווית</string>
<string name="settings_angle_summary">דיווח פניות בזווית </string>
<string name="settings_status_title">מצב שירות</string>
<string name="settings_status_off">הפעל</string>
<string name="settings_status_on">עצור</string>
<string name="settings_status_off_summary">השירות הופסק</string>
<string name="settings_status_on_summary">השירות פועל</string>
<string name="settings_accuracy_title">דיוק מיקום</string>
<string name="settings_accuracy_summary">דיוק מיקום מבוקש</string>
<string name="settings_accuracy_high">מדוייק </string>
<string name="settings_accuracy_medium">בינוני</string>
<string name="settings_accuracy_low">נמוך - משתמש רק בGPS של המכשיר </string>
<string name="settings_foreground_title">אפשר הפעלה ברקע</string>
<string name="settings_foreground_summary">הגדל את עדיפות התוכנה</string>
<string name="menu_status">סטטוס</string>
<string name="menu_shortcuts">הוסף קיצור דרך</string>
<string name="menu_clear">נקה</string>
<string name="status_service_create">אפליקציה הופעלה</string>
<string name="status_service_destroy">אפליקציה הופסקה</string>
<string name="status_send_success">נשלח בהצלחה</string>
<string name="status_send_fail">שליחה נכשלה</string>
<string name="status_location_update">עדכון מיקום</string>
<string name="status_network_online">רשת באונליין </string>
<string name="status_network_offline">רשת מנותקת</string>
<string name="hidden_app_name">הגדרות המכשיר</string>
<string name="hidden_alert">האפליקציה הוסתרה. כדי לפתוח אותה שוב חייג בטלפון 8722227 (TRACCAR).</string>
<string name="error_msg_invalid_url">הזן כתובת חוקית http:// או https:// URL</string>
</resources>
+3
Просмотреть файл
@@ -20,6 +20,9 @@
<string name="settings_status_on">停止</string> <string name="settings_status_on">停止</string>
<string name="settings_status_off_summary">サービス停止中</string> <string name="settings_status_off_summary">サービス停止中</string>
<string name="settings_status_on_summary">サービス実行中</string> <string name="settings_status_on_summary">サービス実行中</string>
<string name="settings_buffer">オフラインバッファリング</string>
<string name="settings_buffer_off_summary">バッファリング オフ</string>
<string name="settings_buffer_on_summary">バッファリング オン</string>
<string name="settings_accuracy_title">位置情報の精度</string> <string name="settings_accuracy_title">位置情報の精度</string>
<string name="settings_accuracy_summary">要求される位置情報の精度</string> <string name="settings_accuracy_summary">要求される位置情報の精度</string>
<string name="settings_accuracy_high"></string> <string name="settings_accuracy_high"></string>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Serviss apturēts</string> <string name="settings_status_off_summary">Serviss apturēts</string>
<string name="settings_status_on_summary">Serviss darbojas</string> <string name="settings_status_on_summary">Serviss darbojas</string>
<string name="settings_buffer">Bezsaistes buferizācija</string>
<string name="settings_buffer_off_summary">Buferizācija izslēgta</string>
<string name="settings_buffer_on_summary">Buferizācija ieslēgta</string>
<string name="settings_wakelock">Pretaizmigšanas funkcija</string>
<string name="settings_wakelock_off_summary">Pretaizmigšanas funkcija izslēgta</string>
<string name="settings_wakelock_on_summary">Pretaizmigšanas funkcija ieslēgta</string>
<string name="settings_accuracy_title">Vietas precizitāte</string> <string name="settings_accuracy_title">Vietas precizitāte</string>
<string name="settings_accuracy_summary">Vēlamās vietas precizitāte</string> <string name="settings_accuracy_summary">Vēlamās vietas precizitāte</string>
<string name="settings_accuracy_high">Augsta</string> <string name="settings_accuracy_high">Augsta</string>
+3
Просмотреть файл
@@ -20,6 +20,9 @@
<string name="settings_status_on">Stopp</string> <string name="settings_status_on">Stopp</string>
<string name="settings_status_off_summary">Tjenesten stoppet</string> <string name="settings_status_off_summary">Tjenesten stoppet</string>
<string name="settings_status_on_summary">Tjenesten kjører</string> <string name="settings_status_on_summary">Tjenesten kjører</string>
<string name="settings_buffer">Frakoblet mellomlagring</string>
<string name="settings_buffer_off_summary">Mellomlagring deaktivert</string>
<string name="settings_buffer_on_summary">Mellomlagring på</string>
<string name="settings_accuracy_title">Posisjonsnøyaktighet</string> <string name="settings_accuracy_title">Posisjonsnøyaktighet</string>
<string name="settings_accuracy_summary">Ønsket plasseringsnøyaktighet</string> <string name="settings_accuracy_summary">Ønsket plasseringsnøyaktighet</string>
<string name="settings_accuracy_high">Høy</string> <string name="settings_accuracy_high">Høy</string>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Service gestopt</string> <string name="settings_status_off_summary">Service gestopt</string>
<string name="settings_status_on_summary">Service draait</string> <string name="settings_status_on_summary">Service draait</string>
<string name="settings_buffer">Offline bufferen</string>
<string name="settings_buffer_off_summary">Bufferen uit</string>
<string name="settings_buffer_on_summary">Bufferen aan</string>
<string name="settings_wakelock">Slaapstand blokkade</string>
<string name="settings_wakelock_off_summary">Slaapstand blokkade uit</string>
<string name="settings_wakelock_on_summary">Slaapstand blokkade aan</string>
<string name="settings_accuracy_title">Locatienauwkeurigheid</string> <string name="settings_accuracy_title">Locatienauwkeurigheid</string>
<string name="settings_accuracy_summary">Gewenste lokatienauwkeurigheid</string> <string name="settings_accuracy_summary">Gewenste lokatienauwkeurigheid</string>
<string name="settings_accuracy_high">Hoog</string> <string name="settings_accuracy_high">Hoog</string>
+3
Просмотреть файл
@@ -20,6 +20,9 @@
<string name="settings_status_on">Stopp</string> <string name="settings_status_on">Stopp</string>
<string name="settings_status_off_summary">Teneste stoppa</string> <string name="settings_status_off_summary">Teneste stoppa</string>
<string name="settings_status_on_summary">Tenar køyrer</string> <string name="settings_status_on_summary">Tenar køyrer</string>
<string name="settings_buffer">Fråkopla mellomlagring</string>
<string name="settings_buffer_off_summary">Mellomlagring deaktivert</string>
<string name="settings_buffer_on_summary">Mellomlagring på</string>
<string name="settings_accuracy_title">Lokasjonsnøyaktighet</string> <string name="settings_accuracy_title">Lokasjonsnøyaktighet</string>
<string name="settings_accuracy_summary">Ønska lokasjonsnøyaktighet</string> <string name="settings_accuracy_summary">Ønska lokasjonsnøyaktighet</string>
<string name="settings_accuracy_high">Høg</string> <string name="settings_accuracy_high">Høg</string>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Zatrzymaj</string> <string name="settings_status_on">Zatrzymaj</string>
<string name="settings_status_off_summary">Usługa zatrzymana</string> <string name="settings_status_off_summary">Usługa zatrzymana</string>
<string name="settings_status_on_summary">Usługa uruchomiona</string> <string name="settings_status_on_summary">Usługa uruchomiona</string>
<string name="settings_buffer">Buforowanie offline</string>
<string name="settings_buffer_off_summary">Buforowanie wyłączone</string>
<string name="settings_buffer_on_summary">Buforowanie włączone</string>
<string name="settings_wakelock">Wybudź blokadę</string>
<string name="settings_wakelock_off_summary">Wybudzenie blokady wyłączone</string>
<string name="settings_wakelock_on_summary">Wybudzenie blokady włączone</string>
<string name="settings_accuracy_title">Dokładność lokalizacji</string> <string name="settings_accuracy_title">Dokładność lokalizacji</string>
<string name="settings_accuracy_summary">Żądana dokładność lokalizacji</string> <string name="settings_accuracy_summary">Żądana dokładność lokalizacji</string>
<string name="settings_accuracy_high">Wysoka</string> <string name="settings_accuracy_high">Wysoka</string>
+9 -3
Просмотреть файл
@@ -20,14 +20,20 @@
<string name="settings_status_on">Parar</string> <string name="settings_status_on">Parar</string>
<string name="settings_status_off_summary">Serviço Parado</string> <string name="settings_status_off_summary">Serviço Parado</string>
<string name="settings_status_on_summary">Serviço em execução</string> <string name="settings_status_on_summary">Serviço em execução</string>
<string name="settings_buffer">Offline buffering</string>
<string name="settings_buffer_off_summary">Buffering Desligado</string>
<string name="settings_buffer_on_summary">Buffering Ligado</string>
<string name="settings_wakelock">Bloqueio de Ativação</string>
<string name="settings_wakelock_off_summary">Bloqueio de Ativação Desligado</string>
<string name="settings_wakelock_on_summary">Bloqueio de Ativação Ligado</string>
<string name="settings_accuracy_title">Precisão de localização</string> <string name="settings_accuracy_title">Precisão de localização</string>
<string name="settings_accuracy_summary">Precisão de localização desejada</string> <string name="settings_accuracy_summary">Precisão de localização desejada</string>
<string name="settings_accuracy_high">Alto</string> <string name="settings_accuracy_high">Alto</string>
<string name="settings_accuracy_medium">Médio</string> <string name="settings_accuracy_medium">Médio</string>
<string name="settings_accuracy_low">Baixo</string> <string name="settings_accuracy_low">Baixo</string>
<string name="settings_foreground_title">Serviço em primeiro plano</string> <string name="settings_foreground_title">Serviço em primeiro plano</string>
<string name="settings_foreground_summary">Aumentar a prioridade de serviço</string> <string name="settings_foreground_summary">Aumentar a prioridade do serviço</string>
<string name="menu_status">Status</string> <string name="menu_status">Estado</string>
<string name="menu_shortcuts">Adicionar atalhos</string> <string name="menu_shortcuts">Adicionar atalhos</string>
<string name="menu_clear">Limpar</string> <string name="menu_clear">Limpar</string>
<string name="status_service_create">Serviço iniciado</string> <string name="status_service_create">Serviço iniciado</string>
@@ -39,5 +45,5 @@
<string name="status_network_offline">Rede offline</string> <string name="status_network_offline">Rede offline</string>
<string name="hidden_app_name">Configurações do Dispositivo</string> <string name="hidden_app_name">Configurações do Dispositivo</string>
<string name="hidden_alert">O aplicativo foi escondido. Para abrir novamente por favor digite 8722227 (TRACCAR).</string> <string name="hidden_alert">O aplicativo foi escondido. Para abrir novamente por favor digite 8722227 (TRACCAR).</string>
<string name="error_msg_invalid_url">Digite um URL http: // ou https: // válido</string> <string name="error_msg_invalid_url">Digite uma URL http: // ou https: // válida</string>
</resources> </resources>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Parar</string> <string name="settings_status_on">Parar</string>
<string name="settings_status_off_summary">Serviço Parado</string> <string name="settings_status_off_summary">Serviço Parado</string>
<string name="settings_status_on_summary">Serviço em execução</string> <string name="settings_status_on_summary">Serviço em execução</string>
<string name="settings_buffer">Buffering Offline</string>
<string name="settings_buffer_off_summary">Buffering desligado</string>
<string name="settings_buffer_on_summary">Buffering ligado</string>
<string name="settings_wakelock">Bloqueio de Ativação</string>
<string name="settings_wakelock_off_summary">Bloqueio de Ativação Desligado</string>
<string name="settings_wakelock_on_summary">Bloqueio de Ativação Ligado</string>
<string name="settings_accuracy_title">Precisão de localização</string> <string name="settings_accuracy_title">Precisão de localização</string>
<string name="settings_accuracy_summary">Precisão de localização desejada</string> <string name="settings_accuracy_summary">Precisão de localização desejada</string>
<string name="settings_accuracy_high">Alto</string> <string name="settings_accuracy_high">Alto</string>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Servis zaustavljen</string> <string name="settings_status_off_summary">Servis zaustavljen</string>
<string name="settings_status_on_summary">Servis pokrenut</string> <string name="settings_status_on_summary">Servis pokrenut</string>
<string name="settings_buffer">Offline buffering</string>
<string name="settings_buffer_off_summary">Buffering uključen</string>
<string name="settings_buffer_on_summary">Buffering isključen</string>
<string name="settings_wakelock">Zaključavanje budnosti</string>
<string name="settings_wakelock_off_summary">Budnost isključena</string>
<string name="settings_wakelock_on_summary">Budnost uključena</string>
<string name="settings_accuracy_title">Tačnost lokacije</string> <string name="settings_accuracy_title">Tačnost lokacije</string>
<string name="settings_accuracy_summary">Željena tačnost lokacije</string> <string name="settings_accuracy_summary">Željena tačnost lokacije</string>
<string name="settings_accuracy_high">Visoka</string> <string name="settings_accuracy_high">Visoka</string>
+8
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string> <string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Service stopped</string> <string name="settings_status_off_summary">Service stopped</string>
<string name="settings_status_on_summary">Service running</string> <string name="settings_status_on_summary">Service running</string>
<string name="settings_buffer">Offline buffering</string>
<string name="settings_buffer_off_summary">Buffering off</string>
<string name="settings_buffer_on_summary">Buffering on</string>
<string name="settings_wakelock">Wake lock</string>
<string name="settings_wakelock_off_summary">Wake lock off</string>
<string name="settings_wakelock_on_summary">Wake lock on</string>
<string name="settings_accuracy_title">Location accuracy</string> <string name="settings_accuracy_title">Location accuracy</string>
<string name="settings_accuracy_summary">Desired location accuracy</string> <string name="settings_accuracy_summary">Desired location accuracy</string>
<string name="settings_accuracy_high">High</string> <string name="settings_accuracy_high">High</string>
@@ -28,6 +34,7 @@
<string name="settings_foreground_title">Foreground service</string> <string name="settings_foreground_title">Foreground service</string>
<string name="settings_foreground_summary">Increase service priority</string> <string name="settings_foreground_summary">Increase service priority</string>
<string name="menu_status">Status</string> <string name="menu_status">Status</string>
<string name="menu_info">Info</string>
<string name="menu_shortcuts">Add shortcuts</string> <string name="menu_shortcuts">Add shortcuts</string>
<string name="menu_clear">Clear</string> <string name="menu_clear">Clear</string>
<string name="status_service_create">Service started</string> <string name="status_service_create">Service started</string>
@@ -40,4 +47,5 @@
<string name="hidden_app_name">Device Settings</string> <string name="hidden_app_name">Device Settings</string>
<string name="hidden_alert">The app has been hidden. To open it again please dial 8722227 (TRACCAR).</string> <string name="hidden_alert">The app has been hidden. To open it again please dial 8722227 (TRACCAR).</string>
<string name="error_msg_invalid_url">Please enter a valid http:// or https:// URL</string> <string name="error_msg_invalid_url">Please enter a valid http:// or https:// URL</string>
<string name="request_exception">To continuously collect location data please turn off battery optimization for the app.</string>
</resources> </resources>
+6 -1
Просмотреть файл
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<base-config cleartextTrafficPermitted="true" /> <base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config> </network-security-config>
+17 -3
Просмотреть файл
@@ -31,22 +31,36 @@
<EditTextPreference <EditTextPreference
android:defaultValue="300" android:defaultValue="300"
android:key="interval" android:key="interval"
android:numeric="integer" android:inputType="number"
android:summary="@string/settings_interval_summary" android:summary="@string/settings_interval_summary"
android:title="@string/settings_interval_title" /> android:title="@string/settings_interval_title" />
<EditTextPreference <EditTextPreference
android:defaultValue="0" android:defaultValue="0"
android:key="distance" android:key="distance"
android:numeric="integer" android:inputType="number"
android:summary="@string/settings_distance_summary" android:summary="@string/settings_distance_summary"
android:title="@string/settings_distance_title" /> android:title="@string/settings_distance_title" />
<EditTextPreference <EditTextPreference
android:defaultValue="0" android:defaultValue="0"
android:key="angle" android:key="angle"
android:numeric="integer" android:inputType="number"
android:summary="@string/settings_angle_summary" android:summary="@string/settings_angle_summary"
android:title="@string/settings_angle_title" /> android:title="@string/settings_angle_title" />
<CheckBoxPreference
android:defaultValue="true"
android:key="buffer"
android:summaryOff="@string/settings_buffer_off_summary"
android:summaryOn="@string/settings_buffer_on_summary"
android:title="@string/settings_buffer" />
<CheckBoxPreference
android:defaultValue="true"
android:key="wakelock"
android:summaryOff="@string/settings_wakelock_off_summary"
android:summaryOn="@string/settings_wakelock_on_summary"
android:title="@string/settings_wakelock" />
</PreferenceScreen> </PreferenceScreen>
@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Anton Tananaev (anton@traccar.org) * Copyright 2019 - 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,18 +13,14 @@
* 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 com.google.firebase.analytics.FirebaseAnalytics; import android.content.Context
import org.traccar.client.PositionProvider.PositionListener
public class GoogleMainApplication extends MainApplication { object PositionProviderFactory {
private FirebaseAnalytics firebaseAnalytics; fun create(context: Context, listener: PositionListener): PositionProvider {
return AndroidPositionProvider(context, listener)
@Override
public void onCreate() {
super.onCreate();
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
} }
} }
-42
Просмотреть файл
@@ -1,42 +0,0 @@
package org.traccar.client;
import android.location.Location;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.Date;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@RunWith(RobolectricTestRunner.class)
public class DatabaseHelperTest {
@Test
public void test() throws Exception {
DatabaseHelper databaseHelper = new DatabaseHelper(RuntimeEnvironment.application);
Position position = new Position("123456789012345", new Location("gps"), 0);
position.setTime(new Date(0));
assertNull(databaseHelper.selectPosition());
databaseHelper.insertPosition(position);
position = databaseHelper.selectPosition();
assertNotNull(position);
databaseHelper.deletePosition(position.getId());
assertNull(databaseHelper.selectPosition());
}
}
+36
Просмотреть файл
@@ -0,0 +1,36 @@
package org.traccar.client
import android.location.Location
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@Config(sdk = [Build.VERSION_CODES.P])
@RunWith(RobolectricTestRunner::class)
class DatabaseHelperTest {
@Test
fun test() {
val databaseHelper = DatabaseHelper(ApplicationProvider.getApplicationContext())
var position: Position? = Position("123456789012345", Location("gps"), 0.0)
Assert.assertNull(databaseHelper.selectPosition())
databaseHelper.insertPosition(position!!)
position = databaseHelper.selectPosition()
Assert.assertNotNull(position)
databaseHelper.deletePosition(position!!.id)
Assert.assertNull(databaseHelper.selectPosition())
}
}
-47
Просмотреть файл
@@ -1,47 +0,0 @@
package org.traccar.client;
import android.location.Location;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Date;
import static org.junit.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
public class ProtocolFormatterTest {
@Test
public void testFormatRequest() throws Exception {
Position position = new Position("123456789012345", new Location("gps"), 0);
position.setTime(new Date(0));
String url = ProtocolFormatter.formatRequest("http://localhost:5055", position);
assertEquals("http://localhost:5055?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url);
}
@Test
public void testFormatPathPortRequest() throws Exception {
Position position = new Position("123456789012345", new Location("gps"), 0);
position.setTime(new Date(0));
String url = ProtocolFormatter.formatRequest("http://localhost:8888/path", position);
assertEquals("http://localhost:8888/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url);
}
@Test
public void testFormatAlarmRequest() throws Exception {
Position position = new Position("123456789012345", new Location("gps"), 0);
position.setTime(new Date(0));
String url = ProtocolFormatter.formatRequest("http://localhost:5055/path", position, "alert message");
assertEquals("http://localhost:5055/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0&alarm=alert%20message", url);
}
}
+37
Просмотреть файл
@@ -0,0 +1,37 @@
package org.traccar.client
import android.location.Location
import android.os.Build
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.traccar.client.ProtocolFormatter.formatRequest
@Config(sdk = [Build.VERSION_CODES.P])
@RunWith(RobolectricTestRunner::class)
class ProtocolFormatterTest {
@Test
fun testFormatRequest() {
val position = Position("123456789012345", Location("gps"), 0.0)
val url = formatRequest("http://localhost:5055", position)
Assert.assertEquals("http://localhost:5055?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url)
}
@Test
fun testFormatPathPortRequest() {
val position = Position("123456789012345", Location("gps"), 0.0)
val url = formatRequest("http://localhost:8888/path", position)
Assert.assertEquals("http://localhost:8888/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url)
}
@Test
fun testFormatAlarmRequest() {
val position = Position("123456789012345", Location("gps"), 0.0)
val url = formatRequest("http://localhost:5055/path", position, "alert message")
Assert.assertEquals("http://localhost:5055/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0&alarm=alert%20message", url)
}
}
-23
Просмотреть файл
@@ -1,23 +0,0 @@
package org.traccar.client;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.assertTrue;
@RunWith(RobolectricTestRunner.class)
public class RequestManagerTest {
@Ignore
@Test
public void testSendRequest() throws Exception {
assertTrue(RequestManager.sendRequest("http://www.google.com"));
}
}
+22
Просмотреть файл
@@ -0,0 +1,22 @@
package org.traccar.client
import android.os.Build
import org.junit.Assert
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.traccar.client.RequestManager.sendRequest
@Config(sdk = [Build.VERSION_CODES.P])
@RunWith(RobolectricTestRunner::class)
class RequestManagerTest {
@Ignore("Not a real unit test")
@Test
fun testSendRequest() {
Assert.assertTrue(sendRequest("http://www.google.com"))
}
}
+8 -6
Просмотреть файл
@@ -1,19 +1,21 @@
buildscript { buildscript {
ext.kotlin_version = '1.5.31'
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url 'https://maven.fabric.io/public' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.android.tools.build:gradle:7.0.2'
classpath 'com.google.gms:google-services:4.3.2' classpath 'com.google.gms:google-services:4.3.10'
classpath 'io.fabric.tools:gradle:1.26.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
maven { url 'https://jitpack.io' }
} }
} }
-2
Просмотреть файл
@@ -1,3 +1 @@
android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true
android.enableUnitTestBinaryResources=true
+1 -1
Просмотреть файл
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+6
Просмотреть файл
@@ -5,6 +5,7 @@ import optparse
import urllib2 import urllib2
import json import json
import base64 import base64
import shutil
parser = optparse.OptionParser() parser = optparse.OptionParser()
parser.add_option("-u", "--user", dest="username", help="transifex user login") parser.add_option("-u", "--user", dest="username", help="transifex user login")
@@ -39,3 +40,8 @@ for language in resource["available_languages"]:
file = open(filename, "wb") file = open(filename, "wb")
file.write(data.read()) file.write(data.read())
file.close() file.close()
filename = path + "values-iw/strings.xml"
if not os.path.exists(os.path.dirname(filename)):
os.makedirs(os.path.dirname(filename))
shutil.copyfile(path + "values-he/strings.xml", filename)