I am developing a application to initiate the Bluetooth device application.
I have a service called MyService.
I have stub MyServiceImpl to handle the AIDL interface which will be called from outside.
I have broadcastreceiver to get the "ACTION_BOND_STATE_CHANGED".
My requirement is whenever device is connected(i.e device?.bondState == BluetoothDevice.BOND_BONDED, I need to send deviceinfo from Broadcast receiver to MyServiceImpl stub.
The Reason - AIDL interface is already handled there, once the aidl is called, new class will be created from there. For that new class MyServiceImpl is needed. In addition, response to that interface call is sent through callbacks is also handled there.
The code is below. Can someone help me to send the device information from Broadcastreceiver to the Service stub Implementation?
class MyService : Service() {
override fun onBind(intent: Intent): IBinder {
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
return super.onUnbind(intent)
}
override fun onCreate() {
binder = MyServiceImpl()
filterNewDevices()
}
private fun filterNewDevices() {
val filter = IntentFilter()
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
registerReceiver(mBroadcastReceiverBond, filter)
}
private val mBroadcastReceiverBond: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
if (device?.bondState == BluetoothDevice.BOND_BONDED) {
//HERE I NEED TO SEND THE device TO
}
if (device?.bondState == BluetoothDevice.BOND_BONDING) {
}
if (device?.bondState == BluetoothDevice.BOND_NONE) {
}
}
}
}
class MyServiceImpl : IBluetoothConnection.Stub(), IBinder {
private lateinit var btConnection: BTConnection
override fun registerUuid(name : String?, uuid : String?, connectionId : Int, btDevice : BluetoothDevice?) {
btConnection = BTConnection(this)
btConnection.initialize(name!!, UUID.fromString(uuid), connectionId, btDevice)
}
}
}
Create a method in MyServiceImpl that takes the device as a parameter. In your BroadcastReceiver call the method on binder to pass the device to the service implementation.
Related
I have a broadcast receiver for wifi scan results as a data source and I'd like to make it in coroutine way. I found an answer for suspend function here:
https://stackoverflow.com/a/53520496/5938671
suspend fun getCurrentScanResult(): List<ScanResult> =
suspendCancellableCoroutine { cont ->
//define broadcast reciever
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {
if (intent.action?.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) == true) {
context.unregisterReceiver(this)
cont.resume(wifiManager.scanResults)
}
}
}
//setup cancellation action on the continuation
cont.invokeOnCancellation {
context.unregisterReceiver(wifiScanReceiver)
}
//register broadcast reciever
context.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
//kick off scanning to eventually receive the broadcast
wifiManager.startScan()
}
This is fine for signle emit, but if I want to get results while scanning is going then I'll get crash because cont.resume() could be called only once. Then I decided to try Flow. And here is my code:
suspend fun getCurrentScanResult(): Flow<List<ScanResult>> =
flow{
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(c: Context, intent: Intent) {
if (intent.action?.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) == true) {
//context.unregisterReceiver(this)
emit(wifiManager.scanResults)
}
}
}
//setup cancellation action on the continuation
//register broadcast reciever
context.registerReceiver(wifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
//kick off scanning to eventually receive the broadcast
wifiManager.startScan()
}
But now Android Stuidio says Suspension functions can be called only within coroutine body for function emit(wifiManager.scanResults) Is there a way to use Flow here?
Please take a look at the callback flow which is specifically designed for this use case. Something like this will do the job:
callbackFlow {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) {
sendBlocking(wifiManager.scanResults) // or non-blocking offer()
}
}
}
context.registerReceiver(receiver, intentFilter)
awaitClose {
context.unregisterReceiver(receiver)
}
}
You also might want to share this flow with e.g. shareIn operator to avoid registering a new receiver for each flow subscriber.
I want to use my BroadcastReceiver as sender of data into my activity. For this reason I'm using LocalBroadcastManager. This manager is used to register and unregister my receiver. Problem is that Context in onReceive method is different than Context in onStart and onStop method.
I need to pass activity context into my BroadcastReceiver or instance of LocalBroadcastManager initialized inside Activity. Because my receiver is not receiving any data.
Maybe it is not fault of this manager context but I don't know why it doesnt work since I implemented this manager.
class GPSReceiver: BroadcastReceiver(){
companion object{
const val GPS_PAYLOAD = "gps_payload"
}
override fun onReceive(context: Context, intent: Intent) {
try {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val int = Intent(GPS_PAYLOAD)
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
int.putExtra(GPS_PAYLOAD, true)
} else {
int.putExtra(GPS_PAYLOAD, false)
}
LocalBroadcastManager.getInstance(context).sendBroadcast(int)
} catch (ex: Exception) {
}
}
}
Registering receiver inside Activity:
private val gpsStatusReceiver = object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
App.log("isGpsEnabled: onReceive")
val gpsStatus = intent?.extras?.getBoolean(GPS_PAYLOAD)
if (gpsStatus != null) {
if (gpsStatus){
App.log("isGpsEnabled: true")
hideGpsSnackbar()
} else {
App.log("isGpsEnabled: false")
showGpsSnackbar()
}
} else {
App.log("isGpsEnabled: null")
}
}
}
override fun onStart() {
super.onStart()
LocalBroadcastManager.getInstance(this).apply {
val filter = IntentFilter()
filter.apply {
addAction("android.location.PROVIDERS_CHANGED")
addAction(GPS_PAYLOAD)
}
registerReceiver(gpsStatusReceiver, filter)
}
}
I have seen your code. So there is not issue with context, but in the approach.
Your are registering your reciever with the same strings in which you are getting you data inside the Reciever.
So Send Your broadcast from Fragment/Activity
Send BroadCast Like
private fun sendSuccessfulCheckoutEvent() {
val intent = Intent("successful_checkout_event")
intent.putExtra("cartID", cartId)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
And Listen it in Activity/Fragment like this
1) Create broadcast Reciever
private val checkoutDoneReciever : BroadcastReceiver = object : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
val cartNumbers = intent.getIntExtra("cartID", 0)
Log.d("receiver", "Got message: $cartNumbers.toString()")
}
}
2) Register it in onCreate()/onStart()
LocalBroadcastManager.getInstance(this).registerReceiver(cartUpdatedReceiver,IntentFilter("successful_checkout_event"))
3) Unregister it in onDestroy()
LocalBroadcastManager.getInstance(this).unregisterReceiver(cartUpdatedReceiver)
I'm building a temporary demo app and I need a service that polls a server every minutes. (I know there are better mechanisms for this). Now I have a class I call APIHandler that both my MainActivity and service should use. I have run into issues providing the service with the instance of my APIHandler class. So what I basically want here is my service to be able to use my APIHandler instance. The APIHandler class cannot be made static as it needs a Volley.newRequestQueue object which needs a context instance.
This is how I start my service from my MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setup()
apiHandler = APIHandler(this) //I neeed this instance...
PollingService.enqueueWork(this, Intent(this, PollingService::class.java))
}
This is my service
class PollingService(private val apiHandler: APIHandler) : JobIntentService() {
private val timer = Timer()
private val tag = "PollingService"
//To be present here!
companion object {
fun enqueueWork(context: Context, work: Intent) {
enqueueWork(context, PollingService::class.java, 1, work)
}
}
override fun onHandleWork(intent: Intent) {
Log.d(tag, "Starting")
timer.scheduleAtFixedRate(timerTask {
run {
Log.d(tag, "Polling...")
apiHandler.getLEDState(1)
apiHandler.getLEDState(2)
}
}, 0, 5000)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
this.timer.cancel()
super.onDestroy()
}
}
If all you need is the context, you can get it in the onHandleWork function, see below (sorry I'm using Java instead of Kotlin):
#Override
protected void onHandleWork(#NonNull Intent intent) {
Context context = getApplicationContext();
// Instantiate your APIHandler with the context here
}
Try copy-pasting this into your project to have AS automatically convert it to Kotlin for you.
I have a service which sends two broadcasts at the same time.
val i = Intent(PlayerService.INTENT_ACTION)
i.putExtra(EVENT_EXTRAS, PlayerEvent.PLAYER_READY.ordinal)
i.putExtra(DURATION_EXTRAS, mp.duration) //some duration
sendBroadcast(i)
val i1 = Intent(PlayerService.INTENT_ACTION)
i1.putExtra(EVENT_EXTRAS, PlayerEvent.ON_SECOND_CHANGED.ordinal)
i1.putExtra(DURATION_EXTRAS, player.duration) //another duration
sendBroadcast(i1)
The action of intents is the same, but the extras is different. Finally, I only get the answer from the second broadcast. Who knows what the cause is?
My Receiver Live Data:
class PlayerLiveEvent(val context: Context) : LiveData<Intent>() {
override fun onActive() {
super.onActive()
context.registerReceiver(receiver, IntentFilter(PlayerService.INTENT_ACTION))
}
override fun onInactive() {
super.onInactive()
context.unregisterReceiver(receiver)
}
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
postValue(intent)
}
}
}
Fragment where I observe these events:
PlayerLiveEvent(activity!!).observe(this, Observer {
it?.apply {
val event = PlayerEvent.values()[getIntExtra(EVENT_EXTRAS, 0)]
when (event) {
PlayerEvent.PLAYER_READY -> {
println("PLAYER_READY")
}
PlayerEvent.ON_SECOND_CHANGED -> {
println("ON_SECOND_CHANGED")
}
else -> println()
}
}
})
Your second onReceive is called before a postValue task from the first onReceive is executed on the main thread and hence the value set the second time is ignored. You can also see this from the implementation of postValue:
...
synchronized (mDataLock) {
// for your second call this will be false as there's a pending value
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
// so this is true and so the method returns prematurely
if (!postTask) {
return;
}
...
Thereof, use setValue because it sets the value immediately and is called from the main thread.
The problem is in LiveData, the events are not being transmitted as needed.
This document explains why postValue posts only once. That is why the solution would be the following:
private val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
value = intent
}
}
I have a service, where I manage music playing. Also I have activity sending intents with user's music. When I open activity, I want to get current status of playing.
I have specific player, what have only two events: playing started and playing ends. So if I use broadcast, I will get only next event.
I save events in variable lastAction when getting it. I can create new command ACTION_SEND_CURRENT_STATE. but it looks not good.
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
null -> {
player?.cancel()
}
ACTION_PLAY -> {
player?.cancel()
player = createPlayer(intent)
player?.start()
}
ACTION_STOP -> {
player?.cancel()
}
}
return START_STICKY
}
override fun onPlayingBegin(p0: player?) {
lastAction = BRODCAST_PLAYING_BEGIN
sendBroadcast(Intent(BRODCAST_PLAYING_BEGIN)
.putExtra(EXTRA_SONG, currentSong)
)
}
How to get current state from service correctly? as state I mean last action.
Use this method
public static boolean isServiceRunning(Context context, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
Hope help you.
As pskink mentioned you need to bind your service to your activity to be able to get information from your service. If your service is working in a remote process then you need to use AIDL to communicate with your service, but I believe that if you do so you're able to find how to do that by yourself.
In your particular case the service communication might look like this(please note that the code may be not completely correct, I wrote it right from my head):
class LocalBinder(val service: MusicService) : Binder
class MusicService : Service() {
var lastAction: String? = null
private set
private val binder: IBinder = LocalBinder(this)
override fun onBind(intent: Intent) = binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
lastAction = intent?.action
when (intent?.action) {
null -> {
player?.cancel()
}
ACTION_PLAY -> {
player?.cancel()
player = createPlayer(intent)
player?.start()
}
ACTION_STOP -> {
player?.cancel()
}
}
return START_STICKY
}
}
class MusicPlayerActivity : Activity() {
private var musicService: MusicService? = null
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = (LocalBinder) service
musicService = binder.service
}
override fun onServiceDisconnected(className: ComponentName) {
musicService = null
}
}
override fun protected onStart() {
super.onStart()
val intent = Intent(this, MusicService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun protected onStop() {
super.onStop()
if (musicService != null) {
unbind(connection)
musicService = null
}
}
fun onClick() {
if (musicService != null) {
musicService.lastAction // do something
}
}
}
No need to worry about the service just use foreground service as used by all music playing applications.