Этот коммит содержится в:
Anton Tananaev
2021-07-17 19:11:29 -07:00
родитель 958eb77c51
Коммит 02295f5fcf
27 изменённых файлов: 1174 добавлений и 1534 удалений
+1
Просмотреть файл
@@ -50,6 +50,7 @@ dependencies {
implementation 'com.google.android.material:material:1.5.0-alpha01' implementation 'com.google.android.material:material:1.5.0-alpha01'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.test:core-ktx:1.4.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.1' testImplementation 'org.robolectric:robolectric:4.1'
googleImplementation 'com.google.firebase:firebase-core:19.0.0' googleImplementation 'com.google.firebase:firebase-core:19.0.0'
+34 -42
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,57 +13,49 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.app.Activity; import android.app.Activity
import android.content.IntentFilter; import android.content.IntentFilter
import android.content.SharedPreferences; import android.os.Build
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
import androidx.annotation.NonNull; class GoogleMainApplication : MainApplication() {
import androidx.annotation.RequiresApi;
import androidx.preference.PreferenceManager;
import com.google.android.play.core.review.ReviewManager; private var firebaseAnalytics: FirebaseAnalytics? = null
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 { override fun onCreate() {
super.onCreate()
private static final String KEY_RATING_SHOWN = "ratingShown"; firebaseAnalytics = FirebaseAnalytics.getInstance(this)
private static final long RATING_THRESHOLD = -24 * 60 * 60 * 1000L; val filter = IntentFilter()
filter.addAction(TrackingService.ACTION_STARTED)
private FirebaseAnalytics firebaseAnalytics; filter.addAction(TrackingService.ACTION_STOPPED)
registerReceiver(ServiceReceiver(), filter)
@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) @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@Override override fun handleRatingFlow(activity: Activity) {
public void handleRatingFlow(@NonNull Activity activity) { val preferences = PreferenceManager.getDefaultSharedPreferences(this)
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); val ratingShown = preferences.getBoolean(KEY_RATING_SHOWN, false)
boolean ratingShown = preferences.getBoolean(KEY_RATING_SHOWN, false); val totalDuration = preferences.getLong(ServiceReceiver.KEY_DURATION, 0)
long totalDuration = preferences.getLong(ServiceReceiver.KEY_DURATION, 0);
if (!ratingShown && totalDuration > RATING_THRESHOLD) { if (!ratingShown && totalDuration > RATING_THRESHOLD) {
ReviewManager reviewManager = ReviewManagerFactory.create(activity); val reviewManager = ReviewManagerFactory.create(activity)
reviewManager.requestReviewFlow().addOnCompleteListener(infoTask -> { reviewManager.requestReviewFlow().addOnCompleteListener { infoTask: Task<ReviewInfo?> ->
if (infoTask.isSuccessful()) { if (infoTask.isSuccessful) {
Task<Void> flow = reviewManager.launchReviewFlow(activity, infoTask.getResult()); val flow = reviewManager.launchReviewFlow(activity, infoTask.result)
flow.addOnCompleteListener(flowTask -> { flow.addOnCompleteListener { preferences.edit().putBoolean(KEY_RATING_SHOWN, true).apply() }
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
}
} }
+40 -58
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2019 Anton Tananaev (anton@traccar.org) * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,72 +13,54 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.annotation.SuppressLint; import android.annotation.SuppressLint
import android.content.Context; import android.content.Context
import android.location.Location; 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
import com.google.android.gms.location.FusedLocationProviderClient; class GooglePositionProvider(context: Context, listener: PositionListener) : PositionProvider(context, listener) {
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 val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
private FusedLocationProviderClient fusedLocationClient; @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())
}
public GooglePositionProvider(Context context, PositionListener listener) { override fun stopUpdates() {
super(context, listener); fusedLocationClient.removeLocationUpdates(locationCallback)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
public void startUpdates() { override fun requestSingleLocation() {
LocationRequest locationRequest = new LocationRequest(); fusedLocationClient.lastLocation.addOnSuccessListener { location ->
locationRequest.setPriority(getPriority(preferences.getString(MainFragment.KEY_ACCURACY, "medium"))); if (location != null) {
locationRequest.setInterval(distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval); listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
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);
}
} }
} }
}; }
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.Context; import android.content.Context
import org.traccar.client.PositionProvider.PositionListener
public class PositionProviderFactory { object PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { fun create(context: Context, listener: PositionListener): PositionProvider {
return new GooglePositionProvider(context, listener); return GooglePositionProvider(context, listener)
} }
} }
+21 -26
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2020 Anton Tananaev (anton@traccar.org) * Copyright 2020 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,37 +13,32 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver
import android.content.Context; import android.content.Context
import android.content.Intent; import android.content.Intent
import android.content.SharedPreferences; import androidx.preference.PreferenceManager
import androidx.preference.PreferenceManager; class ServiceReceiver : BroadcastReceiver() {
public class ServiceReceiver extends BroadcastReceiver { override fun onReceive(context: Context, intent: Intent) {
if (TrackingService.ACTION_STARTED == intent.action) {
public static final String KEY_DURATION = "serviceTime"; startTime = System.currentTimeMillis()
} else if (startTime > 0) {
private static long startTime = 0; updateTime(context, System.currentTimeMillis() - startTime)
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) { private fun updateTime(context: Context, duration: Long) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); val preferences = PreferenceManager.getDefaultSharedPreferences(context)
long totalDuration = preferences.getLong(KEY_DURATION, 0); val totalDuration = preferences.getLong(KEY_DURATION, 0)
preferences.edit().putLong(KEY_DURATION, totalDuration + duration).apply(); preferences.edit().putLong(KEY_DURATION, totalDuration + duration).apply()
} }
companion object {
const val KEY_DURATION = "serviceTime"
private var startTime: Long = 0
}
} }
-110
Просмотреть файл
@@ -1,110 +0,0 @@
/*
* Copyright 2019 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client;
import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Looper;
public class AndroidPositionProvider extends PositionProvider implements LocationListener {
private LocationManager locationManager;
private String provider;
public AndroidPositionProvider(Context context, PositionListener listener) {
super(context, listener);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
provider = getProvider(preferences.getString(MainFragment.KEY_ACCURACY, "medium"));
}
@SuppressLint("MissingPermission")
public void startUpdates() {
try {
locationManager.requestLocationUpdates(
provider, distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval, 0, this);
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
public void stopUpdates() {
locationManager.removeUpdates(this);
}
@SuppressLint("MissingPermission")
public void requestSingleLocation() {
try {
Location location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
if (location != null) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
} else {
locationManager.requestSingleUpdate(provider, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context)));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}, Looper.myLooper());
}
} catch (RuntimeException e) {
listener.onPositionError(e);
}
}
private static String getProvider(String accuracy) {
switch (accuracy) {
case "high":
return LocationManager.GPS_PROVIDER;
case "low":
return LocationManager.PASSIVE_PROVIDER;
default:
return LocationManager.NETWORK_PROVIDER;
}
}
@Override
public void onLocationChanged(Location location) {
processLocation(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
+83
Просмотреть файл
@@ -0,0 +1,83 @@
/*
* Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.traccar.client
import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.os.Looper
class AndroidPositionProvider(context: Context, listener: PositionListener) : PositionProvider(context, listener), LocationListener {
private val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val provider = getProvider(preferences.getString(MainFragment.KEY_ACCURACY, "medium"))
@SuppressLint("MissingPermission")
override fun startUpdates() {
try {
locationManager.requestLocationUpdates(
provider, if (distance > 0 || angle > 0) MINIMUM_INTERVAL else interval, 0f, this)
} catch (e: RuntimeException) {
listener.onPositionError(e)
}
}
override fun stopUpdates() {
locationManager.removeUpdates(this)
}
@Suppress("DEPRECATION", "MissingPermission")
override fun requestSingleLocation() {
try {
val location = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER)
if (location != null) {
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
} else {
locationManager.requestSingleUpdate(provider, object : LocationListener {
override fun onLocationChanged(location: Location) {
listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context)))
}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}, Looper.myLooper())
}
} catch (e: RuntimeException) {
listener.onPositionError(e)
}
}
override fun onLocationChanged(location: Location) {
processLocation(location)
}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
private fun getProvider(accuracy: String?): String {
return when (accuracy) {
"high" -> LocationManager.GPS_PROVIDER
"low" -> LocationManager.PASSIVE_PROVIDER
else -> LocationManager.NETWORK_PROVIDER
}
}
}
+11 -12
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 - 2017 Anton Tananaev (anton@traccar.org) * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,20 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.Context; import android.content.Context
import android.content.Intent; import android.content.Intent
import android.content.SharedPreferences; import androidx.preference.PreferenceManager
import android.preference.PreferenceManager;
public class AutostartReceiver extends WakefulBroadcastReceiver { class AutostartReceiver : WakefulBroadcastReceiver() {
@Override @Suppress("UnsafeProtectedBroadcastReceiver")
public void onReceive(Context context, Intent intent) { override fun onReceive(context: Context, intent: Intent) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) { if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) {
startWakefulForegroundService(context, new Intent(context, TrackingService.class)); startWakefulForegroundService(context, Intent(context, TrackingService::class.java))
} }
} }
+100 -126
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton@traccar.org) * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,168 +13,142 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; @file:Suppress("DEPRECATION", "StaticFieldLeak")
package org.traccar.client
import android.content.ContentValues; import android.content.ContentValues
import android.content.Context; import android.content.Context
import android.database.Cursor; import android.database.SQLException
import android.database.SQLException; import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper
import android.database.sqlite.SQLiteOpenHelper; import android.os.AsyncTask
import android.os.AsyncTask; import java.sql.Date
import java.util.Date; class DatabaseHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
public class DatabaseHelper extends SQLiteOpenHelper { interface DatabaseHandler<T> {
fun onComplete(success: Boolean, result: T)
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 abstract class DatabaseAsyncTask<T>(val handler: DatabaseHandler<T>) : AsyncTask<Unit, Unit, T?>() {
private DatabaseHandler<T> handler; private var error: RuntimeException? = null
private RuntimeException error;
public DatabaseAsyncTask(DatabaseHandler<T> handler) { override fun doInBackground(vararg params: Unit): T? {
this.handler = handler; return try {
} executeMethod()
} catch (error: RuntimeException) {
@Override this.error = error
protected T doInBackground(Void... params) { null
try {
return executeMethod();
} catch (RuntimeException error) {
this.error = error;
return null;
} }
} }
protected abstract T executeMethod(); protected abstract fun executeMethod(): T
@Override override fun onPostExecute(result: T?) {
protected void onPostExecute(T result) { result?.let { handler.onComplete(error == null, result) }
handler.onComplete(error == null, result);
} }
} }
private SQLiteDatabase db; private val db: SQLiteDatabase = writableDatabase
public DatabaseHelper(Context context) { override fun onCreate(db: SQLiteDatabase) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); db.execSQL(
db = getWritableDatabase(); "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 override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
public void onCreate(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS position;")
db.execSQL("CREATE TABLE position (" + onCreate(db)
"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 override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS position;")
db.execSQL("DROP TABLE IF EXISTS position;"); onCreate(db)
onCreate(db);
} }
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { fun insertPosition(position: Position) {
db.execSQL("DROP TABLE IF EXISTS position;"); val values = ContentValues()
onCreate(db); 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)
} }
public void insertPosition(Position position) { fun insertPositionAsync(position: Position, handler: DatabaseHandler<Unit>) {
ContentValues values = new ContentValues(); object : DatabaseAsyncTask<Unit>(handler) {
values.put("deviceId", position.getDeviceId()); override fun executeMethod() {
values.put("time", position.getTime().getTime()); insertPosition(position)
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(); }.execute()
} }
public Position selectPosition() { fun selectPosition(): Position? {
Position position = new Position(); db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null).use { cursor ->
if (cursor.count > 0) {
Cursor cursor = db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null); cursor.moveToFirst()
try { return Position(
if (cursor.getCount() > 0) { id = cursor.getLong(cursor.getColumnIndex("id")),
deviceId = cursor.getString(cursor.getColumnIndex("deviceId")),
cursor.moveToFirst(); time = Date(cursor.getLong(cursor.getColumnIndex("time"))),
latitude = cursor.getDouble(cursor.getColumnIndex("latitude")),
position.setId(cursor.getLong(cursor.getColumnIndex("id"))); longitude = cursor.getDouble(cursor.getColumnIndex("longitude")),
position.setDeviceId(cursor.getString(cursor.getColumnIndex("deviceId"))); altitude = cursor.getDouble(cursor.getColumnIndex("altitude")),
position.setTime(new Date(cursor.getLong(cursor.getColumnIndex("time")))); speed = cursor.getDouble(cursor.getColumnIndex("speed")),
position.setLatitude(cursor.getDouble(cursor.getColumnIndex("latitude"))); course = cursor.getDouble(cursor.getColumnIndex("course")),
position.setLongitude(cursor.getDouble(cursor.getColumnIndex("longitude"))); accuracy = cursor.getDouble(cursor.getColumnIndex("accuracy")),
position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude"))); battery = cursor.getDouble(cursor.getColumnIndex("battery")),
position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed"))); mock = cursor.getInt(cursor.getColumnIndex("mock")) > 0,
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 null
return position;
} }
public void selectPositionAsync(DatabaseHandler<Position> handler) { fun selectPositionAsync(handler: DatabaseHandler<Position?>) {
new DatabaseAsyncTask<Position>(handler) { object : DatabaseAsyncTask<Position?>(handler) {
@Override override fun executeMethod(): Position? {
protected Position executeMethod() { return selectPosition()
return selectPosition();
} }
}.execute(); }.execute()
} }
public void deletePosition(long id) { fun deletePosition(id: Long) {
if (db.delete("position", "id = ?", new String[] { String.valueOf(id) }) != 1) { if (db.delete("position", "id = ?", arrayOf(id.toString())) != 1) {
throw new SQLException(); throw SQLException()
} }
} }
public void deletePositionAsync(final long id, DatabaseHandler<Void> handler) { fun deletePositionAsync(id: Long, handler: DatabaseHandler<Unit>) {
new DatabaseAsyncTask<Void>(handler) { object : DatabaseAsyncTask<Unit>(handler) {
@Override override fun executeMethod() {
protected Void executeMethod() { deletePosition(id)
deletePosition(id);
return null;
} }
}.execute(); }.execute()
}
companion object {
const val DATABASE_VERSION = 3
const val DATABASE_NAME = "traccar.db"
} }
} }
+17 -16
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton@traccar.org) * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,25 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver
import android.content.Context; import android.content.Context
import android.content.Intent; import android.content.Intent
public class DialLaunchReceiver extends BroadcastReceiver { class DialLaunchReceiver : BroadcastReceiver() {
private static final String LAUNCHER_NUMBER = "8722227"; // TRACCAR override fun onReceive(context: Context, intent: Intent) {
val phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
@Override if (phoneNumber == LAUNCHER_NUMBER) {
public void onReceive(Context context, Intent intent) { resultData = null
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); val appIntent = Intent(context, MainActivity::class.java)
if (phoneNumber.equals(LAUNCHER_NUMBER)) { appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setResultData(null); context.startActivity(appIntent)
Intent appIntent = new Intent(context, MainActivity.class);
appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(appIntent);
} }
} }
companion object {
private const val LAUNCHER_NUMBER = "8722227" // TRACCAR
}
} }
+8 -10
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2017 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,18 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity
import androidx.annotation.Nullable; import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity { class MainActivity : AppCompatActivity() {
@Override override fun onCreate(savedInstanceState: Bundle?) {
protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState)
super.onCreate(savedInstanceState); setContentView(R.layout.main)
setContentView(R.layout.main);
} }
} }
+26 -47
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,67 +13,46 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.annotation.TargetApi; import androidx.multidex.MultiDexApplication
import android.app.Activity; import android.annotation.TargetApi
import android.app.Notification; import android.app.NotificationChannel
import android.app.NotificationChannel; import android.app.NotificationManager
import android.app.NotificationManager; import android.app.Notification
import android.content.Context; import android.graphics.Color
import android.content.SharedPreferences; import android.os.Build
import android.graphics.Color; import android.app.Activity
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import androidx.annotation.NonNull; 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)); 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
registerChannel(); registerChannel()
} }
} }
@TargetApi(Build.VERSION_CODES.O) @TargetApi(Build.VERSION_CODES.O)
private void registerChannel() { private fun registerChannel() {
NotificationChannel channel = new NotificationChannel( val channel = NotificationChannel(
PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_LOW); PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_LOW
channel.setLightColor(Color.GREEN); )
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); channel.lightColor = Color.GREEN
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel); channel.lockscreenVisibility = Notification.VISIBILITY_SECRET
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)
} }
private void migrateLegacyPreferences(SharedPreferences preferences) { open fun handleRatingFlow(activity: Activity) {}
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(); companion object {
builder.scheme(scheme).encodedAuthority(host + ":" + port).build(); const val PRIMARY_CHANNEL = "default"
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) {
} }
} }
+194 -222
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,299 +13,271 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.Manifest; import android.Manifest
import android.app.AlarmManager; import android.annotation.SuppressLint
import android.app.PendingIntent; import android.app.AlarmManager
import android.content.ComponentName; import android.app.PendingIntent
import android.content.Context; import android.content.ComponentName
import android.content.Intent; import android.content.Context
import android.content.SharedPreferences; import android.content.Intent
import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.SharedPreferences
import android.content.pm.PackageManager; import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.net.Uri; import android.content.pm.PackageManager
import android.os.Build; import android.net.Uri
import android.os.Bundle; import android.os.Build
import android.text.InputType; import android.os.Bundle
import android.util.Log; import android.text.InputType
import android.view.Menu; import android.util.Log
import android.view.MenuInflater; import android.view.Menu
import android.view.MenuItem; import android.view.MenuInflater
import android.view.View; import android.view.MenuItem
import android.webkit.URLUtil; import android.view.View
import android.widget.EditText; import android.webkit.URLUtil
import android.widget.Toast; 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
import androidx.annotation.NonNull; class MainFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener {
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.Arrays; private lateinit var sharedPreferences: SharedPreferences
import java.util.HashSet; private lateinit var alarmManager: AlarmManager
import java.util.Random; private lateinit var alarmIntent: PendingIntent
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 SharedPreferences sharedPreferences;
private AlarmManager alarmManager;
private PendingIntent alarmIntent;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@SuppressLint("UnspecifiedImmutableFlag")
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
removeLauncherIcon(); removeLauncherIcon()
} }
setHasOptionsMenu(true)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
setPreferencesFromResource(R.xml.preferences, rootKey)
initPreferences()
setHasOptionsMenu(true); findPreference<Preference>(KEY_DEVICE)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
newValue != null && newValue != ""
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); }
setPreferencesFromResource(R.xml.preferences, rootKey); findPreference<Preference>(KEY_URL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
initPreferences(); newValue != null && validateServerURL(newValue.toString())
}
findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { findPreference<Preference>(KEY_INTERVAL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
@Override try {
public boolean onPreferenceChange(Preference preference, Object newValue) { newValue != null && (newValue as String).toInt() > 0
return newValue != null && !newValue.equals(""); } catch (e: NumberFormatException) {
Log.w(TAG, e)
false
} }
}); }
findPreference(KEY_URL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { val numberValidationListener = Preference.OnPreferenceChangeListener { _, newValue ->
@Override try {
public boolean onPreferenceChange(Preference preference, Object newValue) { newValue != null && (newValue as String).toInt() >= 0
return (newValue != null) && validateServerURL(newValue.toString()); } catch (e: NumberFormatException) {
Log.w(TAG, e)
false
} }
}); }
findPreference<Preference>(KEY_DISTANCE)?.onPreferenceChangeListener = numberValidationListener
findPreference(KEY_INTERVAL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { findPreference<Preference>(KEY_ANGLE)?.onPreferenceChangeListener = numberValidationListener
@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);
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)) { if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false); startTrackingService(checkPermission = true, initialPermission = false)
} }
} }
public static class NumericEditTextPreferenceDialogFragment extends EditTextPreferenceDialogFragmentCompat { class NumericEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
public static NumericEditTextPreferenceDialogFragment newInstance(String key) { override fun onBindDialogView(view: View) {
final NumericEditTextPreferenceDialogFragment fragment = new NumericEditTextPreferenceDialogFragment(); val editText = view.findViewById<EditText>(android.R.id.edit)
final Bundle bundle = new Bundle(); editText.inputType = InputType.TYPE_CLASS_NUMBER
bundle.putString(ARG_KEY, key); super.onBindDialogView(view)
fragment.setArguments(bundle);
return fragment;
} }
@Override companion object {
protected void onBindDialogView(View view) { fun newInstance(key: String?): NumericEditTextPreferenceDialogFragment {
EditText editText = view.findViewById(android.R.id.edit); val fragment = NumericEditTextPreferenceDialogFragment()
editText.setInputType(InputType.TYPE_CLASS_NUMBER); val bundle = Bundle()
super.onBindDialogView(view); bundle.putString(ARG_KEY, key)
fragment.arguments = bundle
return fragment
}
} }
} }
@Override override fun onDisplayPreferenceDialog(preference: Preference) {
public void onDisplayPreferenceDialog(Preference preference) { if (listOf(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.key)) {
if (Arrays.asList(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.getKey())) { val f: EditTextPreferenceDialogFragmentCompat =
final EditTextPreferenceDialogFragmentCompat f = NumericEditTextPreferenceDialogFragment.newInstance(preference.getKey()); NumericEditTextPreferenceDialogFragment.newInstance(preference.key)
f.setTargetFragment(this, 0); f.setTargetFragment(this, 0)
f.show(getFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG"); f.show(requireFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG")
} else { } else {
super.onDisplayPreferenceDialog(preference); super.onDisplayPreferenceDialog(preference)
} }
} }
private void removeLauncherIcon() { private fun removeLauncherIcon() {
String className = MainActivity.class.getCanonicalName().replace(".MainActivity", ".Launcher"); val className = MainActivity::class.java.canonicalName!!.replace(".MainActivity", ".Launcher")
ComponentName componentName = new ComponentName(getActivity().getPackageName(), className); val componentName = ComponentName(requireActivity().packageName, className)
PackageManager packageManager = getActivity().getPackageManager(); val packageManager = requireActivity().packageManager
if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
packageManager.setComponentEnabledSetting( packageManager.setComponentEnabledSetting(
componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); componentName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); PackageManager.DONT_KILL_APP
builder.setIcon(android.R.drawable.ic_dialog_alert); )
builder.setMessage(getString(R.string.hidden_alert)); val builder = AlertDialog.Builder(requireActivity())
builder.setPositiveButton(android.R.string.ok, null); builder.setIcon(android.R.drawable.ic_dialog_alert)
builder.show(); builder.setMessage(getString(R.string.hidden_alert))
builder.setPositiveButton(android.R.string.ok, null)
builder.show()
} }
} }
@Override override fun onResume() {
public void onResume() { super.onResume()
super.onResume(); sharedPreferences.registerOnSharedPreferenceChangeListener(this)
sharedPreferences.registerOnSharedPreferenceChangeListener(this);
} }
@Override override fun onPause() {
public void onPause() { super.onPause()
super.onPause(); sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
} }
private void setPreferencesEnabled(boolean enabled) { private fun setPreferencesEnabled(enabled: Boolean) {
findPreference(KEY_DEVICE).setEnabled(enabled); findPreference<Preference>(KEY_DEVICE)?.isEnabled = enabled
findPreference(KEY_URL).setEnabled(enabled); findPreference<Preference>(KEY_URL)?.isEnabled = enabled
findPreference(KEY_INTERVAL).setEnabled(enabled); findPreference<Preference>(KEY_INTERVAL)?.isEnabled = enabled
findPreference(KEY_DISTANCE).setEnabled(enabled); findPreference<Preference>(KEY_DISTANCE)?.isEnabled = enabled
findPreference(KEY_ANGLE).setEnabled(enabled); findPreference<Preference>(KEY_ANGLE)?.isEnabled = enabled
findPreference(KEY_ACCURACY).setEnabled(enabled); findPreference<Preference>(KEY_ACCURACY)?.isEnabled = enabled
findPreference(KEY_BUFFER).setEnabled(enabled); findPreference<Preference>(KEY_BUFFER)?.isEnabled = enabled
findPreference(KEY_WAKELOCK).setEnabled(enabled); findPreference<Preference>(KEY_WAKELOCK)?.isEnabled = enabled
} }
@Override override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key == KEY_STATUS) {
if (key.equals(KEY_STATUS)) {
if (sharedPreferences.getBoolean(KEY_STATUS, false)) { if (sharedPreferences.getBoolean(KEY_STATUS, false)) {
startTrackingService(true, false); startTrackingService(checkPermission = true, initialPermission = false)
} else { } else {
stopTrackingService(); stopTrackingService()
} }
((MainApplication) getActivity().getApplication()).handleRatingFlow(getActivity()); (requireActivity().application as MainApplication).handleRatingFlow(requireActivity())
} else if (key.equals(KEY_DEVICE)) { } else if (key == KEY_DEVICE) {
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null)); findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
} }
} }
@Override override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.main, menu)
inflater.inflate(R.menu.main, menu); super.onCreateOptionsMenu(menu, inflater)
super.onCreateOptionsMenu(menu, inflater);
} }
@Override override fun onOptionsItemSelected(item: MenuItem): Boolean {
public boolean onOptionsItemSelected(MenuItem item) { if (item.itemId == R.id.status) {
if (item.getItemId() == R.id.status) { startActivity(Intent(activity, StatusActivity::class.java))
startActivity(new Intent(getActivity(), StatusActivity.class)); return true
return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item)
} }
private void initPreferences() { private fun initPreferences() {
PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false); PreferenceManager.setDefaultValues(activity, R.xml.preferences, false)
if (!sharedPreferences.contains(KEY_DEVICE)) { if (!sharedPreferences.contains(KEY_DEVICE)) {
String id = String.valueOf(new Random().nextInt(900000) + 100000); val id = (Random().nextInt(900000) + 100000).toString()
sharedPreferences.edit().putString(KEY_DEVICE, id).apply(); sharedPreferences.edit().putString(KEY_DEVICE, id).apply()
((EditTextPreference) findPreference(KEY_DEVICE)).setText(id); findPreference<EditTextPreference>(KEY_DEVICE)?.text = id
} }
findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null)); findPreference<Preference>(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null)
} }
private void startTrackingService(boolean checkPermission, boolean permission) { private fun startTrackingService(checkPermission: Boolean, initialPermission: Boolean) {
var permission = initialPermission
if (checkPermission) { if (checkPermission) {
Set<String> requiredPermissions = new HashSet<>(); val requiredPermissions: MutableSet<String> = HashSet()
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION)
} }
permission = requiredPermissions.isEmpty(); permission = requiredPermissions.isEmpty()
if (!permission) { if (!permission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(requiredPermissions.toArray(new String[requiredPermissions.size()]), PERMISSIONS_REQUEST_LOCATION); requestPermissions(
requiredPermissions.toTypedArray(),
PERMISSIONS_REQUEST_LOCATION
)
} }
return; return
} }
} }
if (permission) { if (permission) {
setPreferencesEnabled(false); setPreferencesEnabled(false)
ContextCompat.startForegroundService(getContext(), new Intent(getActivity(), TrackingService.class)); ContextCompat.startForegroundService(requireContext(), Intent(activity, TrackingService::class.java))
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmManager.setInexactRepeating(
ALARM_MANAGER_INTERVAL, ALARM_MANAGER_INTERVAL, alarmIntent); AlarmManager.ELAPSED_REALTIME_WAKEUP,
ALARM_MANAGER_INTERVAL.toLong(), ALARM_MANAGER_INTERVAL.toLong(), alarmIntent
)
} else { } else {
sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply(); sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply()
TwoStatePreference preference = findPreference(KEY_STATUS); val preference = findPreference<TwoStatePreference>(KEY_STATUS)
preference.setChecked(false); preference?.isChecked = false
} }
} }
private void stopTrackingService() { private fun stopTrackingService() {
alarmManager.cancel(alarmIntent); alarmManager.cancel(alarmIntent)
getActivity().stopService(new Intent(getActivity(), TrackingService.class)); requireActivity().stopService(Intent(activity, TrackingService::class.java))
setPreferencesEnabled(true); setPreferencesEnabled(true)
} }
@Override override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_LOCATION) { if (requestCode == PERMISSIONS_REQUEST_LOCATION) {
boolean granted = true; var granted = true
for (int result : grantResults) { for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) { if (result != PackageManager.PERMISSION_GRANTED) {
granted = false; granted = false
break; break
} }
} }
startTrackingService(false, granted); startTrackingService(false, granted)
} }
} }
private boolean validateServerURL(String userUrl) { private fun validateServerURL(userUrl: String): Boolean {
int port = Uri.parse(userUrl).getPort(); val port = Uri.parse(userUrl).port
if (URLUtil.isValidUrl(userUrl) && (port == -1 || (port > 0 && port <= 65535)) if (
&& (URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))) { URLUtil.isValidUrl(userUrl) &&
return true; (port == -1 || port in 1..65535) &&
(URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))
) {
return true
} }
Toast.makeText(getActivity(), R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show(); Toast.makeText(activity, R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show()
return false; 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
} }
} }
+34 -40
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton@traccar.org) * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,56 +13,50 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; @file:Suppress("DEPRECATION")
package org.traccar.client
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver
import android.content.Context; import android.content.Context
import android.content.Intent; import android.content.Intent
import android.content.IntentFilter; import android.content.IntentFilter
import android.net.ConnectivityManager; import android.net.ConnectivityManager
import android.net.NetworkInfo; import android.util.Log
import android.util.Log;
public class NetworkManager extends BroadcastReceiver { class NetworkManager(private val context: Context, private val handler: NetworkHandler?) : BroadcastReceiver() {
private static final String TAG = NetworkManager.class.getSimpleName(); private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private Context context; interface NetworkHandler {
private NetworkHandler handler; fun onNetworkUpdate(isOnline: Boolean)
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 { val isOnline: Boolean
void onNetworkUpdate(boolean isOnline); get() {
val activeNetwork = connectivityManager.activeNetworkInfo
return activeNetwork != null && activeNetwork.isConnectedOrConnecting
}
fun start() {
val filter = IntentFilter()
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
context.registerReceiver(this, filter)
} }
public boolean isOnline() { fun stop() {
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); context.unregisterReceiver(this)
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
} }
public void start() { override fun onReceive(context: Context, intent: Intent) {
IntentFilter filter = new IntentFilter(); if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION && handler != null) {
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); val isOnline = isOnline
context.registerReceiver(this, filter); Log.i(TAG, "network " + if (isOnline) "on" else "off")
} handler.onNetworkUpdate(isOnline)
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);
} }
} }
companion object {
private val TAG = NetworkManager::class.java.simpleName
}
} }
+31 -138
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 - 2018 Anton Tananaev (anton@traccar.org) * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,144 +13,37 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.location.Location; import android.location.Location
import android.location.LocationManager; import android.location.LocationManager
import android.os.Build; import android.os.Build
import java.util.*
import java.util.Date; data class Position(
val id: Long = 0,
public class Position { val deviceId: String,
val time: Date,
public Position() { val latitude: Double = 0.0,
} val longitude: Double = 0.0,
val altitude: Double = 0.0,
public Position(String deviceId, Location location, double battery) { val speed: Double = 0.0,
this.deviceId = deviceId; val course: Double = 0.0,
time = new Date(location.getTime()); val accuracy: Double = 0.0,
latitude = location.getLatitude(); val battery: Double = 0.0,
longitude = location.getLongitude(); val mock: Boolean = false,
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;
}
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,
)
} }
+48 -61
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013 - 2019 Anton Tananaev (anton@traccar.org) * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,79 +13,66 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.Context; import android.content.Context
import android.content.Intent; import android.content.Intent
import android.content.IntentFilter; import android.content.IntentFilter
import android.content.SharedPreferences; import android.content.SharedPreferences
import android.location.Location; import android.location.Location
import android.os.BatteryManager; import android.os.BatteryManager
import android.preference.PreferenceManager; import androidx.preference.PreferenceManager
import android.util.Log; import android.util.Log
import kotlin.math.abs
public abstract class PositionProvider { abstract class PositionProvider(
protected val context: Context,
protected val listener: PositionListener,
) {
private static final String TAG = PositionProvider.class.getSimpleName(); interface PositionListener {
fun onPositionUpdate(position: Position)
protected static final int MINIMUM_INTERVAL = 1000; fun onPositionError(error: Throwable)
public interface PositionListener {
void onPositionUpdate(Position position);
void onPositionError(Throwable error);
} }
protected final PositionListener listener; 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
protected final Context context; abstract fun startUpdates()
protected SharedPreferences preferences; abstract fun stopUpdates()
abstract fun requestSingleLocation()
protected String deviceId; protected fun processLocation(location: Location?) {
protected long interval; if (location != null &&
protected double distance; (lastLocation == null || location.time - lastLocation!!.time >= interval || distance > 0
protected double angle; && location.distanceTo(lastLocation) >= distance || angle > 0
&& abs(location.bearing - lastLocation!!.bearing) >= angle)
protected Location lastLocation; ) {
Log.i(TAG, "location new")
public PositionProvider(Context context, PositionListener listener) { lastLocation = location
this.context = context; listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(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 { } else {
Log.i(TAG, location != null ? "location ignored" : "location nil"); Log.i(TAG, if (location != null) "location ignored" else "location nil")
} }
} }
protected static double getBatteryLevel(Context context) { protected fun getBatteryLevel(context: Context): Double {
Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); val batteryIntent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
if (batteryIntent != null) { if (batteryIntent != null) {
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); val level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); val scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1)
return (level * 100.0) / scale; return level * 100.0 / scale
} }
return 0; return 0.0
}
companion object {
private val TAG = PositionProvider::class.java.simpleName
const val MINIMUM_INTERVAL: Long = 1000
} }
} }
+20 -27
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2016 Anton Tananaev (anton@traccar.org) * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,37 +13,30 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.net.Uri; import android.net.Uri
public class ProtocolFormatter { object ProtocolFormatter {
public static String formatRequest(String url, Position position) { fun formatRequest(url: String, position: Position, alarm: String? = null): String {
return formatRequest(url, position, null); val serverUrl = Uri.parse(url)
} val builder = serverUrl.buildUpon()
.appendQueryParameter("id", position.deviceId)
public static String formatRequest(String url, Position position, String alarm) { .appendQueryParameter("timestamp", (position.time.time / 1000).toString())
Uri serverUrl = Uri.parse(url); .appendQueryParameter("lat", position.latitude.toString())
Uri.Builder builder = serverUrl.buildUpon() .appendQueryParameter("lon", position.longitude.toString())
.appendQueryParameter("id", position.getDeviceId()) .appendQueryParameter("speed", position.speed.toString())
.appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000)) .appendQueryParameter("bearing", position.course.toString())
.appendQueryParameter("lat", String.valueOf(position.getLatitude())) .appendQueryParameter("altitude", position.altitude.toString())
.appendQueryParameter("lon", String.valueOf(position.getLongitude())) .appendQueryParameter("accuracy", position.accuracy.toString())
.appendQueryParameter("speed", String.valueOf(position.getSpeed())) .appendQueryParameter("batt", position.battery.toString())
.appendQueryParameter("bearing", String.valueOf(position.getCourse())) if (position.mock) {
.appendQueryParameter("altitude", String.valueOf(position.getAltitude())) builder.appendQueryParameter("mock", position.mock.toString())
.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) { if (alarm != null) {
builder.appendQueryParameter("alarm", alarm); builder.appendQueryParameter("alarm", alarm)
} }
return builder.build().toString()
return builder.build().toString();
} }
} }
+44 -56
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Anton Tananaev (anton@traccar.org) * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,71 +13,59 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; @file:Suppress("DEPRECATION")
package org.traccar.client
import android.os.AsyncTask; import android.os.AsyncTask
import android.util.Log; import android.util.Log
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL
import java.io.IOException; object RequestManager {
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class RequestManager { private const val TIMEOUT = 15 * 1000
private static final int TIMEOUT = 15 * 1000; fun sendRequest(request: String?): Boolean {
var inputStream: InputStream? = null
public interface RequestHandler { return try {
void onComplete(boolean success); val url = URL(request)
} val connection = url.openConnection() as HttpURLConnection
connection.readTimeout = TIMEOUT
private static class RequestAsyncTask extends AsyncTask<String, Void, Boolean> { connection.connectTimeout = TIMEOUT
connection.requestMethod = "POST"
private RequestHandler handler; connection.connect()
inputStream = connection.inputStream
public RequestAsyncTask(RequestHandler handler) { while (inputStream.read() != -1) {}
this.handler = handler; true
} } catch (error: IOException) {
false
@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 { } finally {
try { try {
if (inputStream != null) { inputStream?.close()
inputStream.close(); } catch (secondError: IOException) {
} Log.w(RequestManager::class.java.simpleName, secondError)
} catch (IOException secondError) {
Log.w(RequestManager.class.getSimpleName(), secondError);
} }
} }
} }
public static void sendRequestAsync(String request, RequestHandler handler) { fun sendRequestAsync(request: String, handler: RequestHandler) {
RequestAsyncTask task = new RequestAsyncTask(handler); RequestAsyncTask(handler).execute(request)
task.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)
}
}
} }
+94 -120
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016 - 2019 Anton Tananaev (anton@traccar.org) * Copyright 2016 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,152 +13,126 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.Intent; import android.Manifest
import android.content.SharedPreferences; import android.content.Intent
import android.content.pm.PackageManager; import android.content.pm.PackageManager
import android.os.Bundle; import android.os.Bundle
import android.preference.PreferenceManager; import android.widget.AdapterView.OnItemClickListener
import android.view.View; import android.widget.ArrayAdapter
import android.widget.AdapterView; import android.widget.ListView
import android.widget.ArrayAdapter; import android.widget.Toast
import android.widget.ListView; import androidx.annotation.DrawableRes
import android.widget.Toast; 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
import androidx.annotation.DrawableRes; class ShortcutActivity : AppCompatActivity() {
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 override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
public static final String EXTRA_ACTION = "action"; if (!executeAction(intent)) {
public static final String ACTION_START = "start"; setContentView(R.layout.list)
public static final String ACTION_STOP = "stop"; val items = arrayOf(
public static final String ACTION_SOS = "sos"; getString(R.string.shortcut_start),
getString(R.string.shortcut_stop),
private static final String ALARM_SOS = "sos"; getString(R.string.shortcut_sos)
)
@Override val listView = findViewById<ListView>(android.R.id.list)
public void onCreate(Bundle savedInstanceState) { listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items)
super.onCreate(savedInstanceState); listView.onItemClickListener = OnItemClickListener { _, _, position, _ ->
if (!executeAction(getIntent())) { when (position) {
setContentView(R.layout.list); 0 -> setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START)
1 -> setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP)
final String[] items = new String[] { 2 -> setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS)
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();
} }
}); finish()
}
} }
} }
@Override override fun onNewIntent(intent: Intent) {
protected void onNewIntent(Intent intent) { super.onNewIntent(intent)
super.onNewIntent(intent); executeAction(intent)
executeAction(intent);
} }
private void setShortcutResult(String label, @DrawableRes int iconResId, String action) { private fun setShortcutResult(label: String, @DrawableRes iconResId: Int, action: String) {
Intent intent = new Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity.class); val intent = Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity::class.java)
intent.putExtra(EXTRA_ACTION, action); intent.putExtra(EXTRA_ACTION, action)
val shortcut = ShortcutInfoCompat.Builder(this, action)
ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, action) .setShortLabel(label)
.setShortLabel(label) .setIcon(IconCompat.createWithResource(this, iconResId))
.setIcon(IconCompat.createWithResource(this, iconResId)) .setIntent(intent)
.setIntent(intent) .build()
.build(); setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut));
} }
@SuppressWarnings("MissingPermission") private fun sendAlarm() {
private void sendAlarm() { PositionProviderFactory.create(this, object : PositionListener {
PositionProviderFactory.create(this, new PositionProvider.PositionListener() { override fun onPositionUpdate(position: Position) {
@Override val preferences = PreferenceManager.getDefaultSharedPreferences(this@ShortcutActivity)
public void onPositionUpdate(Position position) { val request = formatRequest(preferences.getString(MainFragment.KEY_URL, null)!!, position, ALARM_SOS)
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ShortcutActivity.this); sendRequestAsync(request, object : RequestHandler {
String request = ProtocolFormatter.formatRequest( override fun onComplete(success: Boolean) {
preferences.getString(MainFragment.KEY_URL, null), position, ALARM_SOS);
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
@Override
public void onComplete(boolean success) {
if (success) { if (success) {
Toast.makeText(ShortcutActivity.this, R.string.status_send_success, Toast.LENGTH_SHORT).show(); Toast.makeText(this@ShortcutActivity, R.string.status_send_success, Toast.LENGTH_SHORT).show()
} else { } else {
Toast.makeText(ShortcutActivity.this, R.string.status_send_fail, Toast.LENGTH_SHORT).show(); Toast.makeText(this@ShortcutActivity, R.string.status_send_fail, Toast.LENGTH_SHORT).show()
} }
} }
}); })
} }
@Override override fun onPositionError(error: Throwable) {
public void onPositionError(Throwable error) { Toast.makeText(this@ShortcutActivity, error.message, Toast.LENGTH_LONG).show()
Toast.makeText(ShortcutActivity.this, error.getMessage(), Toast.LENGTH_LONG).show();
} }
}).requestSingleLocation(); }).requestSingleLocation()
} }
private boolean executeAction(Intent intent) { private fun executeAction(intent: Intent): Boolean {
String action; val action: String? = if (intent.hasExtra("shortcutAction")) {
if (intent.hasExtra("shortcutAction")) { if (intent.getBooleanExtra("shortcutAction", false)) ACTION_START else ACTION_STOP
action = intent.getBooleanExtra("shortcutAction", false)
? ACTION_START : ACTION_STOP;
} else { } else {
action = intent.getStringExtra(EXTRA_ACTION); intent.getStringExtra(EXTRA_ACTION)
} }
if (action != null) { if (action != null) {
switch (action) { when (action) {
case ACTION_START: ACTION_START -> {
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, true).apply()
.edit().putBoolean(MainFragment.KEY_STATUS, true).apply(); ContextCompat.startForegroundService(this, Intent(this, TrackingService::class.java))
ContextCompat.startForegroundService(this, new Intent(this, TrackingService.class)); Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show()
Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show(); }
break; ACTION_STOP -> {
case ACTION_STOP: PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, false).apply()
PreferenceManager.getDefaultSharedPreferences(this) stopService(Intent(this, TrackingService::class.java))
.edit().putBoolean(MainFragment.KEY_STATUS, false).apply(); Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show()
stopService(new Intent(this, TrackingService.class)); }
Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show(); ACTION_SOS -> {
break; if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
case ACTION_SOS: sendAlarm()
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
sendAlarm();
} else { } else {
Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show()
} }
break; }
} }
finish(); finish()
} }
return action != null; 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"
}
} }
+63 -71
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2017 Anton Tananaev (anton@traccar.org) * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,82 +13,74 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import java.text.DateFormat; import androidx.appcompat.app.AppCompatActivity
import java.util.Date; import android.widget.ArrayAdapter
import java.util.HashSet; import android.os.Bundle
import java.util.LinkedList; import android.view.Menu
import java.util.Set; import android.view.MenuItem
import android.widget.ListView
import java.text.DateFormat
import java.util.*
import android.os.Bundle; class StatusActivity : AppCompatActivity() {
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 var adapter: ArrayAdapter<String>? = null
private static final int LIMIT = 20; 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) }
}
private static final LinkedList<String> messages = new LinkedList<>(); override fun onDestroy() {
private static final Set<ArrayAdapter<String>> adapters = new HashSet<>(); adapters.remove(adapter)
super.onDestroy()
}
private static void notifyAdapters() { override fun onCreateOptionsMenu(menu: Menu): Boolean {
for (ArrayAdapter<String> adapter : adapters) { val inflater = menuInflater
adapter.notifyDataSetChanged(); 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()
} }
} }
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);
}
} }
+99 -119
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2015 - 2019 Anton Tananaev (anton@traccar.org) * Copyright 2015 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,92 +13,73 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.Context; import android.content.Context
import android.content.SharedPreferences; import org.traccar.client.ProtocolFormatter.formatRequest
import android.os.Handler; import org.traccar.client.RequestManager.sendRequestAsync
import android.preference.PreferenceManager; import org.traccar.client.PositionProvider.PositionListener
import android.util.Log; 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
public class TrackingController implements PositionProvider.PositionListener, NetworkManager.NetworkHandler { class TrackingController(private val context: Context) : PositionListener, NetworkHandler {
private static final String TAG = TrackingController.class.getSimpleName(); private val handler = Handler(Looper.getMainLooper())
private static final int RETRY_DELAY = 30 * 1000; 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 boolean isOnline; private val url: String = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value))!!
private boolean isWaiting; private val buffer: Boolean = preferences.getBoolean(MainFragment.KEY_BUFFER, true)
private Context context; private var isOnline = networkManager.isOnline
private Handler handler; private var isWaiting = false
private SharedPreferences preferences;
private String url; fun start() {
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) { if (isOnline) {
read(); read()
} }
try { try {
positionProvider.startUpdates(); positionProvider.startUpdates()
} catch (SecurityException e) { } catch (e: SecurityException) {
Log.w(TAG, e); Log.w(TAG, e)
} }
networkManager.start(); networkManager.start()
} }
public void stop() { fun stop() {
networkManager.stop(); networkManager.stop()
try { try {
positionProvider.stopUpdates(); positionProvider.stopUpdates()
} catch (SecurityException e) { } catch (e: SecurityException) {
Log.w(TAG, e); Log.w(TAG, e)
} }
handler.removeCallbacksAndMessages(null); handler.removeCallbacksAndMessages(null)
} }
@Override override fun onPositionUpdate(position: Position) {
public void onPositionUpdate(Position position) { StatusActivity.addMessage(context.getString(R.string.status_location_update))
StatusActivity.addMessage(context.getString(R.string.status_location_update)); if (buffer) {
if (position != null) { write(position)
if (buffer) { } else {
write(position); send(position)
} else {
send(position);
}
} }
} }
@Override override fun onPositionError(error: Throwable) {}
public void onPositionError(Throwable error) { 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))
@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) { if (!this.isOnline && isOnline) {
read(); read()
} }
this.isOnline = isOnline; this.isOnline = isOnline
} }
// //
@@ -109,98 +90,97 @@ public class TrackingController implements PositionProvider.PositionListener, Ne
// read -> send -> retry -> read -> send // read -> send -> retry -> read -> send
// //
private void log(String action, Position position) { private fun log(action: String, position: Position?) {
var formattedAction: String = action
if (position != null) { if (position != null) {
action += " (" + formattedAction +=
"id:" + position.getId() + " (id:" + position.id +
" time:" + position.getTime().getTime() / 1000 + " time:" + position.time.time / 1000 +
" lat:" + position.getLatitude() + " lat:" + position.latitude +
" lon:" + position.getLongitude() + ")"; " lon:" + position.longitude + ")"
} }
Log.d(TAG, action); Log.d(TAG, formattedAction)
} }
private void write(Position position) { private fun write(position: Position) {
log("write", position); log("write", position)
databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() { databaseHelper.insertPositionAsync(position, object : DatabaseHandler<Unit> {
@Override override fun onComplete(success: Boolean, result: Unit) {
public void onComplete(boolean success, Void result) {
if (success) { if (success) {
if (isOnline && isWaiting) { if (isOnline && isWaiting) {
read(); read()
isWaiting = false; isWaiting = false
} }
} }
} }
}); })
} }
private void read() { private fun read() {
log("read", null); log("read", null)
databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() { databaseHelper.selectPositionAsync(object : DatabaseHandler<Position?> {
@Override override fun onComplete(success: Boolean, result: Position?) {
public void onComplete(boolean success, Position result) {
if (success) { if (success) {
if (result != null) { if (result != null) {
if (result.getDeviceId().equals(preferences.getString(MainFragment.KEY_DEVICE, null))) { if (result.deviceId == preferences.getString(MainFragment.KEY_DEVICE, null)) {
send(result); send(result)
} else { } else {
delete(result); delete(result)
} }
} else { } else {
isWaiting = true; isWaiting = true
} }
} else { } else {
retry(); retry()
} }
} }
}); })
} }
private void delete(Position position) { private fun delete(position: Position) {
log("delete", position); log("delete", position)
databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() { databaseHelper.deletePositionAsync(position.id, object : DatabaseHandler<Unit> {
@Override override fun onComplete(success: Boolean, result: Unit) {
public void onComplete(boolean success, Void result) {
if (success) { if (success) {
read(); read()
} else { } else {
retry(); retry()
} }
} }
}); })
} }
private void send(final Position position) { private fun send(position: Position) {
log("send", position); log("send", position)
String request = ProtocolFormatter.formatRequest(url, position); val request = formatRequest(url, position)
RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() { sendRequestAsync(request, object : RequestHandler {
@Override override fun onComplete(success: Boolean) {
public void onComplete(boolean success) {
if (success) { if (success) {
if (buffer) { if (buffer) {
delete(position); delete(position)
} }
} else { } else {
StatusActivity.addMessage(context.getString(R.string.status_send_fail)); StatusActivity.addMessage(context.getString(R.string.status_send_fail))
if (buffer) { if (buffer) {
retry(); retry()
} }
} }
} }
}); })
} }
private void retry() { private fun retry() {
log("retry", null); log("retry", null)
handler.postDelayed(new Runnable() { handler.postDelayed({
@Override if (isOnline) {
public void run() { read()
if (isOnline) {
read();
}
} }
}, RETRY_DELAY); }, RETRY_DELAY.toLong())
}
companion object {
private val TAG = TrackingController::class.java.simpleName
private const val RETRY_DELAY = 30 * 1000
} }
} }
+84 -95
Просмотреть файл
@@ -1,5 +1,5 @@
/* /*
* Copyright 2012 - 2020 Anton Tananaev (anton@traccar.org) * Copyright 2012 - 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,128 +13,117 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.annotation.SuppressLint; import android.Manifest
import android.annotation.TargetApi; import android.annotation.SuppressLint
import android.app.Notification; import android.annotation.TargetApi
import android.app.PendingIntent; import android.app.Notification
import android.app.Service; import android.app.PendingIntent
import android.content.Context; import android.app.Service
import android.content.Intent; import android.content.Context
import android.content.pm.PackageManager; import android.content.Intent
import android.os.Build; import android.content.pm.PackageManager
import android.os.IBinder; import android.os.Build
import androidx.core.app.NotificationCompat; import android.os.IBinder
import androidx.core.content.ContextCompat; 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
import android.os.PowerManager; class TrackingService : Service() {
import android.preference.PreferenceManager;
import android.util.Log;
public class TrackingService extends Service { private var wakeLock: WakeLock? = null
private var trackingController: TrackingController? = null
public static final String ACTION_STARTED = "org.traccar.action.SERVICE_STARTED"; class HideNotificationService : Service() {
public static final String ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED"; override fun onBind(intent: Intent): IBinder? {
return null
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 override fun onCreate() {
public void onCreate() { startForeground(NOTIFICATION_ID, createNotification(this))
startForeground(NOTIFICATION_ID, createNotification(this)); stopForeground(true)
stopForeground(true);
} }
@Override override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
public int onStartCommand(Intent intent, int flags, int startId) { stopSelfResult(startId)
stopSelfResult(startId); return START_NOT_STICKY
return START_NOT_STICKY;
} }
} }
@SuppressLint("WakelockTimeout") @SuppressLint("WakelockTimeout")
@Override override fun onCreate() {
public void onCreate() { Log.i(TAG, "service create")
Log.i(TAG, "service create");
sendBroadcast(new Intent(ACTION_STARTED));
StatusActivity.addMessage(getString(R.string.status_service_create));
startForeground(NOTIFICATION_ID, createNotification(this)); sendBroadcast(Intent(ACTION_STARTED))
StatusActivity.addMessage(getString(R.string.status_service_create))
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { 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)) { if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(MainFragment.KEY_WAKELOCK, true)) {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); val powerManager = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name)
wakeLock.acquire(); wakeLock?.acquire()
} }
trackingController = TrackingController(this)
trackingController = new TrackingController(this); trackingController?.start()
trackingController.start();
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
ContextCompat.startForegroundService(this, new Intent(this, HideNotificationService.class)); ContextCompat.startForegroundService(this, Intent(this, HideNotificationService::class.java))
} }
} }
@Override override fun onBind(intent: Intent): IBinder? {
public IBinder onBind(Intent intent) { return null
return null;
} }
@TargetApi(Build.VERSION_CODES.ECLAIR) @TargetApi(Build.VERSION_CODES.ECLAIR)
@Override override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
public int onStartCommand(Intent intent, int flags, int startId) { WakefulBroadcastReceiver.completeWakefulIntent(intent)
if (intent != null) { return START_STICKY
AutostartReceiver.completeWakefulIntent(intent);
}
return START_STICKY;
} }
@Override override fun onDestroy() {
public void onDestroy() { Log.i(TAG, "service destroy")
Log.i(TAG, "service destroy"); sendBroadcast(Intent(ACTION_STOPPED))
sendBroadcast(new Intent(ACTION_STOPPED)); StatusActivity.addMessage(getString(R.string.status_service_destroy))
StatusActivity.addMessage(getString(R.string.status_service_destroy)); stopForeground(true)
if (wakeLock?.isHeld == true) {
stopForeground(true); wakeLock?.release()
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
}
if (trackingController != null) {
trackingController.stop();
} }
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()
}
}
} }
+48 -40
Просмотреть файл
@@ -1,11 +1,11 @@
/* /*
* Copyright (C) 2013 The Android Open Source Project * Copyright 2021 Anton Tananaev (anton@traccar.org)
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,50 +13,58 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.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 { import android.content.BroadcastReceiver
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid"; import android.content.Context
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks = new SparseArray<>(); import android.util.SparseArray
private static int mNextId = 1; import android.os.PowerManager.WakeLock
import android.content.Intent
import androidx.core.content.ContextCompat
import android.os.PowerManager
public static void startWakefulForegroundService(Context context, Intent intent) { abstract class WakefulBroadcastReceiver : BroadcastReceiver() {
synchronized (mActiveWakeLocks) {
int id = mNextId; companion object {
mNextId++;
if (mNextId <= 0) { private const val EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid"
mNextId = 1; 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)
} }
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) { fun completeWakefulIntent(intent: Intent): Boolean {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0); val id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0)
if (id == 0) { if (id == 0) {
return false; return false
} }
synchronized (mActiveWakeLocks) { synchronized(activeWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id); val wakeLock = activeWakeLocks[id]
if (wl != null) { if (wakeLock != null) {
wl.release(); wakeLock.release()
mActiveWakeLocks.remove(id); activeWakeLocks.remove(id)
return true; return true
}
return true
} }
return true;
} }
} }
} }
+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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.traccar.client; package org.traccar.client
import android.content.Context; import android.content.Context
import org.traccar.client.PositionProvider.PositionListener
public class PositionProviderFactory { object PositionProviderFactory {
public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { fun create(context: Context, listener: PositionListener): PositionProvider {
return new AndroidPositionProvider(context, listener); return AndroidPositionProvider(context, listener)
} }
} }
+21 -29
Просмотреть файл
@@ -1,43 +1,35 @@
package org.traccar.client
package org.traccar.client; import android.location.Location
import android.os.Build
import android.location.Location; import androidx.test.core.app.ApplicationProvider
import android.os.Build; import org.junit.Assert
import org.junit.Test
import org.junit.Test; import org.junit.runner.RunWith
import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner
import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config
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 {
@Config(sdk = [Build.VERSION_CODES.P])
@RunWith(RobolectricTestRunner::class)
class DatabaseHelperTest {
@Test @Test
public void test() throws Exception { fun test() {
DatabaseHelper databaseHelper = new DatabaseHelper(RuntimeEnvironment.application); val databaseHelper = DatabaseHelper(ApplicationProvider.getApplicationContext())
Position position = new Position("123456789012345", new Location("gps"), 0); var position: Position? = Position("123456789012345", Location("gps"), 0.0)
position.setTime(new Date(0));
assertNull(databaseHelper.selectPosition()); Assert.assertNull(databaseHelper.selectPosition())
databaseHelper.insertPosition(position); databaseHelper.insertPosition(position!!)
position = databaseHelper.selectPosition(); position = databaseHelper.selectPosition()
assertNotNull(position); Assert.assertNotNull(position)
databaseHelper.deletePosition(position.getId()); databaseHelper.deletePosition(position!!.id)
assertNull(databaseHelper.selectPosition()); Assert.assertNull(databaseHelper.selectPosition())
} }
+25 -37
Просмотреть файл
@@ -1,49 +1,37 @@
package org.traccar.client
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
import android.location.Location; @Config(sdk = [Build.VERSION_CODES.P])
import android.os.Build; @RunWith(RobolectricTestRunner::class)
class ProtocolFormatterTest {
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 @Test
public void testFormatRequest() throws Exception { fun testFormatRequest() {
val position = Position("123456789012345", Location("gps"), 0.0)
Position position = new Position("123456789012345", new Location("gps"), 0); val url = formatRequest("http://localhost:5055", position)
position.setTime(new Date(0)); Assert.assertEquals("http://localhost:5055?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url)
String url = ProtocolFormatter.formatRequest("http://localhost:5055", position);
assertEquals("http://localhost:5055?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url);
} }
@Test @Test
public void testFormatPathPortRequest() throws Exception { fun testFormatPathPortRequest() {
val position = Position("123456789012345", Location("gps"), 0.0)
Position position = new Position("123456789012345", new Location("gps"), 0); val url = formatRequest("http://localhost:8888/path", position)
position.setTime(new Date(0)); Assert.assertEquals("http://localhost:8888/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url)
String url = ProtocolFormatter.formatRequest("http://localhost:8888/path", position);
assertEquals("http://localhost:8888/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url);
} }
@Test @Test
public void testFormatAlarmRequest() throws Exception { fun testFormatAlarmRequest() {
val position = Position("123456789012345", Location("gps"), 0.0)
Position position = new Position("123456789012345", new Location("gps"), 0); val url = formatRequest("http://localhost:5055/path", position, "alert message")
position.setTime(new Date(0)); Assert.assertEquals("http://localhost:5055/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0&alarm=alert%20message", url)
String url = ProtocolFormatter.formatRequest("http://localhost:5055/path", position, "alert message");
assertEquals("http://localhost:5055/path?id=123456789012345&timestamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0&alarm=alert%20message", url);
} }
} }
+14 -18
Просмотреть файл
@@ -1,26 +1,22 @@
package org.traccar.client
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
import android.os.Build; @Config(sdk = [Build.VERSION_CODES.P])
@RunWith(RobolectricTestRunner::class)
import org.junit.Ignore; class RequestManagerTest {
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") @Ignore("Not a real unit test")
@Test @Test
public void testSendRequest() throws Exception { fun testSendRequest() {
Assert.assertTrue(sendRequest("http://www.google.com"))
assertTrue(RequestManager.sendRequest("http://www.google.com"));
} }
} }