The parts of this application in question are an IntentService (DatabaseService) and an AppWidgetProvider (LotWidget). In LotWidget, when a button is pressed, a PendingIntent is fired which sends a broadcast to itself, and is received in onReceive(). All of this works fine so far. In onReceive, I read the action from the Intent as well as the various extras, and either start this IntentService, or start the main Activity. Starting the activity works fine, but for some reason I can't understand, the DatabaseService isn't being started. I know code leading up to the DatabaseService intent is being sent by testing with Log.
Here is the onReceive code:
#Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if(intent.getAction().equals(RETURN_DATA_WIDGET) && initialize(context)){
updateWidget(context);
}
else if(intent.getAction().equals(INTERNAL_INC)){
Log.d("WIDG", "Incrememnt");
Intent incr = new Intent(context, DatabaseService.class);
incr.setAction(INCREMENT);
incr.putExtra("incval", intent.getIntExtra("incval", 999));
context.startService(intent);
}
else if(intent.getAction().equals(INTERNAL_UP)){
Log.d("WIDG", "UPDATE");
Intent upd = new Intent(context, DatabaseService.class);
upd.setAction(UPDATE_COUNT);
context.startService(intent);
}
else if(intent.getAction().equals(OPEN_APP)){
Intent open = new Intent(context, TheLotActivity.class);
open.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(open);
}
}
Using the same intents to start the DatabaseService from the main Activity works fine. My manifest defines the DatabaseService as a Service like so:
<service android:name="com.bsprague.thelot.DatabaseService" >
</service>
I know the Service isn't being started because a Log at the very beginning of onHandleIntent isn't being displayed, though it displays fine when this Intent is sent from the main Activity. Is there something in Android that I'm missing that makes it so you can't start a Service from a BroadcastReceiver?
You are setting an action on the Intent, and your <intent-filter> does not have that action. You might consider replacing the setAction() calls with putExtra() calls instead.
Never mind -- as you pointed out in a comment, since you are specifying the component, all other routing elements (e.g., action) are ignored. Your error from your updated comment suggests that your Intent is picking up the wrong component, LotWidget instead of DatabaseService.
Related
So, I've been trying to achieve that for at least five hours today and I've tried literally ANY solution found anywhere.
I'm programming a little clock app, using AlarmManager to make the app ring. It works fine when my app is open or minimized. I'm trying to make it work when the app is closed, and that's the problem. So, there is the piece of code that sets the AlarmManager :
AlarmManager am = (AlarmManager) ctxt.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(ctxt, AlarmService.class);
PendingIntent pen = PendingIntent.getBroadcast(ctxt, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
am.setExact(AlarmManager.RTC_WAKEUP, nextRing.getTime(), pen);
(Here, ctxt is the context, I've tried both getApplicationContext() and getBaseContext() and nextRing.getTime() is a long that represents the date)
Then, I have my AlarmService class (wich used to be a service, which explain the name, but is now a BroadcastReceiver and I just don't want to rename it now)
public class AlarmService extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
/*Intent cust = new Intent(context, CustomIntent.class);
context.startService(cust);*/
Bundle extras = intent.getExtras();
Intent newIntent = new Intent(context, AlarmActivity.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.putExtra("AlarmName", extras.getString("AlarmName"));
context.startActivity(newIntent);
}
}
So this is the try with only the BroadcastReceiver, wich doesn't work obviously, so I tried to add a IntentService (commented out code at the top) which has the following code
public class CustomIntent extends IntentService {
public CustomIntent() {
super("CustomIntent");
}
#Override
protected void onHandleIntent(Intent intent) {
Intent newIntent = new Intent(getBaseContext(), AlarmActivity.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(newIntent);
}
}
... and that's not working either ! Finally, here's the manifest I use :
<application>
[Here goes stuff that have nothing to do with my bug]
<receiver android:name=".custom_classes.AlarmService"/>
<service
android:name="com.group9.abclock.custom_classes.CustomIntent"
android:enabled="true" >
<intent-filter>
<action android:name="com.group9.abclock.custom_classes.CustomIntent" />
</intent-filter>
</service>
</application>
Sorry for the (very) long post but I tough I should explain anything I tried. Thanks in advance if you can help !
If your application is closed, you can't trigger a BroadcastReceiver because it's not registered, and you can't use Context methods because there is no Context.
If you want to execute tasks while the application is closed, you have to create another project with a Service, and start it with your application.
A Service runs in background until someone kill it, and once it's started, the main application is not needed anymore. So the ring functionality have to be implemented in this service.
Just remember that the AlarmManager.setExact, is not that exact from API 23, due to Doze Mode.
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.
I have a very strange bug happening in my app. I am building an Alarm APP and I am using SQLite to store alarm data and broadcast receivers to manage the alarm manager calls.
The code of onReceive strangely does not behave in the same way in some cases. I try to start an Activity when the receiver receives a broadcast, nearly 90% of cases everything goes well and I manage to start the Activity, but in some cases strangely enough the receiver executes the instruction "startActivity(i)" but nothing happens.
It is really hard to reproduce the BUG and during my debugging I have learned what I have mentioned, but more than this is really difficult for me to understand how a call to startActivity() in most cases works and in some cases does not work. I have searched through the Stack community but no one seamed to have this kind of problem, everybody just had problems in starting the activity because they had not set the flag or because they had not registered the receiver in the manifest. Below I am posting the code.
public class AlarmReceiver extends WakefulBroadcastReceiver {
// The app's AlarmManager, which provides access to the system alarm services.
private AlarmManager alarmMgr;
// The pending intent that is triggered when the alarm fires.
private PendingIntent alarmIntent;
#Override
public void onReceive(Context context, Intent intent) {
Utils.logToFile("Received Alarm ,I am in onReceive(), ALARM ID: "+intent.getExtras().getInt(Constants.ALARM_ID));
Intent intent = new Intent(context, StopAlarm.class);
Bundle b = new Bundle();
b.putInt(Constants.ALARM_ID, intent.getExtras().getInt(Constants.ALARM_ID));
if(intent.getExtras().containsKey(Constants.SNOOZE_ALARM)){
b.putString(Constants.SNOOZE_ALARM, intent.getExtras().getString(Constants.SNOOZE_ALARM));
}
i.putExtras(b);
//this flag is needed to start an Activity from a BroadcastReceiver
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
//this method reads from the DB and sets the next alarm
//I tried commenting this method so that no DB action is
//performed and still the bug happened
setAlarm(context.getApplicationContext());
//this method just logs data into a file that I have created to keep track of events
//since not always the device is connected with LogCat
Utils.logToFile("Received Alarm, Intent(context, StopAlarm.class);");
}
Do I need to set any other flag and how is it possible that startActivity(intent) behaves incorrectly in some cases?
EDIT
<activity
android:label="#string/app_name"
android:name="package.activity.StopAlarm"
android:windowSoftInputMode="stateAlwaysHidden"
android:screenOrientation="sensorPortrait">
</activity>
<receiver android:name="package.receivers.AlarmReceiver" />
I have finally solved the issue by creating an IntentService and by starting the activity from the IntentService and setting two flags to the Intent. After doing this I placed the code which reads from DB in the activity that is started from the IntentService. I have tested nearly 60 times the behaviour and in all the tests the app behaved correctly. I am posting the code below.
public class MyAlarmReceiver extends WakefulBroadcastReceiver {
// The app's AlarmManager, which provides access to the system alarm services.
private static AlarmManager alarmMgr;
// The pending intent that is triggered when the alarm fires.
private static PendingIntent alarmIntent;
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, AlarmIntentService.class);
Bundle b = new Bundle();
b.putInt(Constants.ALARM_ID, intent.getExtras().getInt(Constants.ALARM_ID));
if(intent.getExtras().containsKey(Constants.SNOOZE_ALARM)){
b.putString(Constants.SNOOZE_ALARM, intent.getExtras().getString(Constants.SNOOZE_ALARM));
}
i.putExtras(b);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startWakefulService(context, i);
}
This is the IntentService I needed to implement
public class AlarmIntentService extends IntentService {
public AlarmIntentService() {
super("AlarmIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
Intent i = new Intent(this, StopAlarm.class);
Bundle b = new Bundle();
b.putInt(Constants.ALARM_ID, intent.getExtras().getInt(Constants.ALARM_ID));
if(intent.getExtras().containsKey(Constants.SNOOZE_ALARM)){
b.putString(Constants.SNOOZE_ALARM, intent.getExtras().getString(Constants.SNOOZE_ALARM));
}
i.putExtras(b);
//THESE ARE THE FLAGS NEEDED TO START THE ACTIVITY AND TO PREVENT THE BUG
//(CLEAR_TASK is crucial for the bug and new task is needed to start activity from outside of an activity)
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
MyAlarmReceiver.completeWakefulIntent(intent);
}
}
This is the activity started by the IntentService. Here I set the next alarm.
public class StopAlarm extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.stop_alarm);
//this method reads from the DB and sets the next alarm
MyAlarmReceiver.setAlarm(getApplicationContext());
...
I had a similar issue. From my experience the problem is,that startActivity(i) is asynchronous. So in this case the program will simultaneously call activities onCreate(),onStart(), etc. but also call QlokAlarmReceiver.completeWakefulIntent(intent) (without waiting for the activity to be closed), which will release the wakeLock. Because of that the device can go to sleep during executing the activities onCreate() or onStart().
Rubin, I know, that my answer is a contradiction of your solution, but my logs clearly indicated such order of events:
- startActivity called
- onCreate of the activity called
- completeWakefulIntent(intent); called in between logs from onStart of the activity
My workaround this is to start a wakelock with a timeout of eg 20 seconds just before calling startActivity and then start another wakeLock in the activities onCreate, which will be released in the onDestroy method.
I'm not sure if my solution goes along with best practises, but I haven't found a better solution so far.
I have an application that using "AlarmService". For handling alarms i have a Broadcast receiver. That receiver has to start certain activity. Code i'm using for achieving that is following:
#Override
public void onReceive(Context context, Intent intent) {
...other code....
Intent intIntent = new Intent(context, MainActivity.class);
intIntent .putExtra("IsAlarm", true);
Intent alarmChooser = Intent.createChooser(intIntent , "Alarm");
alarmChooser.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(alarmChooser);
}
That works but only if activity isn't shown already (if it's not in the foreground). If called activity is already opened nothing happens. How can i overcome that?
Is there a flag that will start the activity if it's not started OR send intent to it even if it's in the foreground?
P.S. i tried using dedicated "broadcast" above the provided code. Reciever for that broadcast is registered programmatically in the MainActivity: "onResume" would register dedicated receiver, "onPause" would unregister it. That way in case MainActivity is already on it will receive a broadcast but then i have a problem when phone goes to "stand by" - "dedicated" receiver is unregistered.
Check in the activity onNewIntent callback
there should be the new intent from the receiver
I think you don't need the chooser:
#Override
public void onReceive(Context context, Intent intent) {
Intent intIntent = new Intent(context, MainActivity.class);
intIntent.putExtra("IsAlarm", true);
context.startActivity(intIntent);
}
From the examples this looked straightforward. Maybe you can show me what I did wrong. I can't get an activity to receive a broadcast sent from a local service.
I have Activity1 that start Service1:
startService(new Intent(Activity1.this, Service1.class));
Activity1 then starts Activity2:
startActivity(new Intent(Activity1.this, Activity2.class));
Service1, a local service, listens for downloads:
protected final BroadcastReceiver service2DownloadBroadcastReceiver = new BroadcastReceiver()
{
public void onReceive(final Context context, final Intent intent)
{
...
broadcastDownloadFinished(Uri.fromFile(downloadedFile));
The broadcast receiver of Service1 then broadcasts its own message:
protected Intent broadcastDownloadFinished(final Uri uri)
{
final Intent intent = new Intent(ACTION_DOWNLOAD_FINISHED).setData(checkNotNull(uri));
sendBroadcast(intent);
Activity2, which is in the foreground at the time, listens for the ACTION_DOWNLOAD_FINISHED intent using its own broadcast receiver:
private final BroadcastReceiver activity2DownloadBroadcastReceiver = new BroadcastReceiver()
{
public void onReceive(final Context context, final Intent intent)
{
Log.i(Activity2.class.getSimpleName(), "Received download event: " + intent.getAction() + " " + intent.getData());
Activity2 of course registers the receiver:
protected void onResume()
{
super.onResume();
final IntentFilter downloadIntentFilter = new IntentFilter();
downloadIntentFilter.addAction(ACTION_DOWNLOAD_FINISHED);
registerReceiver(activity2DownloadBroadcastReceiver, downloadIntentFilter);
In case it matters, ACTION_DOWNLOAD_FINISHED is something like "com.example.intent.action.DOWNLOAD_FINISHED".
Service1 receives the download manager event in its receiver and apparently broadcasts its own custom event, but Activity2 never seems to receive it. What did I do wrong? Is it a problem to broadcast an intent in the middle of processing another one? (I wouldn't think so---this is asynchronous, right?)
Update: Just to make sure there is no problem sending a broadcast in the middle of receiving a broadcast, I changed my broadcast code to actually perform the broadcast three seconds later on the main thread:
Log.i(getClass().getSimpleName(), "...ready to broadcast");
final Intent intent = new Intent(ACTION_DOWNLOAD_FINISHED).setData(checkNotNull(uri));
mainThreadHandler.postDelayed(new Runnable()
{
public void run()
{
Log.i(getClass().getSimpleName(), "...broadcasting");
sendBroadcast(intent);
Log.i(getClass().getSimpleName(), "...broadcasted");
}
}, 3000);
Log.i(getClass().getSimpleName(), "...scheduled to broadcast");
As expected, the log says:
...ready to broadcast
...scheduled to broadcast
...broadcasting
...broadcasted
Yet nothing is received in the activity. Please help.
Eureka! I found it! The problem is that I supplied a data URI in my broadcast intent. The Android intent matching rules get a little complicated. If you supply a data URI, then your intent filter must specify a matching MIME type.
Unfortunately, although the Android documentation says that the data type can be inferred from the data URI, apparently Android doesn't know that a file://.../example.jpg is an image. So this doesn't work:
intentFilter.addDataType("image/*");
However, instead of specifying a type, I can specify a scheme that I accept:
intentFilter.addDataScheme("file");
That works! It's a little rough---and a little artificial to restrict my broadcasts to file: URIs, but as that's all I'm using for the moment, it works.
Note that apparently I could manually specify the MIME type in the intent when I broadcast it, but that's too much trouble for now, as I'm downloading images from Picasa so I already know that they are images (and don't care the specific MIME type). And if it gets too much trouble, I could ditch the whole setData() thing altogether and set an extra---but of course I want to do things the Right Way.
have you included your receiver in your activity's manifest?
<receiver
android:name=".YourReceiver">
<intent-filter>
<action
android:name="intent_name"></action>
</intent-filter>
</receiver>