Broadcast by Notification Action not handled in BroadcastReceiver inside Service - android

I am trying to build a notification while a music playback service is running and use the notification to interact with the service (play, pause, stop) using the Broadcast mechanism.
(I know there is also the possibility to use PendingIntent.getService() as an action button in the notification, but I don't like this idea, because this would trigger the onStartCommand() of the service and I need to parse and analyze the Intent object to take action, which seems not as clean as the BroadcastReceiver approach, described below).
Let's illustrate what we have so far with some (truncated) code.
We are creating a Notification object inside the service lifecycle, add an action button, and showing the notification using startForeground().
...
Intent i = new Intent(getBaseContext(), PlayerService.class);
PendingIntent piStop = PendingIntent.getBroadcast(getBaseContext(), 1, i, PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Action actionStopPlayback = new NotificationCompat.Action(R.drawable.ic_stop_white_36dp, "Stop playback", piStop);
notification.addAction(actionStopPlayback);
...
Then we are registering a BroadcastReceiver inside the onCreate() of the service (and unregistering it in onDestroy of course; this is a more simplified example).
IntentFilter intentFilter = new IntentFilter();
registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d(getClass().toString(), "Broadcast received");
}
}, intentFilter);
And the final result is that the onReceive() of the receiver is never called. The service is a continuous one and is active when the Notification action sends the broadcast. Since I have no way of debugging broadcasts due to their nature, I'm kind of blocked here.

You're creating this explicit Intent for the PendingIntent:
Intent i = new Intent(getBaseContext(), PlayerService.class);
This won't work for a couple of reasons. Explicit Intents - those created for a specific target class - do not work with dynamically registered Receiver instances. Also, this is targeting the wrong class. A broadcast Intent with a Service class target will just fail outright. A getBroadcast() PendingIntent would need a BroadcastReceiver class as the target.
With your current setup - the dynamically registered Receiver instance - you'll need to use an implicit Intent; i.e., an Intent with an action String, rather than a target class. For example:
Intent i = new Intent("com.hasmobi.action.STOP_PLAYBACK");
You would then use that action String for the IntentFilter you're using to register the Receiver.
IntentFilter intentFilter = new IntentFilter("com.hasmobi.action.STOP_PLAYBACK");
Do note that an IntentFilter can have multiple actions, so you can register a single Receiver to handle several different actions.
Alternatively, you could stick with using an explicit Intent, and statically register a BroadcastReceiver class in the manifest. For example:
public class NotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
...
}
}
In the manifest:
<receiver android:name=".NotificationReceiver" />
Then your Intent would be similar to:
Intent i = new Intent(PlayerService.this, NotificationReceiver.class);
However, this would require an additional step, as you would then need to somehow pass the broadcast info from NotificationReceiver to the Service; e.g., with an event bus, LocalBroadcastManager, etc.

Related

Android 8.0 Oreo AlarmManager with broadcast receiver and implicit broadcast ban

I have critical reminders that are set via the Alarm Manager (It should function the same way as an alarm clock application).
Previously I had the following in my Android Manifest:
<receiver android:name="com.example.app.AlarmReceiver" >
<intent-filter>
<action android:name="${packageName}.alarm.action.trigger"/>
</intent-filter>
</receiver>
The broadcast receiver:
public class AlarmReceiver extends BroadcastReceiver {
#Override public void onReceive(
final Context context,
final Intent intent) {
// WAKE LOCK
// BUILD NOTIFICATION etc...
}
}
How the alarm is set:
final PendingIntent operation = PendingIntent.getBroadcast(
mContext,
requestCode,
intent,
PendingIntent.FLAG_CANCEL_CURRENT);
if (PlatformUtils.hasMarshmallow()) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, operation);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, operation);
}
}
With Android 8.0 I can no longer use an implicit broadcast as defined in the Manifest. That's fine, the alternative given is to register it manually like so:
final BroadcastReceiver receiver = new AlarmReceiver();
final IntentFilter intentFilter = new IntentFilter(ALARM_RECEIVER_INTENT_TRIGGER);
context.registerReceiver(receiver, intentFilter);
This does not seem logical to me.
The alarm receiver will be tied to the lifetime of the context. This causes an issue when say the application is killed due to memory pressure or when the device is restarted. I need my alarms to fire every time as they are critical for the health of the user.
Even if I listen to "android.intent.action.BOOT_COMPLETED" and register my alarm receiver the app is killed shortly after and no alarm is fired. I also don't see my alarm via
adb shell dumpsys alarm
How do I create a custom broadcast receiver that receives an implicit broadcast to fire an alarm while targeting Android O (8.0)? Can someone enlighten me with a code example or link to documentation. How does Timely or any other alarm clock app function while targeting O?
Revise your code slightly to make the broadcast explicit rather than implicit and you'll be fine (assuming this is an Activity reference or some other Context):
Intent intent = new Intent(ALARM_RECEIVER_INTENT_TRIGGER);
intent.setClass(this, AlarmReceiver.class);
The restriction in Oreo is on implicit broadcast Intent registration, which is to say it you are sending it broadcasts will only action, category, or data specified. You make it an explicit broadcast by specifying the class which is to receive the broadcast.
If you guys are used to check if the alarm has already been registered don't forget to do the same on this verification:
public boolean isAlarmBroadcastRegistered(Context context, String action, Class clazz) {
Intent intent = new Intent(action);
intent.setClass(context, clazz);
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}

Can one BroadcastReceiver be used for multiple IntentServices?

Normally for a single IntentService you can define the broadcast receiver listener like this in an Activity's onCreate() method (for example)
broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//get stuff from the intent and do whatever you want
}
};
And you register the receiver like this (also in onCreate()):
LocalBroadcastManager.getInstance(this)
.registerReceiver(broadcastReceiver, new IntentFilter("my_intent_service"));
And then you start the IntentService with:
Intent intent = new Intent(this, MyIntentService.class);
startService(intent);
And in the IntentService you send messages back to the receiver with:
Intent broadcastIntent = new Intent("my_intent_service");
broadcastIntent.putExtra("whateverData", whateverData);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
which triggers the onReceive method described earlier.
And then to unregister the receivers, in onDestroy method you can do:
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
In the Manifest file, you add for your service:
<service
android:name=".MyIntentService"
android:exported="false" />
I want to receive broadcasts from multiple IntentServices in the same Activity.
Do you need one receiver per IntentService? As in, if I make n IntentServices do I need to register n receivers, make n listener, and unregister n receivers in onDestroy?
The way to filter multiple actions with one BroadcastReceiver is to add them to the IntentFilter:
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("my_intent_service1"); // Action1 to filter
intentFilter.addAction("my_intent_service2"); // Action2 to filter
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, intentFilter);
And in your BroadcastReceiver:
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("my_intent_service1")) {
// Action 1
} else if (intent.getAction().equals("my_intent_service2")) {
// Action 2
}
}
};
yes you can use one broadcast receiver in one activity, that would handle multiple intents from different services.
I’d recommend to add multiple intent-filter to your broadcast receiver to make distinction from which Service you are getting a broadcast.

Not getting the flow of below program

public class MainActivity extends Activity {
public TextView batteryTxt;
private BroadcastReceiver receiver;
BroadcastReceiver mybroadcast = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
int batterylevel = intent.getIntExtra("level", 0);
batteryTxt.setText("Battery Level: " + Integer.toString(batterylevel) + "%");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
batteryTxt = (TextView) findViewById(R.id.textView1);
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(mybroadcast, filter);
}
}
Eventhough i have not used intent.putExtra() in above program, how intent.getIntExtra("level", 0) is working?
It's an IntentFilter, which sends an Intent to the BatteryManager to check ACTION_BATTERY_CHANGED. The BatteryManager then calls intent.putIntExtra to put the int that you're reciving in the Intent.
The intent is what your BroadcastReceiver receives from the system, when the action "ACTION_BATTERY_CHANGED" is performed. It's the information about battery level in this case, and "0" is the default value (in case there isn't extra named "level"). The intent is not created by any activity in this app.
You need to read the documentation on BroadcastReceivers and Intents.
http://developer.android.com/reference/android/content/BroadcastReceiver.html
http://developer.android.com/reference/android/content/Intent.html
Essentially these two mechanisms act as Android's preferred method of transferring state between applications and processes.
In short:
Broadcast Receivers are registered for Intents, and whenever an intent is "Fired" or "Launched" which corresponds to the "Mime-Type" for which your intent is registered, that Broadcast Receiver will be activated. At this time your Broadcast Receiver will be given the opportunity to handle state passed to it via the intent which was sent.
In your case:
You have created a Broadcast Receiver which is registered (presumably) for the Battery Service intents. That means every time the battery service sends out an Intent to all interested parties you'll receive an a message. The Battery Service includes in it's intent certain data which is useful to an application,service or process which is interested in the state of the Battery. In this case it is the "level".

BroadcastReceiver within a Service not receiving broadcasts Android

I've got this app, in which users update certain variables in an Activity, and this Activity passes the new variables to a IntentService using a BroadcastReceiver. However, the BroadcastReceiver in the IntentService doesn't seem to be receiving the broadcasts. This is the code within the service to create the broadcast receiver
protected class UpdateReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent){
Log.d("receiver", "Got message: ");
//state=3;
//Toast.makeText(context, "got it", Toast.LENGTH_SHORT).show();
}
};
And here's the code to register the receiver, in the onHandle() function
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("updates");
UpdateReceiver lol = new UpdateReceiver();
DetectService.this.registerReceiver(lol, intentFilter);
Finally here's the code to send the broadcast, from the activity
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("updates");
HomePageActivity.this.sendBroadcast(broadcastIntent);
Log.d("sender", "send msg");
When I put the receiver in the same activity as the broadcasting part, it works, but not when I put it into the IntentService. Help please!
On another related note, I've tried using LocalBroadcastManager in this project since the broadcasts are all local but eclipse doesn't seem to be able to import the compatibility class. I've installed it using Android SDK manager already. Is there any thing I'm doing wrong here?
this Activity passes the new variables to a IntentService using a BroadcastReceiver.
That makes no sense. Use startService() to send a command to an IntentService. And an IntentService should not have a BroadcastReceiver, because the IntentService will be destroyed as soon as onHandleIntent() completes and therefore will never receive the broadcast.
I've tried using LocalBroadcastManager in this project since the broadcasts are all local but eclipse doesn't seem to be able to import the compatibility class.
:: shrug ::
Here is a sample project with Eclipse project files that uses LocalBroadcastManager. I encountered no particular Eclipse issues when creating the project.
protected class UpdateReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent){
Log.d("receiver", "Got message: ");
//state=3;
//Toast.makeText(context, "got it", Toast.LENGTH_SHORT).show();
}
};
In the onCreate() method or where relevant.
mReceiver = new UpdateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("<your receivers intent goes here>");
this.registerReceiver(mReceiver, filter);
Now you should be able to send a broadcast and it be picked up.
Intent intent = new Intent("<your receivers intent goes here>");
// Add what you want to add to the intent right here.
<context-handle>.sendBroadcast(intent);

Send a broadcast only to specific Activity

I have one Activity which creates a BroadcastReceiver with an IntentFilter in the method onCreate(...):
IntentFilter iFilter = new IntentFilter("action");
receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
}
};
registerReceiver(receiver, iFilter);
On the other side is an IntentService, which shall send some data:
Intent intent = new Intent(getApplicationContext(), receiver.class);
intent.setAction("action");
[...]
sendBroadcast(intent);
But it seems not to work. No Broadcast ist received.
My service class is in an android lib, perhaps this makes trouble.
Thanks for any advices.
Just create the intent with your action.
Intent intent = new Intent("action");
[...]
sendBroadcast(intent);
And consider renaming "action" to something more meaningful, like "com.my.package.actions.SOME_ACTION".
If you only want that your application components receive the broadcast then use:
Register a permission in your Manifest with a signature protection level (and define a use-permission for that permission). More here.
Use sendBroadcast(intent, permission), and specify the permission in 1.
if the intent is inside your app only, consider using LocalBroadcastManager

Categories

Resources