diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 000000000..1aca6f181
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MainActivity.kt b/MainActivity.kt
new file mode 100644
index 000000000..641f9ce13
--- /dev/null
+++ b/MainActivity.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2017 - 2021 Anton Tananaev (anton@traccar.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.client
+
+import android.util.Log
+import android.content.Intent
+import android.os.Bundle
+import android.preference.PreferenceManager
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import android.app.AlarmManager
+import android.app.PendingIntent
+import java.util.Calendar
+import android.content.Context
+import android.content.SharedPreferences
+
+
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ handleDeepLink(intent)
+ setContentView(R.layout.main)
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ handleDeepLink(intent)
+ }
+
+ private fun handleDeepLink(intent: Intent) {
+ val uri = intent.data ?: return
+ if (uri.scheme == "traccar" && uri.host == "config") {
+ val prefs = PreferenceManager.getDefaultSharedPreferences(this)
+ prefs.edit().apply {
+ uri.getQueryParameter("deviceId")?.let { putString("id", it) }
+ uri.getQueryParameter("serverUrl")?.let { putString("url", it) }
+ uri.getQueryParameter("accuracy")?.let { putString("accuracy", it) }
+ uri.getQueryParameter("interval")?.let { putString("interval", it) }
+ uri.getQueryParameter("distance")?.let { putString("distance", it) }
+ uri.getQueryParameter("angle")?.let { putString("angle", it) }
+ uri.getQueryParameter("startTime")?.let { putString("startTime", it) }
+ uri.getQueryParameter("stopTime")?.let { putString("stopTime", it) }
+ apply()
+ }
+ scheduleTrackingFromPreferences()
+ val serviceOn = uri.getQueryParameter("service") == "true"
+ if (serviceOn) {
+ ContextCompat.startForegroundService(this, Intent(this, TrackingService::class.java))
+ } else {
+ stopService(Intent(this, TrackingService::class.java))
+ }
+
+ prefs.edit().putBoolean("status", serviceOn).apply()
+ val fragment = supportFragmentManager.findFragmentById(R.id.fragment)
+ if (fragment is MainFragment) {
+ fragment.updateStatusSwitch(serviceOn)
+ }
+ Toast.makeText(this, "Traccar konfigurert via QR", Toast.LENGTH_SHORT).show()
+ }
+ }
+ private fun scheduleTrackingFromPreferences() {
+ val prefs = PreferenceManager.getDefaultSharedPreferences(this)
+ val startTimeStr = prefs.getString("startTime", null)
+ val stopTimeStr = prefs.getString("stopTime", null)
+
+ val alarmManager = getSystemService(Context.ALARM_SERVICE) as android.app.AlarmManager
+
+ fun schedule(timeStr: String?, action: String) {
+ if (timeStr == null) return
+ val parts = timeStr.split(":")
+ if (parts.size != 2) return
+ val hour = parts[0].toIntOrNull() ?: return
+ val minute = parts[1].toIntOrNull() ?: return
+
+ val time = java.util.Calendar.getInstance().apply {
+ set(java.util.Calendar.HOUR_OF_DAY, hour)
+ set(java.util.Calendar.MINUTE, minute)
+ set(java.util.Calendar.SECOND, 0)
+ set(java.util.Calendar.MILLISECOND, 0)
+ if (before(java.util.Calendar.getInstance())) add(java.util.Calendar.DATE, 1)
+ }
+
+ val intent = Intent(this, TrackingSchedulerReceiver::class.java).apply {
+ this.action = action
+ }
+
+ val flags = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ } else {
+ PendingIntent.FLAG_UPDATE_CURRENT
+ }
+
+ val pendingIntent = PendingIntent.getBroadcast(this, action.hashCode(), intent, flags)
+
+ alarmManager.set(
+ android.app.AlarmManager.RTC_WAKEUP,
+ time.timeInMillis,
+ /*android.app.AlarmManager.INTERVAL_DAY,*/
+ pendingIntent
+ )
+ Log.i("TrackingScheduler", "Scheduled $action at $hour:$minute (${time.time})")
+ }
+
+ schedule(startTimeStr, "START_TRACKING")
+ schedule(stopTimeStr, "STOP_TRACKING")
+}
+}
\ No newline at end of file
diff --git a/TrackingSchedulerReceiver.kt b/TrackingSchedulerReceiver.kt
new file mode 100644
index 000000000..b5bf174f0
--- /dev/null
+++ b/TrackingSchedulerReceiver.kt
@@ -0,0 +1,22 @@
+package org.traccar.client
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.core.content.ContextCompat
+
+class TrackingSchedulerReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.i("TrackingScheduler", ">>> Received intent with action: ${intent.action}")
+ when (intent.action) {
+ "START_TRACKING" -> {
+ ContextCompat.startForegroundService(context, Intent(context, TrackingService::class.java))
+ }
+ "STOP_TRACKING" -> {
+ context.stopService(Intent(context, TrackingService::class.java))
+ }
+ }
+ }
+}
diff --git a/preferences.xml b/preferences.xml
new file mode 100644
index 000000000..91eb1acee
--- /dev/null
+++ b/preferences.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+