From 02295f5fcf0ab4d3203d68136863f72a35cd4c25 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Sat, 17 Jul 2021 19:11:29 -0700 Subject: [PATCH] Convert project to Kotlin --- app/build.gradle | 1 + .../traccar/client/GoogleMainApplication.kt | 76 ++-- .../traccar/client/GooglePositionProvider.kt | 98 ++--- .../traccar/client/PositionProviderFactory.kt | 14 +- .../org/traccar/client/ServiceReceiver.kt | 47 +- .../client/AndroidPositionProvider.java | 110 ----- .../traccar/client/AndroidPositionProvider.kt | 83 ++++ .../org/traccar/client/AutostartReceiver.kt | 23 +- .../java/org/traccar/client/DatabaseHelper.kt | 226 +++++----- .../org/traccar/client/DialLaunchReceiver.kt | 33 +- .../java/org/traccar/client/MainActivity.kt | 18 +- .../org/traccar/client/MainApplication.kt | 73 ++- .../java/org/traccar/client/MainFragment.kt | 416 ++++++++---------- .../java/org/traccar/client/NetworkManager.kt | 74 ++-- .../main/java/org/traccar/client/Position.kt | 169 ++----- .../org/traccar/client/PositionProvider.kt | 109 ++--- .../org/traccar/client/ProtocolFormatter.kt | 47 +- .../java/org/traccar/client/RequestManager.kt | 100 ++--- .../org/traccar/client/ShortcutActivity.kt | 214 ++++----- .../java/org/traccar/client/StatusActivity.kt | 134 +++--- .../org/traccar/client/TrackingController.kt | 218 +++++---- .../org/traccar/client/TrackingService.kt | 179 ++++---- .../client/WakefulBroadcastReceiver.kt | 88 ++-- .../traccar/client/PositionProviderFactory.kt | 14 +- .../org/traccar/client/DatabaseHelperTest.kt | 50 +-- .../traccar/client/ProtocolFormatterTest.kt | 62 ++- .../org/traccar/client/RequestManagerTest.kt | 32 +- 27 files changed, 1174 insertions(+), 1534 deletions(-) delete mode 100644 app/src/main/java/org/traccar/client/AndroidPositionProvider.java create mode 100644 app/src/main/java/org/traccar/client/AndroidPositionProvider.kt diff --git a/app/build.gradle b/app/build.gradle index 2ae8a45..f91a9dd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,6 +50,7 @@ dependencies { implementation 'com.google.android.material:material:1.5.0-alpha01' implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.preference:preference-ktx:1.1.1' + implementation 'androidx.test:core-ktx:1.4.0' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.1' googleImplementation 'com.google.firebase:firebase-core:19.0.0' diff --git a/app/src/google/java/org/traccar/client/GoogleMainApplication.kt b/app/src/google/java/org/traccar/client/GoogleMainApplication.kt index 058919b..596c648 100644 --- a/app/src/google/java/org/traccar/client/GoogleMainApplication.kt +++ b/app/src/google/java/org/traccar/client/GoogleMainApplication.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.app.Activity; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Build; +import android.app.Activity +import android.content.IntentFilter +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.preference.PreferenceManager +import com.google.android.play.core.review.ReviewInfo +import com.google.android.play.core.review.ReviewManagerFactory +import com.google.android.play.core.tasks.Task +import com.google.firebase.analytics.FirebaseAnalytics -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.preference.PreferenceManager; +class GoogleMainApplication : MainApplication() { -import com.google.android.play.core.review.ReviewManager; -import com.google.android.play.core.review.ReviewManagerFactory; -import com.google.android.play.core.tasks.Task; -import com.google.firebase.analytics.FirebaseAnalytics; + private var firebaseAnalytics: FirebaseAnalytics? = null -public class GoogleMainApplication extends MainApplication { - - private static final String KEY_RATING_SHOWN = "ratingShown"; - private static final long RATING_THRESHOLD = -24 * 60 * 60 * 1000L; - - private FirebaseAnalytics firebaseAnalytics; - - @Override - public void onCreate() { - super.onCreate(); - firebaseAnalytics = FirebaseAnalytics.getInstance(this); - - IntentFilter filter = new IntentFilter(); - filter.addAction(TrackingService.ACTION_STARTED); - filter.addAction(TrackingService.ACTION_STOPPED); - registerReceiver(new ServiceReceiver(), filter); + override fun onCreate() { + super.onCreate() + firebaseAnalytics = FirebaseAnalytics.getInstance(this) + val filter = IntentFilter() + filter.addAction(TrackingService.ACTION_STARTED) + filter.addAction(TrackingService.ACTION_STOPPED) + registerReceiver(ServiceReceiver(), filter) } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) - @Override - public void handleRatingFlow(@NonNull Activity activity) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean ratingShown = preferences.getBoolean(KEY_RATING_SHOWN, false); - long totalDuration = preferences.getLong(ServiceReceiver.KEY_DURATION, 0); + override fun handleRatingFlow(activity: Activity) { + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val ratingShown = preferences.getBoolean(KEY_RATING_SHOWN, false) + val totalDuration = preferences.getLong(ServiceReceiver.KEY_DURATION, 0) if (!ratingShown && totalDuration > RATING_THRESHOLD) { - ReviewManager reviewManager = ReviewManagerFactory.create(activity); - reviewManager.requestReviewFlow().addOnCompleteListener(infoTask -> { - if (infoTask.isSuccessful()) { - Task flow = reviewManager.launchReviewFlow(activity, infoTask.getResult()); - flow.addOnCompleteListener(flowTask -> { - preferences.edit().putBoolean(KEY_RATING_SHOWN, true).apply(); - }); + val reviewManager = ReviewManagerFactory.create(activity) + reviewManager.requestReviewFlow().addOnCompleteListener { infoTask: Task -> + if (infoTask.isSuccessful) { + val flow = reviewManager.launchReviewFlow(activity, infoTask.result) + flow.addOnCompleteListener { preferences.edit().putBoolean(KEY_RATING_SHOWN, true).apply() } } - }); + } } } + companion object { + private const val KEY_RATING_SHOWN = "ratingShown" + private const val RATING_THRESHOLD = -24 * 60 * 60 * 1000L + } } diff --git a/app/src/google/java/org/traccar/client/GooglePositionProvider.kt b/app/src/google/java/org/traccar/client/GooglePositionProvider.kt index cb1e20b..16edfe3 100644 --- a/app/src/google/java/org/traccar/client/GooglePositionProvider.kt +++ b/app/src/google/java/org/traccar/client/GooglePositionProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,72 +13,54 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.annotation.SuppressLint; -import android.content.Context; -import android.location.Location; +import android.annotation.SuppressLint +import android.content.Context +import android.os.Looper +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationServices -import com.google.android.gms.location.FusedLocationProviderClient; -import com.google.android.gms.location.LocationCallback; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationResult; -import com.google.android.gms.location.LocationServices; -import com.google.android.gms.tasks.OnSuccessListener; +class GooglePositionProvider(context: Context, listener: PositionListener) : PositionProvider(context, listener) { -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) { - super(context, listener); - fusedLocationClient = LocationServices.getFusedLocationProviderClient(context); + override fun stopUpdates() { + fusedLocationClient.removeLocationUpdates(locationCallback) } @SuppressLint("MissingPermission") - public void startUpdates() { - LocationRequest locationRequest = new LocationRequest(); - locationRequest.setPriority(getPriority(preferences.getString(MainFragment.KEY_ACCURACY, "medium"))); - locationRequest.setInterval(distance > 0 || angle > 0 ? MINIMUM_INTERVAL : interval); - fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null); - } - - public void stopUpdates() { - fusedLocationClient.removeLocationUpdates(locationCallback); - } - - @SuppressLint("MissingPermission") - public void requestSingleLocation() { - fusedLocationClient.getLastLocation().addOnSuccessListener(new OnSuccessListener() { - @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); - } + override fun requestSingleLocation() { + fusedLocationClient.lastLocation.addOnSuccessListener { location -> + if (location != null) { + listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context))) } } - }; + } + private val locationCallback: LocationCallback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + for (location in locationResult.locations) { + processLocation(location) + } + } + } + + private fun getPriority(accuracy: String?): Int { + return when (accuracy) { + "high" -> LocationRequest.PRIORITY_HIGH_ACCURACY + "low" -> LocationRequest.PRIORITY_LOW_POWER + else -> LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY + } + } } diff --git a/app/src/google/java/org/traccar/client/PositionProviderFactory.kt b/app/src/google/java/org/traccar/client/PositionProviderFactory.kt index e221a16..f946e92 100644 --- a/app/src/google/java/org/traccar/client/PositionProviderFactory.kt +++ b/app/src/google/java/org/traccar/client/PositionProviderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.content.Context; +import android.content.Context +import org.traccar.client.PositionProvider.PositionListener -public class PositionProviderFactory { +object PositionProviderFactory { - public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { - return new GooglePositionProvider(context, listener); + fun create(context: Context, listener: PositionListener): PositionProvider { + return GooglePositionProvider(context, listener) } - } diff --git a/app/src/google/java/org/traccar/client/ServiceReceiver.kt b/app/src/google/java/org/traccar/client/ServiceReceiver.kt index b37e1d4..dda0d7c 100644 --- a/app/src/google/java/org/traccar/client/ServiceReceiver.kt +++ b/app/src/google/java/org/traccar/client/ServiceReceiver.kt @@ -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"); * 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 * 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.content.SharedPreferences; +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.preference.PreferenceManager -import androidx.preference.PreferenceManager; +class ServiceReceiver : BroadcastReceiver() { -public class ServiceReceiver extends BroadcastReceiver { - - public static final String KEY_DURATION = "serviceTime"; - - private static long startTime = 0; - - @Override - public void onReceive(Context context, Intent intent) { - if (TrackingService.ACTION_STARTED.equals(intent.getAction())) { - startTime = System.currentTimeMillis(); - } else { - if (startTime > 0) { - updateTime(context, System.currentTimeMillis() - startTime); - startTime = 0; - } + override fun onReceive(context: Context, intent: Intent) { + if (TrackingService.ACTION_STARTED == intent.action) { + startTime = System.currentTimeMillis() + } else if (startTime > 0) { + updateTime(context, System.currentTimeMillis() - startTime) + startTime = 0 } } - private void updateTime(Context context, long duration) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - long totalDuration = preferences.getLong(KEY_DURATION, 0); - preferences.edit().putLong(KEY_DURATION, totalDuration + duration).apply(); + private fun updateTime(context: Context, duration: Long) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val totalDuration = preferences.getLong(KEY_DURATION, 0) + preferences.edit().putLong(KEY_DURATION, totalDuration + duration).apply() } + companion object { + const val KEY_DURATION = "serviceTime" + private var startTime: Long = 0 + } } diff --git a/app/src/main/java/org/traccar/client/AndroidPositionProvider.java b/app/src/main/java/org/traccar/client/AndroidPositionProvider.java deleted file mode 100644 index 842a2e8..0000000 --- a/app/src/main/java/org/traccar/client/AndroidPositionProvider.java +++ /dev/null @@ -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) { - } - -} diff --git a/app/src/main/java/org/traccar/client/AndroidPositionProvider.kt b/app/src/main/java/org/traccar/client/AndroidPositionProvider.kt new file mode 100644 index 0000000..47cdf6d --- /dev/null +++ b/app/src/main/java/org/traccar/client/AndroidPositionProvider.kt @@ -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 + } + } + +} diff --git a/app/src/main/java/org/traccar/client/AutostartReceiver.kt b/app/src/main/java/org/traccar/client/AutostartReceiver.kt index 181d30e..dab8f7e 100644 --- a/app/src/main/java/org/traccar/client/AutostartReceiver.kt +++ b/app/src/main/java/org/traccar/client/AutostartReceiver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2013 - 2017 Anton Tananaev (anton@traccar.org) + * Copyright 2013 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; +import android.content.Context +import android.content.Intent +import androidx.preference.PreferenceManager -public class AutostartReceiver extends WakefulBroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); +class AutostartReceiver : WakefulBroadcastReceiver() { + + @Suppress("UnsafeProtectedBroadcastReceiver") + override fun onReceive(context: Context, intent: Intent) { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) if (sharedPreferences.getBoolean(MainFragment.KEY_STATUS, false)) { - startWakefulForegroundService(context, new Intent(context, TrackingService.class)); + startWakefulForegroundService(context, Intent(context, TrackingService::class.java)) } } diff --git a/app/src/main/java/org/traccar/client/DatabaseHelper.kt b/app/src/main/java/org/traccar/client/DatabaseHelper.kt index ac72163..2c1a0b0 100644 --- a/app/src/main/java/org/traccar/client/DatabaseHelper.kt +++ b/app/src/main/java/org/traccar/client/DatabaseHelper.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +@file:Suppress("DEPRECATION", "StaticFieldLeak") +package org.traccar.client -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.os.AsyncTask; +import android.content.ContentValues +import android.content.Context +import android.database.SQLException +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.os.AsyncTask +import java.sql.Date -import java.util.Date; +class DatabaseHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { -public class DatabaseHelper extends SQLiteOpenHelper { - - public static final int DATABASE_VERSION = 3; - public static final String DATABASE_NAME = "traccar.db"; - - public interface DatabaseHandler { - void onComplete(boolean success, T result); + interface DatabaseHandler { + fun onComplete(success: Boolean, result: T) } - private static abstract class DatabaseAsyncTask extends AsyncTask { + private abstract class DatabaseAsyncTask(val handler: DatabaseHandler) : AsyncTask() { - private DatabaseHandler handler; - private RuntimeException error; + private var error: RuntimeException? = null - public DatabaseAsyncTask(DatabaseHandler handler) { - this.handler = handler; - } - - @Override - protected T doInBackground(Void... params) { - try { - return executeMethod(); - } catch (RuntimeException error) { - this.error = error; - return null; + override fun doInBackground(vararg params: Unit): T? { + return try { + executeMethod() + } catch (error: RuntimeException) { + this.error = error + null } } - protected abstract T executeMethod(); + protected abstract fun executeMethod(): T - @Override - protected void onPostExecute(T result) { - handler.onComplete(error == null, result); + override fun onPostExecute(result: T?) { + result?.let { handler.onComplete(error == null, result) } } } - private SQLiteDatabase db; + private val db: SQLiteDatabase = writableDatabase - public DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - db = getWritableDatabase(); + override fun onCreate(db: SQLiteDatabase) { + db.execSQL( + "CREATE TABLE position (" + + "id INTEGER PRIMARY KEY AUTOINCREMENT," + + "deviceId TEXT," + + "time INTEGER," + + "latitude REAL," + + "longitude REAL," + + "altitude REAL," + + "speed REAL," + + "course REAL," + + "accuracy REAL," + + "battery REAL," + + "mock INTEGER)" + ) } - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE position (" + - "id INTEGER PRIMARY KEY AUTOINCREMENT," + - "deviceId TEXT," + - "time INTEGER," + - "latitude REAL," + - "longitude REAL," + - "altitude REAL," + - "speed REAL," + - "course REAL," + - "accuracy REAL," + - "battery REAL," + - "mock INTEGER)"); + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("DROP TABLE IF EXISTS position;") + onCreate(db) } - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS position;"); - onCreate(db); + override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL("DROP TABLE IF EXISTS position;") + onCreate(db) } - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL("DROP TABLE IF EXISTS position;"); - onCreate(db); + fun insertPosition(position: Position) { + val values = ContentValues() + values.put("deviceId", position.deviceId) + values.put("time", position.time.time) + values.put("latitude", position.latitude) + values.put("longitude", position.longitude) + values.put("altitude", position.altitude) + values.put("speed", position.speed) + values.put("course", position.course) + values.put("accuracy", position.accuracy) + values.put("battery", position.battery) + values.put("mock", if (position.mock) 1 else 0) + db.insertOrThrow("position", null, values) } - public void insertPosition(Position position) { - ContentValues values = new ContentValues(); - values.put("deviceId", position.getDeviceId()); - values.put("time", position.getTime().getTime()); - values.put("latitude", position.getLatitude()); - values.put("longitude", position.getLongitude()); - values.put("altitude", position.getAltitude()); - values.put("speed", position.getSpeed()); - values.put("course", position.getCourse()); - values.put("accuracy", position.getAccuracy()); - values.put("battery", position.getBattery()); - values.put("mock", position.getMock() ? 1 : 0); - - db.insertOrThrow("position", null, values); - } - - public void insertPositionAsync(final Position position, DatabaseHandler handler) { - new DatabaseAsyncTask(handler) { - @Override - protected Void executeMethod() { - insertPosition(position); - return null; + fun insertPositionAsync(position: Position, handler: DatabaseHandler) { + object : DatabaseAsyncTask(handler) { + override fun executeMethod() { + insertPosition(position) } - }.execute(); + }.execute() } - public Position selectPosition() { - Position position = new Position(); - - Cursor cursor = db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null); - try { - if (cursor.getCount() > 0) { - - cursor.moveToFirst(); - - position.setId(cursor.getLong(cursor.getColumnIndex("id"))); - position.setDeviceId(cursor.getString(cursor.getColumnIndex("deviceId"))); - position.setTime(new Date(cursor.getLong(cursor.getColumnIndex("time")))); - position.setLatitude(cursor.getDouble(cursor.getColumnIndex("latitude"))); - position.setLongitude(cursor.getDouble(cursor.getColumnIndex("longitude"))); - position.setAltitude(cursor.getDouble(cursor.getColumnIndex("altitude"))); - position.setSpeed(cursor.getDouble(cursor.getColumnIndex("speed"))); - position.setCourse(cursor.getDouble(cursor.getColumnIndex("course"))); - position.setAccuracy(cursor.getDouble(cursor.getColumnIndex("accuracy"))); - position.setBattery(cursor.getDouble(cursor.getColumnIndex("battery"))); - position.setMock(cursor.getInt(cursor.getColumnIndex("mock")) > 0); - - } else { - return null; + fun selectPosition(): Position? { + db.rawQuery("SELECT * FROM position ORDER BY id LIMIT 1", null).use { cursor -> + if (cursor.count > 0) { + cursor.moveToFirst() + return Position( + id = cursor.getLong(cursor.getColumnIndex("id")), + deviceId = cursor.getString(cursor.getColumnIndex("deviceId")), + time = Date(cursor.getLong(cursor.getColumnIndex("time"))), + latitude = cursor.getDouble(cursor.getColumnIndex("latitude")), + longitude = cursor.getDouble(cursor.getColumnIndex("longitude")), + altitude = cursor.getDouble(cursor.getColumnIndex("altitude")), + speed = cursor.getDouble(cursor.getColumnIndex("speed")), + course = cursor.getDouble(cursor.getColumnIndex("course")), + accuracy = cursor.getDouble(cursor.getColumnIndex("accuracy")), + battery = cursor.getDouble(cursor.getColumnIndex("battery")), + mock = cursor.getInt(cursor.getColumnIndex("mock")) > 0, + ) } - } finally { - cursor.close(); } - - return position; + return null } - public void selectPositionAsync(DatabaseHandler handler) { - new DatabaseAsyncTask(handler) { - @Override - protected Position executeMethod() { - return selectPosition(); + fun selectPositionAsync(handler: DatabaseHandler) { + object : DatabaseAsyncTask(handler) { + override fun executeMethod(): Position? { + return selectPosition() } - }.execute(); + }.execute() } - public void deletePosition(long id) { - if (db.delete("position", "id = ?", new String[] { String.valueOf(id) }) != 1) { - throw new SQLException(); + fun deletePosition(id: Long) { + if (db.delete("position", "id = ?", arrayOf(id.toString())) != 1) { + throw SQLException() } } - public void deletePositionAsync(final long id, DatabaseHandler handler) { - new DatabaseAsyncTask(handler) { - @Override - protected Void executeMethod() { - deletePosition(id); - return null; + fun deletePositionAsync(id: Long, handler: DatabaseHandler) { + object : DatabaseAsyncTask(handler) { + override fun executeMethod() { + deletePosition(id) } - }.execute(); + }.execute() + } + + companion object { + const val DATABASE_VERSION = 3 + const val DATABASE_NAME = "traccar.db" } } diff --git a/app/src/main/java/org/traccar/client/DialLaunchReceiver.kt b/app/src/main/java/org/traccar/client/DialLaunchReceiver.kt index 2c8856a..a53e87b 100644 --- a/app/src/main/java/org/traccar/client/DialLaunchReceiver.kt +++ b/app/src/main/java/org/traccar/client/DialLaunchReceiver.kt @@ -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"); * 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 * 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.content.BroadcastReceiver +import android.content.Context +import android.content.Intent -public class DialLaunchReceiver extends BroadcastReceiver { +class DialLaunchReceiver : BroadcastReceiver() { - private static final String LAUNCHER_NUMBER = "8722227"; // TRACCAR - - @Override - public void onReceive(Context context, Intent intent) { - String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - if (phoneNumber.equals(LAUNCHER_NUMBER)) { - setResultData(null); - Intent appIntent = new Intent(context, MainActivity.class); - appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(appIntent); + override fun onReceive(context: Context, intent: Intent) { + val phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER) + if (phoneNumber == LAUNCHER_NUMBER) { + resultData = null + val appIntent = Intent(context, MainActivity::class.java) + appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(appIntent) } } + companion object { + private const val LAUNCHER_NUMBER = "8722227" // TRACCAR + } + } diff --git a/app/src/main/java/org/traccar/client/MainActivity.kt b/app/src/main/java/org/traccar/client/MainActivity.kt index 9619a5e..133c1e5 100644 --- a/app/src/main/java/org/traccar/client/MainActivity.kt +++ b/app/src/main/java/org/traccar/client/MainActivity.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle -public class MainActivity extends AppCompatActivity { +class MainActivity : AppCompatActivity() { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.main) } } diff --git a/app/src/main/java/org/traccar/client/MainApplication.kt b/app/src/main/java/org/traccar/client/MainApplication.kt index 43416c8..19b11bb 100644 --- a/app/src/main/java/org/traccar/client/MainApplication.kt +++ b/app/src/main/java/org/traccar/client/MainApplication.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.annotation.TargetApi; -import android.app.Activity; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Color; -import android.net.Uri; -import android.os.Build; -import android.preference.PreferenceManager; +import androidx.multidex.MultiDexApplication +import android.annotation.TargetApi +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Notification +import android.graphics.Color +import android.os.Build +import android.app.Activity -import androidx.annotation.NonNull; -import androidx.multidex.MultiDexApplication; +import androidx.annotation.NonNull -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) { - registerChannel(); + registerChannel() } } @TargetApi(Build.VERSION_CODES.O) - private void registerChannel() { - NotificationChannel channel = new NotificationChannel( - PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_LOW); - channel.setLightColor(Color.GREEN); - channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET); - ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel); + private fun registerChannel() { + val channel = NotificationChannel( + PRIMARY_CHANNEL, getString(R.string.channel_default), NotificationManager.IMPORTANCE_LOW + ) + channel.lightColor = Color.GREEN + channel.lockscreenVisibility = Notification.VISIBILITY_SECRET + (getSystemService(NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel) } - private void migrateLegacyPreferences(SharedPreferences preferences) { - String port = preferences.getString("port", null); - if (port != null) { - String host = preferences.getString("address", getString(R.string.settings_url_default_value)); - String scheme = preferences.getBoolean("secure", false) ? "https" : "http"; + open fun handleRatingFlow(activity: Activity) {} - Uri.Builder builder = new Uri.Builder(); - builder.scheme(scheme).encodedAuthority(host + ":" + port).build(); - SharedPreferences.Editor editor = preferences.edit(); - editor.putString(MainFragment.KEY_URL, builder.toString()); - - editor.remove("port"); - editor.remove("address"); - editor.remove("secure"); - editor.apply(); - } - } - - public void handleRatingFlow(@NonNull Activity activity) { + companion object { + const val PRIMARY_CHANNEL = "default" } } diff --git a/app/src/main/java/org/traccar/client/MainFragment.kt b/app/src/main/java/org/traccar/client/MainFragment.kt index b3e511e..ecf55bf 100644 --- a/app/src/main/java/org/traccar/client/MainFragment.kt +++ b/app/src/main/java/org/traccar/client/MainFragment.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.Manifest; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.text.InputType; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.webkit.URLUtil; -import android.widget.EditText; -import android.widget.Toast; +import android.Manifest +import android.annotation.SuppressLint +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.text.InputType +import android.util.Log +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.webkit.URLUtil +import android.widget.EditText +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.preference.EditTextPreference +import androidx.preference.EditTextPreferenceDialogFragmentCompat +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceManager +import androidx.preference.TwoStatePreference +import java.util.* +import kotlin.collections.HashSet -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; -import androidx.preference.EditTextPreference; -import androidx.preference.EditTextPreferenceDialogFragmentCompat; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; -import androidx.preference.TwoStatePreference; +class MainFragment : PreferenceFragmentCompat(), OnSharedPreferenceChangeListener { -import java.util.Arrays; -import java.util.HashSet; -import java.util.Random; -import java.util.Set; - -public class MainFragment extends PreferenceFragmentCompat implements OnSharedPreferenceChangeListener { - - private static final String TAG = MainFragment.class.getSimpleName(); - - private static final int ALARM_MANAGER_INTERVAL = 15000; - - public static final String KEY_DEVICE = "id"; - public static final String KEY_URL = "url"; - public static final String KEY_INTERVAL = "interval"; - public static final String KEY_DISTANCE = "distance"; - public static final String KEY_ANGLE = "angle"; - public static final String KEY_ACCURACY = "accuracy"; - public static final String KEY_STATUS = "status"; - public static final String KEY_BUFFER = "buffer"; - public static final String KEY_WAKELOCK = "wakelock"; - - private static final int PERMISSIONS_REQUEST_LOCATION = 2; - - private SharedPreferences sharedPreferences; - - private AlarmManager alarmManager; - private PendingIntent alarmIntent; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + private lateinit var sharedPreferences: SharedPreferences + private lateinit var alarmManager: AlarmManager + private lateinit var alarmIntent: PendingIntent + @SuppressLint("UnspecifiedImmutableFlag") + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { if (BuildConfig.HIDDEN_APP && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - removeLauncherIcon(); + removeLauncherIcon() } + setHasOptionsMenu(true) + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + setPreferencesFromResource(R.xml.preferences, rootKey) + initPreferences() - setHasOptionsMenu(true); - - sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); - setPreferencesFromResource(R.xml.preferences, rootKey); - initPreferences(); - - findPreference(KEY_DEVICE).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - return newValue != null && !newValue.equals(""); + findPreference(KEY_DEVICE)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + newValue != null && newValue != "" + } + findPreference(KEY_URL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + newValue != null && validateServerURL(newValue.toString()) + } + findPreference(KEY_INTERVAL)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + try { + newValue != null && (newValue as String).toInt() > 0 + } catch (e: NumberFormatException) { + Log.w(TAG, e) + false } - }); - findPreference(KEY_URL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - return (newValue != null) && validateServerURL(newValue.toString()); + } + val numberValidationListener = Preference.OnPreferenceChangeListener { _, newValue -> + try { + newValue != null && (newValue as String).toInt() >= 0 + } catch (e: NumberFormatException) { + Log.w(TAG, e) + false } - }); - - findPreference(KEY_INTERVAL).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if (newValue != null) { - try { - int value = Integer.parseInt((String) newValue); - return value > 0; - } catch (NumberFormatException e) { - Log.w(TAG, e); - } - } - return false; - } - }); - - 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); + } + findPreference(KEY_DISTANCE)?.onPreferenceChangeListener = numberValidationListener + findPreference(KEY_ANGLE)?.onPreferenceChangeListener = numberValidationListener + alarmManager = requireActivity().getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmIntent = PendingIntent.getBroadcast(activity, 0, Intent(activity, AutostartReceiver::class.java), 0) if (sharedPreferences.getBoolean(KEY_STATUS, false)) { - startTrackingService(true, false); + startTrackingService(checkPermission = true, initialPermission = false) } - } - public static class NumericEditTextPreferenceDialogFragment extends EditTextPreferenceDialogFragmentCompat { + class NumericEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() { - public static NumericEditTextPreferenceDialogFragment newInstance(String key) { - final NumericEditTextPreferenceDialogFragment fragment = new NumericEditTextPreferenceDialogFragment(); - final Bundle bundle = new Bundle(); - bundle.putString(ARG_KEY, key); - fragment.setArguments(bundle); - return fragment; + override fun onBindDialogView(view: View) { + val editText = view.findViewById(android.R.id.edit) + editText.inputType = InputType.TYPE_CLASS_NUMBER + super.onBindDialogView(view) } - @Override - protected void onBindDialogView(View view) { - EditText editText = view.findViewById(android.R.id.edit); - editText.setInputType(InputType.TYPE_CLASS_NUMBER); - super.onBindDialogView(view); + companion object { + fun newInstance(key: String?): NumericEditTextPreferenceDialogFragment { + val fragment = NumericEditTextPreferenceDialogFragment() + val bundle = Bundle() + bundle.putString(ARG_KEY, key) + fragment.arguments = bundle + return fragment + } } - } - @Override - public void onDisplayPreferenceDialog(Preference preference) { - if (Arrays.asList(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.getKey())) { - final EditTextPreferenceDialogFragmentCompat f = NumericEditTextPreferenceDialogFragment.newInstance(preference.getKey()); - f.setTargetFragment(this, 0); - f.show(getFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG"); + override fun onDisplayPreferenceDialog(preference: Preference) { + if (listOf(KEY_INTERVAL, KEY_DISTANCE, KEY_ANGLE).contains(preference.key)) { + val f: EditTextPreferenceDialogFragmentCompat = + NumericEditTextPreferenceDialogFragment.newInstance(preference.key) + f.setTargetFragment(this, 0) + f.show(requireFragmentManager(), "androidx.preference.PreferenceFragment.DIALOG") } else { - super.onDisplayPreferenceDialog(preference); + super.onDisplayPreferenceDialog(preference) } } - private void removeLauncherIcon() { - String className = MainActivity.class.getCanonicalName().replace(".MainActivity", ".Launcher"); - ComponentName componentName = new ComponentName(getActivity().getPackageName(), className); - PackageManager packageManager = getActivity().getPackageManager(); + private fun removeLauncherIcon() { + val className = MainActivity::class.java.canonicalName!!.replace(".MainActivity", ".Launcher") + val componentName = ComponentName(requireActivity().packageName, className) + val packageManager = requireActivity().packageManager if (packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) { packageManager.setComponentEnabledSetting( - componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.hidden_alert)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); + componentName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP + ) + val builder = AlertDialog.Builder(requireActivity()) + builder.setIcon(android.R.drawable.ic_dialog_alert) + builder.setMessage(getString(R.string.hidden_alert)) + builder.setPositiveButton(android.R.string.ok, null) + builder.show() } } - @Override - public void onResume() { - super.onResume(); - sharedPreferences.registerOnSharedPreferenceChangeListener(this); + override fun onResume() { + super.onResume() + sharedPreferences.registerOnSharedPreferenceChangeListener(this) } - @Override - public void onPause() { - super.onPause(); - sharedPreferences.unregisterOnSharedPreferenceChangeListener(this); + override fun onPause() { + super.onPause() + sharedPreferences.unregisterOnSharedPreferenceChangeListener(this) } - private void setPreferencesEnabled(boolean enabled) { - findPreference(KEY_DEVICE).setEnabled(enabled); - findPreference(KEY_URL).setEnabled(enabled); - findPreference(KEY_INTERVAL).setEnabled(enabled); - findPreference(KEY_DISTANCE).setEnabled(enabled); - findPreference(KEY_ANGLE).setEnabled(enabled); - findPreference(KEY_ACCURACY).setEnabled(enabled); - findPreference(KEY_BUFFER).setEnabled(enabled); - findPreference(KEY_WAKELOCK).setEnabled(enabled); + private fun setPreferencesEnabled(enabled: Boolean) { + findPreference(KEY_DEVICE)?.isEnabled = enabled + findPreference(KEY_URL)?.isEnabled = enabled + findPreference(KEY_INTERVAL)?.isEnabled = enabled + findPreference(KEY_DISTANCE)?.isEnabled = enabled + findPreference(KEY_ANGLE)?.isEnabled = enabled + findPreference(KEY_ACCURACY)?.isEnabled = enabled + findPreference(KEY_BUFFER)?.isEnabled = enabled + findPreference(KEY_WAKELOCK)?.isEnabled = enabled } - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(KEY_STATUS)) { + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { + if (key == KEY_STATUS) { if (sharedPreferences.getBoolean(KEY_STATUS, false)) { - startTrackingService(true, false); + startTrackingService(checkPermission = true, initialPermission = false) } else { - stopTrackingService(); + stopTrackingService() } - ((MainApplication) getActivity().getApplication()).handleRatingFlow(getActivity()); - } else if (key.equals(KEY_DEVICE)) { - findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null)); + (requireActivity().application as MainApplication).handleRatingFlow(requireActivity()) + } else if (key == KEY_DEVICE) { + findPreference(KEY_DEVICE)?.summary = sharedPreferences.getString(KEY_DEVICE, null) } } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.main, menu); - super.onCreateOptionsMenu(menu, inflater); + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.main, menu) + super.onCreateOptionsMenu(menu, inflater) } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.status) { - startActivity(new Intent(getActivity(), StatusActivity.class)); - return true; + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.status) { + startActivity(Intent(activity, StatusActivity::class.java)) + return true } - return super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item) } - private void initPreferences() { - PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false); - + private fun initPreferences() { + PreferenceManager.setDefaultValues(activity, R.xml.preferences, false) if (!sharedPreferences.contains(KEY_DEVICE)) { - String id = String.valueOf(new Random().nextInt(900000) + 100000); - sharedPreferences.edit().putString(KEY_DEVICE, id).apply(); - ((EditTextPreference) findPreference(KEY_DEVICE)).setText(id); + val id = (Random().nextInt(900000) + 100000).toString() + sharedPreferences.edit().putString(KEY_DEVICE, id).apply() + findPreference(KEY_DEVICE)?.text = id } - findPreference(KEY_DEVICE).setSummary(sharedPreferences.getString(KEY_DEVICE, null)); + findPreference(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) { - Set requiredPermissions = new HashSet<>(); - if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); + val requiredPermissions: MutableSet = HashSet() + if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION) } - permission = requiredPermissions.isEmpty(); + permission = requiredPermissions.isEmpty() if (!permission) { 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) { - setPreferencesEnabled(false); - ContextCompat.startForegroundService(getContext(), new Intent(getActivity(), TrackingService.class)); - alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - ALARM_MANAGER_INTERVAL, ALARM_MANAGER_INTERVAL, alarmIntent); + setPreferencesEnabled(false) + ContextCompat.startForegroundService(requireContext(), Intent(activity, TrackingService::class.java)) + alarmManager.setInexactRepeating( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + ALARM_MANAGER_INTERVAL.toLong(), ALARM_MANAGER_INTERVAL.toLong(), alarmIntent + ) } else { - sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply(); - TwoStatePreference preference = findPreference(KEY_STATUS); - preference.setChecked(false); + sharedPreferences.edit().putBoolean(KEY_STATUS, false).apply() + val preference = findPreference(KEY_STATUS) + preference?.isChecked = false } } - private void stopTrackingService() { - alarmManager.cancel(alarmIntent); - getActivity().stopService(new Intent(getActivity(), TrackingService.class)); - setPreferencesEnabled(true); + private fun stopTrackingService() { + alarmManager.cancel(alarmIntent) + requireActivity().stopService(Intent(activity, TrackingService::class.java)) + setPreferencesEnabled(true) } - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (requestCode == PERMISSIONS_REQUEST_LOCATION) { - boolean granted = true; - for (int result : grantResults) { + var granted = true + for (result in grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { - granted = false; - break; + granted = false + break } } - startTrackingService(false, granted); + startTrackingService(false, granted) } } - private boolean validateServerURL(String userUrl) { - int port = Uri.parse(userUrl).getPort(); - if (URLUtil.isValidUrl(userUrl) && (port == -1 || (port > 0 && port <= 65535)) - && (URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl))) { - return true; + private fun validateServerURL(userUrl: String): Boolean { + val port = Uri.parse(userUrl).port + if ( + URLUtil.isValidUrl(userUrl) && + (port == -1 || port in 1..65535) && + (URLUtil.isHttpUrl(userUrl) || URLUtil.isHttpsUrl(userUrl)) + ) { + return true } - Toast.makeText(getActivity(), R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show(); - return false; + Toast.makeText(activity, R.string.error_msg_invalid_url, Toast.LENGTH_LONG).show() + return false + } + + companion object { + private val TAG = MainFragment::class.java.simpleName + private const val ALARM_MANAGER_INTERVAL = 15000 + const val KEY_DEVICE = "id" + const val KEY_URL = "url" + const val KEY_INTERVAL = "interval" + const val KEY_DISTANCE = "distance" + const val KEY_ANGLE = "angle" + const val KEY_ACCURACY = "accuracy" + const val KEY_STATUS = "status" + const val KEY_BUFFER = "buffer" + const val KEY_WAKELOCK = "wakelock" + private const val PERMISSIONS_REQUEST_LOCATION = 2 } } diff --git a/app/src/main/java/org/traccar/client/NetworkManager.kt b/app/src/main/java/org/traccar/client/NetworkManager.kt index caebdff..c60ae64 100644 --- a/app/src/main/java/org/traccar/client/NetworkManager.kt +++ b/app/src/main/java/org/traccar/client/NetworkManager.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +@file:Suppress("DEPRECATION") +package org.traccar.client -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.util.Log; +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +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; - private NetworkHandler handler; - private ConnectivityManager connectivityManager; - - public NetworkManager(Context context, NetworkHandler handler) { - this.context = context; - this.handler = handler; - connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + interface NetworkHandler { + fun onNetworkUpdate(isOnline: Boolean) } - public interface NetworkHandler { - void onNetworkUpdate(boolean isOnline); + val isOnline: Boolean + get() { + val activeNetwork = connectivityManager.activeNetworkInfo + return activeNetwork != null && activeNetwork.isConnectedOrConnecting + } + + fun start() { + val filter = IntentFilter() + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) + context.registerReceiver(this, filter) } - public boolean isOnline() { - NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo(); - return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); + fun stop() { + context.unregisterReceiver(this) } - public void start() { - IntentFilter filter = new IntentFilter(); - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - context.registerReceiver(this, filter); - } - - public void stop() { - context.unregisterReceiver(this); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION) && handler != null) { - boolean isOnline = isOnline(); - Log.i(TAG, "network " + (isOnline ? "on" : "off")); - handler.onNetworkUpdate(isOnline); + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION && handler != null) { + val isOnline = isOnline + Log.i(TAG, "network " + if (isOnline) "on" else "off") + handler.onNetworkUpdate(isOnline) } } + companion object { + private val TAG = NetworkManager::class.java.simpleName + } + } diff --git a/app/src/main/java/org/traccar/client/Position.kt b/app/src/main/java/org/traccar/client/Position.kt index 8ff8ccb..9188710 100644 --- a/app/src/main/java/org/traccar/client/Position.kt +++ b/app/src/main/java/org/traccar/client/Position.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.location.Location; -import android.location.LocationManager; -import android.os.Build; +import android.location.Location +import android.location.LocationManager +import android.os.Build +import java.util.* -import java.util.Date; - -public class Position { - - public Position() { - } - - public Position(String deviceId, Location location, double battery) { - this.deviceId = deviceId; - time = new Date(location.getTime()); - latitude = location.getLatitude(); - longitude = location.getLongitude(); - altitude = location.getAltitude(); - speed = location.getSpeed() * 1.943844; // speed in knots - course = location.getBearing(); - if (location.getProvider() != null && !location.getProvider().equals(LocationManager.GPS_PROVIDER)) { - accuracy = location.getAccuracy(); - } - this.battery = battery; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - this.mock = location.isFromMockProvider(); - } - } - - private long id; - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - private String deviceId; - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - private Date time; - - public Date getTime() { - return time; - } - - public void setTime(Date time) { - this.time = time; - } - - private double latitude; - - public double getLatitude() { - return latitude; - } - - public void setLatitude(double latitude) { - this.latitude = latitude; - } - - private double longitude; - - public double getLongitude() { - return longitude; - } - - public void setLongitude(double longitude) { - this.longitude = longitude; - } - - private double altitude; - - public double getAltitude() { - return altitude; - } - - public void setAltitude(double altitude) { - this.altitude = altitude; - } - - private double speed; - - public double getSpeed() { - return speed; - } - - public void setSpeed(double speed) { - this.speed = speed; - } - - private double course; - - public double getCourse() { - return course; - } - - public void setCourse(double course) { - this.course = course; - } - - private double accuracy; - - public double getAccuracy() { - return accuracy; - } - - public void setAccuracy(double accuracy) { - this.accuracy = accuracy; - } - - private double battery; - - public double getBattery() { - return battery; - } - - public void setBattery(double battery) { - this.battery = battery; - } - - private boolean mock; - - public boolean getMock() { - return mock; - } - - public void setMock(boolean mock) { - this.mock = mock; - } +data class Position( + val id: Long = 0, + val deviceId: String, + val time: Date, + val latitude: Double = 0.0, + val longitude: Double = 0.0, + val altitude: Double = 0.0, + val speed: Double = 0.0, + val course: Double = 0.0, + val accuracy: Double = 0.0, + val battery: Double = 0.0, + val mock: Boolean = false, +) { + constructor(deviceId: String, location: Location, battery: Double) : this( + deviceId = deviceId, + time = Date(location.time), + latitude = location.latitude, + longitude = location.longitude, + altitude = location.altitude, + speed = location.speed * 1.943844, // speed in knots + course = location.bearing.toDouble(), + accuracy = if (location.provider != null && location.provider != LocationManager.GPS_PROVIDER) location.accuracy.toDouble() else 0.0, + battery = battery, + mock = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) location.isFromMockProvider else false, + ) } diff --git a/app/src/main/java/org/traccar/client/PositionProvider.kt b/app/src/main/java/org/traccar/client/PositionProvider.kt index 3efcf16..6ee5063 100644 --- a/app/src/main/java/org/traccar/client/PositionProvider.kt +++ b/app/src/main/java/org/traccar/client/PositionProvider.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.location.Location; -import android.os.BatteryManager; -import android.preference.PreferenceManager; -import android.util.Log; +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.SharedPreferences +import android.location.Location +import android.os.BatteryManager +import androidx.preference.PreferenceManager +import android.util.Log +import kotlin.math.abs -public abstract class PositionProvider { +abstract class PositionProvider( + protected val context: Context, + protected val listener: PositionListener, +) { - private static final String TAG = PositionProvider.class.getSimpleName(); - - protected static final int MINIMUM_INTERVAL = 1000; - - public interface PositionListener { - void onPositionUpdate(Position position); - void onPositionError(Throwable error); + interface PositionListener { + fun onPositionUpdate(position: Position) + fun onPositionError(error: Throwable) } - 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; - protected SharedPreferences preferences; + abstract fun startUpdates() + abstract fun stopUpdates() + abstract fun requestSingleLocation() - protected String deviceId; - protected long interval; - protected double distance; - protected double angle; - - protected Location lastLocation; - - public PositionProvider(Context context, PositionListener listener) { - this.context = context; - this.listener = listener; - - preferences = PreferenceManager.getDefaultSharedPreferences(context); - - deviceId = preferences.getString(MainFragment.KEY_DEVICE, "undefined"); - interval = Long.parseLong(preferences.getString(MainFragment.KEY_INTERVAL, "600")) * 1000; - distance = Integer.parseInt(preferences.getString(MainFragment.KEY_DISTANCE, "0")); - angle = Integer.parseInt(preferences.getString(MainFragment.KEY_ANGLE, "0")); - } - - public abstract void startUpdates(); - - public abstract void stopUpdates(); - - public abstract void requestSingleLocation(); - - protected void processLocation(Location location) { - if (location != null && (lastLocation == null - || location.getTime() - lastLocation.getTime() >= interval - || distance > 0 && location.distanceTo(lastLocation) >= distance - || angle > 0 && Math.abs(location.getBearing() - lastLocation.getBearing()) >= angle)) { - Log.i(TAG, "location new"); - lastLocation = location; - listener.onPositionUpdate(new Position(deviceId, location, getBatteryLevel(context))); + protected fun processLocation(location: Location?) { + if (location != null && + (lastLocation == null || location.time - lastLocation!!.time >= interval || distance > 0 + && location.distanceTo(lastLocation) >= distance || angle > 0 + && abs(location.bearing - lastLocation!!.bearing) >= angle) + ) { + Log.i(TAG, "location new") + lastLocation = location + listener.onPositionUpdate(Position(deviceId, location, getBatteryLevel(context))) } else { - Log.i(TAG, location != null ? "location ignored" : "location nil"); + Log.i(TAG, if (location != null) "location ignored" else "location nil") } } - protected static double getBatteryLevel(Context context) { - Intent batteryIntent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + protected fun getBatteryLevel(context: Context): Double { + val batteryIntent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) if (batteryIntent != null) { - int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); - int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); - return (level * 100.0) / scale; + val level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) + val scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1) + 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 } } diff --git a/app/src/main/java/org/traccar/client/ProtocolFormatter.kt b/app/src/main/java/org/traccar/client/ProtocolFormatter.kt index da19342..642d88d 100644 --- a/app/src/main/java/org/traccar/client/ProtocolFormatter.kt +++ b/app/src/main/java/org/traccar/client/ProtocolFormatter.kt @@ -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"); * 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 * 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) { - return formatRequest(url, position, null); - } - - public static String formatRequest(String url, Position position, String alarm) { - Uri serverUrl = Uri.parse(url); - Uri.Builder builder = serverUrl.buildUpon() - .appendQueryParameter("id", position.getDeviceId()) - .appendQueryParameter("timestamp", String.valueOf(position.getTime().getTime() / 1000)) - .appendQueryParameter("lat", String.valueOf(position.getLatitude())) - .appendQueryParameter("lon", String.valueOf(position.getLongitude())) - .appendQueryParameter("speed", String.valueOf(position.getSpeed())) - .appendQueryParameter("bearing", String.valueOf(position.getCourse())) - .appendQueryParameter("altitude", String.valueOf(position.getAltitude())) - .appendQueryParameter("accuracy", String.valueOf(position.getAccuracy())) - .appendQueryParameter("batt", String.valueOf(position.getBattery())); - - if (position.getMock()) { - builder.appendQueryParameter("mock", String.valueOf(position.getMock())); + fun formatRequest(url: String, position: Position, alarm: String? = null): String { + val serverUrl = Uri.parse(url) + val builder = serverUrl.buildUpon() + .appendQueryParameter("id", position.deviceId) + .appendQueryParameter("timestamp", (position.time.time / 1000).toString()) + .appendQueryParameter("lat", position.latitude.toString()) + .appendQueryParameter("lon", position.longitude.toString()) + .appendQueryParameter("speed", position.speed.toString()) + .appendQueryParameter("bearing", position.course.toString()) + .appendQueryParameter("altitude", position.altitude.toString()) + .appendQueryParameter("accuracy", position.accuracy.toString()) + .appendQueryParameter("batt", position.battery.toString()) + if (position.mock) { + builder.appendQueryParameter("mock", position.mock.toString()) } - if (alarm != null) { - builder.appendQueryParameter("alarm", alarm); + builder.appendQueryParameter("alarm", alarm) } - - return builder.build().toString(); + return builder.build().toString() } } diff --git a/app/src/main/java/org/traccar/client/RequestManager.kt b/app/src/main/java/org/traccar/client/RequestManager.kt index 287129b..b0334b7 100644 --- a/app/src/main/java/org/traccar/client/RequestManager.kt +++ b/app/src/main/java/org/traccar/client/RequestManager.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +@file:Suppress("DEPRECATION") +package org.traccar.client -import android.os.AsyncTask; -import android.util.Log; +import android.os.AsyncTask +import android.util.Log +import java.io.IOException +import java.io.InputStream +import java.net.HttpURLConnection +import java.net.URL -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; +object RequestManager { -public class RequestManager { + private const val TIMEOUT = 15 * 1000 - private static final int TIMEOUT = 15 * 1000; - - public interface RequestHandler { - void onComplete(boolean success); - } - - private static class RequestAsyncTask extends AsyncTask { - - private RequestHandler handler; - - public RequestAsyncTask(RequestHandler handler) { - this.handler = handler; - } - - @Override - protected Boolean doInBackground(String... request) { - return sendRequest(request[0]); - } - - @Override - protected void onPostExecute(Boolean result) { - handler.onComplete(result); - } - } - - public static boolean sendRequest(String request) { - InputStream inputStream = null; - try { - URL url = new URL(request); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setReadTimeout(TIMEOUT); - connection.setConnectTimeout(TIMEOUT); - connection.setRequestMethod("POST"); - connection.connect(); - inputStream = connection.getInputStream(); - while (inputStream.read() != -1); - return true; - } catch (IOException error) { - return false; + fun sendRequest(request: String?): Boolean { + var inputStream: InputStream? = null + return try { + val url = URL(request) + val connection = url.openConnection() as HttpURLConnection + connection.readTimeout = TIMEOUT + connection.connectTimeout = TIMEOUT + connection.requestMethod = "POST" + connection.connect() + inputStream = connection.inputStream + while (inputStream.read() != -1) {} + true + } catch (error: IOException) { + false } finally { try { - if (inputStream != null) { - inputStream.close(); - } - } catch (IOException secondError) { - Log.w(RequestManager.class.getSimpleName(), secondError); + inputStream?.close() + } catch (secondError: IOException) { + Log.w(RequestManager::class.java.simpleName, secondError) } } } - public static void sendRequestAsync(String request, RequestHandler handler) { - RequestAsyncTask task = new RequestAsyncTask(handler); - task.execute(request); + fun sendRequestAsync(request: String, handler: RequestHandler) { + RequestAsyncTask(handler).execute(request) } + interface RequestHandler { + fun onComplete(success: Boolean) + } + + private class RequestAsyncTask(private val handler: RequestHandler) : AsyncTask() { + + override fun doInBackground(vararg request: String): Boolean { + return sendRequest(request[0]) + } + + override fun onPostExecute(result: Boolean) { + handler.onComplete(result) + } + } } diff --git a/app/src/main/java/org/traccar/client/ShortcutActivity.kt b/app/src/main/java/org/traccar/client/ShortcutActivity.kt index d73f2fa..51bbd86 100644 --- a/app/src/main/java/org/traccar/client/ShortcutActivity.kt +++ b/app/src/main/java/org/traccar/client/ShortcutActivity.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.Toast; +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import android.widget.ListView +import android.widget.Toast +import androidx.annotation.DrawableRes +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import androidx.preference.PreferenceManager +import org.traccar.client.PositionProvider.PositionListener +import org.traccar.client.ProtocolFormatter.formatRequest +import org.traccar.client.RequestManager.RequestHandler +import org.traccar.client.RequestManager.sendRequestAsync -import androidx.annotation.DrawableRes; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import androidx.core.content.pm.ShortcutInfoCompat; -import androidx.core.content.pm.ShortcutManagerCompat; -import androidx.core.graphics.drawable.IconCompat; +class ShortcutActivity : AppCompatActivity() { -public class ShortcutActivity extends AppCompatActivity { - - public static final String EXTRA_ACTION = "action"; - public static final String ACTION_START = "start"; - public static final String ACTION_STOP = "stop"; - public static final String ACTION_SOS = "sos"; - - private static final String ALARM_SOS = "sos"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (!executeAction(getIntent())) { - setContentView(R.layout.list); - - final String[] items = new String[] { - getString(R.string.shortcut_start), - getString(R.string.shortcut_stop), - getString(R.string.shortcut_sos) - }; - - ListView listView = findViewById(android.R.id.list); - - listView.setAdapter(new ArrayAdapter<>( - this, android.R.layout.simple_list_item_1, items)); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - switch (position) { - case 0: - setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START); - break; - case 1: - setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP); - break; - case 2: - setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS); - break; - } - finish(); + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (!executeAction(intent)) { + setContentView(R.layout.list) + val items = arrayOf( + getString(R.string.shortcut_start), + getString(R.string.shortcut_stop), + getString(R.string.shortcut_sos) + ) + val listView = findViewById(android.R.id.list) + listView.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, items) + listView.onItemClickListener = OnItemClickListener { _, _, position, _ -> + when (position) { + 0 -> setShortcutResult(items[position], R.mipmap.ic_start, ACTION_START) + 1 -> setShortcutResult(items[position], R.mipmap.ic_stop, ACTION_STOP) + 2 -> setShortcutResult(items[position], R.mipmap.ic_sos, ACTION_SOS) } - }); + finish() + } } } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - executeAction(intent); + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + executeAction(intent) } - private void setShortcutResult(String label, @DrawableRes int iconResId, String action) { - Intent intent = new Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity.class); - intent.putExtra(EXTRA_ACTION, action); - - ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, action) - .setShortLabel(label) - .setIcon(IconCompat.createWithResource(this, iconResId)) - .setIntent(intent) - .build(); - - setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut)); + private fun setShortcutResult(label: String, @DrawableRes iconResId: Int, action: String) { + val intent = Intent(Intent.ACTION_DEFAULT, null, this, ShortcutActivity::class.java) + intent.putExtra(EXTRA_ACTION, action) + val shortcut = ShortcutInfoCompat.Builder(this, action) + .setShortLabel(label) + .setIcon(IconCompat.createWithResource(this, iconResId)) + .setIntent(intent) + .build() + setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut)) } - @SuppressWarnings("MissingPermission") - private void sendAlarm() { - PositionProviderFactory.create(this, new PositionProvider.PositionListener() { - @Override - public void onPositionUpdate(Position position) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ShortcutActivity.this); - String request = ProtocolFormatter.formatRequest( - preferences.getString(MainFragment.KEY_URL, null), position, ALARM_SOS); - - RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() { - @Override - public void onComplete(boolean success) { + private fun sendAlarm() { + PositionProviderFactory.create(this, object : PositionListener { + override fun onPositionUpdate(position: Position) { + val preferences = PreferenceManager.getDefaultSharedPreferences(this@ShortcutActivity) + val request = formatRequest(preferences.getString(MainFragment.KEY_URL, null)!!, position, ALARM_SOS) + sendRequestAsync(request, object : RequestHandler { + override fun onComplete(success: Boolean) { if (success) { - Toast.makeText(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 { - 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 - public void onPositionError(Throwable error) { - Toast.makeText(ShortcutActivity.this, error.getMessage(), Toast.LENGTH_LONG).show(); + override fun onPositionError(error: Throwable) { + Toast.makeText(this@ShortcutActivity, error.message, Toast.LENGTH_LONG).show() } - }).requestSingleLocation(); + }).requestSingleLocation() } - private boolean executeAction(Intent intent) { - String action; - if (intent.hasExtra("shortcutAction")) { - action = intent.getBooleanExtra("shortcutAction", false) - ? ACTION_START : ACTION_STOP; + private fun executeAction(intent: Intent): Boolean { + val action: String? = if (intent.hasExtra("shortcutAction")) { + if (intent.getBooleanExtra("shortcutAction", false)) ACTION_START else ACTION_STOP } else { - action = intent.getStringExtra(EXTRA_ACTION); + intent.getStringExtra(EXTRA_ACTION) } if (action != null) { - switch (action) { - case ACTION_START: - PreferenceManager.getDefaultSharedPreferences(this) - .edit().putBoolean(MainFragment.KEY_STATUS, true).apply(); - ContextCompat.startForegroundService(this, new Intent(this, TrackingService.class)); - Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show(); - break; - case ACTION_STOP: - PreferenceManager.getDefaultSharedPreferences(this) - .edit().putBoolean(MainFragment.KEY_STATUS, false).apply(); - stopService(new Intent(this, TrackingService.class)); - Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show(); - break; - case ACTION_SOS: - if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - sendAlarm(); + when (action) { + ACTION_START -> { + PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, true).apply() + ContextCompat.startForegroundService(this, Intent(this, TrackingService::class.java)) + Toast.makeText(this, R.string.status_service_create, Toast.LENGTH_SHORT).show() + } + ACTION_STOP -> { + PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean(MainFragment.KEY_STATUS, false).apply() + stopService(Intent(this, TrackingService::class.java)) + Toast.makeText(this, R.string.status_service_destroy, Toast.LENGTH_SHORT).show() + } + ACTION_SOS -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + sendAlarm() } else { - Toast.makeText(this, R.string.status_send_fail, Toast.LENGTH_SHORT).show(); + 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" + } } diff --git a/app/src/main/java/org/traccar/client/StatusActivity.kt b/app/src/main/java/org/traccar/client/StatusActivity.kt index b550fe2..bebf234 100644 --- a/app/src/main/java/org/traccar/client/StatusActivity.kt +++ b/app/src/main/java/org/traccar/client/StatusActivity.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import java.text.DateFormat; -import java.util.Date; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; +import androidx.appcompat.app.AppCompatActivity +import android.widget.ArrayAdapter +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.widget.ListView +import java.text.DateFormat +import java.util.* -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.ArrayAdapter; -import android.widget.ListView; +class StatusActivity : AppCompatActivity() { -public class StatusActivity extends AppCompatActivity { + private var adapter: ArrayAdapter? = 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(android.R.id.list) + listView.adapter = adapter + adapter?.let { adapters.add(it) } + } - private static final LinkedList messages = new LinkedList<>(); - private static final Set> adapters = new HashSet<>(); + override fun onDestroy() { + adapters.remove(adapter) + super.onDestroy() + } - private static void notifyAdapters() { - for (ArrayAdapter adapter : adapters) { - adapter.notifyDataSetChanged(); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater = menuInflater + inflater.inflate(R.menu.status, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.clear) { + clearMessages() + return true + } + return super.onOptionsItemSelected(item) + } + + companion object { + private const val LIMIT = 20 + private val messages = LinkedList() + private val adapters: MutableSet> = 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 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); - } - } diff --git a/app/src/main/java/org/traccar/client/TrackingController.kt b/app/src/main/java/org/traccar/client/TrackingController.kt index a50d71f..301a6cf 100644 --- a/app/src/main/java/org/traccar/client/TrackingController.kt +++ b/app/src/main/java/org/traccar/client/TrackingController.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.util.Log; +import android.content.Context +import org.traccar.client.ProtocolFormatter.formatRequest +import org.traccar.client.RequestManager.sendRequestAsync +import org.traccar.client.PositionProvider.PositionListener +import org.traccar.client.NetworkManager.NetworkHandler +import android.os.Handler +import android.os.Looper +import androidx.preference.PreferenceManager +import android.util.Log +import org.traccar.client.DatabaseHelper.DatabaseHandler +import org.traccar.client.RequestManager.RequestHandler -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 static final int RETRY_DELAY = 30 * 1000; + private val handler = Handler(Looper.getMainLooper()) + private val preferences = PreferenceManager.getDefaultSharedPreferences(context) + private val positionProvider = PositionProviderFactory.create(context, this) + private val databaseHelper = DatabaseHelper(context) + private val networkManager = NetworkManager(context, this) - private boolean isOnline; - private boolean isWaiting; + private val url: String = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value))!! + private val buffer: Boolean = preferences.getBoolean(MainFragment.KEY_BUFFER, true) - private Context context; - private Handler handler; - private SharedPreferences preferences; + private var isOnline = networkManager.isOnline + private var isWaiting = false - private String url; - private boolean buffer; - - private PositionProvider positionProvider; - private DatabaseHelper databaseHelper; - private NetworkManager networkManager; - - public TrackingController(Context context) { - this.context = context; - handler = new Handler(); - preferences = PreferenceManager.getDefaultSharedPreferences(context); - positionProvider = PositionProviderFactory.create(context, this); - databaseHelper = new DatabaseHelper(context); - networkManager = new NetworkManager(context, this); - isOnline = networkManager.isOnline(); - - url = preferences.getString(MainFragment.KEY_URL, context.getString(R.string.settings_url_default_value)); - buffer = preferences.getBoolean(MainFragment.KEY_BUFFER, true); - } - - public void start() { + fun start() { if (isOnline) { - read(); + read() } try { - positionProvider.startUpdates(); - } catch (SecurityException e) { - Log.w(TAG, e); + positionProvider.startUpdates() + } catch (e: SecurityException) { + Log.w(TAG, e) } - networkManager.start(); + networkManager.start() } - public void stop() { - networkManager.stop(); + fun stop() { + networkManager.stop() try { - positionProvider.stopUpdates(); - } catch (SecurityException e) { - Log.w(TAG, e); + positionProvider.stopUpdates() + } catch (e: SecurityException) { + Log.w(TAG, e) } - handler.removeCallbacksAndMessages(null); + handler.removeCallbacksAndMessages(null) } - @Override - public void onPositionUpdate(Position position) { - StatusActivity.addMessage(context.getString(R.string.status_location_update)); - if (position != null) { - if (buffer) { - write(position); - } else { - send(position); - } + override fun onPositionUpdate(position: Position) { + StatusActivity.addMessage(context.getString(R.string.status_location_update)) + if (buffer) { + write(position) + } else { + send(position) } } - @Override - public void onPositionError(Throwable error) { - } - - @Override - public void onNetworkUpdate(boolean isOnline) { - int message = isOnline ? R.string.status_network_online : R.string.status_network_offline; - StatusActivity.addMessage(context.getString(message)); + override fun onPositionError(error: Throwable) {} + override fun onNetworkUpdate(isOnline: Boolean) { + val message = if (isOnline) R.string.status_network_online else R.string.status_network_offline + StatusActivity.addMessage(context.getString(message)) if (!this.isOnline && isOnline) { - read(); + read() } - this.isOnline = isOnline; + this.isOnline = isOnline } // @@ -109,98 +90,97 @@ public class TrackingController implements PositionProvider.PositionListener, Ne // 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) { - action += " (" + - "id:" + position.getId() + - " time:" + position.getTime().getTime() / 1000 + - " lat:" + position.getLatitude() + - " lon:" + position.getLongitude() + ")"; + formattedAction += + " (id:" + position.id + + " time:" + position.time.time / 1000 + + " lat:" + position.latitude + + " lon:" + position.longitude + ")" } - Log.d(TAG, action); + Log.d(TAG, formattedAction) } - private void write(Position position) { - log("write", position); - databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler() { - @Override - public void onComplete(boolean success, Void result) { + private fun write(position: Position) { + log("write", position) + databaseHelper.insertPositionAsync(position, object : DatabaseHandler { + override fun onComplete(success: Boolean, result: Unit) { if (success) { if (isOnline && isWaiting) { - read(); - isWaiting = false; + read() + isWaiting = false } } } - }); + }) } - private void read() { - log("read", null); - databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler() { - @Override - public void onComplete(boolean success, Position result) { + private fun read() { + log("read", null) + databaseHelper.selectPositionAsync(object : DatabaseHandler { + override fun onComplete(success: Boolean, result: Position?) { if (success) { if (result != null) { - if (result.getDeviceId().equals(preferences.getString(MainFragment.KEY_DEVICE, null))) { - send(result); + if (result.deviceId == preferences.getString(MainFragment.KEY_DEVICE, null)) { + send(result) } else { - delete(result); + delete(result) } } else { - isWaiting = true; + isWaiting = true } } else { - retry(); + retry() } } - }); + }) } - private void delete(Position position) { - log("delete", position); - databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler() { - @Override - public void onComplete(boolean success, Void result) { + private fun delete(position: Position) { + log("delete", position) + databaseHelper.deletePositionAsync(position.id, object : DatabaseHandler { + override fun onComplete(success: Boolean, result: Unit) { if (success) { - read(); + read() } else { - retry(); + retry() } } - }); + }) } - private void send(final Position position) { - log("send", position); - String request = ProtocolFormatter.formatRequest(url, position); - RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() { - @Override - public void onComplete(boolean success) { + private fun send(position: Position) { + log("send", position) + val request = formatRequest(url, position) + sendRequestAsync(request, object : RequestHandler { + override fun onComplete(success: Boolean) { if (success) { if (buffer) { - delete(position); + delete(position) } } else { - StatusActivity.addMessage(context.getString(R.string.status_send_fail)); + StatusActivity.addMessage(context.getString(R.string.status_send_fail)) if (buffer) { - retry(); + retry() } } } - }); + }) } - private void retry() { - log("retry", null); - handler.postDelayed(new Runnable() { - @Override - public void run() { - if (isOnline) { - read(); - } + private fun retry() { + log("retry", null) + handler.postDelayed({ + if (isOnline) { + read() } - }, RETRY_DELAY); + }, RETRY_DELAY.toLong()) + } + + companion object { + private val TAG = TrackingController::class.java.simpleName + private const val RETRY_DELAY = 30 * 1000 } } diff --git a/app/src/main/java/org/traccar/client/TrackingService.kt b/app/src/main/java/org/traccar/client/TrackingService.kt index 0cc8446..9fbd3aa 100644 --- a/app/src/main/java/org/traccar/client/TrackingService.kt +++ b/app/src/main/java/org/traccar/client/TrackingService.kt @@ -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"); * 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 * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.IBinder; -import androidx.core.app.NotificationCompat; -import androidx.core.content.ContextCompat; +import android.Manifest +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.app.Notification +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build +import android.os.IBinder +import android.os.PowerManager +import android.os.PowerManager.WakeLock +import android.provider.Settings +import android.util.Log +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import androidx.preference.PreferenceManager -import android.os.PowerManager; -import android.preference.PreferenceManager; -import android.util.Log; +class TrackingService : Service() { -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"; - public static final String ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED"; - - private static final String TAG = TrackingService.class.getSimpleName(); - private static final int NOTIFICATION_ID = 1; - - private PowerManager.WakeLock wakeLock; - private TrackingController trackingController; - - private static Notification createNotification(Context context) { - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL) - .setSmallIcon(R.drawable.ic_stat_notify) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setCategory(NotificationCompat.CATEGORY_SERVICE); - Intent intent; - if (!BuildConfig.HIDDEN_APP) { - intent = new Intent(context, MainActivity.class); - builder - .setContentTitle(context.getString(R.string.settings_status_on_summary)) - .setTicker(context.getString(R.string.settings_status_on_summary)) - .setColor(ContextCompat.getColor(context, R.color.primary_dark)); - } else { - intent = new Intent(android.provider.Settings.ACTION_SETTINGS); - } - builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0)); - return builder.build(); - } - - public static class HideNotificationService extends Service { - @Override - public IBinder onBind(Intent intent) { - return null; + class HideNotificationService : Service() { + override fun onBind(intent: Intent): IBinder? { + return null } - @Override - public void onCreate() { - startForeground(NOTIFICATION_ID, createNotification(this)); - stopForeground(true); + override fun onCreate() { + startForeground(NOTIFICATION_ID, createNotification(this)) + stopForeground(true) } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - stopSelfResult(startId); - return START_NOT_STICKY; + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + stopSelfResult(startId) + return START_NOT_STICKY } } @SuppressLint("WakelockTimeout") - @Override - public void onCreate() { - Log.i(TAG, "service create"); - sendBroadcast(new Intent(ACTION_STARTED)); - StatusActivity.addMessage(getString(R.string.status_service_create)); + override fun onCreate() { + Log.i(TAG, "service create") - startForeground(NOTIFICATION_ID, createNotification(this)); - - if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + sendBroadcast(Intent(ACTION_STARTED)) + StatusActivity.addMessage(getString(R.string.status_service_create)) + startForeground(NOTIFICATION_ID, createNotification(this)) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(MainFragment.KEY_WAKELOCK, true)) { - PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); - wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); - wakeLock.acquire(); + val powerManager = getSystemService(POWER_SERVICE) as PowerManager + wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, javaClass.name) + wakeLock?.acquire() } - - trackingController = new TrackingController(this); - trackingController.start(); + trackingController = TrackingController(this) + trackingController?.start() } 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 - public IBinder onBind(Intent intent) { - return null; + override fun onBind(intent: Intent): IBinder? { + return null } @TargetApi(Build.VERSION_CODES.ECLAIR) - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null) { - AutostartReceiver.completeWakefulIntent(intent); - } - return START_STICKY; + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + WakefulBroadcastReceiver.completeWakefulIntent(intent) + return START_STICKY } - @Override - public void onDestroy() { - Log.i(TAG, "service destroy"); - sendBroadcast(new Intent(ACTION_STOPPED)); - StatusActivity.addMessage(getString(R.string.status_service_destroy)); - - stopForeground(true); - - if (wakeLock != null && wakeLock.isHeld()) { - wakeLock.release(); - } - if (trackingController != null) { - trackingController.stop(); + override fun onDestroy() { + Log.i(TAG, "service destroy") + sendBroadcast(Intent(ACTION_STOPPED)) + StatusActivity.addMessage(getString(R.string.status_service_destroy)) + stopForeground(true) + if (wakeLock?.isHeld == true) { + wakeLock?.release() } + trackingController?.stop() } + companion object { + + const val ACTION_STARTED = "org.traccar.action.SERVICE_STARTED" + const val ACTION_STOPPED = "org.traccar.action.SERVICE_STOPPED" + private val TAG = TrackingService::class.java.simpleName + private const val NOTIFICATION_ID = 1 + + @SuppressLint("UnspecifiedImmutableFlag") + private fun createNotification(context: Context): Notification { + val builder = NotificationCompat.Builder(context, MainApplication.PRIMARY_CHANNEL) + .setSmallIcon(R.drawable.ic_stat_notify) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + val intent: Intent + if (!BuildConfig.HIDDEN_APP) { + intent = Intent(context, MainActivity::class.java) + builder + .setContentTitle(context.getString(R.string.settings_status_on_summary)) + .setTicker(context.getString(R.string.settings_status_on_summary)) + .color = ContextCompat.getColor(context, R.color.primary_dark) + } else { + intent = Intent(Settings.ACTION_SETTINGS) + } + builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, 0)) + return builder.build() + } + } } diff --git a/app/src/main/java/org/traccar/client/WakefulBroadcastReceiver.kt b/app/src/main/java/org/traccar/client/WakefulBroadcastReceiver.kt index ecf6e78..5f70143 100644 --- a/app/src/main/java/org/traccar/client/WakefulBroadcastReceiver.kt +++ b/app/src/main/java/org/traccar/client/WakefulBroadcastReceiver.kt @@ -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"); * 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 + * 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, @@ -13,50 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.client; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.PowerManager; -import androidx.core.content.ContextCompat; -import android.util.SparseArray; +package org.traccar.client -public abstract class WakefulBroadcastReceiver extends BroadcastReceiver { - private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid"; - private static final SparseArray mActiveWakeLocks = new SparseArray<>(); - private static int mNextId = 1; +import android.content.BroadcastReceiver +import android.content.Context +import android.util.SparseArray +import android.os.PowerManager.WakeLock +import android.content.Intent +import androidx.core.content.ContextCompat +import android.os.PowerManager - public static void startWakefulForegroundService(Context context, Intent intent) { - synchronized (mActiveWakeLocks) { - int id = mNextId; - mNextId++; - if (mNextId <= 0) { - mNextId = 1; +abstract class WakefulBroadcastReceiver : BroadcastReceiver() { + + companion object { + + private const val EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid" + private val activeWakeLocks = SparseArray() + 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) { - final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0); - if (id == 0) { - return false; - } - synchronized (mActiveWakeLocks) { - PowerManager.WakeLock wl = mActiveWakeLocks.get(id); - if (wl != null) { - wl.release(); - mActiveWakeLocks.remove(id); - return true; + fun completeWakefulIntent(intent: Intent): Boolean { + val id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0) + if (id == 0) { + return false + } + synchronized(activeWakeLocks) { + val wakeLock = activeWakeLocks[id] + if (wakeLock != null) { + wakeLock.release() + activeWakeLocks.remove(id) + return true + } + return true } - return true; } } } diff --git a/app/src/regular/java/org/traccar/client/PositionProviderFactory.kt b/app/src/regular/java/org/traccar/client/PositionProviderFactory.kt index e39c2f4..77851d8 100644 --- a/app/src/regular/java/org/traccar/client/PositionProviderFactory.kt +++ b/app/src/regular/java/org/traccar/client/PositionProviderFactory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 Anton Tananaev (anton@traccar.org) + * Copyright 2019 - 2021 Anton Tananaev (anton@traccar.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.traccar.client; +package org.traccar.client -import android.content.Context; +import android.content.Context +import org.traccar.client.PositionProvider.PositionListener -public class PositionProviderFactory { +object PositionProviderFactory { - public static PositionProvider create(Context context, PositionProvider.PositionListener listener) { - return new AndroidPositionProvider(context, listener); + fun create(context: Context, listener: PositionListener): PositionProvider { + return AndroidPositionProvider(context, listener) } - } diff --git a/app/src/test/java/org/traccar/client/DatabaseHelperTest.kt b/app/src/test/java/org/traccar/client/DatabaseHelperTest.kt index 714e214..730d051 100644 --- a/app/src/test/java/org/traccar/client/DatabaseHelperTest.kt +++ b/app/src/test/java/org/traccar/client/DatabaseHelperTest.kt @@ -1,43 +1,35 @@ +package org.traccar.client -package org.traccar.client; - -import android.location.Location; -import android.os.Build; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.util.Date; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -@Config(sdk = Build.VERSION_CODES.P) -@RunWith(RobolectricTestRunner.class) -public class DatabaseHelperTest { +import android.location.Location +import android.os.Build +import androidx.test.core.app.ApplicationProvider +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +@Config(sdk = [Build.VERSION_CODES.P]) +@RunWith(RobolectricTestRunner::class) +class DatabaseHelperTest { @Test - 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); - position.setTime(new Date(0)); + var position: Position? = Position("123456789012345", Location("gps"), 0.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()) } diff --git a/app/src/test/java/org/traccar/client/ProtocolFormatterTest.kt b/app/src/test/java/org/traccar/client/ProtocolFormatterTest.kt index 313e27e..115653d 100644 --- a/app/src/test/java/org/traccar/client/ProtocolFormatterTest.kt +++ b/app/src/test/java/org/traccar/client/ProtocolFormatterTest.kt @@ -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; -import android.os.Build; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.util.Date; - -import static org.junit.Assert.assertEquals; - -@Config(sdk = Build.VERSION_CODES.P) -@RunWith(RobolectricTestRunner.class) -public class ProtocolFormatterTest { +@Config(sdk = [Build.VERSION_CODES.P]) +@RunWith(RobolectricTestRunner::class) +class ProtocolFormatterTest { @Test - public void testFormatRequest() throws Exception { - - Position position = new Position("123456789012345", new Location("gps"), 0); - position.setTime(new Date(0)); - - String url = ProtocolFormatter.formatRequest("http://localhost:5055", position); - assertEquals("http://localhost:5055?id=123456789012345×tamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url); + fun testFormatRequest() { + val position = Position("123456789012345", Location("gps"), 0.0) + val url = formatRequest("http://localhost:5055", position) + Assert.assertEquals("http://localhost:5055?id=123456789012345×tamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url) } @Test - public void testFormatPathPortRequest() throws Exception { - - Position position = new Position("123456789012345", new Location("gps"), 0); - position.setTime(new Date(0)); - - String url = ProtocolFormatter.formatRequest("http://localhost:8888/path", position); - assertEquals("http://localhost:8888/path?id=123456789012345×tamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url); + fun testFormatPathPortRequest() { + val position = Position("123456789012345", Location("gps"), 0.0) + val url = formatRequest("http://localhost:8888/path", position) + Assert.assertEquals("http://localhost:8888/path?id=123456789012345×tamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0", url) } @Test - public void testFormatAlarmRequest() throws Exception { - - Position position = new Position("123456789012345", new Location("gps"), 0); - position.setTime(new Date(0)); - - String url = ProtocolFormatter.formatRequest("http://localhost:5055/path", position, "alert message"); - assertEquals("http://localhost:5055/path?id=123456789012345×tamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0&alarm=alert%20message", url); + fun testFormatAlarmRequest() { + val position = Position("123456789012345", Location("gps"), 0.0) + val url = formatRequest("http://localhost:5055/path", position, "alert message") + Assert.assertEquals("http://localhost:5055/path?id=123456789012345×tamp=0&lat=0.0&lon=0.0&speed=0.0&bearing=0.0&altitude=0.0&accuracy=0.0&batt=0.0&alarm=alert%20message", url) } + } diff --git a/app/src/test/java/org/traccar/client/RequestManagerTest.kt b/app/src/test/java/org/traccar/client/RequestManagerTest.kt index ccf7b6e..6e9c567 100644 --- a/app/src/test/java/org/traccar/client/RequestManagerTest.kt +++ b/app/src/test/java/org/traccar/client/RequestManagerTest.kt @@ -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; - -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import static org.junit.Assert.assertTrue; - -@Config(sdk = Build.VERSION_CODES.P) -@RunWith(RobolectricTestRunner.class) -public class RequestManagerTest { +@Config(sdk = [Build.VERSION_CODES.P]) +@RunWith(RobolectricTestRunner::class) +class RequestManagerTest { @Ignore("Not a real unit test") @Test - public void testSendRequest() throws Exception { - - assertTrue(RequestManager.sendRequest("http://www.google.com")); - + fun testSendRequest() { + Assert.assertTrue(sendRequest("http://www.google.com")) } }