Starting Intent Service from BroadcastReceiver - OnHandleEvent is never called - java

I am working on an Android application and I have stumbled upon a problem for which I cannot seem to find a solution. I've tried different approaches, but with no luck. I am aware there are similar questions asked, but no answer seems to help. Here's the problem:
I am adding an action button to my notifications by adding the following line to my NotificationBuilder:
.addAction(R.drawable.done,"Complete", completeTaskPI)
Here's the code for the pending intent completeTaskPI:
Intent completeTask = new Intent(getActivity(),NotificationActionReceiver.class);
completeTask.setAction("COMPLETE_TASK");
completeTask.putExtra("taskId",taskId);
PendingIntent completeTaskPI = PendingIntent.getBroadcast(getActivity(),COMPLETE_TASK_CODE,completeTask,0);
It sends a broadcast to NotificationActionReceiver when the Action Button is clicked. From here, I simply start MyIntentService:
public class NotificationActionReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
Intent completeTask = new Intent(context, MyIntentService.class);
context.startService(completeTask);
}
}
Everything works properly before this moment. The receiver receives the pending intent with all the info and starts the service. The service starts, but never gets to onHandleIntent. The code for the IntentService:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i("onHandleIntent","triggered");
}
}
I've tried creating a new IntentService, a different BroadcastReceiver, still nothing.
I'd like to note few things:
- The service is properly declared in the Android Manifest;
- I already have several other services that work just fine (with broadcast receiver, as well);
- I need the action done on a background thread and not in the broadcast receiver;
- I am assuming there is some problem with the context, although I am not sure what could be the problem.
I've been trying to locate the problem for several hours now and I just can't see it, so I thought I'd ask for help here.
Thanks in advance!

Following #pskink's advice, I've solved the problem by starting the service with the pending intent, avoiding the Broadcast Receiver entirely.
I hope this helps someone else in the future.
Thank you!

Related

How can I verify that the onReceive method of a Broadcast Receiver from a different App is actually called?

I'm a beginner with Android and I am confronted with the following problem.
I'm trying to call a Broadcast Receiver registered by App A from (a different) App B.
In App A's Broadcast Receiver I have implemented the following onReceive() method, for testing purposes:
#Override
public void onReceive(Context context, Intent intent) {
//TODO: React to the Intent received.
System.out.println("onReceive has been called");
}
The Boradcast Receiver is called with the following code within App B:
Intent explicitIntent = new Intent();
explicitIntent.setComponent(new ComponentName("<A's package name>", "<fully qualified BR class name>"));
sendBroadcast(explicitIntent);
When running App B, I expected to see the onReceive has been called string appearing on the Android Studio console, but it doesn't.
Is it because the method is in fact not called (for some reason) or because it is naive to expect that App B console can visusalize System.out.println messages from App A?
In the second case, is there another simple way to test in Android Studio that the onReceive() method from App A is actually called?
I do not have your API ver to test with, but your setup will work if you register your receiver a bit differently:
<receiver android:name=".MyBroadcastReceiver" android:exported="true"/>

App crashes after onTaskRemoved executed

I edited the question so it's not a duplicate
of this
In MainActivity I am doing some file operations. These operations are processed in a single file. So, after that I pass the file through intent at ForceShut. This is because I want to detect when user swipes the app out from recent apps which is to say onTaskRemoved() is called, into which this file is deleted. Now no problem so far. The file is successfully transmitted through the intents, and onTaskRemoved() is called as seen from the Logs. Also the file which I try to delete in onTaskRemoved() is successfuly deleted when I swipe the app and the Logs in there all run fine until the "Application Terminated" shows. But few seconds after, I get a crash alert saying App has stopped while app was even removed from the recent. The crash though appears twice in a row then no crashs appear further on. What could be the problem ? :(
My MainActivity class looks like this
public MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//several file operations go here <--
//removed for simplification
Intent mIntent = new Intent(this, ForceShut.class);
mIntent.putExtra("file", file);
startService(mIntent);
}
}
and ForceShut class looks like this :
public class ForceShut extends Service {
File file;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId){
file =(File) intent.getExtras().get("file");
return START_STICKY;
}
#Override
public void onTaskRemoved(Intent rootIntent){
if(file.exists())
file.delete();
}
}
EDIT
So as #CommonsWare was suggesting I had forgotten to look at the LogCat, instead I was looking only at the "Run" tab logs. So I looked it over and it seems like there is a null pointer exception :
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Bundle android.content.Intent.getExtras()' on a null object reference so it seems like even after swiped onStartCommand is called again. Why could the service start over even after app was swiped ?
EDIT 2
Question is not a duplicate, as #Andreas has pointed out. I edited the question. However I found the workaround myself. I shut down the Service with stopSelf() as it seems like swiping the app out of Recents sort of doesn't get rid of the service which ocasionally restarts. Anyway hope this helps anyone
Why could the service start over even after app was swiped ?
You have started a "sticky" service. The system will automatically restart any sticky service until it is explicitly stopped.
#Override
public int onStartCommand(Intent intent, int flags, int startId){
file =(File) intent.getExtras().get("file");
return START_STICKY;
}
I don't see where you actually stop it with stopSelf().
As far as your NullPointerExceptions, simply check if the objects exist before trying to read from them.
if (intent != null && intent.hasExtra("file"))
file =(File) intent.getExtras().get("file");
The service restarts because of the START_STICKY inside onStartCommand.
You need to use START_NOT_STICKY instead to prevent the service from restarting.

BroadcastReceiver onReceive method not called?

I have a service running in which I am getting location updates. The service returns the location successfully. But after that I am trying to broadcast the location to any activity that might be listening. I have registered the receiver in my activity but for some reason the onReceive method is not being called.
Here is the code inside my onLocationChanged method inside my service.
#Override
public void onLocationChanged(Location location) {
Intent intent = new Intent();
intent.setAction("LocationBroadcast");
double lat = location.getLatitude();
double lng = location.getLongitude();
intent.putExtra("lat", lat);
intent.putExtra("lng", lng);
//I am initializing the broadcaster object in onCreate method of my service but I am putting it here for simplicity
broadcaster = LocalBroadcastManager.getInstance(this);
//This Toast successfully shows my coordinates so I know the problem is not with this method
Toast.makeText(GoogleFusedLocationApiService.this, ""+lat+", "+lng+"", Toast.LENGTH_SHORT).show();
broadcaster.sendBroadcast(intent);
}
Inside my activity in my onCreate method, I am registering for the LocationBroadcast like so.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
...
IntentFilter intentFilter = new IntentFilter("LocationBroadcast");
super.registerReceiver(mMessageReceiver, intentFilter);
startService(new Intent(MyApp.getAppContext(), GoogleFusedLocationApiService.class));
}
I've tried this.registerReceiver(mMessageReceiver, intentFilter); and LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, intentFilter); but neither worked.
Here is my mMessageReceiver defined,
public BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
double lat = intent.getDoubleExtra("lat", 0);
double lng = intent.getDoubleExtra("lng", 0);
// This Toast never shows and neither can I debug this method at all
// so for now the only conclusion is the broadcast is not being received
Toast.makeText(MyApp.getAppContext(), "Cordinates are "+lat+", "+lng+"", Toast.LENGTH_SHORT).show();
}
};
Moreover, some of the details that I might think matter after some research. I haven't declared receiver in the manifest because I read that you only do that when you want your application to launch when the broadcast is received but I only want my application to react when it is already running. Not launch whenever the services sends a broadcast.
And I haven't extended the activity to BroadcastReceiver either since the activity is already extended to FragmentActivity
The app does not crash and onLocationChanged is located inside the service that is being started after the BroadcastReceiver is registered so onLocationChanged is not invoked before the BroadcastReceiver has been registered
I managed to solve the issue. I will post my findings and the answer for anyone else faced with this issue in the future. I have probably dumbed down a lot of concepts here but for the purpose of understanding this specific problem I'll try to be accurate to the best of my understanding.
The Answer:
The problem was that I was not sending the Broadcast and receiving the broadcast with the same Context. What I mean by that is, this is how I had declared my service in the Manifest file
<service
android:name=".GoogleFusedLocationApiService"
android:process=":google_fused_location_api_service"/>
The android:process attribute meant that this service would run on a different process from the process that the app is running on. So when I was calling, super.registerReceiver(mMessageReceiver, intentFilter); it was being called from the context of the Activity and when I was calling broadcaster = LocalBroadcastManager.getInstance(this); broadcaster.sendBroadcast(intent); here sendBroadcast is being called from the context of the service which has a different process running for it. So you see I was registering the receiver from a different context and sending broadcast from a different context.
LocalBroadcastManager only works when the broadcast is being sent and received from the same process i.e., context. So in this case, since my service and my app/activity are running or separate processes i.e., contexts I cannot use LocalBroadcastManager. I need to use the Global broadcasts and make sure that I am registering the broadcast and sending the broadcast from the same context.
Now since the I have a static context of the app that I can use anywhere by simply calling, MyApp.getAppContext() which you can learn how to do from this answer now if I register the broadcast receiver and send broadcasts using this context, that means both are done from the same context which is MyApp.getAppContext() and now I begin to receive broadcasts successfully.
So to sum it up, if you have separate process for your service, use MyApp.getAppContext().registerReceiver() and MyApp.getAppContext().sendBroadcast()
If you have the same process for your service or don't have the android:process attribute in your service tag in Manifest file, then you can use LocalBroadcastManager.getInstance(this).registerReceiver() and LocalBroadcastManager.getInstance(this).sendBroadcast().
You can still use MyApp.getAppContext here but using LocalBroadcastManager is the best practice and the proper way of doing things in the second case.

How do I process broadcasts that were sent while my activity was stopped?

My activity starts a service which runs a CountDownTimer. The timer sends broadcasts back to the activity as it counts down. The activity processes the broadcasts in the onReceive method of a BroadcastReceiver. All of this works fine.
My problem comes when the following events happen in this order:
App is stopped (via onPause())
Timer finishes
App is resumed (via onResume())
When the app is resumed the service is no longer sending broadcasts, so the activity does not know how much time is left on the timer or if it's finished. This prevents the activity from updating the UI.
I've tried a dozen ways of dealing with this, and read through many Stack Overflow questions and answers, but I've yet to find a solution. I would think that there's a way to pick up a broadcast that was sent while the activity was not active, but I've yet to find a way.
For the record, here is my relevant Activity and Service code:
activity.java
// Start service
timerIntent.putExtra("totalLength", totalLength);
this.startService(timerIntent);
// ...
// BroadcastReceiver
private BroadcastReceiver br = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getExtras() != null && inSession) {
session.setRemaining(intent.getExtras().getLong("millisUntilFinished"));
updateProgress();
}
}
};
// ...
// onResume
#Override
public void onResume() {
super.onResume();
registerReceiver(br, new IntentFilter(TimerService.COUNTDOWN_TS));
}
service.java
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
long length = intent.getExtras().getLong("totalLength");
countDownTimer = new CountDownTimer(length, 1000) {
#Override
public void onTick(long millisUntilFinished) {
timerServiceIntent.putExtra("millisUntilFinished", millisUntilFinished);
sendBroadcast(timerServiceIntent);
}
#Override
public void onFinish() {
}
};
countDownTimer.start();
return super.onStartCommand(intent, flags, startId);
}
What's the best way to process the broadcasts that the service sent while the activity was stopped?
Use the BroadcastReceiver to store the last request (SharedPreferences perhaps) it received and check it when the Activity starts.
Alternatively, instead of processing a countdown using broadcasts, just store the time that the countdown would end. The Activity can then handle the countdown all by itself as it knows when it should end. Using a service and broadcasts seem to be a little over-engineered for such a simple task.
Update:
From the way you have described your task, I see you needing to handle 2 scenarios. This is how I would likely do it.
Assuming that "XYZ" is the service\intent\whatever starting the countdown and "ABC" is the Activity displaying the progress. (ABC and XYZ could be the same activity if that is what you wanted)
Requirements:
When the countdown starts, I would make XYZ store the time that the countdown should end in SharedPreferences.
ABC is already running when the countdown starts. As Commonsware said, the Eventbus model is excellent for handling this scenario so long as XYZ and ABC are running in the same process. Just fire an event to read the preference value and count down to the specified time. If the user closes ABC and reopens it, Scenario 2 will kick in.
ABC is not running. Check in OnResume whether the countdown time has elapsed. If not, set up ABC to display the countdown again. If there is no countdown active, do something else.
If you also need to do something when the countdown has elapsed regardless of whether you have a UI active, then again Commonsware's suggestion of AlarmManager is perfect.
Let's pretend for a moment that using a Service with a CountDownTimer to track some passage of time for the purposes of updating an Activity actually is a good idea. It's not out of the question, assuming that the Service is actually doing something for real and this timing thing is some by-product.
An activity does not receive broadcasts while stopped, mostly for performance/battery reasons. Instead, the activity needs to pull in the current status when it starts, then use events (e.g., your current broadcasts) to be informed of changes in the data while it is started.
This would be simplified by using something like greenrobot's EventBus and their sticky events, as the activity would automatically get the last event when it subscribes to get events. Using greenrobot's EventBus for this purpose would also reduce the security and performance issues that you are introducing by your use of system broadcasts to talk between two Java classes in the same process.
Also, please stick with lifecycle pairs. onResume() is not the counterpart to onStop(). onStart() is the counterpart to onStop(); onResume() is the counterpart to onPause(). Initializing something in one pair (e.g., onResume()) and cleaning it up in the other pair (e.g., onStop()) runs the risk of double-initialization or double-cleanup errors.
What's the best way to process the broadcasts that the service sent
while the activity was stopped?
Using sticky broadcast intents from the service and then retrieving them from the activity would be a way to process the broadcasts that the service sent while the activity was stopped. I can only offer that as a possible solution rather than claiming it is the "best way".
http://developer.android.com/reference/android/content/Context.html#sendStickyBroadcast(android.content.Intent)
They have however, been deprecated since API level 21 due to security concerns.
Instead of using Normal broadcast you can use Ordered broadcast (sent with Context.sendOrderedBroadcast). For this along with defining a BroadcastReceiver in your activity you required to define BroadcastReceiver in your manifest with same intentfilter. Only change is while registering BroadcastReceiver in your activity you need to set priority to high, so that when your activity is running and activity's BroadcastReceiver is registered it gets called first, and inside onReceive of this BroadcastReceiver you can use abortBroadcast for getting the BroadcastReceiver called which is defined in your android manifest. Now when your activity is not running the BroadcastReceiver defined in your android manifest will get called. So this way you can have the status and if you wish you can display updates to user by notification even if your activity is not running.

Android GCM: different way of handling push depending on whether the app is visible or not

I've got a couple of activities and an intent service which handles GCM incoming messages.
Right now for every push, I'm sending a Notification, and after the user clicks it, he is redirected to appropriate screen.
I would like to alter this behavior that if the app is visible (any activity is in the foreground), instead of the notification a dialog message is shown (with appropriate action).
Any idea how to implement it?
I have 2 ideas but none of them is perfect:
Keep track of every activity in the application, if the activity is visible, don't show notification, but sent an intent to the activity (not nice solution)
register/unregister the second broadcast receiver in each activity's onResume/onPause, "catch" the incoming GCM broadcast (I'm not sure if it is possible).
Any other solutions?
A possible solution (idea 1):
To detect whether your app is running back- or foreground, you can simply set a boolean in onPause/onResume:
#Override
protected void onResume() {
super.onResume();
runningOnBackground = false;
}
#Override
protected void onPause() {
super.onPause();
runningOnBackground = true;
}
When you start a new intent from an notification this method gets called: (if you are using singleTop), with the boolean you can determine what to do in the onNewIntent method.
#Override
protected void onNewIntent (Intent intent){
if(runningOnBackground){
//do this
}
else{
//do that
}
}
Hope it helps!
I didn't test it, but the docs say you can get the number of running activities per each task.
Try to find your application's task among currently running tasks:
ActivityManager acitivityManager = (ActivityManager)
context.getSystemService(Context.ACTIVITY_SERVICE);
// Get the top of running tasks, limit by 100
List<RunningTaskInfo> tasks = acitivityManager.getRunningTasks(100);
for (RunningTaskInfo taskInfo : tasks) {
if (YOUR_PACKAGE_NAME.equals(taskInfo.baseActivity.getPackageName())) {
if (taskInfo.numRunning > 0) {
// Show dialog
} else {
// Show notification
}
break;
}
}
Google added a note on getRunningTasks():
Note: this method is only intended for debugging and presenting task management user interfaces. This should never be used for core logic in an application, such as deciding between different behaviors based on the information found here. Such uses are not supported, and will likely break in the future. For example, if multiple applications can be actively running at the same time, assumptions made about the meaning of the data here for purposes of control flow will be incorrect.
So use it at your own risk.
Also check if GCM broadcasts are ordered. If so, you can "override" your default BroadcastReceiver with the other ones in each Activity. Just play with the priority of IntentFilters. When the BroadcastReceiver with higher priority receives the message, it can abort it's further propagation. For your application this means that when some Activity is running, it registers the receiver which shows the dialog and aborts broadcast. If no activity is active, then your default receiver shows the notification.

Categories