I'm trying a simple thing, but unfortunately, I'm unable to achieve it. I want to receive activity transition recognition callbacks from Android SDK 26 - 29:
I have registered the following permissions in the manifest:
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"/>
In the main activity, I check the permission:
private val activityRecognitionPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) permission.ACTIVITY_RECOGNITION else "com.google.android.gms.permission.ACTIVITY_RECOGNITION"
if( PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, activityRecognitionPermission) )
permissionsToRequest += activityRecognitionPermission
This works fine. The permission is granted.
Afterwards I register the listener:
if (PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(this, activityRecognitionPermission)) {
val transitions = ArrayList<ActivityTransition>()
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.IN_VEHICLE)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.IN_VEHICLE)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.ON_BICYCLE)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.ON_BICYCLE)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.ON_FOOT)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.ON_FOOT)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.RUNNING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build())
transitions.add(ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build())
val request = ActivityTransitionRequest( transitions )
val client = ActivityRecognition.getClient(this)
val intent = Intent(this, ActivityRecognitionReceiver::class.java)
activityRecognitionPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
val task = client.requestActivityTransitionUpdates(request, activityRecognitionPendingIntent!!)
task.addOnSuccessListener ( object: OnSuccessListener<Void> {
override fun onSuccess(p0: Void?) {
return
}
})
task.addOnFailureListener ( object: OnFailureListener {
override fun onFailure(p0: Exception) {
return
}
})
}
I debuged and the onSuccess method is called after that.
This is the code of my receiver:
class ActivityRecognitionReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if( ActivityTransitionResult.hasResult(intent)) {
val resultList = ActivityTransitionResult.extractResult(intent)
if( resultList != null ) {
for (event in resultList.transitionEvents) {
// TODO
}
}
}
}
}
My problem is, that the onReceive method is never called back.
Does anyone have an idea, what the problem could be?
Related
I implemented the Activity Transitions API with a PendingIntent and a BroadcastReceiver as seen below. The code works perfectly fine on a Pixel 3a. However, on a Samsung A32 and Samsung S22 Pro, the Broadcast receiver is never reached, eventhough the ActivityRecognition.getClient(mainActivity).requestActivityTransitionUpdates() succeeds and enters the onSuccessListener().
After a lot of time spent reading through the internet, I wasn't able to find any further information. Neither concerning the Activity Transitions API, nor concerning such problems on Samsung devices (e.g. not raching BroadcastReceiver). Some people hint to disabling battery saving features from Samsung, but the App runs currently only in foreground in the MainActivty thread, therefore I don't think my problem is related to that. Other point out that, for example Huawai devices, need a diffferent permission than the in the android docs specified one for the Activity Transition API. So currently I'm specifing those three permissions (and check them run-time with ContextCompat.checkSelfPermission()):
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="com.huawei.hms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
Code
Main class with
initalization function initController() called in the form of
mActivityTransitionController = ActivityTransitionController().also { it.initController(this#MainActivity) }
and the entrypoint onClickEnableOrDisableActivityRecognition():
class ActivityTransitionController() {
companion object {
internal val TRANSITION_RECEIVER_ACTION: String =
"MyMachineLearningStalkingProtection.TRANSITIONS_RECEIVER_ACTION"
}
private var activityTrackingOn: Boolean = false
private lateinit var activityTransitionList: List<ActivityTransition>
private lateinit var mActivityTransitionPendingIntent: PendingIntent
internal fun initController(mainActivity: MainActivity) {
activityTrackingOn = false
activityTransitionList = buildTransitionList()
val intent = Intent(TRANSITION_RECEIVER_ACTION)
mActivityTransitionPendingIntent =
PendingIntent.getBroadcast(mainActivity, 0, intent, PendingIntent.FLAG_MUTABLE)
Utils.makeSnackBar("Activity Recognition initialized!", mainActivity)
}
internal fun onClickEnableOrDisableActivityRecognition(mainActivity: MainActivity) {
if (activityTrackingOn) {
disableActivityTransitions(mainActivity)
} else {
enableActivityTransitions(mainActivity)
}
}
private fun buildTransitionList(): ArrayList<ActivityTransition> {
val list = ArrayList<ActivityTransition>()
list.add(
ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
)
list.add(
ActivityTransition.Builder()
.setActivityType(DetectedActivity.WALKING)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build()
)
list.add(
ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
)
list.add(
ActivityTransition.Builder()
.setActivityType(DetectedActivity.STILL)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT)
.build()
)
return list
}
#SuppressLint("MissingPermission")
internal fun disableActivityTransitions(mainActivity: MainActivity) {
Log.d(Utils.MY_LOG_TAG, "disableActivityTransitions()")
ActivityRecognition.getClient(mainActivity)
.removeActivityTransitionUpdates(mActivityTransitionPendingIntent)
.addOnSuccessListener {
activityTrackingOn = false
Utils.makeSnackBar("Transitions successfully unregistered.", mainActivity)
}.addOnFailureListener {
Utils.makeSnackBar("Transitions could NOT be unregistered.", mainActivity)
Log.e(Utils.MY_LOG_TAG, "Transitions could not be unregistered $it")
}
}
#SuppressLint("MissingPermission")
internal fun enableActivityTransitions(mainActivity: MainActivity) {
Log.d(Utils.MY_LOG_TAG, "enableActivityTransitions()")
val request = ActivityTransitionRequest(activityTransitionList)
ActivityRecognition.getClient(mainActivity)
.requestActivityTransitionUpdates(request, mActivityTransitionPendingIntent)
.addOnSuccessListener {
Utils.makeSnackBar("Transitions Api was successfully registered", mainActivity)
activityTrackingOn = true
}
.addOnFailureListener {
Utils.makeSnackBar("Transitions Api could NOT be registered", mainActivity)
Log.e(Utils.MY_LOG_TAG, "Transitions Api could NOT be registered. $it")
}
}
}
Boradcast receiver
registerd in MainActivity's onStart()
unregisterd in MainActivity's onStop()
class ActivityTransitionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val mainActivity = context as MainActivity
val findViewById = mainActivity.findViewById<TextView>(R.id.txt_activity)
val now = Calendar.getInstance().time.toString()
val currentText = findViewById.text
if (currentText.isEmpty()) {
findViewById.text = "1##$now"
} else {
val split = currentText.split("##")
val num = split[0].toInt() + 1
findViewById.text = "$num##$now"
}
if (ActivityRecognitionResult.hasResult(intent)) {
Log.d(Utils.MY_LOG_TAG, "RECOGNITION called")
}
if (ActivityTransitionResult.hasResult(intent)) {
Log.d(Utils.MY_LOG_TAG, "TRANSITION called")
val result = ActivityTransitionResult.extractResult(intent!!)
for (event in result!!.transitionEvents) {
val activityType = event.activityType
val transitionType = event.transitionType
val elapsedRealTimeNanos = event.elapsedRealTimeNanos
val findViewById1 = mainActivity.findViewById<TextView>(R.id.txt_confidence)
findViewById1.text ="${findViewById1.text} + $activityType + $transitionType"
}
}
}
}
With this code, the Pixel 3a is able to detect my activities as soon as I call the onClickEnableOrDisableActivityRecognition() entrypoint. On the Samsung devices however, nothing happens, the requestActivityTransitionUpdates() succeeds, though the broadcast receiver ActivityTransitionReceiver is never reached. Do you guys have any idea why I expereience this behaviour? Maybe you experienced similar behaviour with a BroadcastReceiver and were able to fix it?
On a short side note: I also tested if the ActivityRecognition API is available on the Samsung devices using code which is equivalent as described in the docs https://developers.google.com/android/guides/api-client#check-api-availability which succeeded.
If something is unclear, do not hesitate to ask for clarification. Thanks in advance!
I'm trying to implement geofencing as mentioned here: https://developer.android.com/training/location/geofencing
I get my BroadcastReceiver's onReceive called, but then a weird thing happens, geofencingEvent.geofenceTransition is always -1, instead of Geofence.GEOFENCE_TRANSITION_ENTER.
In any given time i have only 1 geofence. Tested on several devices and emulators. On real devices used Lockito app to simulate movement, on emulators used it's own control panel to change location.
CODE:
RECEIVER:
class GeofenceBroadcastReceiver : BroadcastReceiver(),KoinComponent {
val navInManager:NavInManager = get()
override fun onReceive(context: Context, intent: Intent) {
Timber.d("***** GeofenceBroadcastReceiver onReceive")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes
.getStatusCodeString(geofencingEvent.errorCode)
Timber.e("***** error $errorMessage")
return
}
val geofenceTransition = geofencingEvent.geofenceTransition
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
Timber.i("***** ENTER triggered id ${triggeringGeofences[0].requestId}")
} else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
val triggeringGeofences = geofencingEvent.triggeringGeofences
Timber.i("***** EXIT triggered id ${triggeringGeofences[0].requestId}")
} else {
Timber.e("***** invalid_type $geofenceTransition")
}
}
}
CREATING THE GEOFENCE:
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(App.instance, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(App.instance, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
private fun getGeofencingRequest(geofence: Geofence): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofence(geofence)
}.build()
}
fun setGeofenceForPoint(stopPoint: StopPoint){
val sessionId = "DELIVERY"
Timber.d("***** before Geofence $sessionId")
val geofence =
Geofence.Builder()
.setRequestId(sessionId)
.setCircularRegion(
stopPoint.location.latitude,
stopPoint.location.longitude,
1000F
)
.setExpirationDuration(1800000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build()
if (ActivityCompat.checkSelfPermission(
GetPackageCourierApp.instance,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
geofencingClient?.addGeofences(getGeofencingRequest(geofence), geofencePendingIntent)?.run {
addOnSuccessListener {
Timber.d("***** Geofence(s) added")
}
addOnFailureListener {
Timber.e("***** Failed to add geofence(s)")
}
}
}
}
So I created a simple Worker class. I would like to start this worker as onTimeWork and Periodic Work as well.
Before WorkManager I used Android Job, and inside the Job there is a dedicated method to decide whetherthe current job is periodic: params.isPeriodic
Is there any way to check this in Worker class inside doWork method?
**Worker:**
override fun doWork(): Result {
var workResult = Result.success()
val isPeriodic = false
if (isPeriodic) {
...
}
launch {
}
return workResult
}
**Schedules:**
un schedulePeriodicAsync(context: Context) {
val constraint =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build()
val newMessageWorker =
PeriodicWorkRequest.Builder(
NewMessageWorker::class.java,5,
TimeUnit.MINUTES)
.setConstraints(constraint)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
DailySyncWorker.TAG,
ExistingPeriodicWorkPolicy.REPLACE,newMessageWorker)
}
fun scheduleNowAsync(context: Context, workCallback: JobCallback? = null) {
jobCallback = workCallback
val constraint =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build()
val newMessageWorker =
OneTimeWorkRequest.Builder(NewMessageWorker::class.java)
.setConstraints(constraint)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
TAG,
ExistingWorkPolicy.REPLACE,newMessageWorker)
}
I am using altbeacon library for contact tracing. But i am not able to find/trace my device when it comes near my other scanning phone. I have two mobiles basically, one i am using for scanning and other as beacon transmitter. I am able to transmit as beacon from my phone number 2. I tested also in Locate Beacon app. It showed my phone in that. But when i am testing on my phone number 1, its not working. I am not able to see any beacon on any logs even though my didDetermineStateForRegion and onBeaconServiceConnect is being called.
Here is my application class below:
package com.example.mybeaconprojectaye
import android.app.*
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.RemoteException
import android.util.Log
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.altbeacon.beacon.*
import org.altbeacon.beacon.startup.BootstrapNotifier
import org.altbeacon.beacon.startup.RegionBootstrap
import java.util.*
class MyApplication : Application(), BootstrapNotifier, BeaconConsumer {
val CHANNEL_ID = "myproximityservice"
val CHANNEL_NAME = "My Proximity Service Channel"
val backgroundBetweenScanPeriod = 6200L
val backgroundScanPeriod = 3000L
val TAG: String = "xoxo"
val REGIONID = "rangeid"
val uuidString: String= "id1"
lateinit var beaconManager: BeaconManager
private var regionBootstrap: RegionBootstrap? = null
override fun onCreate() {
super.onCreate()
beaconManager = BeaconManager.getInstanceForApplication(this)
setupBeaconScanning()
beaconManager.bind(this)
}
override fun onBeaconServiceConnect() {
Log.e(TAG, "Service connected ")
val rangeNotifier = RangeNotifier { beacons, region ->
if (beacons.size > 0) {
Log.e(TAG, "found new beacons " + beacons.size)
for (beacon: Beacon in beacons){
Log.e(TAG,"New Beacon before condition check=${beacon.id2}-${beacon.id3}-${beacon.id1}")
GlobalScope.launch {
try {
val deviceUUID: String = beacon.id1.toString()
Log.e(TAG, " before condition check=${deviceUUID}")
Log.e(
TAG,
"New Beacon=${beacon.id2}/${beacon.id3}/${beacon.id1}"
)
Log.e("xoxo","${beacon.id2}/${beacon.id3}/${beacon.id1} + "+ beacon.distance.toLong())
if (beacon.distance.toInt() < 2) {
/* val intentNotification = Intent(this#BeaconApp, HomeActivity::class.java)
intentNotification.putExtra(Constants.DeviceConstants.IS_VIBRATOR, true)
intentNotification.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intentNotification)*/
//sendSafetyNotification()
}
}catch (ex: Exception){
Log.e(TAG, " EXCEPTION: "+ex.toString())
}
}
}
// sendBroadcast(Intent(NEW_DEVICE_ACTION))
}
}
try {
beaconManager.startRangingBeaconsInRegion(
Region(
REGIONID,
null,
null,
null
)
)
beaconManager.addRangeNotifier(rangeNotifier)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun didDetermineStateForRegion(state: Int, p1: Region?) {
Log.e("xoxo", "didDetermineStateForRegion state: "+state )
}
override fun didEnterRegion(p0: Region?) {
Log.e("xoxo", "i just saw a beacon")
}
override fun didExitRegion(p0: Region?) {
}
fun setupBeaconScanning() {
beaconManager.beaconParsers.clear()
val altbeaconParser =
BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")
altbeaconParser.setHardwareAssistManufacturerCodes(intArrayOf(0x0118))
beaconManager.beaconParsers
.add(altbeaconParser)
val iBeaconParser =
BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
iBeaconParser.setHardwareAssistManufacturerCodes(intArrayOf(0x004c))
beaconManager.beaconParsers
.add(iBeaconParser)
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.URI_BEACON_LAYOUT))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_TLM_LAYOUT))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_UID_LAYOUT))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_URL_LAYOUT))
/* beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("x,s:0-1=feaa,m:2-2=20,d:3-3,d:4-5,d:6-7,d:8-11,d:12-15"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"))
beaconManager.beaconParsers
.add(BeaconParser().setBeaconLayout("m:0-3=4c000215,i:4-19,i:20-21,i:22-23,p:24-24"))
*/
BeaconManager.setDebug(false)
val builder = Notification.Builder(this)
builder.setSmallIcon(R.drawable.ic_launcher_background)
builder.setContentTitle("Proximity Service Running")
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
)
builder.setContentIntent(pendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "Used for scanning near by device"
val notificationManager = getSystemService(
Context.NOTIFICATION_SERVICE
) as NotificationManager
notificationManager.createNotificationChannel(channel)
builder.setChannelId(channel.id)
}
beaconManager.enableForegroundServiceScanning(builder.build(), 456)
// For the above foreground scanning service to be useful, you need to disable
// JobScheduler-based scans (used on Android 8+) and set a fast background scan
// cycle that would otherwise be disallowed by the operating system.
beaconManager.setEnableScheduledScanJobs(false)
beaconManager.backgroundBetweenScanPeriod = backgroundBetweenScanPeriod
beaconManager.backgroundScanPeriod = backgroundScanPeriod
Log.d(TAG, "setting up background monitoring for beacons and power saving")
// wake up the app when a beacon is seen
// wake up the app when a beacon is seentitle getting
val region = Region(
REGIONID,
null, null, null
)
regionBootstrap = RegionBootstrap(this, region)
}
fun startAdvertising(listener: AdvertiseListener):Boolean {
val result = BeaconTransmitter.checkTransmissionSupported(this)
Log.e("xoxo", "BLE TRANSMITTER STATUS " +(result== BeaconTransmitter.SUPPORTED).toString())
if (BeaconTransmitter.SUPPORTED != result)
return false
val beacon = Beacon.Builder()
.setId1("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6")
.setId2("1")
.setId3("2")
.setManufacturer(0x0118) // Radius Networks. Change this for other beacon layouts
.setTxPower(-59)
.setDataFields(Arrays.asList(*arrayOf(0L))) // Remove this for beacon layouts without d: fields
.build()
// Change the layout below for other beacon types
val beaconParser = BeaconParser()
.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25")
val beaconTransmitter =
BeaconTransmitter(applicationContext, beaconParser)
beaconTransmitter.startAdvertising(beacon, object : AdvertiseCallback() {
override fun onStartFailure(errorCode: Int) {
Log.e(TAG, "Advertisement start failed with code: $errorCode")
listener.onAdvertiseStatus(false)
}
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
Log.e(TAG, "Advertisement start succeeded. uuid"+uuidString)
listener.onAdvertiseStatus(true)
}
})
return true
}
interface AdvertiseListener{
fun onAdvertiseStatus(success:Boolean)
}
}
and my MainActivity:
package com.example.mybeaconprojectaye
import android.Manifest
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*
import org.altbeacon.beacon.BeaconTransmitter
class MainActivity : AppCompatActivity() {
companion object {
private const val PERMISSION_REQUEST_FINE_LOCATION = 1
private const val PERMISSION_REQUEST_BACKGROUND_LOCATION = 2
}
val TAG = "xoxo"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var result : Int = BeaconTransmitter.checkTransmissionSupported(this#MainActivity)
Log.e("xoxo", "result: "+result)
btn.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
requestPerms()
}
})
}
fun requestPerms() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED
) {
val builder =
AlertDialog.Builder(this)
builder.setTitle("Location is off")
builder.setMessage("Please allow location permission.")
builder.setPositiveButton(android.R.string.ok, null)
builder.setOnDismissListener {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
PERMISSION_REQUEST_BACKGROUND_LOCATION
)
}
builder.show()
} else startAdvertiseBeacons()
} else startAdvertiseBeacons()
} else {
requestPermissions(
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
PERMISSION_REQUEST_FINE_LOCATION
)
}
} else startAdvertiseBeacons()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
when (requestCode) {
PERMISSION_REQUEST_FINE_LOCATION -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "fine location permission granted")
requestPerms()
} else {
val builder =
AlertDialog.Builder(this)
builder.setTitle("Functionality limited")
builder.setMessage("Since location access has not been granted, this app will not be able to discover devices.")
builder.setPositiveButton(android.R.string.ok, null)
builder.setOnDismissListener { }
builder.show()
}
return
}
PERMISSION_REQUEST_BACKGROUND_LOCATION -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "background location permission granted")
requestPerms()
} else {
val builder =
AlertDialog.Builder(this)
builder.setTitle("Functionality limited")
builder.setMessage("Since background location access has not been granted, this app will not be able to discover devices when in the background.")
builder.setPositiveButton(
android.R.string.ok,
DialogInterface.OnClickListener { dialog, which ->
dialog.cancel()
requestPerms()
})
builder.setOnDismissListener {
requestPerms()
}
builder.show()
}
return
}
}
}
private fun startAdvertiseBeacons() {
(application as MyApplication).startAdvertising(object : MyApplication.AdvertiseListener {
override fun onAdvertiseStatus(success: Boolean) {
}
})
}
}
And here are all the logs that are being printed:
2020-06-07 05:20:25.566 23822-23822/? E/libc: Access denied finding property "persist.vendor.sys.activitylog"
2020-06-07 05:20:26.468 23822-23822/com.example.mybeaconprojectaye E/xoxo: result: 0
2020-06-07 05:20:26.810 23822-23822/com.example.mybeaconprojectaye E/xoxo: Service connected
2020-06-07 05:20:26.848 23822-23822/com.example.mybeaconprojectaye E/xoxo: didDetermineStateForRegion state: 0
Can anyone please tell me what i am doing wrong or if some step is missing. I tried looking into other answers on stackoverflow, but this is the only library where i am seeing new classes and interfaces everywhere in all answers. Too much confusion.
P.S.
I am trying to make contact tracing app. Any other library or something you can suggest will also be appreciated.
Try reversing phones 1 and 2 for your tests. If you use BeaconScope to scan, can it see the transmissions of both phones 1&2? If you use BeaconScope to transmit, can your app on either phone 1 or 2 see the beacon tranamission?
If you cannot detect beacon scope on one or both phones, check app permissions to confirm that location permission has been granted to your app. Go to Settings -> Applications -> Your App and check the granted permissions.
Also check that location is enabled globally on the phone and that Bluetooth is on.
I have a custom worker which has the job to fetch some contacts from an API, and then store these contacts in the contact list of the phone. This worker is still freezing the UI. Any help would be appreciated.
This is the Worker's doWork method:
override fun doWork(): Result {
return try {
makeStatusNotification("Saving Contacts", applicationContext, "WorkRequest Starting")
//MAIN METHOD CALL
if (!checkContactListEmpty()) {
removeAllContacts()
}
connectAndGetApiData()
val x = Data.Builder()
Result.success(x.build())
} catch (e: Exception) {
Log.e("NoWork","Unable to save image to Gallery $e")
Result.failure()
}
}
This is the connectAndGetApi method:
fun connectAndGetApiData() {
val BASE_URL = "HTTP_URL"
val res = Data.Builder()
allNames = object : ArrayList<String>(){}
allNumbers = object : ArrayList<String>(){}
Log.d("entering", "connecting")
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.build()
}
val contactsApiService: RestApi? = retrofit?.create(RestApi::class.java)
val call: Call<ContactList>? = contactsApiService?.getAllContactDetails()
call?.enqueue(object : Callback<ContactList> {
override fun onResponse(
call: Call<ContactList>,
response: Response<ContactList>
) {
Log.d("AllContacts",response.message())
val contactList: ContactList = response.body()
if(contactList!= null){
Log.d("AllContacts", contactList.allContacts.size.toString())
for(x in contactList.allContacts){
addContact(x.name,x.phoneNo)
Thread.sleep(50)
}
}else{
Log.d("AllContacts", "contacts null")
}
}
override fun onFailure(
call: Call<ContactList>,
throwable: Throwable
) {
val TAG = "AllContacts"
Log.e(TAG, throwable.toString())
}
})
}
The removeAllContacts() is standard function to remove all contacts
The Api results in a set of around 22000 contacts
Edited:
This is the addContact() method
private fun addContact(name:String?, number: String?) {
var finalName = ""
val finalNumber = number
if(name?.isEmpty() == true){
val tsLong = System.currentTimeMillis() / 1000
val ts = tsLong.toString()
finalName = "NoName$ts"
}else{
finalName = name.toString()
finalName = finalName.substring(1,(finalName.length - 2))
}
val ops = ArrayList<ContentProviderOperation>()
val rawContactID: Int = ops.size
ops.add(
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, finalName)
.build()
)
ops.add(
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactID)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, finalNumber)
.withValue(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
.build()
//
)
try { // Executing all the insert operations as a single database transaction
Log.d(
"AddingContact", "Name: $finalName Number: $finalNumber"
)
applicationContext.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
Thread.sleep(50)
Log.d("Contact Saved","Saved")
} catch (e: RemoteException) {
e.printStackTrace()
} catch (e: OperationApplicationException) {
e.printStackTrace()
}
}
Whilst this Retrofit call is done off of the MainThread, the callback is on the MainThread.
i.e. call?.enqueue(object : Callback<ContactList> {
anything you do in this callback will be on the UI Thread.
And you do this code:
for(x in contactList.allContacts){
addContact(x.name,x.phoneNo)
Thread.sleep(50)
}
Which is sleeping the UI thread for 50 milliseconds for every contact you have in that list. Which in the comments says has 22,000 items???
You'll need to use a background thread if you want to work with datasets that large.
A quick and dirty solution would be to do this:
Use Coroutines:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
Change Retrofit to use Coroutines:
interface ContactsApiService {
#GET("whateverYourEndPointIs")
suspend fun getAllApiDetails(): Response<ContactList>
}
Run it all on a background thread:
GlobalScope.launch {
val response: Response<ContactList> = contactsApiService?.getAllContactDetails()
Log.d("AllContacts",response.message())
val contactList: ContactList = response.body()
if (contactList!= null) {
Log.d("AllContacts", contactList.allContacts.size.toString())
for(x in contactList.allContacts){
addContact(x.name,x.phoneNo)
delay(50)
}
} else {
Log.d("AllContacts", "contacts null")
}
}