I'm a beginner to Android and Kotlin. Im developing an audio player with audio controls in notification. In my Audio activity I have public methods playAudio() and pauseAudio(). I need to call this method from a broadcast receiver as below
class AudioControlsInNotification : BroadcastReceiver() {
var audioMain: Audio? = null
fun setMainActivityHandler(main: Audio) {
audioMain = main
}
override fun onReceive(context: Context?, intent: Intent?) {
if (intent != null) {
when (intent.action) {
"PAUSEAUDIO" -> {
audioMain?.pauseAudio()
}
"PLAYAUDIO" -> {
audioMain?.playAudio()
}
}
}
}
audioMain always returns null.
While clicking Play and Pause button,
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
mBuilder = NotificationCompat.Builder(applicationContext, "MYAUDIOCHANNEL")
contentView = RemoteViews(packageName, R.layout.audio_notification)
val notifyPauseIntent = Intent(this, AudioControlsInNotification::class.java).apply {
action = "PAUSEAUDIO"
}
val notifyPausePendingIntent: PendingIntent = PendingIntent.getBroadcast(this,0,notifyPauseIntent,0)
contentView!!.setOnClickPendingIntent(R.id.notifyPause, notifyPausePendingIntent)
val notifyPlayIntent = Intent(this, AudioControlsInNotification::class.java).apply {
action = "PLAYAUDIO"
}
val notifyPlayPendingIntent: PendingIntent = PendingIntent.getBroadcast(this,0,notifyPlayIntent,0)
contentView!!.setOnClickPendingIntent(R.id.notifyPlay, notifyPlayPendingIntent)
I received intent actions in the AudioControlsInNotification but i can't access the methods of Audio Activity. I tried many answers from stackoverflow but nothing helps me. I need this BroadcastReceiver on for this Audio activity only.
In my Audio activity I have public methods
That can only make sense if they are declared static in your activity class.
If they are not static then hat does not make sense as you have no pointer to your activity as activities cannot be created with the new operater but only using an intent.
Related
I was creating a simple notification app with Alarm Manager. The alarm manager trigger for 1 minute and it should give a notification. but when I tapped on the notification moves to details screen with first content.
It not changing.
Below broadcast receiver calls each 1 minute.
notification changing with different data but not in details screen. that is the problem.
AlarmReceiver
class AlarmReceiver : BroadcastReceiver()
{
override fun onReceive(context: Context?, intent: Intent?)
{
var dbHelper = context?.let { DbHelper(it) }
val question = dbHelper?.getQuestion((0..1280).random())
val i = Intent(context, SecondActivity::class.java)
i.putExtra("LAW", question)
i!!.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(context, 0, i, 0)
val builder = NotificationCompat.Builder(context!!, "foxandroid")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("${question!!.type} ${question!!.code} ${question!!.subcode} ${question!!.shortDesc}")
.setContentText("${question!!.fullDesc}")
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(123, builder.build())
}
}
Below class shows the notification data in detail.
Second Activity
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val law = if (Build.VERSION.SDK_INT >= 33) {
intent.getSerializableExtra("LAW", Question::class.java)
} else {
intent.getSerializableExtra("LAW") as Question
}// as? Question
Log.e("law:", "${law!!.shortDesc}")
if (law != null) {
textTitle.text = "${law.type} ${law.code} ${law.subcode} ${law.shortDesc}"
}
if (law != null) {
textContent.text = law.fullDesc
}
button.setOnClickListener {
val intent= Intent(this,WebActivity::class.java)
intent.putExtra("LAW", "${law?.type} ${law?.code} ${law?.subcode}")
startActivity(intent)
}
}
}
you should use Activity#onNewIntent,
Your Activity was already created, when you start activity again,the Activity may be not run onCreate lifecycle function, and run onNewIntent. OnNewIntent somelike onCreate, they are lifecycle function.
You just try log extra bundle in Intent at OnNewIntent, and you will know.
Broadcast receiver class inside the service
inner class ServiceNotificationReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent!!.action
Util.log("action from foreground services")
when (action) {
FOREGROUND_NEXT -> {
next()
}
FOREGROUND_PREVIOUS -> {
prevoius()
}
FOREGROUND_PLAY_PAUSE -> {
if (exoPlayer.isPlaying) {
pause()
} else {
play()
}
}
FOREGROUND_STOP -> {
stopSelf()
}
}
}
}
I am registering it inside the onCreate() of the service like so
serviceNotificationListener = ServiceNotificationReceiver()
val intentfliter = IntentFilter().apply {
addAction(FOREGROUND_PLAY_PAUSE)
addAction(FOREGROUND_PREVIOUS)
addAction(FOREGROUND_NEXT)
addAction(FOREGROUND_STOP)
}
this.registerReceiver(serviceNotificationListener, intentfliter)
The pending intent
playintent = Intent(this, ServiceNotificationReceiver::class.java).setAction(
FOREGROUND_PLAY_PAUSE
)
playpendingIntent =
PendingIntent.getBroadcast(this, 0, playintent, PendingIntent.FLAG_UPDATE_CURRENT)
I am adding it as an action inside the notification builder like so
addAction(
com.google.android.exoplayer2.R.drawable.exo_icon_previous,
"Previous",
previouspendingIntent
)
However the clicks are not registering inside the service. I cannot add it in the manifest due to some complexity in the app and this is the only way. So what could be the issue. Is it the flags or something else.
You're setting your intent target component to yourpackage.YourService.ServiceNotificationReceiver which is not registered in manifest, system will not be able to resolve it and nothing is executed.
Modify your intent to target only your apps package then your receiver will be able to match it:
playintent = Intent().setPackage(this.packageName).setAction(FOREGROUND_PLAY_PAUSE)
I am coding a simple app that measures all available sensors of the android device (Wifi, BT, etc). One of them is the user activity (via ActivityRecognition API), but I can't make it works properly.
I code a class to do everything related to user activity. I want to get only 4 states and one attribute to store the current one:
var VEHICLE = "vehicle"
var WALKING = "walking"
var STILL = "still"
var UNKNOWN = "unknown"
private var current: String? = null
It also includes a BroadcastReceiver object to handle activity transitions:
private var recognitionHandler = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (ActivityRecognitionResult.hasResult(intent)) {
val result = ActivityRecognitionResult.extractResult(intent)
val activity = result.mostProbableActivity
current = when(activity.type) {
DetectedActivity.IN_VEHICLE,
DetectedActivity.ON_BICYCLE -> VEHICLE
DetectedActivity.WALKING,
DetectedActivity.RUNNING -> WALKING
DetectedActivity.STILL -> STILL
else -> UNKNOWN
}
}
}
}
The class also have two methods to define the intent and request:
private fun createIntent() : PendingIntent {
val intent = Intent(context, recognitionHandler.javaClass)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
context.registerReceiver(recognitionHandler, IntentFilter())
return pendingIntent
}
private fun createRequest() : ActivityTransitionRequest {
val types = listOf(
DetectedActivity.IN_VEHICLE,
DetectedActivity.WALKING,
DetectedActivity.RUNNING,
DetectedActivity.ON_BICYCLE,
DetectedActivity.STILL
)
val transitions = mutableListOf<ActivityTransition>()
types.forEach { activity ->
transitions.add(
ActivityTransition.Builder()
.setActivityType(activity)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
)
}
return ActivityTransitionRequest(transitions)
}
And also one to start listening:
override fun start(onResult: (res: String?) -> Unit) {
// ...
intent = createIntent()
val request = createRequest()
ActivityRecognition.getClient(context)
.requestActivityTransitionUpdates(request, intent)
.addOnSuccessListener {
Log.d("UserActivity Service info", "listening...")
}
.addOnFailureListener { e ->
Log.d("UserActivity Service error", e.toString())
}
// ...
}
The problem is that the current attribute is always null. I think I have some issues with intent or handler registration, but I have no idea where.
Does someone have any comments? :)
Thanks!
This is your problem. In this code from createIntent():
val intent = Intent(context, recognitionHandler.javaClass)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
context.registerReceiver(recognitionHandler, IntentFilter())
return pendingIntent
You return a PendingIntent that you use in the call to requestActivityTransitionUpdates(). However, that PendingIntent refers to a dynamically created inner class (your BroadcastReceiver) and Android cannot instantiate that class.
You also additionally call registerReceiver(), however you pass an empty IntentFilter in that call so the registered BroadcastReceiver is never called.
To fix the problem, you can either provide a correctIntentFilter that matches your PendingIntent OR you can refactor your BroadcastReceiver into a proper class (not a private inner class) and make sure that you've added the BroadcastReceiver to your manifest and make it publicly available (exported="true").
Here's an example of how to do this using a BroadcastReceiver:
https://steemit.com/utopian-io/#betheleyo/implementing-android-s-new-activity-recognition-transition-api
Initially I setup a BroadcastReceiver to receive intents from the Nearby Messages API.
class BeaconMessageReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Nearby.getMessagesClient(context).handleIntent(intent, object : MessageListener() {
override fun onFound(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Found iBeacon=$id")
sendNotification(context, "Found iBeacon=$id")
}
override fun onLost(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Lost iBeacon=$id")
sendNotification(context, "Lost iBeacon=$id")
}
})
}
private fun sendNotification(context: Context, text: String) {
Timber.d("Send notification.")
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(context, Notifications.CHANNEL_GENERAL)
.setContentTitle("Beacons")
.setContentText(text)
.setSmallIcon(R.drawable.ic_notification_white)
.build()
manager.notify(NotificationIdGenerator.nextID(), notification)
}
}
Then registered this receiver in my MainActivity after location permissions have been granted.
class MainActivity : AppCompatActivity() {
// ...
private fun onLocationPermissionsGranted() {
val filter = MessageFilter.Builder()
.includeIBeaconIds(UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FEED"), null, null)
.build()
val options = SubscribeOptions.Builder().setStrategy(Strategy.BLE_ONLY).setFilter(filter).build()
Nearby.getMessagesClient(context).subscribe(getPendingIntent(), options)
}
private fun getPendingIntent(): PendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(context, BeaconMessageReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
}
This worked well while the app was open, but does not work when the app is closed. So I found this example, that demonstrates how to setup an IntentService to receive messages while the app is in the background.
The example does use the Nearby.Messages class, which was deprecated in favor of the MessagesClient. So I replaced the deprecated code with the MessagesClient implementation.
class MainActivity : AppCompatActivity() {
// ...
private fun onLocationPermissionsGranted() {
val filter = MessageFilter.Builder()
.includeIBeaconIds(UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FEED"), null, null)
.build()
val options = SubscribeOptions.Builder().setStrategy(Strategy.BLE_ONLY).setFilter(filter).build()
Nearby.getMessagesClient(context).subscribe(getPendingIntent(), options)
.addOnSuccessListener {
Timber.i("Subscribed successfully.")
startService(Intent(this, BeaconMessageIntentService::class.java))
}.addOnFailureListener {
Timber.e(exception, "Subscription failed.")
}
}
private fun getPendingIntent(): PendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(context, BeaconMessageIntentService::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
}
And this is the IntentService (which is almost identical to my BroadcastReceiver).
class BeaconMessageIntentService : IntentService("BeaconMessageIntentService") {
override fun onHandleIntent(intent: Intent?) {
intent?.let {
Nearby.getMessagesClient(this)
.handleIntent(it, object : MessageListener() {
override fun onFound(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Found iBeacon=$id")
sendNotification("Found iBeacon=$id")
}
override fun onLost(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Lost iBeacon=$id")
sendNotification("Lost iBeacon=$id")
}
})
}
}
private fun sendNotification(text: String) {
Timber.d("Send notification.")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(this, Notifications.CHANNEL_GENERAL)
.setContentTitle("Beacons")
.setContentText(text)
.setSmallIcon(R.drawable.ic_notification_white)
.build()
manager.notify(NotificationIdGenerator.nextID(), notification)
}
}
onHandleIntent is called, and the Intent is not null; yet for some reason onFound() and onLost() are never called. Why would this be the case?
It's not really a solution but what I found is this (credit to this answer):
I've tried a few configurations including a BroadcastReceiver and adding a JobIntentService to run the code in the background, but every time I got this the onExpired callback which you can set to the SubscribeOptions:
options.setCallback(new SubscribeCallback() {
#Override
public void onExpired() {
super.onExpired();
Toast.makeText(context.get(), "No longer Subscribing!", Toast.LENGTH_SHORT).show();
}
}
When the subscribe occurred in the background it was delayed, but it was still called.
Notes:
1. When I've tested with Strategy.BLE_ONLY I did not get the onFound callback.
2. From Google's documentation:
Background subscriptions consumes less power than foreground
subscriptions, but have higher latency and lower reliability
When testing I found this "lower reliability" to be an understatement: onFound was rarely called and I never got the onLost.
I know this is a late reply, but I had the same problem and found out by debugging that it is an issue related to this error: "Attempting to perform a high-power operation from a non-Activity Context". This can be solved when calling Nearby.getMessagesClient(this) by passing in an activity context instead of this.
In my case I added a class extending Application which helps in returning this context (the below is in java but should be translatable to kotlin easily)
public class MyApplication extends Application {
private Activity currentActivity = null;
public Activity getCurrentActivity(){
return currentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity){
this.currentActivity = mCurrentActivity;
}
}
And in my base activity, from which all activities extend, I set the current activity by calling ((MyApplication) this.getApplicationContext()).setCurrentActivity(this); in the constructor.
My service can then call getMessagesClient with the correct context like below:
final Activity context = ((MyApplication)getApplicationContext()).getCurrentActivity();
Nearby.getMessagesClient(context).[...]
Do not forget to register your Application class in the AndroidManifest:
<application
android:name="com.example.xxx.MyApplication"`
I have a custom notification shown in the lock screen. I am using a broadcast pending intent to send a message to my app once the notification is clicked. Which later starts an activity in the broadcast receiver.
The problem is, once I click on the notification, it disappears from the lock-screen and the activity is launched behind the lock-screen. It does not ask the user to unlock the screen.
My requirement is to ask the user to unlock the screen as soon as the broadcast is sent by the notification's click event. How do I do that?
I could find this question which looks like my problem. But unfortunately there is no answer.
Here is some code that explains the notification creation I did.
/**
* Method to render Notification
*/
private void showNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
/* set mandatory stuff on builder */
Notification notification = builder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification.bigContentView = createCustomView(context);
}
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(getNotificationId(), notification);
}
/**
* Method to return notification click's PendingIntent
*/
private PendingIntent getNotificationClickIntent() {
Intent bcIntent = new Intent(OPEN_APP);
bcIntent.putExtra(EXTRA_DEEP_LINK_URL, getDeepLinkUrl());
return PendingIntent.getBroadcast(
context, getReqCode(), bcIntent, PendingIntent.FLAG_ONE_SHOT);
}
/**
* Method to create custom view for notification
*/
private RemoteViews createCustomView(Context context) {
RemoteViews customView = new RemoteViews(context.getPackageName(), R.layout.custom_layout);
if (customView != null) {
// set dat on views
customView.setOnClickPendingIntent(R.id.my_view, getNotificationClickIntent());
}
return customView;
}
Use Activity intent in pending intent instead of Service or Broadcast
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Update:
In my case i use service.
private fun activityPendingIntent(context: Context, action: String? = null): PendingIntent {
Timber.d("activityPendingIntent")
val intent = Intent(context, DummyBackgroundActivity::class.java)
action?.let { intent.action = action }
return PendingIntent.getActivity(context, ACTIVITY_NOTIFICATION_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT)
}
DummyBackgroundActivity
class DummyBackgroundActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val service = Intent(this, BackgroundService::class.java)
service.action = intent.action
startService(service)
}
override fun onResume() {
super.onResume()
finish()
}
}
Service:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.d("onStartCommand intent = %s", intent?.action)
when (intent?.action) {
//Handle it
}
return Service.START_NOT_STICKY
}
I hope you replicate the same using Broadcast.