I used the android beacon library to do the following action:
I switched on and off with a fast pace. On and off, on and off, and so on for 8-9 times.
However, the beacon then lost the signal for about 10 seconds and then the signal started to be received again.
Also, I tried an Android API function, "lescan", which resulted in the same situation.
Does anyone know why this happens?
MY testing device is:
HUAWEI P20 Pro 8.1
Samsung S6 7.0
override fun onResume() {
beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(IBEACON_LAYOUT))
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(EDDYSTONE_UID_LAYOUT))
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(EDDYSTONE_URL_LAYOUT))
beaconManager.getBeaconParsers().add(BeaconParser().
setBeaconLayout(EDDYSTONE_TLM_LAYOUT))
beaconManager.bind(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val permissions = ArrayList<String>()
if (PackageManager.PERMISSION_GRANTED != ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)) permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION)
if (permissions.size != 0) {
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 100)
}
}
}
override fun onBeaconServiceConnect() {
beaconManager.addRangeNotifier{ beacons,region ->
Log.d("addRangeNotifier",beacons.size.toString())
}
try {
beaconManager.startRangingBeaconsInRegion(Region("com.gigabyte.testkotlin", null, null, null))
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onPause() {
super.onPause()
beaconManager.unbind(this)
}
It's hard to say exactly what you are witnessing without seeing exact code to reproduce, but turning scanning on and off quickly is not necessarily a problem on all devices.
By default, the Android Beacon Library uses a foreground scan period of 1100 ms and a between scan period of 0ms, so it effectively turns scanning on and off 9 times in just over 10 seconds -- similar to what you describe.
I have never noticed these symptoms in normal use of the library on Samsung devices or the Huawei P9, so something else must be triggering this behaviour in your test case.
EDIT: The posted code indicates that the activity itself is what is started and stopped rapidly, and because it binds and unbinds to the beaconManager as it starts and stops, it also starts and stops the Android service that scans for beacons. These are heavy weight data structures that are not designed to be started and stopped rapidly. Short answer: don't do this. If you really need to start and stop your activity rapidly, bind to the beaconManager outside the activity lifcycle, perhaps only one at app startup in the onCreate method of a custom Android Application class.
Related
I use AltBeacon library for simple ble scanning. Scanning for about 7 or 8 seconds then I stop it. tapping button for rescanning. the problem that I have had from the previous version of this library , when I scan then stop it, and disconnect beacon power and I rescan , rangenotifier or observer(another method to watch beacons) could see disconnected beacon! for first time after disconnected it is happened. after that works correctly and if I do the whole process again it is happens.
in the previous library version I had to bind and unbind each time.(not good approach but I had to do) .but in newer version there are no unbind or bind methods. most of methods and functions are deprecated.
I use scanner in fragment. even it is not matter if switch to another fragment. when I come back to scanning fragment again it finds disconnected beacon for fist time after beacon power disconnected. I'm not sure if this library is suitable for a simple bacon scanning. But it is very powerful and simplified some complex thing.
class ScanningFragment() : androidx.fragment.app.Fragment(){
lateinit var beaconManager:BeaconManager
lateinit var region:Region
val rangeNotifier =object:RangeNotifier{
override fun didRangeBeaconsInRegion(beacons: MutableCollection<Beacon>?, region: Region?) {
Log.d(TAG,"in didRangeBeacon")
if (beacons!!.size > 0) {
Log.d(TAG, "didRangeBeaconsInRegion called count: " + beacons.size + beacons.iterator().next().id1)
val firstBeacon = beacons.iterator().next()
}
override fun onCreate(savedInstanceState: Bundle?)
{
Log.d("lifecycl","it is oncreate ")
super.onCreate(savedInstanceState)
BeaconManager.setDebug(true)
beaconManager=BeaconManager.getInstanceForApplication(requireContext()).apply {
foregroundScanPeriod=7000L
foregroundBetweenScanPeriod=5000L
updateScanPeriods()
beaconParsers.clear()
beaconParsers.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-8,i:4-19,i:20-21,i:22-23,p:24-24"))
region = Region("prefixRegion", Identifier.parse("0x0000000000"), null, null)
}
setupPermissions()
}
fun rangingButtonTapped() {
if (beaconManager.rangedRegions.size == 0) {
beaconManager.addRangeNotifier(rangeNotifier)
beaconManager.startRangingBeacons(region)
binding.insideviewmodel?.isScanning?.value = true
}
else {
beaconManager.stopRangingBeacons(region)
binding.BTNScan.run {
Handler(Looper.getMainLooper()).postDelayed({ stopAnimation() }, 1000)
Handler(Looper.getMainLooper()).postDelayed({ revertAnimation() }, 2000)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.insideviewmodel?.isScanning?.observe(viewLifecycleOwner,Observer{
currentStatusScan->
if(currentStatusScan)
{
object : CountDownTimer(8500, 1000) {
override fun onTick(p0: Long) {
}
override fun onFinish() {
beaconManager.stopRangingBeacons(region)
beaconManager.removeRangeNotifier(rangeNotifier)
binding.insideviewmodel?.isScanning?.value = false
}
}.start()
}
})
}
}
}
it is a debug log for first time scanning.
https://jpst.it/2LVY4
it is a debug log for rescanning after disconnecting beacon power.
https://jpst.it/2LVZs
Profiler:
The second log line "after disconnecting beacon power" shows that the Android OS BLE scanner does indeed deliver an iBeacon detection at 12:53:31:
2022-02-24 12:53:31.117 23528-23528/ D/CycledLeScannerForLollipop: got record ... Processing pdu type FF: 0201041aff4c000215....
The library source code shows that this log line is issued immediately upon a callback from the operating system about a BLE advertisement detection. See here.
Clearly it is not possible for a Bluetooth scanner to detect an advertisement from a powered-off BLE device so there must be an alternate explanation. A few possibilities:
The BLE transmitter is not really powered off at (or slightly before) 12:53:31.117
The detected advertisement comes from a different transmitter
The callback from the Android OS is delayed, perhaps because the main thread on which is delivered was blocked by lots of CPU usage in the app.
Some flaw in the bluetooth stack or UI thread handling for the phone in question is delaying delivery of detections.
In order to figure out the cause I would suggest the following:
To eliminate a bluetooth stack flaw, test the same code on a different Android phone, preferably by a different manufacturer.
To eliminate the possibility of the UI thread being blocked, run this in the Android Studio profiler, or simply cut out as much code as possible that executes before the delay is seen.
To verify the transmitter is really off and that there are no other transmitters around, use a second phone with an off the shelf beacon scanner to monitor what devices are actually transmitting. Only perform your test when you confirm with a second device there are no other visible transmitters.
I've been working on a navigation feature for a maps app which has voice instructions. The problem is that when announcing the instructions, the first 500 milliseconds of the instruction gets cut off. For eg, if the instruction is "In 200m turn right", in the bluetooth earphone it ends up sounding like "200m turn right". Or if the instruction is "Continue for 2 kilometers", then it sounds like "tinue for 2 kilometers".
This is the code I'm using for TTS -
//Initialisation happens only once
var textToSpeechEngine = TextToSpeech(this) { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeechEngine?.language = Locale.ENGLISH
textToSpeechEngine?.setSpeechRate(0.8f)
}
}
...
//When text to speak is ready, invoking the speak method
textToSpeechEngine?.speak(textToSpeak, TextToSpeech.QUEUE_FLUSH, null, "tts1")
Additionally, I'm also using AudioFocusRequest to request and abandon focus so that any music playing in the background lowers its volume while the instruction is being spoken. This is the code I'm using for that -
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
var focusRequest: AudioFocusRequest? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build())
.setAcceptsDelayedFocusGain(false)
.build()
audioManager.requestAudioFocus(focusRequest)
} else {
audioManager.requestAudioFocus(null, AudioManager.STREAM_NOTIFICATION, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
}
textToSpeechEngine?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
fun abandonFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
focusRequest?.let { request -> audioManager.abandonAudioFocusRequest(request) }
} else {
audioManager.abandonAudioFocus(null)
}
}
override fun onStart(utteranceId: String?) {}
override fun onDone(utteranceId: String?) {
abandonFocus()
}
override fun onError(utteranceId: String?) {
abandonFocus()
}
})
Point to note here is that this issue is not happening in all the bluetooth devices I've tested with. The issue happened with 2 Chinese brands bluetooth earphones but is not happening with a bluetooth Sony over-the-head headset. Also, the issue does not happen if music is playing in the background while navigation is going on.
As I understand it, it seems that the cheaper bluetooth earphones seem to keep the "connection alive" only when audio is actively coming through else it stops the connection temporarily in order to save battery I guess? However, when music is playing in the background, the connection is kept alive constantly so the instruction speech does not get cut off.
What can I do to fix this or work around it?
note that requestAudioFocus may take a OnAudioFocusChangeListener as first param, you are passing null in there (also Builder have this param). switching focus may take some (short) time, so I would recomend to fire your TTS when you get this callback fired with AUDIOFOCUS_GAIN
Assuming that you're correct that it is purely hardware at fault (which is what it seems like):
You can use the playSilence() or playSilentUtterance() methods of the TextToSpeech class to play silence for 500ms prior to your main speak() command... which should fool the speakers/headphones.
It may help to also use QUEUE_ADD instead of QUEUE_FLUSH for your main speak() request to make sure that it it attached the the previous (silence) with no gap... and that it doesn't prematurely end the first (silent) utterance.
There are a lot of variables at play here. You could consider:
Can you find an app that uses TextToSpeech which DOES work correctly even on these problematic devices? If so, then the problem must be solvable in code, and maybe you could find the source code for the app you tested and look at how they are setting up the AudioManager.
What I want to achieve is, when user enter geofencing, the beacons foreground service will start to run and after one beacon detected, I will kill this foreground service and start to run it on the background just like the sample code on android-beacon-library-reference library.
private fun monitorBeacons(startForegroundService: Boolean) {
var beaconManager = WolApp.appContext?.beaconManager
if (beaconManager == null) {
WolApp.appContext?.beaconManager = BeaconManager.getInstanceForApplication(WolApp.appContext!!)
beaconManager = WolApp.appContext?.beaconManager
beaconManager?.backgroundMode = true
beaconManager?.beaconParsers?.clear()
beaconManager?.beaconParsers?.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
beaconManager?.removeAllMonitorNotifiers()
}
if (startForegroundService) {
setupForegroundNotificationService(WolApp.appContext!!)
} else {
WolApp.appContext?.regionBootstrap?.disable()
WolApp.appContext?.regionBootstrap = null
try {
WolApp.appContext?.beaconManager?.disableForegroundServiceScanning()
} catch (e: IllegalStateException) {}
if (beaconManager?.scheduledScanJobsEnabled == false) {
beaconManager.setEnableScheduledScanJobs(true)
beaconManager.backgroundBetweenScanPeriod = BeaconManager.DEFAULT_BACKGROUND_BETWEEN_SCAN_PERIOD
beaconManager.backgroundScanPeriod = BeaconManager.DEFAULT_BACKGROUND_SCAN_PERIOD
}
}
if (WolApp.appContext?.regionBootstrap == null) {
WolApp.appContext?.regionBootstrap = RegionBootstrap(WolApp.appContext!!, regions)//regions are some iBeacon regions
}
if (!startForegroundService) {
WolApp.appContext?.backgroundPowerSaver = BackgroundPowerSaver(WolApp.appContext!!)
}
}
For setupForegroundNotificationService method is same with android-beacon-library-reference library.
I'm not quite sure if I'm doing this right or wrong, can anyone help, please?
It is a little bit tricky to switch a foreground service on or off because you are trying to change the behavior of multiple threads of execution that are already running behind the scenes in existing services.
The key thing missing from the code shown is that you must also make sure you have stopped the library from scanning before you can switch. This is complex because it is asynchronous -- it takes time for the scanner to shut down its threads.
If using regionBootstrap, the call to regionBootstrap.disable() does this. (You can also use beaconManager.unbind(...) if not using regionBootstrap). But the problem is those APIs do not give you a callback when the scaning service is fully shut down. And restarting it again before it is shut down can cause problems. I do not have a great suggestion here , other than perhaps using a timer -- say one second between stop and start?
You might also want to look at this discussion of a similar setup:
https://github.com/AltBeacon/android-beacon-library/issues/845
I'm working on an app that runs a background service to range bluetooth beacons in intervals.
I start a ForegroundService with a timer to start ranging beacons for 10 seconds every minute, with an interval of 200 millis, calculates the strongest beacon and submits it to the backend API.
This works neatly while the app is in foreground, and, when the screen is off, as long as i'm hooked up using adb logcat. As soon as I take it off, nothing gets submitted to the servers anymore, meaning that no beacons are being ranged anymore.
Here are the relevant code pieces, I hope I didn't simplify too much:
class BeaconService : Service(), BeaconConsumer {
private var beaconManager: BeaconManager? = null
private var rangingTimer = Timer("rangingTimer", true)
private val region = Region("com.beacon.test", Identifier.parse("f7826da6-4fa2-4e98-8024-bc5b71e0893e"), null, null)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return Service.START_STICKY
}
override fun onCreate() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = MyNotificationManager.getInstance()
val notification = notificationManager.buildBeaconServiceNotification(this, "iBeacon service", null)
startForeground(NOTIFICATION_ID, notification)
}
initBeaconManager()
}
private fun initBeaconManager() {
BeaconManager.setDebug(true)
beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager?.foregroundScanPeriod = 200L
beaconManager?.beaconParsers?.add(BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"))
beaconManager?.bind(this)
}
override fun onBeaconServiceConnect() {
beaconManager?.addRangeNotifier { beacons, _ ->
if (beacons.isNotEmpty()) {
//code add ranged beacons to list
}
}
startRanging()
}
private fun startRanging() {
//code to reset the list of ranged beacons
beaconManager?.startRangingBeaconsInRegion(region)
rangingTimer.schedule(10000L) {
stopRanging(50000L)
}
}
private fun stopRanging(restartRangingAfter: Long? = null) {
beaconManager?.stopRangingBeaconsInRegion(region)
//code calcuate the strongest beacon and submit to server
if (restartRangingAfter != null) {
rangingTimer.schedule(restartRangingAfter) {
startRanging()
}
}
}
}
On OS Versions 8+, Android limits background processing unless it is part of a foreground service or a job initiated by the JobScheduler. As a result of this limitation, the Android Beacon Library will by default use the JobScheduler on Android 8+. In the foreground, an "immediate" ScanJob will run constantly to do scans. In the background (meaning when no activities are visible with the screen unlocked), Android does not allow this. A job may be scheduled at most once every ~15 minutes. This is why you see scans stop.
It does not matter that you have your own foreground service. Android still enforces these limits on any background processing performed outside that foreground service.
Two alternatives:
Live with the job limitations (scan once every 15 minutes). Use BackgroundPowerSaver to auto switch between foreground/background mode and set beaconManager.setBackgroundScanPeriod(5000) (for a 5 second scan every 15 minutes.) For clarity, you should also set beaconManager.setBackgroundBetweenScanPeriod(15*60*1000) (15 minutes), although you can set a lower value that will be disallowed by the OS on Android 8+.
Set up the library to scan with its own Foreground Service (yes as second foreground service) as described here. You can then stop using your own foreground service, or keep it. If you keep it, you will see two notification icons about the two foreground services running. It is possible to combine those two notifications if you want to keep two foreground services and show just one notification.
I'm trying to get my app working on Android 9. The following code works fine up to Android 8, but for some reason, the JobService does not get rescheduled on android 9. It gets scheduled the first time but does not get rescheduled according to the set periodic.
class RetrieveJobService : JobService() {
override fun onStartJob(params: JobParameters): Boolean {
doBackgroundWork(params)
return true
}
private fun doBackgroundWork(params: JobParameters) {
Thread {
try {
doRetrieveBackgroundStuff(this)
jobFinished(params, false)
} catch (e: Exception) {
jobFinished(params, false)
}
}.start()
}
override fun onStopJob(params: JobParameters): Boolean {
return false
}
}
And here my JobInfo.Builder
val builder = JobInfo.Builder(jobID, componentName)
.setPersisted(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setPeriodic(millis, 15 * 60 * 1000) //15 min
} else {
builder.setPeriodic(millis)
}
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
val scheduler = context.getSystemService(JOB_SCHEDULER_SERVICE) as
JobScheduler
val resultCode = scheduler.schedule(builder.build())
Any ideas?
EDIT: Just to be clear, this code worked fine on Android 8 and below and does also work on the Android Studio emulator running Android 9. As far as I can test, it does not work on any physic device running Android 9.
If you go through THE LINK, you will find:
Unfortunately, some devices implement killing the app from the recents menu as a force stop. Stock Android does not do this. When an app is force stopped, it cannot execute jobs, receive alarms or broadcasts, etc. So unfortunately, it's infeasible for us to address it - the problem lies in the OS and there is no workaround.
It is a known issue. To save battery, many manufacturers force close the app, thus cancelling all the period tasks, alarms and broadcast recievers etc. Major manufacturers being OnePlus(you have option to toogle),Redmi,Vivo,Oppo,Huwaei.
UPDATE
Each of these devices have AutoStartManagers/AutoLaunch/StartManager type of optimization managers. Which prevent the background activities to start again. You will have to manually ask the user to whitelist your application, so that app can auto start its background processess. Follow THIS and THIS link, for more info.
The methods to add to whitelist for different manufactures are give in this stackoverflow answer. Even after adding to whitelist, your app might not work because of DOZE Mode, for that you will have to ignore battery otimizations
Also in case you might be wondering, apps like Gmail/Hangout/WhatsApp/Slack/LinkedIn etc are already whitelisted by these AutoStart Managers. Hence, there is no effect on their background processes. You always receive timely updates & notifications.