I'm trying to implement CompanionDeviceService in order to interact with our BLE device. According to the documentation
System will keep this service bound whenever an associated device is nearby, ensuring app stays alive
But that's not what I'm seeing
17:47:48.563 MyCompanionDeviceService: onDeviceAppeared FF:FF:6D:10:F1:16
17:47:48.565 MyCompanionDeviceService: onUnbind
17:47:48.568 MyCompanionDeviceService: onDestroy
Around 1 minute later, onDeviceAppeared is invoked again, with the same result.
FF:FF:6D:10:F1:16 is not bonded. createBond is never invoked on BleDevice. I haven't found whether this is relevant or not.
I'm running on a Pixel 4a on latest available Android 12 version
Edit: Adding more code for reference
Manifest
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" />
<service
android:name="com.mycompany.MyCompanionDeviceService"
android:exported="true"
android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
<intent-filter>
<action android:name="android.companion.CompanionDeviceService" />
</intent-filter>
</service>
The startObservingDevicePresence succeeds, otherwise my service wouldn't be called at all
And there's nothing relevant on the service
#RequiresApi(Build.VERSION_CODES.S)
internal class MyCompanionDeviceService : CompanionDeviceService() {
override fun onCreate() {
appComponent.inject(this)
super.onCreate()
}
override fun onUnbind(intent: Intent?): Boolean {
Timber.d("onUnbind")
return super.onUnbind(intent)
}
override fun onDeviceAppeared(address: String) {
Timber.d("onDeviceAppeared $address")
}
override fun onDeviceDisappeared(address: String) {
Timber.tag("companionservice").d("onDeviceDisappeared $address")
}
override fun onDestroy() {
super.onDestroy()
Timber.d("onDestroy")
}
}
MyCompanionDeviceService: onUnbind means the system unbinds your MyCompanionDeviceService. unbind here is not related to your device binding.
Related
In my case , I use std::this_thread::sleep_for(10ms) to sleep 10ms.
If the Android app is in foreground, will sleep about 10ms.
But if app in background ,it will sleep about 50ms~.
I also tried usleep(),nanosleep(),std::condition::wait_for(), and also java Thread.sleep(), NONE of them works fine in this case.
But this code works fine always:
int64_t startTimeStemp = now_ms();
while(true) {
int64_t nowTimeStemp = now_ms();
if(now - start > 10) {
break;
}
}
How can I solve this problem? Thanks.
So there is a solution. Its called foreground notification service. First I was doing wake lock but this is insufficient:
Now my code looks like this (sorry I use a lot of custom extensions)
app.startService<LooperPlayNotificationService>()
wakeLock.acquire()
So I keep app alive and working fine in background.
class LooperPlayNotificationService : Service() {
companion object {
val NOTIFICATIONS_CHANNEL = "${app.packageName} notifications"
}
override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
start()
return START_STICKY
}
override fun onCreate() {
super.onCreate()
start()
}
private val playButtonActionId = "play_button_action"
private lateinit var playButtonAction: BroadcastReceiver
private var started = false
// https://stackoverflow.com/questions/6619143/start-sticky-foreground-android-service-goes-away-without-notice
// There's a bug in 2.3 (not sure if it was fixed yet) where when a Service is killed and restarted,
// its onStartCommand() will NOT be called again. Instead you're going to have to do any setting up in onCreate()
private fun start() {
if (started) return
started = true
startForeground(647823876, createNotification())
playButtonAction = register(playButtonActionId) {
main.looper?.player?.asStarted { it.stop() }
}
}
override fun onDestroy() {
super.onDestroy()
unregister(this.playButtonAction)
}
private fun createNotification() = Builder(this, NOTIFICATIONS_CHANNEL)
.setSmallIcon(outline_all_inclusive_24)
.setContentIntent(getActivity(this, 0, Intent<InstrumentsActivity>(this),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
.setPriority(PRIORITY_DEFAULT)
.setAutoCancel(false).setOngoing(true)
.addAction(ic_stop_circle_black_24dp, "Stop",
getBroadcast(this, 0, Intent(playButtonActionId),
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
.setContentText(getString(R.string.app_name))
.setContentText(main.looper?.preset?.item?.value?.title?.value).build()
}
this is basically just a service, it has to be defined in manifest:
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".model.mode.looper.player.state.LooperPlayNotificationService"
android:enabled="true"
android:exported="true" />
And so on, there is bunch of examples about this matter, but overall it was not so trivial to implement due to various details you can see in code I posted.
So I have a connected Wear Emulator and a android phone to test.
Sending data maps from phone to wearable works fine.
Now I want to send a message from the wearable to the phone.
I've tried this with AsyncTask and without it. Messages are being successfully sent in both cases, but the message never reaches my phone.
My application IDs are the same.
Here's my code:
//Wear
//Try with Async
inner class requestTokenTask : AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
Wearable.getMessageClient(this).sendMessage(_connectedNode.toString(), "/requesttoken", null)
return null
}
override fun onPostExecute(aVoid: Void?) {
super.onPostExecute(aVoid)
Log.d(TAG, "Message sent: $aVoid")
}
}
//Try without async
fun requestToken() {
if(_connectedNode?.id != null){
val sendTask: Task<*> = Wearable.getMessageClient(this).sendMessage(
_connectedNode!!.id!!,
"/requesttoken",
null
).apply {
addOnSuccessListener {
Log.d(TAG, "Message sent: $it")
}
addOnFailureListener {
Log.d(TAG, "Message NOT sent, error: $it")
}
}
}
}
Handheld code:
public override fun onResume() {
super.onResume()
Wearable.getDataClient(this).addListener(this)
Wearable.getMessageClient(this).addListener(this)
Wearable.getCapabilityClient(this)
.addListener(this, Uri.parse("wear://"), CapabilityClient.FILTER_REACHABLE)
}
override fun onMessageReceived(messageEvent: MessageEvent) {
Log.d(TAG, "onMessageReceived()")
//Receive the message from wear
if (messageEvent.path.equals("/requesttoken")) {
//Do stuff
}
}
Manifest part:
<activity
android:name=".wear.WearableActivity"
android:theme="#style/Theme.Transparent">
<intent-filter>
<action
android:name="com.google.android.gms.wearable.BIND_LISTENER"/>
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data android:scheme="wear" android:host="*" android:pathPrefix="/token"/>
</intent-filter>
</activity>
UPDATE: Just found out that "BIND_LISTENER" is deprecated, tried again with removing it and adding "MESSAGE_RECIEVED" instead, but it's still not working.
I tried another way with Broadcast Recievers with this tutorial and the communication now works in both ways
so recently my friends and i thought about creating an app that allows somewhat of realtime communication between the connected devices, without the need of a webserver. More explicitily, it should be an app, where a single device (master/host) create a game/session and multiple devices (slaves/clients) can join. after establishing all necessary connections (4-5 clients), the host should be able to broadcast data to the clients. Hence i researched a bit and if i understand it correctly the best guess for android are either the WiFi direct oder the google nearby connections api.
Q1. Is this the most simple approach to the desired goal ? or is this already too deep?
So i played a bit around with the connections api, i made a simple application and just used the code from the Nearby Documentation. Since im new to Kotlin, it could also be a rather simple mistake, however after a 2 hours, i swapped back to java with the same error. when the clients try to discover the host, they triggered their OnFailureListener. I tried to search for a solution online (including SO), but i could not find any useful information.
Im testing the application on a HTC ONE M8 and a Samsung Galaxy S7. To Ensure the Nearby Connection API features should work I also downloaded 2 example apps and those worked just fine. I tried how these handled the usage of the api but could not find the important part.
Q2. Where do i use the API wrong ? Or is it really just a error in the coding ?
MainActivity.kt
private const val TAG = android.R.attr.packageNames.toString() + "/Filter"
class MainActivity : AppCompatActivity() {
private lateinit var connectionClient : ConnectionsClient
private val payloadCallback = object : PayloadCallback() {
override fun onPayloadReceived(p0: String, p1: Payload) {
Toast.makeText(applicationContext, "Payload Received", Toast.LENGTH_SHORT).show()
}
override fun onPayloadTransferUpdate(p0: String, p1: PayloadTransferUpdate) {
Toast.makeText(applicationContext, "Payload Transfer Update", Toast.LENGTH_SHORT).show()
}
}
private val connPoint = object : ConnectionLifecycleCallback() {
override fun onConnectionInitiated(p0: String, p1: ConnectionInfo) {
connectionClient.acceptConnection(p0, payloadCallback)
Log.i(TAG, "OnConnectionInitiated")
}
override fun onConnectionResult(p0: String, p1: ConnectionResolution) {
when(p1.status.statusCode){
ConnectionsStatusCodes.STATUS_OK -> Log.i(TAG, "ConnectionsStatusCodes STATUS_OK")
ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED -> Log.i(TAG, "ConnectionsStatusCodes STATUS_CONNECTION_REJECTED")
ConnectionsStatusCodes.STATUS_ERROR -> Log.i(TAG, "ConnectionsStatusCodes STATUS_ERROR")
else -> Log.i(TAG, "ConnectionsStatusCodes STATUS_UNKNOWN")
}
}
override fun onDisconnected(p0: String) {
Log.i(TAG, "onDisconnected $p0")
}
}
private val endPoint = object : EndpointDiscoveryCallback() {
override fun onEndpointFound(p0: String, p1: DiscoveredEndpointInfo) {
Log.i(TAG, "onEndpointFound ID: $p0 Name: ${p1.endpointName} ")
connectionClient.requestConnection(p1.endpointName, p0, connPoint)
.addOnSuccessListener {
Log.i(TAG, "OnSuccessListener requestConnection")
}
.addOnFailureListener {
Log.i(TAG, "OnFailureListener requestConnection")
}
}
override fun onEndpointLost(p0: String) {
Log.i(TAG, "$p0 disconnected")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
connectionClient = Nearby.getConnectionsClient(this.applicationContext)
//Toast.makeText(applicationContext, connectionClient.instanceId, Toast.LENGTH_SHORT).show()
setButtonOnClick()
}
override fun onStop() {
connectionClient.stopAllEndpoints()
connectionClient.stopAdvertising()
connectionClient.stopDiscovery()
super.onStop()
}
private fun setButtonOnClick(){
val create = findViewById<Button>(R.id.create_btn)
val join = findViewById<Button>(R.id.join_btn)
create.setOnClickListener{ _ -> CreateGroup()}
join.setOnClickListener{ _ -> JoinGroup()}
Log.i(TAG, "On Click Listener set")
}
private fun CreateGroup(){
Log.i(TAG, "Starting Advertising")
connectionClient
.startAdvertising(android.os.Build.MODEL,
packageName.toString(),
connPoint,
AdvertisingOptions.Builder().apply{
setStrategy(Strategy.P2P_CLUSTER)
}.build())
.addOnSuccessListener {
OnSuccessListener<Void> {
Log.i(TAG, "OnSuccessListener CreateGroup() was triggered")
}
}
.addOnFailureListener {
OnFailureListener {
Log.i(TAG, "OnFailureListener CreateGroup() was triggered")
}
}
}
private fun JoinGroup(){
Log.i(TAG, "Starting Discovering")
connectionClient.startDiscovery(packageName.toString(),
endPoint,
DiscoveryOptions.Builder().apply{
setStrategy(Strategy.P2P_CLUSTER)
}.build())
.addOnSuccessListener {
OnSuccessListener<Void> {
Log.i(TAG, "OnSuccessListener JoinGroup() was triggered")
}
}
.addOnFailureListener {
OnFailureListener {
Log.i(TAG, "OnSuccessListener JoinGroup() was triggered")
}
}
}
}
Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.testapplication">
<!-- Required for Nearby Connections -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Optional: only required for FILE payloads -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Okay, after figuring out how to correctly setup the Listeners using Kotlin i found out that i got the exception of a missing permission, 01-20 21:11:14.269 1058-1058/com.example.testapplication I/16843649/Filter: 8034: MISSING_PERMISSION_ACCESS_COARSE_LOCATION, which i thought was strange since its in the manifest. However i went to the normal app settings and turned on the permissions manually, and it works now.
I want to do some work when user unlock their phone
I define a receiver in AndroidManifest.xml
<receiver
android:enabled="true"
android:name=".service.ScreenReceiver">
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
and Receiver
class ScreenReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
}
private fun checkClearSavedNote(context: Context) {
AppPref.getInstance(context).putString(AppPref.KEY_ID_CURRENT_NOTE, "")
Log.e("Quang", "clear note")
}
}
But it was not called when fired
I've tried using Service and registerBroadcastReceiver inside
and start it when application start
class MyApplication : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
instance = this
MultiDex.install(applicationContext)
try {
startService(Intent(this, NoteService::class.java))
} catch (e: IllegalStateException) {
e.printStackTrace()
}
}
}
but it work only with Android API < 8.0 because background execution limit
You can set your targetSdk < 26 to leverage implicit broadcasts. Otherwise you have to change your design according to latest list of exempted broadcasts.
Maybe stupid question, but I have already spent to many hours on this.
I have my Kotlin listener:
package pl.bmideas.michal.bmnotifier
public class MyNotificationListener : NotificationListenerService() {
private var apiService :BackendApi? = null;
override fun onCreate() {
Log.i("MyNotificationListener" , "Creating NotificationListenerService service")
super.onCreate()
(.........SOMETHING ELSE..............)
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "DESTROING")
(.........SOMETHING ELSE..............)
}
override fun onNotificationRemoved(sbn: StatusBarNotification) {
val sbnInfo = StatusBarNotificationExtended(sbn)
Log.i(TAG, "REMOVED")
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
Log.i(TAG, "RECIVED`")
(.........SOMETHING ELSE..............)
}
companion object {
var TAG = "MyNotificationListener"
}
}
and my config looks looks this:
<service
android:enabled="true"
android:name="pl.bmideas.michal.bmnotifier.MyNotificationListener"
android:label="#string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
I'm not doing anything special in Activity.
Yes - I've checked security option and my app has access to notifications.
Yes - I've tried pointing to service by dot instead of full package
In logcat I can only see:
12-23 12:56:54.989 889-889/? V/NotificationListeners: enabling notification listener for 0:
ComponentInfo{pl.bmideas.michal.bmnotifier/pl.bmideas.michal.bmnotifier.MyNotificationListener}
I cant get instance unless i will bidn to this service in Activity wchich creates the service but still I get no info in logcat about notifications.
Can you guys help?
Holly....
after rewriting this code to pure Java it works..... but why?