Explorar o código

Tracking of service state. Buttons

Thomas Chef %!s(int64=3) %!d(string=hai) anos
pai
achega
6528781bbc

+ 6 - 0
app/src/main/java/com/flacksta/chef/journeygpstracker/Actions.kt

@@ -0,0 +1,6 @@
+package com.flacksta.chef.journeygpstracker
+
+enum class Actions {
+    START,
+    STOP
+}

+ 17 - 37
app/src/main/java/com/flacksta/chef/journeygpstracker/HomeFragment.kt

@@ -1,57 +1,37 @@
 package com.flacksta.chef.journeygpstracker
 
 import android.os.Bundle
+import android.util.Log
 import androidx.fragment.app.Fragment
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import com.flacksta.chef.journeygpstracker.databinding.ActivityMainScreenBinding
+import com.flacksta.chef.journeygpstracker.databinding.FragmentHomeBinding
 
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
 
-/**
- * A simple [Fragment] subclass.
- * Use the [HomeFragment.newInstance] factory method to
- * create an instance of this fragment.
- */
 class HomeFragment : Fragment() {
-    // TODO: Rename and change types of parameters
-    private var param1: String? = null
-    private var param2: String? = null
+
+    private val mTag: String = "MainActivity"
+
+    private lateinit var binding: FragmentHomeBinding
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        arguments?.let {
-            param1 = it.getString(ARG_PARAM1)
-            param2 = it.getString(ARG_PARAM2)
-        }
+
     }
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                               savedInstanceState: Bundle?): View? {
-        // Inflate the layout for this fragment
-        return inflater.inflate(R.layout.fragment_home, container, false)
-    }
 
-    companion object {
-        /**
-         * Use this factory method to create a new instance of
-         * this fragment using the provided parameters.
-         *
-         * @param param1 Parameter 1.
-         * @param param2 Parameter 2.
-         * @return A new instance of fragment HomeFragment.
-         */
-        // TODO: Rename and change types and number of parameters
-        @JvmStatic
-        fun newInstance(param1: String, param2: String) =
-                HomeFragment().apply {
-                    arguments = Bundle().apply {
-                        putString(ARG_PARAM1, param1)
-                        putString(ARG_PARAM2, param2)
-                    }
-                }
+        binding = FragmentHomeBinding.inflate(inflater, container, false)
+        val view = binding.root
+
+        //binding.buttonStart.setOnClickListener { Log.i(mTag,"START BUTTON") }
+        //binding.buttonStop.setOnClickListener{ Log.i(mTag,"STOP BUTTON") }
+
+        return view
     }
+
+
 }

+ 51 - 13
app/src/main/java/com/flacksta/chef/journeygpstracker/MainActivity.kt

@@ -1,6 +1,8 @@
 package com.flacksta.chef.journeygpstracker
 
 import android.Manifest
+import android.app.ActivityManager
+import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.os.Bundle
@@ -11,22 +13,20 @@ import androidx.core.content.ContextCompat
 import androidx.fragment.app.Fragment
 import com.flacksta.chef.journeygpstracker.databinding.ActivityMainScreenBinding
 
+
 class MainActivity : AppCompatActivity() {
 
     private lateinit var binding: ActivityMainScreenBinding
     private var mPermApproved: Boolean = false
-    private lateinit var mTrackingServiceIntent: Intent
-    private lateinit var mTrackingService: TrackingService
 
     private val mTag: String = "MainActivity"
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         binding = ActivityMainScreenBinding.inflate(layoutInflater)
-
         setContentView(binding.root)
 
-        binding.bottomNavigationView.setOnItemSelectedListener {
+        /*binding.bottomNavigationView.setOnItemSelectedListener {
             val fragment = when (it.itemId) {
                 R.id.map -> {
                     MapFragment()
@@ -35,13 +35,13 @@ class MainActivity : AppCompatActivity() {
                     SettingsFragment()
                 }
                 else -> {
-                    HomeFragment()
+                    null//HomeFragment()
                 }
             }
-            loadFragment(fragment)
+            if( fragment == null ) closeFragment() else loadFragment(fragment)
             true
-        }
-        loadFragment(HomeFragment())
+        }*/
+        //loadFragment(HomeFragment())
 
         val locationPermissionRequest = registerForActivityResult(
                 ActivityResultContracts.RequestMultiplePermissions()
@@ -71,9 +71,31 @@ class MainActivity : AppCompatActivity() {
             mPermApproved = true
         }
 
-        mTrackingService = TrackingService()
-        mTrackingServiceIntent = Intent(this, mTrackingService.javaClass)
-        startForegroundService(mTrackingServiceIntent)
+        val state : Boolean = isServiceRunning(this)
+        binding.buttonStart.isEnabled = !state
+        binding.buttonStop.isEnabled = state
+
+        binding.buttonStart.setOnClickListener {
+            actionOnService(Actions.START)
+            binding.buttonStart.isEnabled = false
+            binding.buttonStop.isEnabled = true
+        }
+        binding.buttonStop.setOnClickListener{
+            actionOnService(Actions.STOP)
+            binding.buttonStart.isEnabled = true
+            binding.buttonStop.isEnabled = false
+        }
+    }
+
+    private fun actionOnService(action: Actions) {
+        Log.i(mTag, "actionOnService() ${action.toString()}")
+        val state : ServiceState = getServiceState(this)
+        if( state == ServiceState.STOPPED && action == Actions.STOP) return
+        if( state == ServiceState.STARTED && action == Actions.START) return
+        Intent(this, TrackingService::class.java).also {
+            it.action = action.name
+            startForegroundService(it)
+        }
     }
 
     private fun loadFragment(fragment: Fragment) {
@@ -83,11 +105,27 @@ class MainActivity : AppCompatActivity() {
                 .commit()
     }
 
+    private fun closeFragment() {
+        val fragment = supportFragmentManager.findFragmentById(R.id.main_frame_layout)
+        if( fragment != null ) {
+            supportFragmentManager
+                    .beginTransaction()
+                    .remove(fragment)
+                    .commit()
+        }
+    }
+
+    @Suppress("DEPRECATION") // Deprecated for third party Services.
+    inline fun <reified T> Context.isServiceRunning2() =
+            (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager)
+                    .getRunningServices(Integer.MAX_VALUE)
+                    .any { it.service.className == T::class.java.name }
+
     override fun onDestroy() {
-        if (::mTrackingServiceIntent.isInitialized) {
+/*        if (::mTrackingServiceIntent.isInitialized) {
             stopService(mTrackingServiceIntent)
             Log.i(mTag,"Stopping service")
-        }
+        }*/
         Log.i(mTag,"onDestroy()")
         super.onDestroy()
     }

+ 37 - 0
app/src/main/java/com/flacksta/chef/journeygpstracker/StateOfService.kt

@@ -0,0 +1,37 @@
+package com.flacksta.chef.journeygpstracker
+
+import android.content.Context
+import android.content.SharedPreferences
+
+enum class ServiceState {
+    STARTED,
+    STOPPED,
+}
+
+private const val name = "JGPSTRACKER_KEY"
+private const val key = "JGPSTRACKER_STATE"
+
+fun setServiceState(context: Context, state: ServiceState) {
+    val sharedPrefs = getPreferences(context)
+    sharedPrefs.edit().let {
+        it.putString(key, state.name)
+        it.apply()
+    }
+}
+
+fun getServiceState(context: Context): ServiceState {
+    val sharedPrefs = getPreferences(context)
+    val value = sharedPrefs.getString(key, ServiceState.STOPPED.name)
+    return ServiceState.valueOf(value.toString())
+}
+
+fun isServiceRunning(context: Context): Boolean {
+    val sharedPrefs = getPreferences(context)
+    if( ServiceState.valueOf(sharedPrefs.getString(key, ServiceState.STOPPED.name).toString()) == ServiceState.STARTED )
+        return true
+    return false
+}
+
+private fun getPreferences(context: Context): SharedPreferences {
+    return context.getSharedPreferences(name, 0)
+}

+ 55 - 16
app/src/main/java/com/flacksta/chef/journeygpstracker/TrackingService.kt

@@ -12,6 +12,7 @@ import android.location.Location
 import android.os.IBinder
 import android.os.Looper
 import android.util.Log
+import android.widget.Toast
 import androidx.core.app.ActivityCompat
 import androidx.core.app.NotificationCompat
 import androidx.core.content.ContextCompat
@@ -31,10 +32,9 @@ class TrackingService : Service() {
     private lateinit var mFusedLocationClient: FusedLocationProviderClient
     private lateinit var mLocationRequest: LocationRequest
     private lateinit var mLocationCallback: LocationCallback
-
     private lateinit var trackApp : JourneyGpsTrackerApplication
-
     private val mNotificationId = 123
+    private var isServiceStarted = false
 
     override fun onCreate() {
         super.onCreate()
@@ -43,6 +43,39 @@ class TrackingService : Service() {
         startForeground(mNotificationId, generateForegroundNotification() )
     }
 
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        super.onStartCommand(intent, flags, startId)
+        Log.i(mTag,"onStartCommand()")
+        if (intent != null) {
+            val action = intent.action
+            Log.i(mTag, "onStartCommand() action $action")
+            when (action) {
+                Actions.START.name -> startService()
+                Actions.STOP.name -> stopService()
+                else -> Log.e(mTag, "onStartCommand() This should never happen. No action in the received intent")
+            }
+        } else {
+            Log.e(mTag,"onStartCommand() with a null intent. It has been probably restarted by the system."
+            )
+        }
+        return START_STICKY
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        Log.i(mTag,"onDestroy()")
+        Toast.makeText(this, "Service destroyed", Toast.LENGTH_SHORT).show()
+    }
+
+    override fun onBind(intent: Intent): IBinder? {
+        Log.i(mTag,"onBind()")
+        return null
+    }
+
+    override fun onTaskRemoved(rootIntent: Intent) {
+        Log.e(mTag,"onTaskRemoved()")
+    }
+
     private fun requestLocationUpdates() {
 
         mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
@@ -81,18 +114,27 @@ class TrackingService : Service() {
         repo.insert(d)
     }
 
-    override fun onBind(intent: Intent): IBinder? {
-        Log.i(mTag,"onBind()")
-        return null
-    }
-
-    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
-        super.onStartCommand(intent, flags, startId)
-        Log.i(mTag,"onStartCommand()")
-        startForeground(mNotificationId, generateForegroundNotification() )
+    private fun startService() {
+        if(isServiceStarted) return
+        Log.i(mTag,"startService()")
+        Toast.makeText(this, "Starting JourneyGPSTracker service", Toast.LENGTH_SHORT).show()
+        isServiceStarted = true
+        setServiceState(this, ServiceState.STARTED)
         requestLocationUpdates()
         BackendClient()
-        return START_STICKY
+    }
+
+    private fun stopService() {
+        Log.i(mTag,"stopService()")
+        try {
+            stopForeground(true)
+            stopSelf()
+            mFusedLocationClient.removeLocationUpdates(mLocationCallback)
+        } catch (e: Exception) {
+            Log.e(mTag, "Service stopped without being started: ${e.message}")
+        }
+        isServiceStarted = false
+        setServiceState(this, ServiceState.STOPPED)
     }
 
     private fun generateForegroundNotification() : Notification {
@@ -131,8 +173,5 @@ class TrackingService : Service() {
         return builder.build()
     }
 
-    override fun onDestroy() {
-        super.onDestroy()
-        Log.i(mTag,"onDestroy()")
-    }
+
 }

+ 4 - 4
app/src/main/java/com/flacksta/chef/journeygpstracker/backend/BackendClient.kt

@@ -22,6 +22,7 @@ import kotlin.concurrent.fixedRateTimer
 
 class BackendClient {
 
+    private val mTag: String = "TrackingService"
     private var trackApp : JourneyGpsTrackerApplication = JourneyGpsTrackerApplication()
 
     init {
@@ -34,12 +35,11 @@ class BackendClient {
 
             val  dao :GpsDataDao = trackApp.database.gpsDataDao()
 
-            Log.i("Thc","Sending data....")
+            Log.i(mTag,"Sending data....")
             val pos : List<GpsData> = dao.getUnsentGpsPositions()
             if( pos.isNotEmpty() ) {
                 val rawJson: String = convertPosListToRawJson(pos)
                 val retVal: Boolean = sendGpsPosDataToBackendServer(rawJson)
-                Log.i("Thc", "retVal post: $retVal")
                 if (retVal) {
                     // Delete the successful ones
                     val first: Long = pos[0].ts
@@ -95,10 +95,10 @@ class BackendClient {
                                     ?.string() // About this thread blocking annotation : https://github.com/square/retrofit/issues/3255
                     )
             )
-            Log.i("Thc", "HTTP Reply: $prettyJson")
+            Log.i(mTag, "HTTP Reply: $prettyJson")
             retVal = true
         } else {
-            Log.e("Thc", "RETROFIT_ERROR:" + response.code().toString())
+            Log.e(mTag, "RETROFIT_ERROR:" + response.code().toString())
         }
 
         retVal

+ 20 - 0
app/src/main/res/layout/activity_main_screen.xml

@@ -6,6 +6,26 @@
     android:layout_height="match_parent"
     tools:context=".MainActivity">
 
+    <Button
+        android:id="@+id/buttonStart"
+        android:layout_width="120dp"
+        android:layout_height="80dp"
+        android:layout_marginTop="32dp"
+        android:text="Start"
+        app:layout_constraintEnd_toStartOf="@+id/buttonStop"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <Button
+        android:id="@+id/buttonStop"
+        android:layout_width="120dp"
+        android:layout_height="80dp"
+        android:layout_marginTop="32dp"
+        android:text="STOP"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/buttonStart"
+        app:layout_constraintTop_toTopOf="parent" />
+
     <FrameLayout
         android:id="@+id/main_frame_layout"
         android:layout_width="0dp"

+ 9 - 3
app/src/main/res/layout/fragment_home.xml

@@ -1,16 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraintLayout"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/teal_200"
     tools:context=".HomeFragment">
 
     <TextView
+        android:id="@+id/textView"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="Home fragment"
         android:textSize="40dp"
-        android:layout_gravity="center"/>
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
 
-</FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>