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

..
36 Коммитов
Автор SHA1 Сообщение Дата
Anton Tananaev 8b985526dd Update version number 2022-01-30 10:18:36 -08:00
Anton Tananaev 5e8ea134e0 Attempt at fixing crashes 2022-01-30 10:18:09 -08:00
Anton Tananaev ec85edd56f Update version number 2021-11-04 19:21:19 -07:00
Anton Tananaev c5ab10919e Update dependencies 2021-11-04 19:20:08 -07:00
Anton Tananaev 3e8ba9a603 Fix Android 11 location permissions 2021-11-04 19:18:09 -07:00
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
69 изменённых файлов: 2153 добавлений и 2264 удалений
+22 -13
Просмотреть файл
@@ -1,15 +1,16 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
compileSdkVersion 31
defaultConfig {
applicationId 'org.traccar.client'
buildConfigField 'boolean', 'HIDDEN_APP', 'false'
minSdkVersion 16
targetSdkVersion 30
versionCode 67
versionName '6.6'
targetSdkVersion 31
versionCode 77
versionName '6.16'
multiDexEnabled true
}
@@ -46,16 +47,24 @@ android {
}
dependencies {
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'com.google.android.material:material:1.5.0-alpha05'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.preference:preference:1.1.1'
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.1'
googleImplementation 'com.google.firebase:firebase-core:17.5.0'
googleImplementation 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
googleImplementation 'com.google.firebase:firebase-analytics:17.5.0'
googleImplementation 'com.google.android.gms:play-services-location:17.1.0'
googleImplementation 'com.google.android.play:core:1.8.2'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.test:core-ktx:1.4.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.github.judemanutd:autostarter:1.1.0'
implementation('dev.doubledot.doki:library:0.0.1@aar') {
transitive = true
}
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.6.1'
googleImplementation platform('com.google.firebase:firebase-bom:28.2.1')
googleImplementation 'com.google.firebase:firebase-core'
googleImplementation 'com.google.firebase:firebase-analytics'
googleImplementation 'com.google.firebase:firebase-crashlytics'
googleImplementation 'com.google.android.gms:play-services-location:18.0.0'
googleImplementation 'com.google.android.play:core:1.10.2'
}
if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
-69
Просмотреть файл
@@ -1,69 +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.app.Activity;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.preference.PreferenceManager;
import com.google.android.play.core.review.ReviewManager;
import com.google.android.play.core.review.ReviewManagerFactory;
import com.google.android.play.core.tasks.Task;
import com.google.firebase.analytics.FirebaseAnalytics;
public class GoogleMainApplication extends MainApplication {
private static final String KEY_RATING_SHOWN = "ratingShown";
private static final long RATING_THRESHOLD = -24 * 60 * 60 * 1000L;
private FirebaseAnalytics firebaseAnalytics;
@Override
public void onCreate() {
super.onCreate();
firebaseAnalytics = FirebaseAnalytics.getInstance(this);
IntentFilter filter = new IntentFilter();
filter.addAction(TrackingService.ACTION_STARTED);
filter.addAction(TrackingService.ACTION_STOPPED);
registerReceiver(new ServiceReceiver(), filter);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@Override
public void handleRatingFlow(@NonNull Activity activity) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean ratingShown = preferences.getBoolean(KEY_RATING_SHOWN, false);
long totalDuration = preferences.getLong(ServiceReceiver.KEY_DURATION, 0);
if (!ratingShown && totalDuration > RATING_THRESHOLD) {
ReviewManager reviewManager = ReviewManagerFactory.create(activity);
reviewManager.requestReviewFlow().addOnCompleteListener(infoTask -> {
if (infoTask.isSuccessful()) {
Task<Void> flow = reviewManager.launchReviewFlow(activity, infoTask.getResult());
flow.addOnCompleteListener(flowTask -> {
preferences.edit().putBoolean(KEY_RATING_SHOWN, true).apply();
});
}
});
}
}
}
+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");
* 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
* 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) {
return new AndroidPositionProvider(context, listener);
fun create(context: Context, listener: PositionListener): PositionProvider {
return GooglePositionProvider(context, listener)
}
}
-49
Просмотреть файл
@@ -1,49 +0,0 @@
/*
* Copyright 2020 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.SharedPreferences;
import androidx.preference.PreferenceManager;
public class ServiceReceiver extends BroadcastReceiver {
public static final String KEY_DURATION = "serviceTime";
private static long startTime = 0;
@Override
public void onReceive(Context context, Intent intent) {
if (TrackingService.ACTION_STARTED.equals(intent.getAction())) {
startTime = System.currentTimeMillis();
} else {
if (startTime > 0) {
updateTime(context, System.currentTimeMillis() - startTime);
startTime = 0;
}
}
}
private void updateTime(Context context, long duration) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
long totalDuration = preferences.getLong(KEY_DURATION, 0);
preferences.edit().putLong(KEY_DURATION, totalDuration + duration).apply();
}
}
+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">
<activity
android:name="org.traccar.client.MainActivity">
android:name="org.traccar.client.MainActivity"
android:exported="true">
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -18,7 +19,9 @@
</intent-filter>
</activity>
<receiver android:name="org.traccar.client.DialLaunchReceiver" >
<receiver
android:name="org.traccar.client.DialLaunchReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</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");
* 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
* 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) {
return new GooglePositionProvider(context, listener);
fun create(context: Context, listener: PositionListener): PositionProvider {
return AndroidPositionProvider(context, listener)
}
}
+15 -3
Просмотреть файл
@@ -14,6 +14,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="oppo.permission.OPPO_COMPONENT_SAFE" />
<uses-permission android:name="com.huawei.permission.external_app_settings.USE_COMPONENT" />
<uses-feature android:name="android.hardware.location.network" />
<uses-feature android:name="android.hardware.location.gps" />
@@ -32,10 +35,12 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask" />
<activity-alias
android:name=".Launcher"
android:exported="true"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -46,10 +51,13 @@
android:resource="@xml/shortcuts" />
</activity-alias>
<activity android:name=".StatusActivity"/>
<activity
android:name=".StatusActivity"
android:exported="false" />
<activity
android:name=".ShortcutActivity"
android:exported="true"
android:label="@string/menu_shortcuts">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -59,12 +67,16 @@
<service
android:name=".TrackingService"
android:exported="false"
android:foregroundServiceType="location" />
<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>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
-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");
* 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
* limitations under the License.
*/
package org.traccar.client;
package org.traccar.client
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.content.Context
import android.content.Intent
import androidx.preference.PreferenceManager
public class AutostartReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
class AutostartReceiver : WakefulBroadcastReceiver() {
@Suppress("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
startWakefulForegroundService(context, new Intent(context, TrackingService.class));
startWakefulForegroundService(context, Intent(context, TrackingService::class.java))
}
}
+102
Просмотреть файл
@@ -0,0 +1,102 @@
/*
* Copyright 2021 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.appcompat.app.AlertDialog
import androidx.preference.PreferenceManager
import com.judemanutd.autostarter.AutoStartPermissionHelper
class BatteryOptimizationHelper {
private fun showDialog(context: Context, onSuccess: () -> Unit) {
val builder = AlertDialog.Builder(context)
builder.setMessage(context.getString(R.string.request_exception))
builder.setPositiveButton(android.R.string.ok) { _, _ -> onSuccess() }
builder.setNegativeButton(android.R.string.cancel, null)
builder.show()
}
private fun requestVendorException(context: Context) {
val vendorIntentList = listOf(
Intent().setComponent(ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")),
Intent().setComponent(ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")),
Intent().setComponent(ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity")),
Intent().setComponent(ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")),
Intent().setComponent(ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.appcontrol.activity.StartupAppControlActivity")),
Intent().setComponent(ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")),
Intent().setComponent(ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")),
Intent().setComponent(ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")),
Intent().setComponent(ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")),
Intent().setComponent(ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")),
Intent().setComponent(ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")),
Intent().setComponent(ComponentName("com.samsung.android.lool", "com.samsung.android.sm.ui.battery.BatteryActivity")),
Intent().setComponent(ComponentName("com.htc.pitroad", "com.htc.pitroad.landingpage.activity.LandingPageActivity")),
Intent().setComponent(ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.MainActivity")),
)
for (vendorIntent in vendorIntentList) {
if (context.packageManager.resolveActivity(vendorIntent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
try {
context.startActivity(vendorIntent)
return
} catch (e: Exception) {
continue
}
}
}
}
fun requestException(context: Context): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (!sharedPreferences.getBoolean(KEY_EXCEPTION_REQUESTED, false)) {
sharedPreferences.edit().putBoolean(KEY_EXCEPTION_REQUESTED, true).apply()
val powerManager = context.getSystemService(PowerManager::class.java)
if (!powerManager.isIgnoringBatteryOptimizations(context.packageName)) {
showDialog(context) {
try {
context.startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
} catch (e: ActivityNotFoundException) {
requestVendorException(context)
}
}
return true
}
} else if (!sharedPreferences.getBoolean(KEY_AUTOSTART_REQUESTED, false)) {
sharedPreferences.edit().putBoolean(KEY_AUTOSTART_REQUESTED, true).apply()
try {
if (AutoStartPermissionHelper.getInstance().getAutoStartPermission(context)) {
return true
}
} catch (e: SecurityException) {
}
}
}
return false
}
companion object {
private const val KEY_EXCEPTION_REQUESTED = "exceptionRequested"
private const val KEY_AUTOSTART_REQUESTED = "autostartRequested"
}
}
-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
}
}
@@ -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");
* 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
* 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) {
return new AndroidPositionProvider(context, listener);
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
}
-79
Просмотреть файл
@@ -1,79 +0,0 @@
/*
* Copyright 2016 - 2020 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.Activity;
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;
import androidx.annotation.NonNull;
import androidx.multidex.MultiDexApplication;
public class MainApplication extends MultiDexApplication {
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_LOW);
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();
}
}
public void handleRatingFlow(@NonNull Activity activity) {
}
}
+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"
}
}
-329
Просмотреть файл
@@ -1,329 +0,0 @@
/*
* Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.Manifest;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.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.annotation.NonNull;
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 com.google.android.material.snackbar.Snackbar;
import java.util.Arrays;
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";
public static final String KEY_BUFFER = "buffer";
public static final String KEY_WAKELOCK = "wakelock";
private static final int PERMISSIONS_REQUEST_LOCATION = 2;
private static final String KEY_DISCLOSURE_SHOWN = "disclosureShown";
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());
setPreferencesFromResource(R.xml.preferences, rootKey);
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);
}
}
public static class NumericEditTextPreferenceDialogFragment extends EditTextPreferenceDialogFragmentCompat {
public static NumericEditTextPreferenceDialogFragment newInstance(String key) {
final NumericEditTextPreferenceDialogFragment fragment = new NumericEditTextPreferenceDialogFragment();
final Bundle bundle = new Bundle();
bundle.putString(ARG_KEY, key);
fragment.setArguments(bundle);
return fragment;
}
@Override
protected void onBindDialogView(View view) {
EditText editText = view.findViewById(android.R.id.edit);
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
super.onBindDialogView(view);
}
}
@Override
public void onDisplayPreferenceDialog(Preference preference) {
if (Arrays.asList(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.getKey())) {
final EditTextPreferenceDialogFragmentCompat f = NumericEditTextPreferenceDialogFragment.newInstance(preference.getKey());
f.setTargetFragment(this, 0);
f.show(getFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG");
} else {
super.onDisplayPreferenceDialog(preference);
}
}
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);
if (!sharedPreferences.getBoolean(KEY_DISCLOSURE_SHOWN, false)) {
Snackbar.make(getView(), R.string.disclosure_location, Snackbar.LENGTH_INDEFINITE)
.addCallback(new Snackbar.Callback() {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
sharedPreferences.edit().putBoolean(KEY_DISCLOSURE_SHOWN, true).apply();
}
})
.show();
}
}
@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);
findPreference(KEY_BUFFER).setEnabled(enabled);
findPreference(KEY_WAKELOCK).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();
}
((MainApplication) getActivity().getApplication()).handleRatingFlow(getActivity());
} 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;
}
}
+329
Просмотреть файл
@@ -0,0 +1,329 @@
/*
* Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client
import android.Manifest
import android.annotation.SuppressLint
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.webkit.URLUtil
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.preference.EditTextPreference
import androidx.preference.EditTextPreferenceDialogFragmentCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.TwoStatePreference
import dev.doubledot.doki.ui.DokiActivity
import java.util.*
import kotlin.collections.HashSet
class MainFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener {
private lateinit var sharedPreferences: SharedPreferences
private lateinit var alarmManager: AlarmManager
private lateinit var alarmIntent: PendingIntent
private var requestingPermissions: Boolean = false
@SuppressLint("UnspecifiedImmutableFlag")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
removeLauncherIcon()
}
setHasOptionsMenu(true)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
setPreferencesFromResource(R.xml.preferences, rootKey)
initPreferences()
findPreference<Preference>(KEY_DEVICE)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
newValue != null && newValue != ""
}
findPreference<Preference>(KEY_URL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
newValue != null && validateServerURL(newValue.toString())
}
findPreference<Preference>(KEY_INTERVAL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
try {
newValue != null && (newValue as String).toInt() > 0
} catch (e: NumberFormatException) {
Log.w(TAG, e)
false
}
}
val numberValidationListener = Preference.OnPreferenceChangeListener { _, newValue ->
try {
newValue != null && (newValue as String).toInt() >= 0
} catch (e: NumberFormatException) {
Log.w(TAG, e)
false
}
}
findPreference<Preference>(KEY_DISTANCE)?.onPreferenceChangeListener = numberValidationListener
findPreference<Preference>(KEY_ANGLE)?.onPreferenceChangeListener = numberValidationListener
alarmManager = requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
val originalIntent = Intent(activity, AutostartReceiver::class.java)
originalIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
alarmIntent = PendingIntent.getBroadcast(activity, 0, originalIntent, flags)
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(checkPermission = true, initialPermission = false)
}
}
class NumericEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
override fun onBindDialogView(view: View) {
val editText = view.findViewById<EditText>(android.R.id.edit)
editText.inputType = InputType.TYPE_CLASS_NUMBER
super.onBindDialogView(view)
}
companion object {
fun newInstance(key: String?): NumericEditTextPreferenceDialogFragment {
val fragment = NumericEditTextPreferenceDialogFragment()
val bundle = Bundle()
bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
}
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (listOf(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.key)) {
val f: EditTextPreferenceDialogFragmentCompat =
NumericEditTextPreferenceDialogFragment.newInstance(preference.key)
f.setTargetFragment(this, 0)
f.show(requireFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG")
} else {
super.onDisplayPreferenceDialog(preference)
}
}
private fun removeLauncherIcon() {
val className = MainActivity::class.java.canonicalName!!.replace(".MainActivity", ".Launcher")
val componentName = ComponentName(requireActivity().packageName, className)
val packageManager = requireActivity().packageManager
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting(
componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
val builder = AlertDialog.Builder(requireActivity())
builder.setIcon(android.R.drawable.ic_dialog_alert)
builder.setMessage(getString(R.string.hidden_alert))
builder.setPositiveButton(android.R.string.ok, null)
builder.show()
}
}
override fun onStart() {
super.onStart()
if (requestingPermissions) {
requestingPermissions = BatteryOptimizationHelper().requestException(requireContext())
}
}
override fun onResume() {
super.onResume()
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
super.onPause()
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
}
private fun setPreferencesEnabled(enabled: Boolean) {
findPreference<Preference>(KEY_DEVICE)?.isEnabled = enabled
findPreference<Preference>(KEY_URL)?.isEnabled = enabled
findPreference<Preference>(KEY_INTERVAL)?.isEnabled = enabled
findPreference<Preference>(KEY_DISTANCE)?.isEnabled = enabled
findPreference<Preference>(KEY_ANGLE)?.isEnabled = enabled
findPreference<Preference>(KEY_ACCURACY)?.isEnabled = enabled
findPreference<Preference>(KEY_BUFFER)?.isEnabled = enabled
findPreference<Preference>(KEY_WAKELOCK)?.isEnabled = enabled
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (key == KEY_STATUS) {
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(checkPermission = true, initialPermission = false)
} else {
stopTrackingService()
}
(requireActivity().application as MainApplication).handleRatingFlow(requireActivity())
} else if (key == KEY_DEVICE) {
findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.main, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.status) {
startActivity(Intent(activity, StatusActivity::class.java))
return true
} else if (item.itemId == R.id.info) {
DokiActivity.start(requireContext())
return true
}
return super.onOptionsItemSelected(item)
}
private fun initPreferences() {
PreferenceManager.setDefaultValues(activity, R.xml.preferences, false)
if (!sharedPreferences.contains(KEY_DEVICE)) {
val id = (Random().nextInt(900000) + 100000).toString()
sharedPreferences.edit().putString(KEY_DEVICE, id).apply()
findPreference<EditTextPreference>(KEY_DEVICE)?.text = id
}
findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
}
private fun showBackgroundLocationDialog(context: Context, onSuccess: () -> Unit) {
val builder = AlertDialog.Builder(context)
val option = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.packageManager.backgroundPermissionOptionLabel
} else {
context.getString(R.string.request_background_option)
}
builder.setMessage(context.getString(R.string.request_background, option))
builder.setPositiveButton(android.R.string.ok) { _, _ -> onSuccess() }
builder.setNegativeButton(android.R.string.cancel, null)
builder.show()
}
private fun startTrackingService(checkPermission: Boolean, initialPermission: Boolean) {
var permission = initialPermission
if (checkPermission) {
val requiredPermissions: MutableSet<String> = HashSet()
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
permission = requiredPermissions.isEmpty()
if (!permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(requiredPermissions.toTypedArray(), PERMISSIONS_REQUEST_LOCATION)
}
return
}
}
if (permission) {
setPreferencesEnabled(false)
ContextCompat.startForegroundService(requireContext(), Intent(activity, TrackingService::class.java))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
ALARM_MANAGER_INTERVAL.toLong(), ALARM_MANAGER_INTERVAL.toLong(), alarmIntent
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
&& ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestingPermissions = true
showBackgroundLocationDialog(requireContext()) {
requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), PERMISSIONS_REQUEST_BACKGROUND_LOCATION)
}
} else {
requestingPermissions = BatteryOptimizationHelper().requestException(requireContext())
}
} else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply()
val preference = findPreference<TwoStatePreference>(KEY_STATUS)
preference?.isChecked = false
}
}
private fun stopTrackingService() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
alarmManager.cancel(alarmIntent)
}
requireActivity().stopService(Intent(activity, TrackingService::class.java))
setPreferencesEnabled(true)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
var granted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
granted = false
break
}
}
startTrackingService(false, granted)
}
}
private fun validateServerURL(userUrl: String): Boolean {
val port = Uri.parse(userUrl).port
if (
URLUtil.isValidUrl(userUrl) &&
(port == -1 || port in 1..65535) &&
(URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))
) {
return true
}
Toast.makeText(activity, R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show()
return false
}
companion object {
private val TAG = MainFragment::class.java.simpleName
private const val ALARM_MANAGER_INTERVAL = 15000
const val KEY_DEVICE = "id"
const val KEY_URL = "url"
const val KEY_INTERVAL = "interval"
const val KEY_DISTANCE = "distance"
const val KEY_ANGLE = "angle"
const val KEY_ACCURACY = "accuracy"
const val KEY_STATUS = "status"
const val KEY_BUFFER = "buffer"
const val KEY_WAKELOCK = "wakelock"
private const val PERMISSIONS_REQUEST_LOCATION = 2
private const val PERMISSIONS_REQUEST_BACKGROUND_LOCATION = 3
}
}
-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()
}
}
}
-206
Просмотреть файл
@@ -1,206 +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 boolean isOnline;
private boolean isWaiting;
private Context context;
private Handler handler;
private SharedPreferences preferences;
private String url;
private boolean buffer;
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));
buffer = preferences.getBoolean(MainFragment.KEY_BUFFER, true);
}
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) {
if (buffer) {
write(position);
} else {
send(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) {
if (buffer) {
delete(position);
}
} else {
StatusActivity.addMessage(context.getString(R.string.status_send_fail));
if (buffer) {
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
}
}
-140
Просмотреть файл
@@ -1,140 +0,0 @@
/*
* Copyright 2012 - 2020 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.preference.PreferenceManager;
import android.util.Log;
public class TrackingService extends Service {
public static final String ACTION_STARTED = "org.traccar.action.SERVICE_STARTED";
public static final String ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED";
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");
sendBroadcast(new Intent(ACTION_STARTED));
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) {
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(MainFragment.KEY_WAKELOCK, true)) {
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");
sendBroadcast(new Intent(ACTION_STOPPED));
StatusActivity.addMessage(getString(R.string.status_service_destroy));
stopForeground(true);
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
if (trackingController != null) {
trackingController.stop();
}
}
}
+133
Просмотреть файл
@@ -0,0 +1,133 @@
/*
* Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client
import android.Manifest
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Notification
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.os.PowerManager.WakeLock
import android.provider.Settings
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
class TrackingService : Service() {
private var wakeLock: WakeLock? = null
private var trackingController: TrackingController? = null
class HideNotificationService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
startForeground(NOTIFICATION_ID, createNotification(this))
stopForeground(true)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
stopSelfResult(startId)
return START_NOT_STICKY
}
}
@SuppressLint("WakelockTimeout")
override fun onCreate() {
startForeground(NOTIFICATION_ID, createNotification(this))
Log.i(TAG, "service create")
sendBroadcast(Intent(ACTION_STARTED))
StatusActivity.addMessage(getString(R.string.status_service_create))
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(MainFragment.KEY_WAKELOCK, true)) {
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name)
wakeLock?.acquire()
}
trackingController = TrackingController(this)
trackingController?.start()
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(this, Intent(this, HideNotificationService::class.java))
}
}
override fun onBind(intent: Intent): IBinder? {
return null
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
WakefulBroadcastReceiver.completeWakefulIntent(intent)
return START_STICKY
}
override fun onDestroy() {
stopForeground(true)
Log.i(TAG, "service destroy")
sendBroadcast(Intent(ACTION_STOPPED))
StatusActivity.addMessage(getString(R.string.status_service_destroy))
if (wakeLock?.isHeld == true) {
wakeLock?.release()
}
trackingController?.stop()
}
companion object {
const val ACTION_STARTED = "org.traccar.action.SERVICE_STARTED"
const val ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED"
private val TAG = TrackingService::class.java.simpleName
private const val NOTIFICATION_ID = 1
@SuppressLint("UnspecifiedImmutableFlag")
private fun createNotification(context: Context): Notification {
val builder = NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
.setSmallIcon(R.drawable.ic_stat_notify)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
val intent: Intent
if (!BuildConfig.HIDDEN_APP) {
intent = Intent(context, MainActivity::class.java)
builder
.setContentTitle(context.getString(R.string.settings_status_on_summary))
.setTicker(context.getString(R.string.settings_status_on_summary))
.color = ContextCompat.getColor(context, R.color.primary_dark)
} else {
intent = Intent(Settings.ACTION_SETTINGS)
}
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, flags))
return builder.build()
}
}
}
-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
}
}
}
}
+5
Просмотреть файл
@@ -8,4 +8,9 @@
android:title="@string/menu_status"
app:showAsAction="ifRoom" />
<item
android:id="@+id/info"
android:title="@string/menu_info"
app:showAsAction="ifRoom" />
</menu>
+8 -2
Просмотреть файл
@@ -20,9 +20,15 @@
<string name="settings_status_on">إيقاف</string>
<string name="settings_status_off_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_summary">دقة الموقع المطلوبة</string>
<string name="settings_accuracy_high">عالي</string>
<string name="settings_accuracy_high">مرتفع</string>
<string name="settings_accuracy_medium">متوسط</string>
<string name="settings_accuracy_low">منخفض</string>
<string name="settings_foreground_title">الخدمة الأمامية</string>
@@ -39,5 +45,5 @@
<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://</string>
<string name="error_msg_invalid_url">الرجاء إدخال رابط صحيح http:// أو https://</string>
</resources>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Zastaveno</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_summary">Požadovaná přesnost umístění</string>
<string name="settings_accuracy_high">Vysoká</string>
+13
Просмотреть файл
@@ -20,6 +20,17 @@
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Tjenesten stoppet</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_summary">Øg tjeneste prioritet</string>
<string name="menu_status">Status</string>
@@ -30,6 +41,8 @@
<string name="status_send_success">Send succesfuld</string>
<string name="status_send_fail">Send fejlede</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_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>
+3
Просмотреть файл
@@ -20,6 +20,9 @@
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Dienst inaktiv</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_summary">Erwünschte Positionsgenauigkeit</string>
<string name="settings_accuracy_high">Hoch</string>
+16 -10
Просмотреть файл
@@ -1,14 +1,14 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">πελάτης Traccar</string>
<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">Αποστολή SOS</string>
<string name="channel_default">Κύριο Κανάλι</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_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>
@@ -20,20 +20,26 @@
<string name="settings_status_on">Παύση</string>
<string name="settings_status_off_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_summary">Επιθυμητή ακρίβεια θέσης</string>
<string name="settings_accuracy_high">Υψηλό</string>
<string name="settings_accuracy_medium">Μέσο</string>
<string name="settings_accuracy_low">Χαμηλό</string>
<string name="settings_accuracy_high">Υψηλή</string>
<string name="settings_accuracy_medium">Μέση</string>
<string name="settings_accuracy_low">Χαμηλή</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_service_create">Η υπηρεσία εκκινήθηκε</string>
<string name="status_service_destroy">Η υπηρεσία διακόπηκε</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_network_online">Ενεργό δίκτυο</string>
<string name="status_network_offline">Ανενεργό δίκτυο</string>
+13 -7
Просмотреть файл
@@ -16,15 +16,21 @@
<string name="settings_angle_title">Ángulo</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_off">Activado</string>
<string name="settings_status_on">Desactivado</string>
<string name="settings_status_off">Activar</string>
<string name="settings_status_on">Desactivar</string>
<string name="settings_status_off_summary">Servicio detenido</string>
<string name="settings_status_on_summary">Servicio en ejecución</string>
<string name="settings_accuracy_title">Precisión</string>
<string name="settings_accuracy_summary">Precisión deseada</string>
<string name="settings_accuracy_high">Alto</string>
<string name="settings_accuracy_medium">Medio</string>
<string name="settings_accuracy_low">Bajo</string>
<string name="settings_buffer">Búfer fuera de línea</string>
<string name="settings_buffer_off_summary">Búfer activado</string>
<string name="settings_buffer_on_summary">Búfer desactivado</string>
<string name="settings_wakelock">Estado despierto forzado</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_summary">Aumentar prioridad del servicio</string>
<string name="menu_status">Estado</string>
+11
Просмотреть файл
@@ -4,6 +4,8 @@
<string name="app_logo">Traccar</string>
<string name="shortcut_start">Pokreni 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_url_title">Adresa poslužitelja</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_off_summary">Servis zaustavljen</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_summary">Željena preciznost lokacije</string>
<string name="settings_accuracy_high">Visoki</string>
<string name="settings_accuracy_medium">Srednji</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_fail">Greška pri slanju</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_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>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Serviss apturēts</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_summary">Vēlamās vietas precizitāte</string>
<string name="settings_accuracy_high">Augsta</string>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Service gestopt</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_summary">Gewenste lokatienauwkeurigheid</string>
<string name="settings_accuracy_high">Hoog</string>
+5 -2
Просмотреть файл
@@ -21,8 +21,11 @@
<string name="settings_status_off_summary">Usługa zatrzymana</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ł.</string>
<string name="settings_buffer_on_summary"> Buforowanie wł.</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_summary">Żądana dokładność lokalizacji</string>
<string name="settings_accuracy_high">Wysoka</string>
+8 -5
Просмотреть файл
@@ -21,16 +21,19 @@
<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_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_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_summary">Precisão de localização desejada</string>
<string name="settings_accuracy_high">Alto</string>
<string name="settings_accuracy_medium">Médio</string>
<string name="settings_accuracy_low">Baixo</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="menu_status">Status</string>
<string name="settings_foreground_summary">Aumentar a prioridade do serviço</string>
<string name="menu_status">Estado</string>
<string name="menu_shortcuts">Adicionar atalhos</string>
<string name="menu_clear">Limpar</string>
<string name="status_service_create">Serviço iniciado</string>
@@ -42,5 +45,5 @@
<string name="status_network_offline">Rede offline</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="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>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Parar</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_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_summary">Precisão de localização desejada</string>
<string name="settings_accuracy_high">Alto</string>
+6
Просмотреть файл
@@ -20,6 +20,12 @@
<string name="settings_status_on">Stop</string>
<string name="settings_status_off_summary">Servis zaustavljen</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_summary">Željena tačnost lokacije</string>
<string name="settings_accuracy_high">Visoka</string>
+4 -1
Просмотреть файл
@@ -34,6 +34,7 @@
<string name="settings_foreground_title">Foreground service</string>
<string name="settings_foreground_summary">Increase service priority</string>
<string name="menu_status">Status</string>
<string name="menu_info">Info</string>
<string name="menu_shortcuts">Add shortcuts</string>
<string name="menu_clear">Clear</string>
<string name="status_service_create">Service started</string>
@@ -46,5 +47,7 @@
<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="error_msg_invalid_url">Please enter a valid http:// or https:// URL</string>
<string name="disclosure_location">This app collects location data to enable live tracking even when the app is closed or not in use.</string>
<string name="request_exception">To continuously collect location data please turn off battery optimization for the app.</string>
<string name="request_background">To continuously collect location data please enable \"%s\" permission.</string>
<string name="request_background_option">Allow all the time</string>
</resources>
+6 -1
Просмотреть файл
@@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
@@ -1,5 +1,5 @@
/*
* Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org)
* 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.
@@ -13,18 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
package org.traccar.client
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context
import org.traccar.client.PositionProvider.PositionListener
public class MainActivity extends AppCompatActivity {
object PositionProviderFactory {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
fun create(context: Context, listener: PositionListener): PositionProvider {
return AndroidPositionProvider(context, listener)
}
}
-44
Просмотреть файл
@@ -1,44 +0,0 @@
package org.traccar.client;
import android.location.Location;
import android.os.Build;
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;
@Config(sdk = Build.VERSION_CODES.P)
@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())
}
}
-49
Просмотреть файл
@@ -1,49 +0,0 @@
package org.traccar.client;
import android.location.Location;
import android.os.Build;
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;
@Config(sdk = Build.VERSION_CODES.P)
@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)
}
}
-26
Просмотреть файл
@@ -1,26 +0,0 @@
package org.traccar.client;
import android.os.Build;
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;
@Config(sdk = Build.VERSION_CODES.P)
@RunWith(RobolectricTestRunner.class)
public class RequestManagerTest {
@Ignore("Not a real unit test")
@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 -5
Просмотреть файл
@@ -1,18 +1,21 @@
buildscript {
ext.kotlin_version = '1.5.31'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-beta01'
classpath 'com.google.gms:google-services:4.3.4'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.google.gms:google-services:4.3.10'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
+1 -1
Просмотреть файл
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-6.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip