I have implemented SMSBroadcast Receiver functionality in native android (Kotlin) code.
The functionality is like whenever a new text message (SMS) is received I need to call an api (even the app is in background).
When i run as standalone android application its working fine. But how to integrate this with my react-native app?
The below is the code i wrote natively.
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.SmsMessage
import android.util.Log
import android.widget.Toast
import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil
import com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier
class SMSBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.i(
TAG,
"Intent received: " + intent.action
)
if (intent.action === SMS_RECEIVED) {
val bundle = intent.extras
var data = ""
if (bundle != null) {
val pdus =
bundle["pdus"] as Array<Any>?
val messages =
arrayOfNulls<SmsMessage>(pdus!!.size)
for (i in pdus.indices) {
messages[i] =
SmsMessage.createFromPdu(pdus[i] as ByteArray)
}
if (messages.size > -1) {
data = messages[0]!!.messageBody
Log.i(
TAG,
"Message recieved: $data"
)
}
}
Toast.makeText(context,"Message received : $data", Toast.LENGTH_LONG).show()
// var accessToken = AsyncLocalStorageUtil.getItemImpl(ReactDatabaseSupplier.getInstance(context).get(), "");
// if (accessToken != null){
//
// }
if(listener!= null)
{
listener?.onSmsReceive(data)
}
}
}
companion object {
private const val SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
private const val TAG = "SMSBroadcastReceiver"
var listener : OnCustomSmsListener ?= null
}
}
** Package suggestions are also welcome **
Related
I have created an app to record the audio in the android wear but I am not able to transfer that recorded file to mobile phone as there is no dedicated file manager in Samsung galaxy watch 4. So for that I need to upload the the recorded file to the AWS S3 STORAGE.
Here is the code for audio recording.
package com.example.watch
import android.Manifest.permission
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.media.MediaRecorder
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.util.Log
import android.view.WindowManager
import android.widget.Button
import android.widget.Chronometer
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil
import com.amplifyframework.core.Amplify
import com.amplifyframework.storage.StorageException
import com.amplifyframework.storage.result.StorageUploadFileResult
import com.example.watch.databinding.ActivityMicrophoneBinding
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
#Suppress("DEPRECATION")
class Microphone : AppCompatActivity() {
// Initializing all variables..
private var Stop: Button? = null
private var Start: Button? = null
private var statusTV:TextView? = null
private var Chronometer:TextView?=null
var fileName: String? = null
var audioRecorder: MediaRecorder? = null
var audiouri: Uri? = null
var file: ParcelFileDescriptor? = null
lateinit var status:TextView
private var binding: ActivityMicrophoneBinding? = null
private lateinit var chronometer: Chronometer
private var audioFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_microphone)
if (!CheckPermissions())
RequestPermissions()
AmplifyInit().intializeAmplify(this#Microphone)
this.window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
)
supportActionBar!!.hide()
// initialize all variables with their layout items.
Start=binding!!.accelerometerStartButton
Stop=binding!!.accelerometerStopButton
status=binding!!.Status
chronometer=binding!!.chronometer
Start!!.setOnClickListener { // start recording method will
// start the recording of audio.
startRecording()
Start!!.isEnabled=false
status.text="Recording..."
chronometer.base = SystemClock.elapsedRealtime()
chronometer.start()
startService()
}
Stop!!.setOnClickListener { // pause Recording method will
// pause the recording of audio.
pauseRecording()
Start!!.isEnabled=true
status.text="Recording Stopped"
chronometer.stop()
uploadFile()
stopService()
}
}
private fun startRecording() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
val values = ContentValues(4)
values.put(MediaStore.Audio.Media.TITLE, fileName)
values.put(
MediaStore.Audio.Media.DATE_ADDED,
SimpleDateFormat("yyyyMMddHHmmss").format(Date())
)
values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/Recordings/")
audiouri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)
file = contentResolver.openFileDescriptor(audiouri!!, "w")
if (file != null) {
audioRecorder = MediaRecorder()
audioRecorder!!.setAudioSource(MediaRecorder.AudioSource.MIC)
audioRecorder!!.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
audioRecorder!!.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
audioRecorder!!.setOutputFile(file!!.fileDescriptor)
audioRecorder!!.setAudioChannels(1)
audioRecorder!!.prepare()
audioRecorder!!.start()
audioRecorder!!.setOutputFile(getAudioFile().absolutePath)
}
}
else {
RequestPermissions()
}
}
private fun getAudioFile(): File {
if (audioFile == null) {
audioFile = File(getExternalFilesDir(null), "Music/Recordings/")
}
return audioFile!!
}
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray,
) {
// this method is called when user will
// grant the permission for audio recording.
when (requestCode) {
REQUEST_AUDIO_PERMISSION_CODE -> {
if (grantResults.isNotEmpty()) {
val permissionToRecord = grantResults[0] == PackageManager.PERMISSION_GRANTED
val permissionToStore = grantResults[1] == PackageManager.PERMISSION_GRANTED
if (permissionToRecord && permissionToStore) {
Toast.makeText(applicationContext, "Permission Granted", Toast.LENGTH_LONG)
.show()
} else {
Toast.makeText(applicationContext, "Permission Denied", Toast.LENGTH_LONG)
.show()
}
}
}
}
}
private fun CheckPermissions(): Boolean {
// this method is used to check permission
val result = ContextCompat.checkSelfPermission(applicationContext, permission.WRITE_EXTERNAL_STORAGE)
val result1 = ContextCompat.checkSelfPermission(applicationContext, permission.RECORD_AUDIO)
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED
}
private fun RequestPermissions() {
// this method is used to request the
// permission for audio recording and storage.
ActivityCompat.requestPermissions(this,
arrayOf(permission.RECORD_AUDIO, permission.WRITE_EXTERNAL_STORAGE),
REQUEST_AUDIO_PERMISSION_CODE
)
}
private fun pauseRecording() {
Start!!.isEnabled=true
stopService(Intent(this, ForegroundService::class.java))
// below method will stop
// the audio recording.
try {
audioRecorder!!.stop()
} catch (stopException: RuntimeException) {
// handle cleanup here
}
}
fun startService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
serviceIntent.putExtra("inputExtra", "Foreground Service Example in Android")
ContextCompat.startForegroundService(this, serviceIntent)
}
fun stopService() {
val serviceIntent = Intent(this, ForegroundService::class.java)
stopService(serviceIntent)
}
companion object {
// string variable is created for storing a file name
private var mFileName: String? = null
var permissionAccepted = false
var permissions = arrayOf(android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
// constant for storing audio permission
const val REQUEST_AUDIO_PERMISSION_CODE = 1000
}
private fun uploadFile() {
Amplify.Storage.uploadFile(
"Audio/audio.mp3",
getAudioFile(),
{ result: StorageUploadFileResult ->
Log.i(
"MyAmplifyApp",
"Successfully uploaded: " + result.key
)
Toast.makeText(this, "File has Successfully Uploaded:" , Toast.LENGTH_SHORT).show()
}
) { storageFailure: StorageException? ->
Log.e(
"MyAmplifyApp",
"Upload failed",
storageFailure
)
Toast.makeText(this, "Upload failed", Toast.LENGTH_SHORT).show()
}
}
}
Now how can i modify my uploadfile function to upload the fiole to AWS S3, As i am getting this error.
E/amplify:aws-s3-storage:SinglePartUploadWorker: SinglePartUploadWorker failed with exception: kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}#9b17179
I/WM-WorkerWrapper: Worker result RETRY for Work [ id=48cd3651-35e2-456e-9329-81930f5277a5, tags={ UPLOAD, awsS3StoragePlugin, 9, com.amplifyframework.storage.s3.transfer.worker.RouterWorker } ]
I am using dependecies:
implementation 'com.amplifyframework:aws-api:2.1.0'
implementation 'com.amplifyframework:aws-datastore:2.1.0'
implementation 'com.amplifyframework:aws-storage-s3:2.1.0'
implementation 'com.amplifyframework:aws-auth-cognito:2.1.0'
Library:
<uses-library
android:name="com.google.android.wearable"
android:required="true" />
I am trying to send a string message from Android to Arduino with the PN532 module. When I approach the smartphone (with the app open) to the module, the "tap to beam" UI shows up, but after I tap on the screen, the phone tells me to approach the two devices again. After I approach them again (with the "approach the devices again" message still displayed on the screen), nothing happens and arduino prints out "failed".
Here is the Kotlin code:
package com.cerowski.nfcclient
import android.nfc.NdefMessage
import android.nfc.NdefRecord
import android.nfc.NfcAdapter
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager.widget.ViewPager
import com.cerowski.nfcclient.databinding.ActivityMainBinding
import com.cerowski.nfcclient.ui.main.SectionsPagerAdapter
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import java.io.File
import java.io.UnsupportedEncodingException
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {config(); sendid();
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager)
val viewPager: ViewPager = binding.viewPager
viewPager.adapter = sectionsPagerAdapter
val tabs: TabLayout = binding.tabs
tabs.setupWithViewPager(viewPager)
val fab: FloatingActionButton = binding.fab
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
}
fun config(){
val dir = getExternalFilesDir(null);
val file = File(dir,"config");
var msgtext="";
val msg = TextView (this)
if (dir!=null) {
if (!file.exists()) {msgtext = "config not found, attempting to create..."; file.createNewFile(); if(file.exists()) {msgtext = "config created successfully"; file.writeText(idgenerator())} else {msgtext = "problem creating file"}}
else {msgtext = (file.readText())}
} else {msgtext = "app directory not found"}
}
fun idgenerator() : String {
val allowedChars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..16)
.map { allowedChars.random() }
.joinToString("")
}
fun sendid() {
val dir = getExternalFilesDir(null);
val file = File(dir,"config");
//var bytes = file.readBytes();
//var nfcmsg = NdefMessage(bytes);
//var msgtext="";
var nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter != null) {
val msg: String = file.readText().toString()
val languageCode: ByteArray
val msgBytes: ByteArray
try {
languageCode = "en".toByteArray(charset("US-ASCII"))
msgBytes = msg.toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
return
}
val messagePayload = ByteArray(
1 + languageCode.size
+ msgBytes.size
)
messagePayload[0] = 0x02.toByte() // status byte: UTF-8 encoding and
// length of language code is 2
// length of language code is 2
System.arraycopy(
languageCode, 0, messagePayload, 1,
languageCode.size
)
System.arraycopy(
msgBytes, 0, messagePayload, 1 + languageCode.size,
msgBytes.size
)
val message: NdefMessage
val records = arrayOfNulls<NdefRecord>(1)
val textRecord = NdefRecord(
NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_TEXT, byteArrayOf(), messagePayload
)
records[0] = textRecord
message = NdefMessage(records)
nfcAdapter.setNdefPushMessage(message, this);
}
else {Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();}
}
}
And here is the code for my Arduino Uno with the PN532 module (the red one, if you search for photos online):
// Receive a NDEF message from a Peer
// Requires SPI. Tested with Seeed Studio NFC Shield v2
#include "SPI.h"
#include "PN532_SPI.h"
#include "snep.h"
#include "NdefMessage.h"
PN532_SPI pn532spi(SPI, 10);
SNEP nfc(pn532spi);
uint8_t ndefBuf[128];
void setup() {
Serial.begin(9600);
Serial.println("NFC Peer to Peer Example - Receive Message");
}
void loop() {
Serial.println("Waiting for message from Peer");
int msgSize = nfc.read(ndefBuf, sizeof(ndefBuf));
if (msgSize > 0) {
NdefMessage msg = NdefMessage(ndefBuf, msgSize);
msg.print();
Serial.println("\nSuccess");
} else {
Serial.println("Failed");
}
delay(3000);
}
The little switches on the NFC module are in the right positions and the module is connected to the board as it should be (for SPI), So I do not know what is the thing that causes it to fail.
Any help is much appreciated.
First check if it's even a) available, b) enabled and c) register & implement the callbacks. In Java:
MainActivity extends AppCompatActivity implements
NfcAdapter.CreateNdefMessageCallback,
NfcAdapter.OnNdefPushCompleteCallback,
NfcAdapter.CreateBeamUrisCallback {
...
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (!nfcAdapter.isEnabled()) {
startActivity(new Intent(Settings.ACTION_NFC_SETTINGS));
} else if (!nfcAdapter.isNdefPushEnabled()) {
startActivity(new Intent(Settings.ACTION_NFCSHARING_SETTINGS));
} else {
nfcAdapter.setNdefPushMessageCallback(this);
nfcAdapter.setOnNdefPushCompleteCallback(this);
nfcAdapter.setNdefPushMessage(nfcMessage, this);
}
}
Deprecated in Build.VERSION_CODES.Q means still available; maybe <=.
These callbacks might also provide you with more detail, why it even fails.
Else you'll send a NDEF message and it will not know what to do next ...
Alike this you might also be able to produce a proper error message.
Also see: https://developer.android.com/training/beam-files/send-files
I'm working with AWS Amplify + Android Studio. In the backend.kt file, I call UserData.setSignedIn which gives me a Unresolved reference: setSignedIn error. I imported the file where this function is but it still gives me that error. Here are the two files:
package com.wcsng.dlocapp
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.service.autofill.UserData
import android.util.Log
import com.amplifyframework.AmplifyException
import com.amplifyframework.api.aws.AWSApiPlugin
import com.amplifyframework.auth.AuthChannelEventName
import com.amplifyframework.auth.AuthException
import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin
import com.amplifyframework.auth.cognito.AWSCognitoAuthSession
import com.amplifyframework.auth.result.AuthSessionResult
import com.amplifyframework.auth.result.AuthSignInResult
import com.amplifyframework.core.Amplify
import com.amplifyframework.core.InitializationStatus
import com.amplifyframework.hub.HubChannel
import com.amplifyframework.hub.HubEvent
import com.wcsng.dlocapp.UserData.setSignedIn
object Backend {
private const val TAG = "Backend"
fun initialize(applicationContext: Context) : Backend {
try {
Amplify.addPlugin(AWSCognitoAuthPlugin())
Amplify.addPlugin(AWSApiPlugin())
Amplify.configure(applicationContext)
Log.i(TAG, "Initialized Amplify")
} catch (e: AmplifyException) {
Log.e(TAG, "Could not initialize Amplify", e)
}
Log.i(TAG, "registering hub event")
// listen to auth event
Amplify.Hub.subscribe(HubChannel.AUTH) { hubEvent: HubEvent<*> ->
when (hubEvent.name) {
InitializationStatus.SUCCEEDED.toString() -> {
Log.i(TAG, "Amplify successfully initialized")
}
InitializationStatus.FAILED.toString() -> {
Log.i(TAG, "Amplify initialization failed")
}
else -> {
when (AuthChannelEventName.valueOf(hubEvent.name)) {
AuthChannelEventName.SIGNED_IN -> {
updateUserData(true)
Log.i(TAG, "HUB : SIGNED_IN")
}
AuthChannelEventName.SIGNED_OUT -> {
updateUserData(false)
Log.i(TAG, "HUB : SIGNED_OUT")
}
else -> Log.i(TAG, """HUB EVENT:${hubEvent.name}""")
}
}
}
}
Log.i(TAG, "retrieving session status")
// is user already authenticated (from a previous execution) ?
Amplify.Auth.fetchAuthSession(
{ result ->
Log.i(TAG, result.toString())
val cognitoAuthSession = result as AWSCognitoAuthSession
// update UI
this.updateUserData(cognitoAuthSession.isSignedIn)
when (cognitoAuthSession.identityId.type) {
AuthSessionResult.Type.SUCCESS -> Log.i(TAG, "IdentityId: " + cognitoAuthSession.identityId.value)
AuthSessionResult.Type.FAILURE -> Log.i(TAG, "IdentityId not present because: " + cognitoAuthSession.identityId.error.toString())
}
},
{ error -> Log.i(TAG, error.toString()) }
)
return this
}
private fun updateUserData(withSignedInStatus : Boolean) {
UserData.setSignedIn(withSignedInStatus) // ERROR IS HERE
}
fun signOut() {
Log.i(TAG, "Initiate Signout Sequence")
Amplify.Auth.signOut(
{ Log.i(TAG, "Signed out!") },
{ error -> Log.e(TAG, error.toString()) }
)
}
fun signIn(callingActivity: Activity) {
Log.i(TAG, "Initiate Signin Sequence")
Amplify.Auth.signInWithWebUI(
callingActivity,
{ result: AuthSignInResult -> Log.i(TAG, result.toString()) },
{ error: AuthException -> Log.e(TAG, error.toString()) }
)
}
// Backend.kt
// pass the data from web redirect to Amplify libs
fun handleWebUISignInResponse(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d(TAG, "received requestCode : $requestCode and resultCode : $resultCode")
if (requestCode == AWSCognitoAuthPlugin.WEB_UI_SIGN_IN_ACTIVITY_CODE) {
Amplify.Auth.handleWebUISignInResponse(data)
}
}
}
package com.wcsng.dlocapp
import android.graphics.Bitmap
import android.location.Location
import android.provider.ContactsContract
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.amplifyframework.datastore.generated.model.LocationData
// a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel)
object UserData {
private const val TAG = "UserData"
//
// observable properties
//
// signed in status
private val _isSignedIn = MutableLiveData<Boolean>(false)
var isSignedIn: LiveData<Boolean> = _isSignedIn
fun setSignedIn(newValue : Boolean) {
// use postvalue() to make the assignation on the main (UI) thread
_isSignedIn.postValue(newValue)
}
// a note
data class Location(val id: String, val name: String, val map: String, var location: String) {
override fun toString(): String = name
// return an API NoteData from this Note object
val data : LocationData?
get() = LocationData.builder()
.name(this.name)
.id(this.id)
.map(this.map)
.location(this.location)
.build()
// static function to create a Note from a NoteData API object
companion object {
fun from(locationData : LocationData) : Location {
val result = Location(locationData.id, locationData.name, locationData.map, locationData.location)
return result
}
}
}
}
Thanks for the help
I'm trying to implement an app using Flutter. The app should be able to send messages from the smartphones to a server using FCM or receive messages from the server via FCM.
I implemented the FCM functionality using the firebase_messaging plugin (https://pub.dev/packages/firebase_messaging). Everything is working fine for downstream messages (server -> device). Now I tried to add upstream messages (device -> server). As far as I know from the docs, the plugin does not support upstream messages yet. So I began to write native code to send the messages to the server and call this code via platform channel functionality (https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-kotlin-tab).
Initial MainActivity.kt:
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
Added the method channel example from Flutter docs. Using the imports specified in the Flutter docs:
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
But unfortunately there occurs a problem. The firebase_messaging plugin requires the mainactivity.kt to extend io.flutter.app.FlutterActivity. Platform channels require the mainactivity.kt to extend io.flutter.embedding.android.FlutterActivity.
Is there any way to use both - firebase_messaging plugin and method channels?
I compared my code to the example project from the firebase_messaging plugin: https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/MainActivity.java
Then I realized that the example uses the io.flutter.embedding.android.FlutterActivity too. After changing the import statements to the io.flutter.embedding... package I removed the GeneratedPluginRegistrant.registerWith(this) as GeneratedPluginRegistrant is an import from io.flutter.plugins.GeneratedPluginRegistrant and does not seem to match the embedding package. Then I checked the fields and functions in io.flutter.embedding.engine.FlutterEngine again and added following line: flutterEngine?.plugins?.add(FirebaseMessagingPlugin())
After this I recompiled the app and at the start it prints out the FCM device ID and the battery status successfully.
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin
class MainActivity : io.flutter.embedding.android.FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
flutterEngine?.plugins?.add(FirebaseMessagingPlugin())
}
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
Hello I'm using RxAndroidBLE to detect a BLE device. On android 6 >= everything seems to work okay but not on a 4.3 device.
My app can only discover the desirable BLE device only once at start. After the device has been discovered no more new discoveries at all until I restart the app. Any advice would be highly appreciated.
Below minimum (not)working code example:
MainActivity
import android.content.Context
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.util.Log
import com.polidea.rxandroidble.RxBleClient
import com.polidea.rxandroidble.exceptions.BleScanException
import com.polidea.rxandroidble.scan.ScanResult
import com.polidea.rxandroidble.scan.ScanSettings
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startLeScan(applicationContext)
}
private var rxBleClient: RxBleClient? = null
private var scanSubscription: Subscription? = null
private var handler: Handler? = null
private var timer: Timer? = null
private var timerTask: TimerTask? = null
private var delay: Int = 0
private fun isScanning(): Boolean {
return scanSubscription != null
}
fun startLeScan(context: Context) {
rxBleClient = MyaPP.getRxBleClient(context)
if (isScanning()) {
scanSubscription?.unsubscribe()
} else {
scanSubscription = rxBleClient?.scanBleDevices(
com.polidea.rxandroidble.scan.ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.build())
?.observeOn(AndroidSchedulers.mainThread())
//?.doOnNext(this::newDevicesFound)
?.doOnUnsubscribe(this::clearSubscription)
?.subscribe(this::newDevicesFound, this::onScanFailure)
}
if(handler == null) {
handler = Handler()
timer = Timer(false)
timerTask = object : TimerTask() {
override fun run() {
handler?.post {
if (delay > 7) {
delay = 0
val service = Executors.newSingleThreadExecutor()
service.submit(Runnable {
//startLeScan(context)
})
} else {
delay = delay + 1
}
}
}
}
timer?.scheduleAtFixedRate(timerTask, 0, 300)
}
}
private fun newDevicesFound(devices: ScanResult) {
Log.d("WHYY??", devices.bleDevice.name)
}
fun stopScan() {
scanSubscription?.unsubscribe()
destroy()
}
private fun clearSubscription() {
scanSubscription = null
}
private fun onScanFailure(throwable: Throwable) {
if (throwable is BleScanException) {
handleBleScanException(throwable)
}
}
private fun handleBleScanException(bleScanException: BleScanException) {
val text: String
when (bleScanException.reason) {
BleScanException.BLUETOOTH_NOT_AVAILABLE -> text = "Bluetooth is not available"
BleScanException.BLUETOOTH_DISABLED -> text = "Enable bluetooth and try again"
BleScanException.LOCATION_PERMISSION_MISSING -> text = "On Android 6.0 location permission is required. Implement Runtime Permissions"
BleScanException.LOCATION_SERVICES_DISABLED -> text = "Location services needs to be enabled on Android 6.0"
BleScanException.SCAN_FAILED_ALREADY_STARTED -> text = "Scan with the same filters is already started"
BleScanException.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> text = "Failed to register application for bluetooth scan"
BleScanException.SCAN_FAILED_FEATURE_UNSUPPORTED -> text = "Scan with specified parameters is not supported"
BleScanException.SCAN_FAILED_INTERNAL_ERROR -> text = "Scan failed due to internal error"
BleScanException.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> text = "Scan cannot start due to limited hardware resources"
BleScanException.UNDOCUMENTED_SCAN_THROTTLE -> text = String.format(
Locale.getDefault(),
"Android 7+ does not allow more scans. Try in %d seconds",
secondsTill(bleScanException.retryDateSuggestion)
)
BleScanException.UNKNOWN_ERROR_CODE, BleScanException.BLUETOOTH_CANNOT_START -> text = "Unable to start scanning"
else -> text = "Unable to start scanning"
}
Log.w("EXCEPTION", text, bleScanException)
}
private fun secondsTill(retryDateSuggestion: Date?): Long {
if (retryDateSuggestion != null) {
return TimeUnit.MILLISECONDS.toSeconds(retryDateSuggestion.time - System.currentTimeMillis())
}
return 0
}
private fun destroy() {
timer?.cancel()
handler?.removeCallbacks(timerTask)
handler = null
timerTask = null
timer = null
}
}
MyaPP
import android.app.Application
import android.content.Context
import com.polidea.rxandroidble.RxBleClient
import com.polidea.rxandroidble.internal.RxBleLog
class MyaPP: Application() {
private var rxBleClient: RxBleClient? = null
companion object {
fun getRxBleClient(context: Context): RxBleClient? {
val application = context.applicationContext as MyaPP
return application.rxBleClient
}
}
override fun onCreate() {
super.onCreate()
rxBleClient = RxBleClient.create(this)
RxBleClient.setLogLevel(RxBleLog.DEBUG)
}
}
build.gradle
compile "com.polidea.rxandroidble:rxandroidble:1.5.0"
implementation 'io.reactivex:rxandroid:1.2.1'
manifest
<application
android:name=".MyaPP"
Your code looks a lot like the library's sample app (version 1.5.0, branch master-rxjava1). I have checked that recently on Android 4.4.4 which is the oldest I have and it worked fine. There were no API changes between 4.3 and 4.4.
What you may be experiencing is a behaviour specific to your device (feel free to share your phone model) in which it only callbacks for the first time it scans a particular peripheral. There are some threads about this topic already like this one.