How to make sure the Broadcast Receiver is disconnected - android

I wrote a simple Broadcast Receiver which catches incoming calls and starts activity with the caller's number:
package com.example.nrsearch;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
public class CallReceiver extends BroadcastReceiver {
public CallReceiver() {
}
public Context context;
#Override
public void onReceive(Context context, Intent intent) {
Log.i("CallReceiverBroadcast", "onReceive() is called. ");
this.context = context;
TelephonyManager teleMgr = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener psl = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
Log.i("CallReceiverBroadcast", "onCallStateChanged() is called. ");
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
Log.i("CallReceiverBroadcast", "Incoming call caught. Caller's number is " + incomingNumber + ".");
startNumberDisplayActivity(incomingNumber);
}
}
};
teleMgr.listen(psl, PhoneStateListener.LISTEN_CALL_STATE);
teleMgr.listen(psl, PhoneStateListener.LISTEN_NONE);
}
public void startNumberDisplayActivity(String incomingNumber) {
Intent i = new Intent(context, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("incomingNumber", incomingNumber);
context.startActivity(i);
}
}
However, after the first incoming call I feel like my device's battery starts to drain pretty quickly. I'm afraid that some process still prevents my Broadcast Receiver from disconnecting (I mean my Broadcast Receiver may be running forever). So, is there something in my code that could cause such a behavior and does this line really stops the TelephonyManager from listening for call state changes: teleMgr.listen(psl, PhoneStateListener.LISTEN_NONE); or should I do it some other way?
EDIT: I'm almost sure that this class causes battery drain, because now I'm testing battery life with my app uninstalled and it's much lower than previous when this app was installed and broadcast receiver was called. I can't swear that this class is the cause of the drain but the battery consumption difference is clearly visible with and without this app. Could somebody look at this code and say what could cause the battety drain? Thanks in advance!

in the method
#Override
public void onPause() {
super.onPause();
mActivity.unregisterReceiver(myReceiver);
}
You can put this other places, but that's a good one. DO your registration in onResume.
Also please read the broadcastreceiver docs, they don't work the way you seem to believe they do:
http://developer.android.com/reference/android/content/BroadcastReceiver.html
Basically the receiver lifecycle is:
Receiver Lifecycle
A BroadcastReceiver object is only valid for the duration of the call
to onReceive(Context, Intent). Once your code returns from this
function, the system considers the object to be finished and no longer
active.
This has important repercussions to what you can do in an
onReceive(Context, Intent) implementation: anything that requires
asynchronous operation is not available, because you will need to
return from the function to handle the asynchronous operation, but at
that point the BroadcastReceiver is no longer active and thus the
system is free to kill its process before the asynchronous operation
completes.
In particular, you may not show a dialog or bind to a service from
within a BroadcastReceiver. For the former, you should instead use the
NotificationManager API. For the latter, you can use
Context.startService() to send a command to the service.
Edit:
As per your comments - the concern about the BroadcastReceiver eating battery life isn't real. The receiver only lasts as long as it takes to run the code in it's on receiver method. At that point Android will clean it up as it deems nescesary. If anything in your code is breaking this it would be:
this.context = context;
/// these three lines
TelephonyManager teleMgr = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
.....
teleMgr.listen(psl, PhoneStateListener.LISTEN_CALL_STATE);
teleMgr.listen(psl, PhoneStateListener.LISTEN_NONE);
since you create a object and then try to listen on it.
However you should really read the docs:
anything that requires
asynchronous operation is not available,
Which is EXACTLY what you are doing in your code - attaching to a service and and waiting for its asyncronous response. This isn't acceptable in a BroadcastReceiver, and is clearly indicated in the docs at this point:
In particular, you may not show a dialog or bind to a service from
within a BroadcastReceiver.

Related

Can we have blocking waits in onDestroy()?

I have a BroadcastReceiver which listens for an intent in onDestroy() callback. And there is a blocking while which goes on till bluetooth discoverability is switched off. Once discoverability is off, the changeModeReceiver will call its onReceive() and set destroy_ok to true, and hence breaking out of the while loop. But, this is not giving desired results.
Toast message, "In onDestroy()" is not getting printed
"In onDestroy()" is getting printed in the logcat
The bluetooth is still switched on
The code is as follows.
boolean destroy_ok = false;
protected void onDestroy(){
System.out.println("In onDestroy()");
Toast.makeText(getApplicationContext(), "In onDestroy()", Toast.LENGTH_LONG).show();
BroadcastReceiver changeModeReceiver = new BroadcastReceiver(){
public void onReceive(Context context, Intent intent){
String mode = intent.getStringExtra(BluetoothAdapter.EXTRA_SCAN_MODE);
if (mode.equals(BluetoothAdapter.SCAN_MODE_NONE))
destroy_ok = true;
}
};
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
registerReceiver (changeModeReceiver, filter);
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,1);
startActivity(discoverableIntent);
while (!destroy_ok){}
unregisterReceiver(changeModeReceiver);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled())
adapter.disable();
System.out.println("Leaving onDestroy()");
super.onDestroy();
}
The onDestroy method (as well as all other activity lifecycle methods, view callback methods, etc.) is called on the application's main UI thread, so no, you shouldn't block for a significant period of time when called. Doing so will likely result in lag, and may even spawn an ANR (application not responding) error if you block for more than 5-10 seconds.
Note: do not count on this method being called as a place for saving
data! For example, if an activity is editing data in a content
provider, those edits should be committed in either onPause() or
onSaveInstanceState(Bundle), not here. This method is usually
implemented to free resources like threads that are associated with an
activity, so that a destroyed activity does not leave such things
around while the rest of its application is still running. There are
situations where the system will simply kill the activity's hosting
process without calling this method (or any others) in it, so it
should not be used to do things that are intended to remain around
after the process goes away.
http://developer.android.com/reference/android/app/Activity.html#onDestroy()
So simply said, use onPause() for such operations. Also, I would use a Service or a new Thread in your case.

Android: ACTION_BATTERY_LOW not triggered in emulator. Receiver registered in code, not manifest

I have seen posts where it was mentioned registerReceiver has to be called (not defined in manifest) to receive ACTION_BATTERY_LOW intent.
public class MainActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
//....
registerReceiver(new BatteryLevelReceiver(), new IntentFilter(
Intent.ACTION_BATTERY_LOW));
}
// .......
}
BroadcastReceiver
public class BatteryLevelReceiver extends BroadcastReceiver
{
private static final String TAG = BatteryLevelReceiver.class.getSimpleName();
#Override
public void onReceive(Context context, Intent intent)
{
Log.d(TAG, "onReceive");
}
}
I do not see the "onReceive" log statement in logcat. I am using emulator to simulate battery low state, using telnet 5554 and executing power capacity 10. I do see the battery status changing in the emulator but no intent triggered.
Also if I have to call registerReceiver() inside an activity and I do not call unregisterReceiver on onStop or onDestroy, is it okay? If not okay, how will I register for a receiver to receive system intents even when my app is not in foreground? (Apart from using manifest).
Make sure that:
you are setting the "Charger connection" to "NONE"
That battery status is "Discharging"
You can add the following code in your manifest.
<receiver android:name=".yourpackage.BroadCastNotifier" >
<intent-filter>
<action android:name="android.intent.action.BATTERY_LOW" />
</intent-filter>
</receiver>
In your BroadCastNotifier Class
package yourpackage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class BroadCastNotifier extends BroadcastReceiver {
private final static String TAG = "BroadCastNotifier";
#Override
public void onReceive(Context context, Intent intent) {
String intentAction = intent.getAction();
if(Intent.ACTION_BATTERY_LOW.equalsIgnoreCase(intentAction)){
Log.e(TAG, "GOT LOW BATTERY WARNING");
}
}
}
You will recieve the log message GOT LOW BATTERY WARNING when your battery is low, also try this in your device.
Although the above code might work the best way is not to use BroadCast for such actions, you can monitor the battery level as described here. You can determine your battery with the code below which is way easier.
int level = battery.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = battery.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryPct = level / (float)scale;
The key is to disable charging with the telnet command
power ac off
If you then set the battery level to a low capacity
power capacity 1
the intent will be triggered
You can register ACTION_BATTERY_LOW in the manifest - see my detailed answer here - you can't register ACTION_BATTERY_CHANGED in the manifest.
The reason you do not receive it is probably you forgot to declare your RECEIVER in the manifest
Also if I have to call registerReceiver() inside an activity and I do not call unregisterReceiver on onStop or onDestroy, is it okay?
No. You should register in onResume and unregister on onPause ALWAYS - after onPause is called your receiver WON'T receive anyway
If not okay, how will I register for a receiver to receive system intents even when my app is not in foreground? (Apart from using manifest).
Only in manifest

Service does not run from BroadcastReceiver? - Android

I am trying to get my service to run when there is an outgoing call on my phone. But for some reason my service does not run when this happens. I know the code for the "CallReceiver" executes since I used a toast message to display if that runs. I am able to run the service through my main activity, but this means it will run regardless of whether an outgoing call is made....
Below is my code:
The Receiver:
package com.example.hiworld;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
public class CallReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, CallService.class));
Toast.makeText(context, "Call Receiver started",
Toast.LENGTH_LONG).show();
Log.d("Calling Someone", "onReceived");
}
}
The Service:
package com.example.hiworld;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.widget.Toast;
public class CallService extends IntentService {
public long StartTime=0;
public long EndTime =0;
public long TotalTime = 0;
public long NumFreeMins = 0;
public CallService() {
super("CallService");
}
#Override
protected void onHandleIntent(Intent intent) {
StartTime = (System.currentTimeMillis())/60;
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if(tm.getCallState()==0) //getting the time whenever the phone is off
{
EndTime = (System.currentTimeMillis())/60;
TotalTime = EndTime-StartTime;
NumFreeMins = 300-TotalTime;
//notify user
this.displaymsg();
}
}
public void displaymsg()
{
Toast toast = Toast.makeText(getApplicationContext(), ""+NumFreeMins, Toast.LENGTH_SHORT);
toast.show();
}
}
I have seen some people use the line:
context.startService(new Intent(this, CallService.class));
instead of:
context.startService(new Intent(context, CallService.class));
but the latter does not work for me...
Try specifying an <intent-filter> for your IntentService in the manifest for a specific 'action'. Example...
<service
android:name=".CallService" >
<intent-filter>
<action android:name="com.example.hiworld.intent.DO_SOMETHING" />
</intent-filter>
</service>
Then in the onReceive(...) method of your BroadcastReceiver do something like the following...
Intent callReceiverIntent = new Intent("com.example.hiworld.intent.DO_SOMETHING");
// Put the Intent received by the BroadcastReceiver as extras so the
// IntentService can process it...
callReceiverIntent.putExtras(intent);
context.startService(callReceiverIntent);
EDIT:
I built a simple test app based on the code you posted to pasrebin. I kept the code identical for the manifest, receiver and service and simply had to add a default Activity in order to get it to run.
I can see from monitoring logcat in the DDMS perspective of eclipse that the CallReceiver successfully receives the NEW_OUTGOING_CALL Intent and does, in fact, start the CallService.
The problem is, however, with attempting to show a Toast from an IntentService which causes an exception due to a 'leaked handler' and silently crashes.
The reason behind this is that an IntentService uses a background thread to carry out its work and trying to show the Toast (i.e., a UI element) from a non-UI thread won't work as the app has no UI components running. So, regardless of whether you use the local context variable or getApplicationContext(), there simply is no UI context with which to associate the Toast.
The reason why it works when starting the CallService from an Activity is obviously because the Activity provides a UI context that can be used by the Toast. In short, it generally seems that attempting to use Toast from an IntentService isn't a good idea unless the IntentService is always started by a UI component and even then, having a background thread create UI elements (such as a Toast) may cause problems.
The only way that this can be made to work with the current model is to change the IntentService to a Service. By default, code execution of a Service is done on the main (UI) thread and it is quite legal for a Service to show a Toast regardless of whether there are any of the app's Activities showing or not. In fact, I modified the code to use a Service and placed the code from onHandleIntent in onStartCommand() and I can see the Toasts when making an outgoing call.
Try
context.startService(new Intent(context.getApplicationContext(), CallService.class));

How do I cancel all pending intents that are qued for intent Service

I have an intentservice that gets qued by the user and by my app automatically. I need to be able to kill all pending intents that are qued when the user logs out of my application, but I cannot seem to get that to work. I have tried stopService() and stopself(), but the intents continue to fire off the intentservice after the user has logged out. I would try to get the id of the intent but that is difficult as everytime the intentservice starts, the variable holding the intent id's is empty. Here is my intentservice code:
public class MainUploadIntentService extends IntentService {
private final String TAG = "MAINUPLOADINTSER";
private GMLHandsetApplication app = null;
private SimpleDateFormat sdf = null;
public boolean recStops = true;
public MainUploadIntentService() {
super("Main Upload Intent Service");
GMLHandsetApplication.writeToLogs(TAG,
"GMLMainUploadIntentService Constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Started");
if (app == null) {
app = (GMLHandsetApplication) getApplication();
}
uploadData(app);
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Finished");
}
#Override
public void onDestroy() {
GMLHandsetApplication.writeToLogs(TAG, "onDestroy Started");
app = null;
stopSelf();
GMLHandsetApplication.writeToLogs(TAG, "onDestroy completed");
}
public void uploadData(GMLHandsetApplication appl) {
//All of my code that needs to be ran
}
Unfortunately, I don't think it's possible to accomplish that with the standard IntentService methods since it doesn't offer a way to interrupt it while it's already going.
There are a few options I can think of that you can try to see if they fit your need.
Copy the IntentService code to make your own modifications to it that would allow you to remove pending messages. Looks like someone had some success with that here: Android: intentservice, how abort or skip a task in the handleintent queue
Instead of copying all the IntentService code, you might also be able to Bind to it like a normal Service (since IntentService extends Service) so you can write your own function to remove pending messages. This one is also mentioned in that link.
Rewrite the IntentService as a regular Service instead. With this option, you'd have more control over adding and removing messages.
I had what sounds like a similar situation where I was using an IntentService, and I eventually just converted it to a Service instead. That let me run the tasks concurrently and also cancel them when I needed to clear them.
Here
When should I free the native (Android NDK) handles? is the HangAroundIntentService class that has the method cancelQueue().
The class also has the method
public static Intent markedAsCancelIntent(Intent intent)
that converts an intent into a cancel intent, and
public static boolean isCancelIntent(Intent intent).
The class is based on the open-sourced Google's code.
Just a thought but inside of your onhandleintent can you have an argument that checks to see if app is running if not then don't run the code? example. In the start of your app you could have a static var
boolean appRunning;
Next in your onhandle of the intent, when you set the appRunning to false, after an onPause or onDestroy of activity, you could wrap the onhandleintent code in a boolean:
protected void onHandleIntent(final Intent intent) {
if(MainActivity.appRunning){
...
}
}
Just a thought

Broadcast Receiver in IntentService (Service has leaked IntentReceiver)

I try to implement an IntentService with a BroadcastReceiver that reacts on the SCAN_RESULTS_AVAILABLE_ACTION.
The IntentService is supposed to compare Lists whenever onReceive is called. I always get the
"Service has leaked IntentReceiver"
error even though I unregister the BroadcastReceiver in onDestroy().
Here is the code:
public class MyClass extends IntentService {
private HashMap<String, List<String>>;
private WifiManager mWifiManager;
private WifiReceiver mWifiReceiver;
public MyClass() {
super("MyClass");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
mWifiReceiver = new WifiReceiver();
registerReceiver(mWifiReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
mWifiManager.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY,"ScanLock");
mWifiManager.setWifiEnabled(true);
return START_NOT_STICKY;
}
#Override
public void onDestroy(){
unregisterReceiver(mWifiReceiver);
mWifiManager.setWifiEnabled(false);
}
#Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub
}
class WifiReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Here I do my stuff with the scan results
//should be called every 5 seconds
}
}
Where is the problem in the code?
Why do I keep getting this error?
I still have to learn a lot about Android, but I think the IntentService is the right way to go since I do not expect any result from this class. It should just stop when I send a call stopService(). This IntentService is called by another IntentService! Is that a problem?
Thanks for helping.
I try to implement an IntentService with a BroadcastReceiver that reacts on the SCAN_RESULTS_AVAILABLE_ACTION.
This is largely pointless. Your receiver will be registered for a few seconds at most, hopefully.
I still have to learn a lot about Android, but I think the IntentService is the right way to go since I do not expect any result from this class.
That makes no sense whatsoever. You use an IntentService when you have a short bit of work that needs to be performed in a background thread. For example, if you use AlarmManager to check for new email messages every 15 minutes, or you have an activity kick off a large file download, you would use IntentService.
It should just stop when I send a call stopService().
You never call stopService() on an IntentService. The IntentService stops itself once onHandleIntent() returns. This is why your BroadcastReceiver will be removed within seconds -- your onHandleIntent() should only be running for seconds.
This IntentService is called by another IntentService!
This is unlikely to be a good design.
Try registering BroadcastReceiver in OnCreate() instead of OnStartCommand(),
That should fix your problem.

Categories

Resources