Сравнить коммиты
| Автор | SHA1 | Дата | |
|---|---|---|---|
|
|
6910790fa7 | ||
|
|
6cab1a0b36 | ||
|
|
edd4f670fe | ||
|
|
298d7becb5 | ||
|
|
9a2e3f9126 | ||
|
|
d3f7ee0c8b | ||
|
|
8ae486dbc8 | ||
|
|
02295f5fcf | ||
|
|
958eb77c51 | ||
|
|
0a3a8381a5 | ||
|
|
6009dba342 | ||
|
|
59085f668c |
+15
-10
@@ -1,4 +1,5 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
@@ -8,8 +9,8 @@ android {
|
||||
buildConfigField 'boolean', 'HIDDEN_APP', 'false'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 68
|
||||
versionName '6.7'
|
||||
versionCode 70
|
||||
versionName '6.9'
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
@@ -46,16 +47,20 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.material:material:1.3.0-alpha03'
|
||||
implementation 'com.google.android.material:material:1.5.0-alpha01'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.test:core-ktx:1.4.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
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.core:core-ktx:1.6.0"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
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.0'
|
||||
}
|
||||
|
||||
if (getGradle().getStartParameter().getTaskRequests().toString().contains('Google')) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+10
-11
@@ -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 {
|
||||
class AutostartReceiver : WakefulBroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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?) {
|
||||
result?.let { handler.onComplete(error == null, result) }
|
||||
}
|
||||
}
|
||||
|
||||
private val db: SQLiteDatabase = writableDatabase
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"CREATE TABLE position (" +
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
"deviceId TEXT," +
|
||||
"time INTEGER," +
|
||||
"latitude REAL," +
|
||||
"longitude REAL," +
|
||||
"altitude REAL," +
|
||||
"speed REAL," +
|
||||
"course REAL," +
|
||||
"accuracy REAL," +
|
||||
"battery REAL," +
|
||||
"mock INTEGER)"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("DROP TABLE IF EXISTS position;")
|
||||
onCreate(db)
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("DROP TABLE IF EXISTS position;")
|
||||
onCreate(db)
|
||||
}
|
||||
|
||||
fun insertPosition(position: Position) {
|
||||
val values = ContentValues()
|
||||
values.put("deviceId", position.deviceId)
|
||||
values.put("time", position.time.time)
|
||||
values.put("latitude", position.latitude)
|
||||
values.put("longitude", position.longitude)
|
||||
values.put("altitude", position.altitude)
|
||||
values.put("speed", position.speed)
|
||||
values.put("course", position.course)
|
||||
values.put("accuracy", position.accuracy)
|
||||
values.put("battery", position.battery)
|
||||
values.put("mock", if (position.mock) 1 else 0)
|
||||
db.insertOrThrow("position", null, values)
|
||||
}
|
||||
|
||||
fun insertPositionAsync(position: Position, handler: DatabaseHandler<Unit>) {
|
||||
object : DatabaseAsyncTask<Unit>(handler) {
|
||||
override fun executeMethod() {
|
||||
insertPosition(position)
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
|
||||
fun selectPosition(): Position? {
|
||||
db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null).use { cursor ->
|
||||
if (cursor.count > 0) {
|
||||
cursor.moveToFirst()
|
||||
return Position(
|
||||
id = cursor.getLong(cursor.getColumnIndex("id")),
|
||||
deviceId = cursor.getString(cursor.getColumnIndex("deviceId")),
|
||||
time = Date(cursor.getLong(cursor.getColumnIndex("time"))),
|
||||
latitude = cursor.getDouble(cursor.getColumnIndex("latitude")),
|
||||
longitude = cursor.getDouble(cursor.getColumnIndex("longitude")),
|
||||
altitude = cursor.getDouble(cursor.getColumnIndex("altitude")),
|
||||
speed = cursor.getDouble(cursor.getColumnIndex("speed")),
|
||||
course = cursor.getDouble(cursor.getColumnIndex("course")),
|
||||
accuracy = cursor.getDouble(cursor.getColumnIndex("accuracy")),
|
||||
battery = cursor.getDouble(cursor.getColumnIndex("battery")),
|
||||
mock = cursor.getInt(cursor.getColumnIndex("mock")) > 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun selectPositionAsync(handler: DatabaseHandler<Position?>) {
|
||||
object : DatabaseAsyncTask<Position?>(handler) {
|
||||
override fun executeMethod(): Position? {
|
||||
return selectPosition()
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
|
||||
fun deletePosition(id: Long) {
|
||||
if (db.delete("position", "id = ?", arrayOf(id.toString())) != 1) {
|
||||
throw SQLException()
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePositionAsync(id: Long, handler: DatabaseHandler<Unit>) {
|
||||
object : DatabaseAsyncTask<Unit>(handler) {
|
||||
override fun executeMethod() {
|
||||
deletePosition(id)
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DATABASE_VERSION = 3
|
||||
const val DATABASE_NAME = "traccar.db"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Anton Tananaev (anton@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
+8
-6
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,325 +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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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 java.util.*
|
||||
import kotlin.collections.HashSet
|
||||
|
||||
class MainFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener {
|
||||
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private lateinit var alarmManager: AlarmManager
|
||||
private lateinit var alarmIntent: PendingIntent
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
removeLauncherIcon()
|
||||
}
|
||||
setHasOptionsMenu(true)
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
initPreferences()
|
||||
|
||||
findPreference<Preference>(KEY_DEVICE)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
newValue != null && newValue != ""
|
||||
}
|
||||
findPreference<Preference>(KEY_URL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
newValue != null && validateServerURL(newValue.toString())
|
||||
}
|
||||
findPreference<Preference>(KEY_INTERVAL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
newValue != null && (newValue as String).toInt() > 0
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.w(TAG, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
val numberValidationListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
try {
|
||||
newValue != null && (newValue as String).toInt() >= 0
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.w(TAG, e)
|
||||
false
|
||||
}
|
||||
}
|
||||
findPreference<Preference>(KEY_DISTANCE)?.onPreferenceChangeListener = numberValidationListener
|
||||
findPreference<Preference>(KEY_ANGLE)?.onPreferenceChangeListener = numberValidationListener
|
||||
|
||||
alarmManager = requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
alarmIntent = PendingIntent.getBroadcast(activity, 0, Intent(activity, AutostartReceiver::class.java), 0)
|
||||
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
|
||||
startTrackingService(checkPermission = true, initialPermission = false)
|
||||
}
|
||||
}
|
||||
|
||||
class NumericEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
|
||||
|
||||
override fun onBindDialogView(view: View) {
|
||||
val editText = view.findViewById<EditText>(android.R.id.edit)
|
||||
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||
super.onBindDialogView(view)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(key: String?): NumericEditTextPreferenceDialogFragment {
|
||||
val fragment = NumericEditTextPreferenceDialogFragment()
|
||||
val bundle = Bundle()
|
||||
bundle.putString(ARG_KEY, key)
|
||||
fragment.arguments = bundle
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||
if (listOf(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.key)) {
|
||||
val f: EditTextPreferenceDialogFragmentCompat =
|
||||
NumericEditTextPreferenceDialogFragment.newInstance(preference.key)
|
||||
f.setTargetFragment(this, 0)
|
||||
f.show(requireFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG")
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeLauncherIcon() {
|
||||
val className = MainActivity::class.java.canonicalName!!.replace(".MainActivity", ".Launcher")
|
||||
val componentName = ComponentName(requireActivity().packageName, className)
|
||||
val packageManager = requireActivity().packageManager
|
||||
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
|
||||
packageManager.setComponentEnabledSetting(
|
||||
componentName,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
val builder = AlertDialog.Builder(requireActivity())
|
||||
builder.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
builder.setMessage(getString(R.string.hidden_alert))
|
||||
builder.setPositiveButton(android.R.string.ok, null)
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
private fun setPreferencesEnabled(enabled: Boolean) {
|
||||
findPreference<Preference>(KEY_DEVICE)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_URL)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_INTERVAL)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_DISTANCE)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_ANGLE)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_ACCURACY)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_BUFFER)?.isEnabled = enabled
|
||||
findPreference<Preference>(KEY_WAKELOCK)?.isEnabled = enabled
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
if (key == KEY_STATUS) {
|
||||
if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
|
||||
startTrackingService(checkPermission = true, initialPermission = false)
|
||||
} else {
|
||||
stopTrackingService()
|
||||
}
|
||||
(requireActivity().application as MainApplication).handleRatingFlow(requireActivity())
|
||||
} else if (key == KEY_DEVICE) {
|
||||
findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.main, menu)
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.status) {
|
||||
startActivity(Intent(activity, StatusActivity::class.java))
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun initPreferences() {
|
||||
PreferenceManager.setDefaultValues(activity, R.xml.preferences, false)
|
||||
if (!sharedPreferences.contains(KEY_DEVICE)) {
|
||||
val id = (Random().nextInt(900000) + 100000).toString()
|
||||
sharedPreferences.edit().putString(KEY_DEVICE, id).apply()
|
||||
findPreference<EditTextPreference>(KEY_DEVICE)?.text = id
|
||||
}
|
||||
findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
|
||||
}
|
||||
|
||||
private fun startTrackingService(checkPermission: Boolean, initialPermission: Boolean) {
|
||||
var permission = initialPermission
|
||||
if (checkPermission) {
|
||||
val requiredPermissions: MutableSet<String> = HashSet()
|
||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
permission = requiredPermissions.isEmpty()
|
||||
if (!permission) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
requestPermissions(
|
||||
requiredPermissions.toTypedArray(),
|
||||
PERMISSIONS_REQUEST_LOCATION
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if (permission) {
|
||||
setPreferencesEnabled(false)
|
||||
ContextCompat.startForegroundService(requireContext(), Intent(activity, TrackingService::class.java))
|
||||
alarmManager.setInexactRepeating(
|
||||
AlarmManager.ELAPSED_REALTIME_WAKEUP,
|
||||
ALARM_MANAGER_INTERVAL.toLong(), ALARM_MANAGER_INTERVAL.toLong(), alarmIntent
|
||||
)
|
||||
} else {
|
||||
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply()
|
||||
val preference = findPreference<TwoStatePreference>(KEY_STATUS)
|
||||
preference?.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopTrackingService() {
|
||||
alarmManager.cancel(alarmIntent)
|
||||
requireActivity().stopService(Intent(activity, TrackingService::class.java))
|
||||
setPreferencesEnabled(true)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
|
||||
var granted = true
|
||||
for (result in grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
granted = false
|
||||
break
|
||||
}
|
||||
}
|
||||
startTrackingService(false, granted)
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateServerURL(userUrl: String): Boolean {
|
||||
val port = Uri.parse(userUrl).port
|
||||
if (
|
||||
URLUtil.isValidUrl(userUrl) &&
|
||||
(port == -1 || port in 1..65535) &&
|
||||
(URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
Toast.makeText(activity, R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show()
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = MainFragment::class.java.simpleName
|
||||
private const val ALARM_MANAGER_INTERVAL = 15000
|
||||
const val KEY_DEVICE = "id"
|
||||
const val KEY_URL = "url"
|
||||
const val KEY_INTERVAL = "interval"
|
||||
const val KEY_DISTANCE = "distance"
|
||||
const val KEY_ANGLE = "angle"
|
||||
const val KEY_ACCURACY = "accuracy"
|
||||
const val KEY_STATUS = "status"
|
||||
const val KEY_BUFFER = "buffer"
|
||||
const val KEY_WAKELOCK = "wakelock"
|
||||
private const val PERMISSIONS_REQUEST_LOCATION = 2
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
package org.traccar.client
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.ConnectivityManager
|
||||
import android.util.Log
|
||||
|
||||
class NetworkManager(private val context: Context, private val handler: NetworkHandler?) : BroadcastReceiver() {
|
||||
|
||||
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
interface NetworkHandler {
|
||||
fun onNetworkUpdate(isOnline: Boolean)
|
||||
}
|
||||
|
||||
val isOnline: Boolean
|
||||
get() {
|
||||
val activeNetwork = connectivityManager.activeNetworkInfo
|
||||
return activeNetwork != null && activeNetwork.isConnectedOrConnecting
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
context.registerReceiver(this, filter)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
context.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION && handler != null) {
|
||||
val isOnline = isOnline
|
||||
Log.i(TAG, "network " + if (isOnline) "on" else "off")
|
||||
handler.onNetworkUpdate(isOnline)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = NetworkManager::class.java.simpleName
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import java.util.*
|
||||
|
||||
data class Position(
|
||||
val id: Long = 0,
|
||||
val deviceId: String,
|
||||
val time: Date,
|
||||
val latitude: Double = 0.0,
|
||||
val longitude: Double = 0.0,
|
||||
val altitude: Double = 0.0,
|
||||
val speed: Double = 0.0,
|
||||
val course: Double = 0.0,
|
||||
val accuracy: Double = 0.0,
|
||||
val battery: Double = 0.0,
|
||||
val mock: Boolean = false,
|
||||
) {
|
||||
|
||||
constructor(deviceId: String, location: Location, battery: Double) : this(
|
||||
deviceId = deviceId,
|
||||
time = Date(location.time),
|
||||
latitude = location.latitude,
|
||||
longitude = location.longitude,
|
||||
altitude = location.altitude,
|
||||
speed = location.speed * 1.943844, // speed in knots
|
||||
course = location.bearing.toDouble(),
|
||||
accuracy = if (location.provider != null && location.provider != LocationManager.GPS_PROVIDER) location.accuracy.toDouble() else 0.0,
|
||||
battery = battery,
|
||||
mock = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) location.isFromMockProvider else false,
|
||||
)
|
||||
}
|
||||
@@ -1,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.location.Location
|
||||
import android.os.BatteryManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import kotlin.math.abs
|
||||
|
||||
abstract class PositionProvider(
|
||||
protected val context: Context,
|
||||
protected val listener: PositionListener,
|
||||
) {
|
||||
|
||||
interface PositionListener {
|
||||
fun onPositionUpdate(position: Position)
|
||||
fun onPositionError(error: Throwable)
|
||||
}
|
||||
|
||||
protected var preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
protected var deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined")!!
|
||||
protected var interval = preferences.getString(MainFragment.KEY_INTERVAL, "600")!!.toLong() * 1000
|
||||
protected var distance: Double = preferences.getString(MainFragment.KEY_DISTANCE, "0")!!.toInt().toDouble()
|
||||
protected var angle: Double = preferences.getString(MainFragment.KEY_ANGLE, "0")!!.toInt().toDouble()
|
||||
private var lastLocation: Location? = null
|
||||
|
||||
abstract fun startUpdates()
|
||||
abstract fun stopUpdates()
|
||||
abstract fun requestSingleLocation()
|
||||
|
||||
protected fun processLocation(location: Location?) {
|
||||
if (location != null &&
|
||||
(lastLocation == null || location.time - lastLocation!!.time >= interval || distance > 0
|
||||
&& location.distanceTo(lastLocation) >= distance || angle > 0
|
||||
&& abs(location.bearing - lastLocation!!.bearing) >= angle)
|
||||
) {
|
||||
Log.i(TAG, "location new")
|
||||
lastLocation = location
|
||||
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
|
||||
} else {
|
||||
Log.i(TAG, if (location != null) "location ignored" else "location nil")
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getBatteryLevel(context: Context): Double {
|
||||
val batteryIntent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
if (batteryIntent != null) {
|
||||
val level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
|
||||
val scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1)
|
||||
return level * 100.0 / scale
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = PositionProvider::class.java.simpleName
|
||||
const val MINIMUM_INTERVAL: Long = 1000
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
object ProtocolFormatter {
|
||||
|
||||
fun formatRequest(url: String, position: Position, alarm: String? = null): String {
|
||||
val serverUrl = Uri.parse(url)
|
||||
val builder = serverUrl.buildUpon()
|
||||
.appendQueryParameter("id", position.deviceId)
|
||||
.appendQueryParameter("timestamp", (position.time.time / 1000).toString())
|
||||
.appendQueryParameter("lat", position.latitude.toString())
|
||||
.appendQueryParameter("lon", position.longitude.toString())
|
||||
.appendQueryParameter("speed", position.speed.toString())
|
||||
.appendQueryParameter("bearing", position.course.toString())
|
||||
.appendQueryParameter("altitude", position.altitude.toString())
|
||||
.appendQueryParameter("accuracy", position.accuracy.toString())
|
||||
.appendQueryParameter("batt", position.battery.toString())
|
||||
if (position.mock) {
|
||||
builder.appendQueryParameter("mock", position.mock.toString())
|
||||
}
|
||||
if (alarm != null) {
|
||||
builder.appendQueryParameter("alarm", alarm)
|
||||
}
|
||||
return builder.build().toString()
|
||||
}
|
||||
}
|
||||
@@ -1,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@file:Suppress("DEPRECATION")
|
||||
package org.traccar.client
|
||||
|
||||
import android.os.AsyncTask
|
||||
import android.util.Log
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
object RequestManager {
|
||||
|
||||
private const val TIMEOUT = 15 * 1000
|
||||
|
||||
fun sendRequest(request: String?): Boolean {
|
||||
var inputStream: InputStream? = null
|
||||
return try {
|
||||
val url = URL(request)
|
||||
val connection = url.openConnection() as HttpURLConnection
|
||||
connection.readTimeout = TIMEOUT
|
||||
connection.connectTimeout = TIMEOUT
|
||||
connection.requestMethod = "POST"
|
||||
connection.connect()
|
||||
inputStream = connection.inputStream
|
||||
while (inputStream.read() != -1) {}
|
||||
true
|
||||
} catch (error: IOException) {
|
||||
false
|
||||
} finally {
|
||||
try {
|
||||
inputStream?.close()
|
||||
} catch (secondError: IOException) {
|
||||
Log.w(RequestManager::class.java.simpleName, secondError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendRequestAsync(request: String, handler: RequestHandler) {
|
||||
RequestAsyncTask(handler).execute(request)
|
||||
}
|
||||
|
||||
interface RequestHandler {
|
||||
fun onComplete(success: Boolean)
|
||||
}
|
||||
|
||||
private class RequestAsyncTask(private val handler: RequestHandler) : AsyncTask<String, Unit, Boolean>() {
|
||||
|
||||
override fun doInBackground(vararg request: String): Boolean {
|
||||
return sendRequest(request[0])
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: Boolean) {
|
||||
handler.onComplete(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.traccar.client.PositionProvider.PositionListener
|
||||
import org.traccar.client.ProtocolFormatter.formatRequest
|
||||
import org.traccar.client.RequestManager.RequestHandler
|
||||
import org.traccar.client.RequestManager.sendRequestAsync
|
||||
|
||||
class ShortcutActivity : AppCompatActivity() {
|
||||
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!executeAction(intent)) {
|
||||
setContentView(R.layout.list)
|
||||
val items = arrayOf(
|
||||
getString(R.string.shortcut_start),
|
||||
getString(R.string.shortcut_stop),
|
||||
getString(R.string.shortcut_sos)
|
||||
)
|
||||
val listView = findViewById<ListView>(android.R.id.list)
|
||||
listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)
|
||||
listView.onItemClickListener = OnItemClickListener { _, _, position, _ ->
|
||||
when (position) {
|
||||
0 -> setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START)
|
||||
1 -> setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP)
|
||||
2 -> setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
executeAction(intent)
|
||||
}
|
||||
|
||||
private fun setShortcutResult(label: String, @DrawableRes iconResId: Int, action: String) {
|
||||
val intent = Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity::class.java)
|
||||
intent.putExtra(EXTRA_ACTION, action)
|
||||
val shortcut = ShortcutInfoCompat.Builder(this, action)
|
||||
.setShortLabel(label)
|
||||
.setIcon(IconCompat.createWithResource(this, iconResId))
|
||||
.setIntent(intent)
|
||||
.build()
|
||||
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
|
||||
}
|
||||
|
||||
private fun sendAlarm() {
|
||||
PositionProviderFactory.create(this, object : PositionListener {
|
||||
override fun onPositionUpdate(position: Position) {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this@ShortcutActivity)
|
||||
val request = formatRequest(preferences.getString(MainFragment.KEY_URL, null)!!, position, ALARM_SOS)
|
||||
sendRequestAsync(request, object : RequestHandler {
|
||||
override fun onComplete(success: Boolean) {
|
||||
if (success) {
|
||||
Toast.makeText(this@ShortcutActivity, R.string.status_send_success, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(this@ShortcutActivity, R.string.status_send_fail, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPositionError(error: Throwable) {
|
||||
Toast.makeText(this@ShortcutActivity, error.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}).requestSingleLocation()
|
||||
}
|
||||
|
||||
private fun executeAction(intent: Intent): Boolean {
|
||||
val action: String? = if (intent.hasExtra("shortcutAction")) {
|
||||
if (intent.getBooleanExtra("shortcutAction", false)) ACTION_START else ACTION_STOP
|
||||
} else {
|
||||
intent.getStringExtra(EXTRA_ACTION)
|
||||
}
|
||||
if (action != null) {
|
||||
when (action) {
|
||||
ACTION_START -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, true).apply()
|
||||
ContextCompat.startForegroundService(this, Intent(this, TrackingService::class.java))
|
||||
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
ACTION_STOP -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, false).apply()
|
||||
stopService(Intent(this, TrackingService::class.java))
|
||||
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
ACTION_SOS -> {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
sendAlarm()
|
||||
} else {
|
||||
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
return action != null
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_ACTION = "action"
|
||||
const val ACTION_START = "start"
|
||||
const val ACTION_STOP = "stop"
|
||||
const val ACTION_SOS = "sos"
|
||||
private const val ALARM_SOS = "sos"
|
||||
}
|
||||
}
|
||||
@@ -1,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.widget.ArrayAdapter
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.ListView
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
class StatusActivity : AppCompatActivity() {
|
||||
|
||||
private var adapter: ArrayAdapter<String>? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.list)
|
||||
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, android.R.id.text1, messages)
|
||||
val listView = findViewById<ListView>(android.R.id.list)
|
||||
listView.adapter = adapter
|
||||
adapter?.let { adapters.add(it) }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
adapters.remove(adapter)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.status, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.clear) {
|
||||
clearMessages()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LIMIT = 20
|
||||
private val messages = LinkedList<String>()
|
||||
private val adapters: MutableSet<ArrayAdapter<String>> = HashSet()
|
||||
|
||||
private fun notifyAdapters() {
|
||||
for (adapter in adapters) {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun addMessage(originalMessage: String) {
|
||||
var message = originalMessage
|
||||
val format = DateFormat.getTimeInstance(DateFormat.MEDIUM)
|
||||
message = format.format(Date()) + " - " + message
|
||||
messages.add(message)
|
||||
while (messages.size > LIMIT) {
|
||||
messages.removeFirst()
|
||||
}
|
||||
notifyAdapters()
|
||||
}
|
||||
|
||||
fun clearMessages() {
|
||||
messages.clear()
|
||||
notifyAdapters()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import android.content.Context
|
||||
import org.traccar.client.ProtocolFormatter.formatRequest
|
||||
import org.traccar.client.RequestManager.sendRequestAsync
|
||||
import org.traccar.client.PositionProvider.PositionListener
|
||||
import org.traccar.client.NetworkManager.NetworkHandler
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import org.traccar.client.DatabaseHelper.DatabaseHandler
|
||||
import org.traccar.client.RequestManager.RequestHandler
|
||||
|
||||
class TrackingController(private val context: Context) : PositionListener, NetworkHandler {
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
private val positionProvider = PositionProviderFactory.create(context, this)
|
||||
private val databaseHelper = DatabaseHelper(context)
|
||||
private val networkManager = NetworkManager(context, this)
|
||||
|
||||
private val url: String = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value))!!
|
||||
private val buffer: Boolean = preferences.getBoolean(MainFragment.KEY_BUFFER, true)
|
||||
|
||||
private var isOnline = networkManager.isOnline
|
||||
private var isWaiting = false
|
||||
|
||||
fun start() {
|
||||
if (isOnline) {
|
||||
read()
|
||||
}
|
||||
try {
|
||||
positionProvider.startUpdates()
|
||||
} catch (e: SecurityException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
networkManager.start()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
networkManager.stop()
|
||||
try {
|
||||
positionProvider.stopUpdates()
|
||||
} catch (e: SecurityException) {
|
||||
Log.w(TAG, e)
|
||||
}
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
override fun onPositionUpdate(position: Position) {
|
||||
StatusActivity.addMessage(context.getString(R.string.status_location_update))
|
||||
if (buffer) {
|
||||
write(position)
|
||||
} else {
|
||||
send(position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPositionError(error: Throwable) {}
|
||||
override fun onNetworkUpdate(isOnline: Boolean) {
|
||||
val message = if (isOnline) R.string.status_network_online else R.string.status_network_offline
|
||||
StatusActivity.addMessage(context.getString(message))
|
||||
if (!this.isOnline && isOnline) {
|
||||
read()
|
||||
}
|
||||
this.isOnline = isOnline
|
||||
}
|
||||
|
||||
//
|
||||
// State transition examples:
|
||||
//
|
||||
// write -> read -> send -> delete -> read
|
||||
//
|
||||
// read -> send -> retry -> read -> send
|
||||
//
|
||||
|
||||
private fun log(action: String, position: Position?) {
|
||||
var formattedAction: String = action
|
||||
if (position != null) {
|
||||
formattedAction +=
|
||||
" (id:" + position.id +
|
||||
" time:" + position.time.time / 1000 +
|
||||
" lat:" + position.latitude +
|
||||
" lon:" + position.longitude + ")"
|
||||
}
|
||||
Log.d(TAG, formattedAction)
|
||||
}
|
||||
|
||||
private fun write(position: Position) {
|
||||
log("write", position)
|
||||
databaseHelper.insertPositionAsync(position, object : DatabaseHandler<Unit> {
|
||||
override fun onComplete(success: Boolean, result: Unit) {
|
||||
if (success) {
|
||||
if (isOnline && isWaiting) {
|
||||
read()
|
||||
isWaiting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun read() {
|
||||
log("read", null)
|
||||
databaseHelper.selectPositionAsync(object : DatabaseHandler<Position?> {
|
||||
override fun onComplete(success: Boolean, result: Position?) {
|
||||
if (success) {
|
||||
if (result != null) {
|
||||
if (result.deviceId == preferences.getString(MainFragment.KEY_DEVICE, null)) {
|
||||
send(result)
|
||||
} else {
|
||||
delete(result)
|
||||
}
|
||||
} else {
|
||||
isWaiting = true
|
||||
}
|
||||
} else {
|
||||
retry()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun delete(position: Position) {
|
||||
log("delete", position)
|
||||
databaseHelper.deletePositionAsync(position.id, object : DatabaseHandler<Unit> {
|
||||
override fun onComplete(success: Boolean, result: Unit) {
|
||||
if (success) {
|
||||
read()
|
||||
} else {
|
||||
retry()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun send(position: Position) {
|
||||
log("send", position)
|
||||
val request = formatRequest(url, position)
|
||||
sendRequestAsync(request, object : RequestHandler {
|
||||
override fun onComplete(success: Boolean) {
|
||||
if (success) {
|
||||
if (buffer) {
|
||||
delete(position)
|
||||
}
|
||||
} else {
|
||||
StatusActivity.addMessage(context.getString(R.string.status_send_fail))
|
||||
if (buffer) {
|
||||
retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun retry() {
|
||||
log("retry", null)
|
||||
handler.postDelayed({
|
||||
if (isOnline) {
|
||||
read()
|
||||
}
|
||||
}, RETRY_DELAY.toLong())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = TrackingController::class.java.simpleName
|
||||
private const val RETRY_DELAY = 30 * 1000
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.traccar.client
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.os.PowerManager.WakeLock
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
class TrackingService : Service() {
|
||||
|
||||
private var wakeLock: WakeLock? = null
|
||||
private var trackingController: TrackingController? = null
|
||||
|
||||
class HideNotificationService : Service() {
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
startForeground(NOTIFICATION_ID, createNotification(this))
|
||||
stopForeground(true)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
stopSelfResult(startId)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
override fun onCreate() {
|
||||
Log.i(TAG, "service create")
|
||||
|
||||
sendBroadcast(Intent(ACTION_STARTED))
|
||||
StatusActivity.addMessage(getString(R.string.status_service_create))
|
||||
startForeground(NOTIFICATION_ID, createNotification(this))
|
||||
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(MainFragment.KEY_WAKELOCK, true)) {
|
||||
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
|
||||
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name)
|
||||
wakeLock?.acquire()
|
||||
}
|
||||
trackingController = TrackingController(this)
|
||||
trackingController?.start()
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
ContextCompat.startForegroundService(this, Intent(this, HideNotificationService::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ECLAIR)
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
WakefulBroadcastReceiver.completeWakefulIntent(intent)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.i(TAG, "service destroy")
|
||||
sendBroadcast(Intent(ACTION_STOPPED))
|
||||
StatusActivity.addMessage(getString(R.string.status_service_destroy))
|
||||
stopForeground(true)
|
||||
if (wakeLock?.isHeld == true) {
|
||||
wakeLock?.release()
|
||||
}
|
||||
trackingController?.stop()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val ACTION_STARTED = "org.traccar.action.SERVICE_STARTED"
|
||||
const val ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED"
|
||||
private val TAG = TrackingService::class.java.simpleName
|
||||
private const val NOTIFICATION_ID = 1
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
private fun createNotification(context: Context): Notification {
|
||||
val builder = NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL)
|
||||
.setSmallIcon(R.drawable.ic_stat_notify)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
val intent: Intent
|
||||
if (!BuildConfig.HIDDEN_APP) {
|
||||
intent = Intent(context, MainActivity::class.java)
|
||||
builder
|
||||
.setContentTitle(context.getString(R.string.settings_status_on_summary))
|
||||
.setTicker(context.getString(R.string.settings_status_on_summary))
|
||||
.color = ContextCompat.getColor(context, R.color.primary_dark)
|
||||
} else {
|
||||
intent = Intent(Settings.ACTION_SETTINGS)
|
||||
}
|
||||
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0))
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
if (id == 0) {
|
||||
return false
|
||||
}
|
||||
synchronized(activeWakeLocks) {
|
||||
val wakeLock = activeWakeLocks[id]
|
||||
if (wakeLock != null) {
|
||||
wakeLock.release()
|
||||
activeWakeLocks.remove(id)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -46,5 +46,4 @@
|
||||
<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>
|
||||
</resources>
|
||||
|
||||
+7
-11
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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×tamp=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×tamp=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×tamp=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);
|
||||
}
|
||||
}
|
||||
@@ -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×tamp=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×tamp=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×tamp=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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
}
|
||||
+7
-5
@@ -1,18 +1,20 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.21'
|
||||
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.0-beta05'
|
||||
classpath 'com.google.gms:google-services:4.3.8'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
Ссылка в новой задаче
Block a user