I have defined a BroacastReceiver according to the Geofencing docs in order to receive ENTRY & EXIT updates when the user interacts with a geofence. My issue is that the app is going to be used on the road so, when a user drives and enters a geofence I'm getting notified about it and the same happens when he exits it. However, when the exit event is received I need to remove the triggered geofence(s) from both the Client & the Google Maps map. Both of those exist in my MapsActivity (which is where I set up the receiver & event notification process based on the documentation) so I'd like to call the removeGeofences(...) method of my activity from the receiver. I looked at a TON of posts regarding this matter but none seems to cover the Geofencing use case. I've tried declaring the receiver dynamically through code instead of statically through the manifest but in that case, I'd need intent filters which I can't find for Geofencing. Any ideas on how to achieve this?
BroadcastReceiver:
class GeofenceReceiver : BroadcastReceiver() {
private val TAG = GeofenceReceiver::class.java.simpleName
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes
.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAG, "error: $errorMessage")
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
val ids = arrayListOf<String>()
for (geofence in triggeringGeofences) {
Log.d(TAG, "Geofence ${geofence.requestId} triggered!")
ids.add(geofence.requestId)
}
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
Log.d(TAG, "User entered geofence!")
else {
Log.d(TAG, "User exited geofence!")
//activity.removeGeofences(ids)
}
} else {
// Log the error.
Log.e(TAG, "Invalid transition")
}
}
}
MapsActivity:
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private var mMap: GoogleMap? = null
private var geofenceClient: GeofencingClient? = null
private var geofenceList: ArrayList<Geofence> = arrayListOf()
private var geofenceMapMarks: MutableMap<String, Pair<Marker, Circle>> = mutableMapOf()
private var currLocationMarker: Marker? = null
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceReceiver::class.java)
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences()
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
private val mLocationListener: LocationListener = LocationListener {
currLocationMarker?.remove()
currLocationMarker = mMap?.addMarker(
MarkerOptions().position(LatLng(it.latitude, it.longitude)).title("Current Location")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ORANGE))
)
animateCameraToLocation(currLocationMarker?.position!!)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
geofenceClient = LocationServices.getGeofencingClient(this)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
val mLocationManager = getSystemService(LOCATION_SERVICE) as LocationManager
if (!shouldRequestPermissions()) subscribeToLiveCurrentLocation(mLocationManager)
}
#SuppressLint("MissingPermission")
private fun subscribeToLiveCurrentLocation(mLocationManager: LocationManager) {
mLocationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000,
50F, mLocationListener
)
}
private fun requestPermissionsIfNeeded() {
if (!shouldRequestPermissions()) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
2
)
else ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
1
)
}
#SuppressLint("InlinedApi")
private fun shouldRequestPermissions(): Boolean = ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
mMap?.setOnMapClickListener { latlong ->
drawGeofenceOnMap(latlong)
addGeofenceToList(latlong)
}
}
private fun drawGeofenceOnMap(latlong: LatLng) {
val marker = mMap?.addMarker(MarkerOptions().position(latlong).title("Geofence"))
val circle = mMap?.addCircle(
CircleOptions().center(latlong).radius(defaultGeofenceRadius.toDouble()).strokeColor(
resources.getColor(android.R.color.holo_red_light, null)
).fillColor(
resources.getColor(android.R.color.transparent, null)
)
)
mMap?.moveCamera(CameraUpdateFactory.newLatLng(latlong))
geofenceMapMarks["Geofence #" + (geofenceList.size + 1)] = Pair(marker!!, circle!!)
}
private fun animateCameraToLocation(latlong: LatLng) {
val cameraPosition = CameraPosition.Builder()
.target(latlong)
.zoom(17f)
.build()
mMap?.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
}
private fun addGeofenceToList(latlong: LatLng) {
geofenceList.add(
Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId("Geofence #" + (geofenceList.size + 1))
// Set the circular region of this geofence.
.setCircularRegion(
latlong.latitude,
latlong.longitude,
defaultGeofenceRadius
)
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
.setExpirationDuration(Geofence.NEVER_EXPIRE)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
// Create the geofence.
.build()
)
}
private fun getGeofencingRequest(): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
}
private fun addGeofences() {
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "Need to grant permissions to continue", Toast.LENGTH_LONG).show()
requestPermissionsIfNeeded()
return
}
geofenceClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
addOnSuccessListener {
Toast.makeText(this#MapsActivity, "Geofences added!", Toast.LENGTH_LONG).show()
}
addOnFailureListener {
Toast.makeText(this#MapsActivity, "Failed to add geofences!", Toast.LENGTH_LONG)
.show()
Log.d("MAPSACTIVITY", it.message.toString())
}
}
}
fun removeGeofences(ids: ArrayList<String>) {
removeGeofencesFromClient(ids)
removeGeofencesFromMap(ids)
}
private fun removeGeofencesFromMap(ids: ArrayList<String>) {
for (id in ids) {
if (geofenceMapMarks.keys.contains(id)) geofenceMapMarks.remove(id)
}
}
private fun removeGeofencesFromClient(ids: ArrayList<String>) {
for (fence in geofenceList) {
if (ids.contains(fence.requestId)) geofenceList.remove(fence)
}
geofenceClient?.removeGeofences(ids)
?.addOnCompleteListener { task ->
if (task.isSuccessful) {
Toast.makeText(
this,
"Geofences have been removed!",
Toast.LENGTH_LONG
).show()
} else {
Toast.makeText(
this,
"Failed to remove geofences",
Toast.LENGTH_LONG
).show()
Log.d("MAPSACTIVITY", "error ==> " + task.exception)
}
}
}
override fun onDestroy() {
super.onDestroy()
mMap = null
geofenceClient = null
}
#SuppressLint("InlinedApi")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
var grantFailed = false
when (requestCode) {
1 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "GRANTED", Toast.LENGTH_SHORT).show()
} else grantFailed = true
}
2 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "GRANTED first", Toast.LENGTH_SHORT).show()
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
3
)
} else grantFailed = true
}
3 -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "GRANTED second", Toast.LENGTH_SHORT).show()
} else grantFailed = true
}
}
if (grantFailed) {
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) ||
shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
) {
Toast.makeText(this, "Show permission rationale", Toast.LENGTH_LONG).show()
} else Toast.makeText(
this,
"You must grant the requested permissions to continue",
Toast.LENGTH_SHORT
).show()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.addGeofence -> {
addGeofences()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.options_menu, menu)
return true
}
}
Manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.spap.geofencepoc">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.GeoFencePoC"
tools:ignore="AllowBackup">
<receiver android:name=".receivers.GeofenceReceiver"/>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key" />
<activity
android:name=".presentation.MapsActivity"
android:label="#string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
You can use SharesPreference to notify triggered geoFenceId to MainActivity.
class GeofenceReceiver : BroadcastReceiver() {
private val TAG = GeofenceReceiver::class.java.simpleName
private var geoFencePref: SharedPreferences? = null
private val triggeredExitGeofenceIds: HashSet<String> = HashSet()
private var triggedGeofenceIdsList: ArrayList<String> = ArrayList()
override fun onReceive(context: Context?, intent: Intent?) {
geoFencePref = context?.getSharedPreferences("TriggerdExitedId",Context.MODE_PRIVATE)
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes
.getStatusCodeString(geofencingEvent.errorCode)
Log.e(TAG, "error: $errorMessage")
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
storeGeofenceTransitionDetails(geofenceTransition,triggeringGeofences)
for (geofence in triggeringGeofences) Log.d(TAG, "Geofence ${geofence.requestId} triggered!")
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER)
Log.d(TAG, "User entered geofence!")
else {
Log.d(TAG, "User exited geofence!")
}
} else {
// Log the error.
Log.e(TAG, "Invalid transition")
}
}
private fun storeGeofenceTransitionDetails(
geofenceTransition: Int,
triggeredGeofences: List<Geofence>
) {
triggeredExitGeofenceIds.clear()
for (geofence in triggeredGeofences) {
triggedGeofenceIdsList.add(geofence.requestId)
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
triggeredExitGeofenceIds.add(geofence.requestId)
}
}
geoFencePref?.edit()?.putStringSet("geoFenceId", triggeredExitGeofenceIds)?.apply()
}
}
//Then in MainActivity , register sharedpreference to listen for changes.
class MapsActivity : AppCompatActivity(), OnMapReadyCallback,
SharedPreferences.OnSharedPreferenceChangeListener {
....
override fun onStart() {
super.onStart()
requestPermissionsIfNeeded()
geoFencePref = getSharedPreferences("TriggerdExitedId", Context.MODE_PRIVATE)
geoFencePref!!.registerOnSharedPreferenceChangeListener(this)
}
....
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
val triggeredExitFences: HashSet<String>
val triggeredGeofences = ArrayList<String>()
if (key != null) {
Log.d("onSharedChanged: ", key)
}
if (key.equals("geoFenceId")) {
triggeredExitFences = geoFencePref?.getStringSet("geoFenceId", null) as HashSet<String>
if(triggeredExitFences.isEmpty()) Log.d("onSharedChanged: ", "no exit fences triggered")
triggeredGeofences.addAll(triggeredExitFences)
for(fence in triggeredExitFences) Log.d("onSharedChanged: ", "ID: $fence triggered!")
//Here you can call removeGeoFencesFromClient() to unRegister geoFences and removeGeofencesFromMap() to remove marker.
// removeGeofencesFromClient(triggerdIdList);
// removeGeofencesFromMap(triggerdIdList);
}
}
}
Related
I have this simple MainActivity in which I use the WorkManager to enqueue a unique OneTimeWorkRequest:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
WorkManager.getInstance(this).enqueueUniqueWork("work", ExistingWorkPolicy.REPLACE, OneTimeWorkRequest.Builder(BackgroundTask::class.java).build())
findViewById<Button>(R.id.button).setOnClickListener {
BackgroundTask.stopHandler = true
}
}
}
The OneTimeWorkRequest launch a background Worker that is responsible to take updates from the GPS_PROVIDER whether the user keep the App in foreground or the user put the App in background:
class BackgroundTask (context : Context, params : WorkerParameters) : Worker(context, params){
private lateinit var mLocationManager: LocationManager
private lateinit var mLocationListener: LocationUpdaterListener
private var mHandler: Handler = Handler(Looper.getMainLooper())
private var mHandlerTask: Runnable = object : Runnable {
override fun run() {
if (Looper.myLooper() == null) {
Looper.prepare()
}
Toast.makeText(applicationContext,"mHandlerTask running...",Toast.LENGTH_SHORT).show()
if (stopHandler) {
stopH()
return
}
if (!isListening) {
startListening()
}
mHandler.postDelayed(this, TEN_SECONDS)
}
}
fun stopH() {
stopListening()
mHandler.removeCallbacks(mHandlerTask)
}
override fun doWork(): Result {
mLocationManager = applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
mLocationListener = LocationUpdaterListener()
mHandlerTask.run()
return Result.success()
}
private fun startListening() {
if (ContextCompat.checkSelfPermission( applicationContext, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED ) {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, mLocationListener, Looper.getMainLooper())
}
isListening = true
}
private fun stopListening() {
if (ContextCompat.checkSelfPermission( applicationContext, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( applicationContext, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED ) {
mLocationManager.removeUpdates(mLocationListener)
}
isListening = false
}
inner class LocationUpdaterListener : LocationListener {
override fun onLocationChanged(location: Location) {
try {
Log.d("DEBUG", "${location.latitude} , ${location.longitude}")
} catch (e: Exception) {
e.printStackTrace()
} finally {
stopListening()
}
}
override fun onProviderDisabled(provider: String) {
stopListening()
}
}
companion object {
const val TEN_SECONDS: Long = 10000
var isListening = false
var stopHandler = false
}
}
I put a Toast message inside the run of the Runnable object just to have confirmation the Runnable is going.
If I use the button in the MainActivity to stop the Handler everything works fine: the stop() function is called and then the LocationListener and the mHandlerTask callback are removed.
I was wondering what happens if I suddenly close the App (swiping up the App from the Recents Screen), because I can't see the Toast message prints anymore and so I guess someway the Runnable object is killed, but I want to be sure that also the LocationListener and the mHandlerTask callback are removed.
Does this happen automatically?
I am using and Android App to start a Camera preview in order to do some OCR operations , but i can't make it work on Android 5.0.1 device.
i am getting an error on surfaceCreated :
Fail to connect to camera service
here is my sample code :
class OCRActivity : AppCompatActivity()
{
private var mCameraSource by Delegates.notNull<CameraSource>()
private var textRecognizer by Delegates.notNull<TextRecognizer>()
private var tryToOpenView = false
private val PERMISSION_REQUEST_CAMERA = 100
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ocr)
supportActionBar?.hide()
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
.......
generateOCRView()
}
override fun onResume()
{
super.onResume()
}
private fun generateOCRView()
{
// Create text Recognizer
textRecognizer = TextRecognizer.Builder(this).build()
if (!textRecognizer.isOperational)
{
Toast.makeText(this, MSG, Toast.LENGTH_LONG ).show()
finish()
return
}
var bestPreviewSize: Camera.Size? = null
if (isCameraPermissionGranted())
{
// Get best preview size for the camera
val mCamera = Camera.open()
if (mCamera != null)
{
val mCameraParameters = mCamera.parameters
if (mCameraParameters != null)
{
val thisBestPreviewSize = getBestPreviewSize(mCameraParameters)
if (thisBestPreviewSize != null)
{
bestPreviewSize = thisBestPreviewSize
}
}
}
}
// Init camera source to use high resolution and auto focus
mCameraSource = if (bestPreviewSize != null)
{
CameraSource.Builder(this, textRecognizer)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setAutoFocusEnabled(true)
.setRequestedFps(30.0f)
.setRequestedPreviewSize(bestPreviewSize.height, bestPreviewSize.width)
.build()
}
else
{
// Default camera size, can be stretched...
CameraSource.Builder(this, textRecognizer)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setAutoFocusEnabled(true)
.setRequestedFps(30.0f)
.setRequestedPreviewSize(1920, 1080)
.build()
}
surface_camera_preview.holder.addCallback(object : SurfaceHolder.Callback
{
override fun surfaceChanged(p0: SurfaceHolder?, p1: Int, p2: Int, p3: Int)
{
Log.d("surfaceChanged", "surfaceChanged")
return
}
override fun surfaceDestroyed(p0: SurfaceHolder?)
{
Log.d("surfaceDestroyed", "surfaceDestroyed")
mCameraSource.stop()
}
#SuppressLint("MissingPermission")
override fun surfaceCreated(p0: SurfaceHolder?)
{
try
{
// If the camera permission is granted
if (isCameraPermissionGranted())
{
Log.d("surfaceCreated", "surfaceCreated with permission granted")
// Show camera preview
mCameraSource.start(surface_camera_preview.holder)
}
else
{
requestForPermission()
}
} catch (e: Exception) {
Log.e(localClassName, "surfaceCreated error: ${e.message}")
finish()
}
}
})
textRecognizer.setProcessor(object : Detector.Processor<TextBlock>
{
override fun release()
{
return
}
override fun receiveDetections(detections: Detector.Detections<TextBlock>)
{
val items = detections.detectedItems
if (items.size() <= 0)
{
return
}
tryToOpenView = false
mrzResult.post {
val stringBuilder = StringBuilder()
for (i in 0 until items.size())
{
val item = items.valueAt(i)
stringBuilder.append(item.value)
stringBuilder.append("\n")
}
...........
}
}
})
}
/**
* isCameraPermissionGranted function: Check if camera permission is granted.
*
* #return Boolean: true if camera permission is granted and false if not.
*/
fun isCameraPermissionGranted(): Boolean
{
return ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
}
/**
* requestForPermission function:
*/
private fun requestForPermission()
{
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), PERMISSION_REQUEST_CAMERA)
}
#SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
{
// Go back to previous if camera permission is refuses
if (requestCode != PERMISSION_REQUEST_CAMERA)
{
// Go to previous view
Toast.makeText(this, MSG, Toast.LENGTH_LONG).show()
finish()
}
else
{
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
// If the camera permission is granted
if (isCameraPermissionGranted())
{
// Show camera preview
mCameraSource.start(surface_camera_preview.holder)
}
else
{
// Go to previous view
Toast.makeText(this, MSG, Toast.LENGTH_LONG).show()
finish()
}
}
else
{
// Go to previous view
Toast.makeText(this, MSG, Toast.LENGTH_LONG).show()
finish()
}
}
}
private fun getBestPreviewSize(parameters: Camera.Parameters): Camera.Size?
{
var bestSize: Camera.Size? = null
val sizeList = parameters.supportedPictureSizes
bestSize = sizeList[0]
for (i in 1 until sizeList.size)
{
if (sizeList[i].width * sizeList[i].height > bestSize!!.width * bestSize.height)
{
bestSize = sizeList[i]
}
}
return bestSize
}
}
Make sure that you set these permissions at the top of your manifest:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
If this isn't the case, initialize the mCamera as a instance variable outside of your method definition. Hope this helps :)
I know this:
A broadcast receiver is ALWAYS called because It sets the alarm for the next day.
When It works if the App is in Foreground, or if it is closed or if it is closed screen.
I use 'com.github.thefuntasty.hauler:library:2.0.0' to dismiss the activity with swipe
No other alarm is set at that time
I don't get any crash reports
I have NO idea, why the activity doesn't start. I tried doing try and catch, to see if that would help but it didn't.
EDIT:
Expected:
When the Broadcast reciever triggers, set the alarm for the next day, start the Activity in the foreground, even if the phone is locked and play the alarm.
On swipe down destroy the activity
Currently:
Sometimes it opens the activity other times it doesn't and I don't know why. If I set it for 5 min from now (locked screen, closed app), it works without problem. If I set it for tomorrow, it works 90% of the time.
I'm trying to figure out, why the activity sometimes doesn't start, because I'm not getting any crash reports from the firebase.
This is the code:
Manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
android:name=".views.alarm.broadcast.ResumeOnBootAlarm"
android:enabled="true"
android:exported="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name=".views.alarm.broadcast.MyAlarm"
android:exported="true" />
<activity
android:name=".views.alarm.broadcast.TriggeredAlarmActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance"
android:theme="#style/AppTheme.Draggable" />
This is my BroadCastReciever:
class MyAlarm : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val alarmID = intent.extras?.getInt(ARG_ALARM_ID)
//Immediatly set new alarm
val realm = Realm.getDefaultInstance()
val alarmFromRealm = DataHelper.getAlarm(realm, alarmID!!)
alarmFromRealm?.let { alarm ->
val shouldEnableAlarm = alarm.isEnabled && alarm.daysList!!.isNotEmpty()
DataHelper.enableAlarm(alarmID, shouldEnableAlarm, realm)
if (shouldEnableAlarm) {
setAlarm(context, alarm, false)
} else {
cancelAlarm(context, alarm.id)
}
}
try {
context.startActivity(TriggeredAlarmActivity.getIntent(context, alarmID))
} catch (ex: Exception) {
Crashlytics.logException(ex)
context.startActivity(TriggeredAlarmActivity.getIntent(MyApplication.appContext, null))
}
}
}
And this is the TriggeredActivity:
class TriggeredAlarmActivity : BaseActivity() {
private var currentUserVolume: Int = 0
private lateinit var timer: Timer
private lateinit var realm: Realm
private lateinit var vibrator: Vibrator
private var mediaPlayer: MediaPlayer? = null
private var shouldResumePlaying: Boolean = false
private val alarmID: Int?
get() {
return intent.extras?.getInt(ARG_ALARM_ID)
}
private val mAudioManager: AudioManager by lazy {
baseContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
}
/*
LifeCycle
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_triggered_alarm)
initTextClock()
showIfScreenIsLocked()
showDanceAnimation()
if (alarmID == null) {
val uri = getDefaultRingtone()
mediaPlayer= MediaPlayer()
mediaPlayer?.setDataSource(this,uri)
mediaPlayer?.prepare()
mediaPlayer?.start()
} else {
realm = Realm.getDefaultInstance()
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
val alarmFromRealm = DataHelper.getAlarm(realm, alarmID!!)
alarmFromRealm?.let { alarm ->
try {
if (alarm.useDefaultRingtone) {
val uri = getDefaultRingtone()
mediaPlayer = MediaPlayer()
mediaPlayer?.let { increaseVolumeOverTime(it, alarm.shouldVibrate) }
mediaPlayer?.setDataSource(this, uri)
mediaPlayer?.isLooping = true
shouldResumePlaying = alarm.shouldResumePlaying
mediaPlayer?.setOnPreparedListener {
if (alarm.shouldResumePlaying) {
mediaPlayer?.seekTo(alarm.secondsPlayed)
}
mediaPlayer?.start()
}
mediaPlayer?.prepareAsync()
} else {
initMediaPlayer(alarm)
}
} catch (exception: Exception) {
Crashlytics.logException(exception)
val uri = getDefaultRingtone()
mediaPlayer = MediaPlayer()
mediaPlayer?.let { increaseVolumeOverTime(it, true) }
mediaPlayer?.setDataSource(this, uri)
mediaPlayer?.isLooping = true
mediaPlayer?.setOnPreparedListener {
mediaPlayer?.start()
}
mediaPlayer?.prepareAsync()
}
}
}
haulerView?.setOnDragDismissedListener {
finish() // finish activity when dismissed
}
}
private fun getDefaultRingtone(): Uri {
var uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
if (uri == null) {
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
if (uri == null) {
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
}
}
return uri
}
private fun initTextClock() {
val period = 5000L
val timer = Timer()
val formatter = SimpleDateFormat("HH:mm")
val task = object : TimerTask() {
override fun run() {
val today = Date()
runOnUiThread {
tvCurrentTimeActual?.text = formatter.format(today)
}
}
}
timer.scheduleAtFixedRate(task, 0L, period)
}
override fun onDestroy() {
super.onDestroy()
if (shouldResumePlaying) {
mediaPlayer?.let {
alarmID?.let { it1 -> DataHelper.updateProgress(it1, it.currentPosition) }
}
}
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentUserVolume, 0)
timer.cancel()
mediaPlayer?.stop()
vibrator.cancel()
realm.close()
}
/*
Private
*/
private fun showIfScreenIsLocked() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(this, null)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
)
}
}
private fun showDanceAnimation() {
val lottieFiles = mutableListOf(
"lottie/dance/chicken_6.json",
"lottie/dance/sound.json", //White
"lottie/dance/pinguin.json" //White
)
val file = lottieFiles.random()
messageLottie?.setAnimation(file)
if (file == "lottie/dance/pinguin.json"
|| file == "lottie/dance/sound.json"
) {
messageLottie?.addValueCallback(
KeyPath("**"), LottieProperty.COLOR_FILTER,
{ PorterDuffColorFilter(getColor(R.color.white), PorterDuff.Mode.SRC_ATOP) }
)
}
messageLottie?.playAnimation()
}
private fun increaseVolumeOverTime(mediaPlayer: MediaPlayer, shouldVibrate: Boolean) {
mediaPlayer.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
)
currentUserVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
var currentVolume = 1
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0)
if (shouldVibrate) {
startVibrating()
}
timer = Timer()
timer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
currentVolume += 1
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0)
if (currentVolume % 10 == 0) {
if (shouldVibrate) {
startVibrating(currentVolume)
}
}
if (currentVolume >= 90) this.cancel()
}
}, 0, 2000)
}
private fun startVibrating(currentVolume: Int = 10) {
val vibratorLength = ((50 * currentVolume) / 1.2).roundToInt().toLong()
val patternShort = longArrayOf(1200, vibratorLength)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(patternShort, 0))
} else {
vibrator.vibrate(patternShort, 0)
}
}
private fun initMediaPlayer(alarm: Alarm) {
mediaPlayer = MediaPlayer()
mediaPlayer?.let { increaseVolumeOverTime(it, alarm.shouldVibrate) }
val currentlySelectedPath = alarm.currentlySelectedPath
if (currentlySelectedPath == null) {
var uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
if (uri == null) {
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
if (uri == null) {
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
}
}
mediaPlayer?.setDataSource(this, uri)
} else {
mediaPlayer?.setDataSource(this, Uri.parse(currentlySelectedPath))
}
mediaPlayer?.isLooping = false
mediaPlayer?.setOnCompletionListener {
it?.stop()
it?.reset()
it?.isLooping = false
val path = alarm.songsList?.random()?.path
it?.setDataSource(this, Uri.parse(path))
alarmID?.let { it1 -> DataHelper.nextRandomSong(it1, path) }
it?.setOnPreparedListener {
mediaPlayer?.start()
}
it?.prepareAsync()
}
mediaPlayer?.setOnPreparedListener {
if (alarm.shouldResumePlaying) {
mediaPlayer?.seekTo(alarm.secondsPlayed)
}
mediaPlayer?.start()
}
mediaPlayer?.prepareAsync()
shouldResumePlaying = alarm.shouldResumePlaying
}
companion object {
const val ARG_ALARM_ID = "AlarmID"
fun getIntent(context: Context, alarmID: Int?): Intent {
val intent = Intent(context, TriggeredAlarmActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_HISTORY) //If it doesn't hide in recent use or Intent.FLAG_ACTIVITY_CLEAR_TASK
intent.putExtra(ARG_ALARM_ID, alarmID)
return intent
}
}
}
I want to detect if the user turned off the location at runtime. I can check if he turns it on or if the location was turned off by user before the app was started but I can't check if he turned it off after.
Code Sample:
MapEntity extends LocationListener
class MapViewer(a: MainActivity, parentView: ViewGroup) : MapEntity(a, parentView) {
override fun onProviderEnabled(provider: String?) {
activity.hideGpsSnackbar()
}
override fun onProviderDisabled(provider: String?) {
activity.showGpsSnackbar()
}
}
For realtime GPS location checking, I'm using GnssStatus.Callback()
UPDATE:
I've created BroadcastReceiver according to the answer below.
abstract class GPSReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
val locationManager = context.getSystemService(LOCATION_SERVICE) as LocationManager
if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
onGpsChanged(true)
} else {
onGpsChanged(false)
}
} catch (ex: Exception) {
App.log("IsGPSEnabled: $ex")
}
}
abstract fun onGpsChanged(isEnabled: Boolean)
}
Code inside one of my Activities:
private val gpsStatusReceiver = object : GPSReceiver() {
override fun onGpsChanged(isEnabled: Boolean) {
if (isEnabled){
hideGpsSnackbar()
} else {
showGpsSnackbar()
}
}
}
override fun onStart() {
super.onStart()
registerReceiver(gpsStatusReceiver, IntentFilter())
}
override fun onStop() {
super.onStop()
unregisterReceiver(gpsStatusReceiver)
}
UPDATE
If you want to support Android 6.0, you cannot use abstract class. Because it will try to create object out of this class defined in AndroidManifest. Android 8.0+ will not check receiver inside AndroidManifest so you can instantiate object out of Abstract Class. So instead of it create interface.
I'm actually doing it with a BroadcastReceiver.
I can share my code; it's java but I think you can easily convert it into kotlin.
Create a Broadcastreceiver
Example:
public class GPSBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
try {
LocationManager locationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
//isGPSEnabled = true;
} else {
//isGPSEnabled = false;
}
}catch (Exception ex){
}
}
}
Add your BroadcastReceiver to the manifest
Example:
<receiver android:name=".others.GPSBroadcastReceiver">
<intent-filter>
<action android:name="android.location.PROVIDERS_CHANGED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
Then (for my case) I manage it in my ApplicationContext as follows:
private GPSBroadcastReceiver gpsBroadcastReceiver = new GPSBroadcastReceiver();
#Override
public void onCreate() {
....
registerReceiver(gpsBroadcastReceiver, new IntentFilter());
}
#Override
public void onTerminate() {
...
unregisterReceiver(gpsBroadcastReceiver);
}
That's just an example, there might be other ways for it but actually I'm fine with that one; good luck!
Edit:
try adding this permission in your manifest
<uses-permission android:name="android.permission.ACCESS_GPS" />
Edit:
For Android 8.0+ register your BroadcastReceiver like this:
registerReceiver(gpsBroadcastReceiver,IntentFilter("android.location.PROVIDERS_CHANGED"))
Adding action inside AndroidManifest will not work.
You can use Broadcast Recievers for that purpose that will be triggered when state has changed.
Check this Answer
Please use the following approach to enable location settings:
LocationServices
.getSettingsClient(this)
.checkLocationSettings(mLocationSettingsRequest)
.addOnSuccessListener(this, object : OnSuccessListener<LocationSettingsResponse> {
override fun onSuccess(locationSettingsResponse: LocationSettingsResponse) {
Log.i(TAG, "LocationManager: All location settings are satisfied.");
mLocationCallback?.let {
fusedLocationClient?.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
}
//updateUI();
}
})
.addOnFailureListener(this, object : OnFailureListener {
override fun onFailure(e: Exception) {
var statusCode = (e as ApiException).getStatusCode()
when (statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> {
try {
// Show the dialog by calling startResolutionForResult(), and check the
// result in onActivityResult().
var rae = e as ResolvableApiException;
rae.startResolutionForResult(this#BaseLocationActivity, REQUEST_CHECK_SETTINGS);
} catch (sie: IntentSender.SendIntentException) {
Log.i(TAG, "PendingIntent unable to execute request.");
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
mRequestingLocationUpdates = false;
}
}
}
})
Create location request
private fun createLocationRequest(interval: Long, distance: Float = 0f) {
mLocationRequest = LocationRequest()
mLocationRequest.interval = interval
mLocationRequest.fastestInterval = interval
Log.v(TAG, "distance => $distance")
if (distance > 0) {
mLocationRequest.smallestDisplacement = distance
}
mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
I created service and firebase job service too. They work perfectly when app is closed.
I want get user's location every minute and send to server when app is closed
This is my jobservice:
class NeverEndingJob : JobService() {
var counter = 0
var TAG = "NeverEndingJOb"
private val NOTIFICATION_ID = 404
private val CHANNEL_ID = "AppC"
internal var name: CharSequence = "AppC"
internal var importance: Int = 0
internal var mChannel: NotificationChannel? = null
internal var mNotificationManager: NotificationManager? = null
private var mLocationManager: LocationManager? = null
private var owner: ICapture? = null
var model :MyRestModel? = null
var notificationManager: NotificationManagerCompat? = null
private var mLocationListeners = arrayOf(LocationListener(LocationManager.GPS_PROVIDER), LocationListener(LocationManager.NETWORK_PROVIDER))
init {
MLog.d(TAG,"job created")
}
override fun onCreate() {
super.onCreate()
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importance = NotificationManager.IMPORTANCE_MIN
mChannel = NotificationChannel(CHANNEL_ID, name, importance)
mNotificationManager!!.createNotificationChannel(mChannel)
}
}
override fun onStopJob(job: JobParameters?): Boolean {
MLog.d(TAG,"job destroy")
val intent = Intent("app.name.RestartService")
sendBroadcast(intent)
stopTimer()
return true
}
override fun onStartJob(job: JobParameters?): Boolean {
mNotificationManager!!.notify(NOTIFICATION_ID, getNotification(R.string.working.getResource()))
startForeground(NOTIFICATION_ID, getNotification(R.string.working.getResource()))
startTimer()
return true
}
private var timer: Timer? = null
private var timerTask: TimerTask? = null
var oldTime: Long = 0
companion object {
private val TAG = "AppC"
private var LOCATION_INTERVAL:Long = 0
private var LOCATION_DISTANCE = 10f
}
private fun startTimer(){
timer = Timer()
initializeTimerTask()
val apiService = ApiService(Handler())
App.courInfo = Prefs.instance(App.preferences).getCourierLocInfo()
notificationManager = NotificationManagerCompat.from(this);
model = MyRestModel(apiService)
model!!.apiCallback = ApiCallback()
Log.e(TAG, "onCreate")
initializeLocationManager()
setLocationUpdates()
timer!!.schedule(timerTask, NeverEndingJob.LOCATION_INTERVAL, NeverEndingJob.LOCATION_INTERVAL) //
}
internal var mLastLocation: Location =Location(LocationManager.NETWORK_PROVIDER)
fun initializeTimerTask() {
timerTask = object : TimerTask() {
override fun run() {
Handler(Looper.getMainLooper()).post {
setLocationUpdates()
}
}
}
}
private fun stopTimer(){
if (timer != null) {
timer?.cancel();
timer = null;
}
}
private fun getNotification(title : String): Notification {
Log.d(TAG, "create notofication")
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
NotificationCompat.Builder(this, CHANNEL_ID)
else
NotificationCompat.Builder(this)
builder.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(0)
builder.setContentTitle(title)
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher, null))
val not: Notification
not = builder.setOngoing(true).build()
return not
}
private inner class LocationListener(provider: String) : android.location.LocationListener {
init {
Log.e(TAG, "LocationListener $provider")
mLastLocation = Location(provider)
}
override fun onLocationChanged(location: Location) {
Log.e(TAG, "onLocationChanged: $location")
try{
val distance = location.distanceTo(mLastLocation)
mNotificationManager!!.notify(NOTIFICATION_ID, getNotification("${R.string.get_location.getResource()} Метр: ${distance.toInt()}"))
Handler().postDelayed({
mNotificationManager!!.notify(NOTIFICATION_ID, getNotification(R.string.working.getResource()))
},3000)
Log.e(TAG,"distance"+ "$distance")
if (distance > 10){
mLastLocation = location
sendLocation(mLastLocation)
}
Prefs.instance(App.preferences).setLastLocate(location)
Prefs.instance(App.preferences).setLastLocate(location)
}catch (e :java.lang.Exception){
Log.e(TAG, "send http lat lon exception: $e")
}
mLastLocation.set(location)
}
override fun onProviderDisabled(provider: String) {
Log.e(TAG, "onProviderDisabled: $provider")
}
override fun onProviderEnabled(provider: String) {
Log.e(TAG, "onProviderEnabled: $provider")
}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
Log.e(TAG, "onStatusChanged: $provider")
}
}
fun sendLocation(location : Location){
Log.e(TAG, "ready for send http lat lon $location ")
val currentTime = Functions.calculateDifference(System.currentTimeMillis())
Log.e(TAG, "get current time $currentTime ")
if (App.courInfo != null){
Log.e(TAG, "get open time ${App.courInfo!!.startTime} ")
Log.e(TAG, "get close time ${App.courInfo!!.endTime} ")
val startTime = App.courInfo!!.startTime
val endTime = App.courInfo!!.endTime
val isHourLess =currentTime.hour.toInt() > startTime.hour.toInt()
val isHourLessEqual =currentTime.hour.toInt() == startTime.hour.toInt()
val isMinLess = currentTime.minute.toInt() >= startTime.minute.toInt()
val isHourMore =currentTime.hour.toInt() < endTime.hour.toInt()
val isHourMoreEqual =currentTime.hour.toInt() == endTime.hour.toInt()
val isMinMore = currentTime.minute.toInt() <= endTime.minute.toInt()
if (isHourLess && isHourMore){
if (model != null){
model!!.setLocation(App.userData!!.phone, App.userData!!.token, App.userData!!.cId,location.latitude.toString(),location.longitude.toString())
}
}else if (isHourLessEqual && isHourMore){
if (isMinLess){
if (model != null){
model!!.setLocation(App.userData!!.phone, App.userData!!.token, App.userData!!.cId,location.latitude.toString(),location.longitude.toString())
}
}
}else if (isHourLess && isHourMoreEqual){
if (isMinMore){
if (model != null){
model!!.setLocation(App.userData!!.phone, App.userData!!.token, App.userData!!.cId,location.latitude.toString(),location.longitude.toString())
}
}
}
}
}
private fun initializeLocationManager() {
Log.e(TAG, "initializeLocationManager")
mLocationManager = applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
fun setLocationUpdates(){
try {
val locInfo = Prefs.instance(App.preferences).getCourierLocInfo()
if (locInfo != null){
NeverEndingJob.LOCATION_INTERVAL = 3000
NeverEndingJob.LOCATION_DISTANCE = locInfo.metres.toFloat()
}
Log.e(TAG, "onCreate $locInfo")
mLocationManager!!.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 1000, 0.toFloat(),
mLocationListeners[1])
} catch (ex: java.lang.SecurityException) {
Log.i(TAG, "fail to request location update, ignore", ex)
} catch (ex: IllegalArgumentException) {
Log.d(TAG, "network provider does not exist, " + ex.message)
}
}
inner class ApiCallback : ApiService.Callback{
override fun onSuccess(result: String) {
mNotificationManager!!.notify(NOTIFICATION_ID, getNotification(R.string.success_sended.getResource()))
Handler().postDelayed({
mNotificationManager!!.notify(NOTIFICATION_ID, getNotification(R.string.working.getResource()))
},3000)
notificationManager!!.notify(1,mBuilder.build())
}
override fun onFail(failure: String) {
Log.e(TAG, "onFail $failure")
}
}
fun clearLocationListeners(){
if (mLocationManager != null) {
for (i in mLocationListeners.indices) {
try {
mLocationManager!!.removeUpdates(mLocationListeners[i])
} catch (ex: Exception) {
Log.i(NeverEndingJob.TAG, "fail to remove location listners, ignore", ex)
}
}
}
}
}
This code work perfectly when app is opened, send every time location to server, but when I close the app, service works every minute says me you should get location but onLocationChanged not called:
timerTask = object : TimerTask() {
override fun run() {
Handler(Looper.getMainLooper()).post {
setLocationUpdates()
}
}
}
Manifest:
<service
android:name=".service.NeverEndingJob"
android:enabled="true">
<!--<intent-filter>-->
<!--<action android:name="com.firebase.jobdispatcher.ACTION_EXECUTE"/>-->
<!--</intent-filter>-->
</service>
<receiver
android:name=".service.RestartService"
android:enabled="true"
android:exported="true"
android:label="RestartServiceWhenStopped">
<intent-filter>
<action android:name="app.name.RestartService" />
<action android:name="android.net.wifi.STATE_CHANGE" />
<!--<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />-->
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
Receiver
class RestartService : BroadcastReceiver() {
var TAG = "RestartService"
override fun onReceive(p0: Context?, p1: Intent?) {
MLog.d(TAG,"service stopppedd")
val dispatcher = FirebaseJobDispatcher(GooglePlayDriver(p0))
val job = dispatcher
.newJobBuilder()
.setService(NeverEndingJob::class.java)
.setTag("AppC-Bg-Job")
.setRecurring(false)
.setLifetime(Lifetime.FOREVER)
.setTrigger(Trigger.executionWindow(0,0))
.setReplaceCurrent(false)
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
.setConstraints(Constraint.ON_ANY_NETWORK)
.build()
dispatcher.mustSchedule(job)
}
}
Activity:
lateinit var dispatcher: FirebaseJobDispatcher
private fun startLocationService() {
dispatcher = FirebaseJobDispatcher(GooglePlayDriver(this))
val job = dispatcher
.newJobBuilder()
.setService(NeverEndingJob::class.java)
.setTag("Bringo-Couirer-Bg-Job")
.setRecurring(false)
.setLifetime(Lifetime.FOREVER)
.setTrigger(Trigger.executionWindow(0, 0))
.setReplaceCurrent(false)
.setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
.setConstraints(Constraint.ON_ANY_NETWORK)
.build()
dispatcher.mustSchedule(job)
}
override fun onDestroy() {
super.onDestroy()
dispatcher.cancelAll()
}
Why onLocationChanged not called after few minutes when app is closed in alive service?
I think you need to use the Location Manager.
LocationManager locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER,
2000,
10, this);
the first parameters is the provider you are using (in this case the GPS Provider). The second parameter (2000) is the minimum time in milliseconds between each update. The third parameter (10) is the minimum distance. The last parameters is your LocationListener (this).
It is also a good idea to implement onProviderDisabled in case the user has his GPS turned off.
override this method in ur service
public void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
Utils.showLog("onTaskedremoved called");
PendingIntent service = PendingIntent.getService(
getApplicationContext(),
1001,
new Intent(getApplicationContext(), ''your service name''.class),
PendingIntent.FLAG_ONE_SHOT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 1000, service);
}