Reliable way to track Android battery charging/discharging state - android

Like many others, I am trying to create a simple app that logs an entry every time the phone is connected or disconnected from the charger. I plan to use this data to calculate average charge and discharge rate over several weeks/months to get an idea of how well the battery is performing over time.
I have got Intent filters declared in the manifest for ACTION_POWER_CONNECTED and ACTION_POWER_DISCONNECTED, and they seem to be firing fine.
Now when they are fired, I am registering to receive ACTION_BATTERY_CHANGED to get the battery EXTRA_LEVEL, EXTRA_STATUS and EXTRA_PLUGGED. (Reference - Obtaining usb cable plugged IN/OUT event using EXTRA_PLUGGED does not work)
public class PowerConnectionReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intentPowerConn) {
Intent intentBatChange = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int chargeStatus = intentBatChange.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
int chargeType = intentBatChange.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
int chargeLevel = intentBatChange.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
Toast.makeText(context, chargeStatus + " " + chargeType + " " + chargeLevel, Toast.LENGTH_SHORT).show();
}
}
However, the EXTRA_STATUS that is returned by this sticky broadcast still shows as Discharging when fired immediately after the phone is connected. Other extras like EXTRA_PLUGGED and EXTRA_LEVEL return correct values though.
A manual refresh to get the ACTION_BATTERY_CHANGED action after connecting returns the STATUS as Charging just fine.
It can't be that the intent received immediately after connecting is stale, since the EXTRA_PLUGGED returns the correct type (AC or USB).
I have a workaround where I use the ACTION_POWER_CONNECTED/ACTION_POWER_DISCONNECTED intent action to determine the charge state (as described here - How to detect power connected state?).
public class PowerConnectionReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context , Intent intentPowerConn) {
Intent intentBatChange = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int chargeStatus = intentBatChange.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
int chargeType = intentBatChange.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
int chargeLevel = intentBatChange.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
String action = intentPowerConn.getAction();
boolean chargeFlag = false;
if(action.equals(Intent.ACTION_POWER_CONNECTED)) {
chargeFlag = true;
}
else if(action.equals(Intent.ACTION_POWER_DISCONNECTED)) {
chargeFlag = false;
}
Toast.makeText(context, chargeStatus + " " + chargeType + " " + chargeLevel + " " + chargeFlag, Toast.LENGTH_SHORT).show();
}
}
I might need to make use of STATUS for other scenarios like BATTERY_STATUS_FULL, BATTERY_STATUS_NOT_CHARGING and BATTERY_STATUS_UNKNOWN.
So I don't want to rely on the Intent.ACTION_POWER_CONNECTED action.
Given this, what is a reliable way to detect the current charge state?
I was considering adding a 2 sec delay before I register the ACTION_BATTERY_CHANGED intent; though I haven't tested this out yet, I figure it might work.

Related

Detect user muting ring signal

Is there a way to get notified when the user mutes the incoming call ring signal by pressing the volume button and/or turn to silence?
I have tried all methods I can find, using AudioManager.RINGER_MODE_CHANGE_ACTION, android.media.VOLUME_CHANGE_ACTION, android.media.MASTER_MUTE_CHANGED_ACTION and a few more...
None of them give a clear indication that the ring signal is muted.
My problem is not solved by the "possible duplicate", so I will expand with parts of my code for more help.
In my service:
#Override
public void onCreate() {
super.onCreate();
mVolumeReceiver = new VolumeReceiver();
IntentFilter intentFilter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mVolumeReceiver, intentFilter);
}
private class VolumeReceiver extends BroadcastReceiver {
private final String TAG = VolumeReceiver.class.getSimpleName();
#Override
public void onReceive(Context context, Intent intent) {
final String[] Modes = { "Unknown", "Silent", "Vibrate", "Normal" };
if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
int newMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
Log.i(TAG, "Ringer mode changed to: " + Modes[newMode + 1]);
}
}
}
I am certain that my service is started, since it's doing other things as it should.
I also tried to register a BroadcastReceiver class in my manifest, but that didn't work either.
I do get one Logcat entry telling me mode is changed to "Normal", probably when the receiver is registered. After that, nothing.

Android, getting strength signal (PhoneStateListener) while device in sleep mode

I have problem and after some search I have not found any positive solutions.
After research I have idea that there is not implementation for my problem but this question may be is my last chance.
What do I need to get?
There is application that gets information about mobile network strength signal. I do it by
PhoneStateListener. Of course it works great but when my device goes to sleep mode, listener does not work:
https://code.google.com/p/android/issues/detail?id=10931
https://code.google.com/p/android/issues/detail?id=7592
WakeLock solves problem only in case, if device switch off by timeout. In case when I press hard power button, my device gets sleep mode as well. We can not override power button action.
My goal is get strength signal always when my device is enabled. It does not matter what mode is. All time it should collecting data.
Question:
Are there any ideas? How to achieve that? Are there ways to do this or may be there are some hacks? All solves are welcome. If you had some useful experience, please share this.
Thanks to all for help!!! I hope, this topic will get complete information about this problem.
Alarm manager is the way to go - the tricky part is to keep the phone awake after the alarm manager receiver returns. So
setup an alarm (notice you should also register an "On Boot completed" receiver to set up the alarm after a reboot - your alarms do not survive a reboot) :
Intent monitoringIntent = new Intent(context, YourReceiver.class);
monitoringIntent.setAction("your action");
PendingIntent pi = PendingIntent.getBroadcast(context, NOT_USED,
monitoringIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
// here is the alarm set up
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + INITIAL_DELAY,
INTERVAL_BETWEEN_ALARMS, pi);
receive it - the receiver holds a WakeLock in its onReceive() which never fails :
public abstract class YourReceiver extends BroadcastReceiver {
#Override
final public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if ("your action".equals(action)) {
// monitoring - got broadcast from ALARM
try {
d("SS : " + new Signal().getSignalStrength(context));
} catch (InterruptedException e) {
e.printStackTrace();
}
// Actu8ally the lines above will ANR
// I did it with WakefulIntentService :
// WakefulIntentService.sendWakefulWork(
// context, YourWakefulService.class);
// Will be posting it asap
} else {
w("Received bogus intent : " + intent);
return;
}
}
}
If you are lucky (yourRetrieveSignal() is fast enough) this will work, otherwise you will need a (Wakeful)IntentService pattern in your receiver.
The WakefulIntentService will take care of the wake lock (if you want to avoid a dependency have a look here) - EDIT : keep in mind you can't define listeners in an intent service - see here.
If the receiver ANRs on you, you have to try the WakefulIntentService pattern. In either case you might use this :
This proved the most difficult part actually :
class Signal {
static volatile CountDownLatch latch; //volatile is an overkill quite probably
static int asu;
private final static String TAG = Signal.class.getName();
int getSignalStrength(Context ctx) throws InterruptedException {
Intent i = new Intent(TAG + ".SIGNAL_ACTION", Uri.EMPTY, ctx,
SignalListenerService.class);
latch = new CountDownLatch(1);
asu = -1;
ctx.startService(i);
Log.d(TAG, "I wait");
latch.await();
ctx.stopService(i);
return asu;
}
}
where :
public class SignalListenerService extends Service {
private TelephonyManager Tel;
private SignalListener listener;
private final static String TAG = SignalListenerService.class.getName();
private static class SignalListener extends PhoneStateListener {
private volatile CountDownLatch latch;
private SignalListener(CountDownLatch la) {
Log.w(this.getClass().getName(), "CSTOR");
this.latch = la;
}
#Override
public void onSignalStrengthChanged(int asu) {
Signal.asu = asu;
latch.countDown();
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.w(TAG, "Received : " + intent.getAction());
Tel = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
listener = new SignalListener(Signal.latch);
#SuppressWarnings("deprecation")
final int listenSs = PhoneStateListener.LISTEN_SIGNAL_STRENGTH;
Tel.listen(listener, listenSs);
return START_STICKY;
}
#Override
public void onDestroy() {
Log.w(TAG, "onDestroy");
Tel.listen(listener, PhoneStateListener.LISTEN_NONE);
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
This is working code (but not the pinnacle of elegance admittedly - comments/corrections welcome). Do not forget to register your services in the manifest and acquire permissions.
EDIT 2013.07.23 : I did not use the onReceive - if you use it it will ANR - this is working code if you use a WakefulIntentService in onReceive and in there you call SignalListenerService.
From my understanding of PhoneStateListener you can't do this while the application CPU is in sleep mode. You can either keep the device awake, which would ruin battery life. Alternatively you can use an alarm (see AlarmManager) to wake the device on intervals, so you can collect the data (impacts battery life still).
Some samples of using AlarmManager can be found here
CommonsWare's location polling example is really good about waking the phone and putting it to sleep again. I think it might help have a look: https://github.com/commonsguy/cwac-locpoll
One of the possible workarounds of android issue 10931 is to send the android.intent.action.SCREEN_ON intent to the 'phone' process after the screen turned off.
Create and register BroadcastReceiver to listen for notifications when the screen turns off
start(Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mScreenReceiver, filter);
}
final BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
#Override
public void onReceive(final Context context, final Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
Log.v(LOGTAG, "Screen is off. Running workaround");
new Thread(mReportScreenIsOnRunnable).start();
}
}
};
Send the SCREEN_ON intent to the phone process only.
public final Runnable mReportScreenIsOnRunnable = new Runnable() {
#Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Runtime.getRuntime().exec(new String[] { "su", "-c",
"am broadcast -a android.intent.action.SCREEN_ON com.android.phone" });
} catch (IOException e) {
e.printStackTrace();
}
}
};
After receiving this intent the phone process would resume sending cell location
updates.
Root privileges are required.
This solution is a bit hacky, dangerous and works not on all phones. It can lead to higher power consumption, but not so much more than if you keep the screen turned on.

Android : Intent resolution & ensure BroadcastReceiver is registered / did receive

Based on a checkbox preference I enable a bunch of receivers and I broadcast to them so they schedule a bunch of alarms for themselves. EDIT : The receivers both set up the alarms and wait for the Broadcast from the AlarmManager (as is clear from the onReceive posted).
SettingsActivity
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
boolean enable = (Boolean) newValue;
Monitor.enableMonitoring(getApplicationContext(), enable);
return true;
}
return false;
}
Monitor.enableMonitoring
public static void enableMonitoring(Context ctx, boolean enable) {
Resources resources = ctx.getResources();
CharSequence ac_setup_alarm = resources
.getText(R.string.intent_action_setup_alarm);
CharSequence ac_cancel_alarm = resources
.getText(R.string.intent_action_cancel_alarm);
// if only I could do the above in a static way someclass once and for all
Intent i = new Intent("" + (enable ? ac_setup_alarm : ac_cancel_alarm));
if (enable) {
Log.d(_tag, "enable receivers / setup alarms int : " + i);
_enableDisableReceivers(ctx, enable); // enable == true
ctx.sendBroadcast(i);
} else {
Log.d(_tag, "cancel alarms / disable receivers int : " + i);
ctx.sendBroadcast(i);
_enableDisableReceivers(ctx, enable); // enable != true (will disable)
}
}
questions
Why
Intent i = new Intent("" + (enable ? ac_setup_alarm : ac_cancel_alarm)); won't get received while
Intent i = new Intent("" + (enable ? ac_setup_alarm : ac_cancel_alarm), Uri.EMPTY, ctx, BatteryMonitoringReceiver.class); will get received as expected ?
Manifest :
<receiver
android:name="di.k23b.hw3.receivers.BatteryMonitoringReceiver"
android:enabled="false" >
<intent-filter>
<action android:name="#string/intent_action_setup_alarm" />
<action android:name="#string/intent_action_cancel_alarm" />
<action android:name="#string/intent_action_monitor" />
</intent-filter>
</receiver>
I would expect implicit intents to propagate to a registered receiver - why do I have to make those implicit intents explicit ? I expect this to be easy to answer - I just overlooked/didn't quite get something in the docs :)
Do I need to make sure that the call to _enableDisableReceivers(ctx, true); will actually enable the receivers before I broadcast the Intent ? Similarly do I have to wait till the intent is received before I call _enableDisableReceivers(ctx, false); to disable the receivers ? If yes how should I go about that ?
(Bonus off the first answer I got) It is not possible to use LocalBroadcastManager with the AlarmaManager - With a boot receiver ? (I guess no)
For completeness :
private static void _enableDisableReceivers(Context ctx, boolean enable) {
Log.d(_tag, "enable/disable receivers");
for (Class<? extends BaseReceiver> receiver : RECEIVERS)
BaseReceiver.enable(ctx, enable, receiver);
}
where :
public static void enable(Context context, boolean enable,
Class<? extends BaseReceiver> receiver) {
PackageManager pacman = context.getPackageManager();
final ComponentName componentName = new ComponentName(context, receiver);
Log.d(_tag, componentName.toString());
final int state = (enable) ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
pacman.setComponentEnabledSetting(componentName, state,
PackageManager.DONT_KILL_APP);
Log.d(_tag,
"pacman :" + pacman.getComponentEnabledSetting(componentName));
}
And for mind boggling completeness (BaseMonitoringReceiver) :
#Override
final public void onReceive(Context context, Intent intent) {
// FIXME : possible NPE below ?
final String action = intent.getAction(); // can intent==null ?
Log.w(TAG, "" + action);
resources = context.getResources();
ac_setup_alarm = resources.getText(R.string.intent_action_setup_alarm);
ac_cancel_alarm = resources
.getText(R.string.intent_action_cancel_alarm);
ac_monitor = resources.getText(R.string.intent_action_monitor);
if (ac_setup_alarm.equals(action) || ac_cancel_alarm.equals(action)) {
monitoringIntent = new Intent(context, this.getClass());
monitoringIntent.setPackage(RECEIVERS_PACKAGE_NAME);// TODO: needed?
final boolean enable = ac_setup_alarm.equals(action);
setupAlarm(context, enable);
} else if (ac_monitor.equals(action)) {
// monitoring - got broadcast from ALARM
Class<? extends WakefulIntentService> serviceClass = getService();
WakefulIntentService.sendWakefulWork(context, serviceClass);
} else {
Log.w(TAG, "Received bogus intent : " + intent);
return;
}
}
Subclasses (like the BatteryMonitoringReceiver in the manifest) just override getService() - neat no ? Notice onReceive is final.
This is not an answer - just wanted to ask questions that won't fit in a comment.
What you are doing - just enabling a receiver for long enough to send it an intent - looks pretty unusual to me. Are you sure that is what you want to do? I'm used to having to enable/disable receivers for system intents, but not for custom intents.
Regarding Q#2, why is there an issue of trying to reduce the window during which the receiver is enabled? Why not enable the receiver before sending the intent, and then don't disable it until after you have received the intent?
I'm assuming that all of this is happening in the same process - it sounds like that is the case. And, if that is the case, why bother declaring your receiver in your manifest at all? You could also consider switching to LocalBroadcastManager.
Edit:
Question 3. As you suspect it is not possible to use LBM with Alarm Manager. See this post.

How to detect switching between users

I have a service running in foreground mode and I'd like to detect switching between user sessions on tablets running Android 4.2 or above.
Is there any broadcast receiver I can register to get notified?
I have noticed that Google Music stops the music playback as soon as another user session is chosen on the lock screen. How does it detect the switch?
ANSWER EXPLAINED
Thanks #CommonsWare for the correct answer. I will explain a bit more how to detect a user switch.
First be aware that the documentation explicitly says that receivers must be registered through Context.registerReceiver. Therefore do something like:
UserSwitchReceiver receiver = new UserSwitchReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction( Intent.ACTION_USER_BACKGROUND );
filter.addAction( Intent.ACTION_USER_FOREGROUND );
registerReceiver( receiver, filter );
Then in the receiver you can also retrieve the user id. Here is a small snippet:
public class UserSwitchReceiver extends BroadcastReceiver {
private static final String TAG = "UserSwitchReceiver";
#Override
public void onReceive(Context context, Intent intent)
{
boolean userSentBackground = intent.getAction().equals( Intent.ACTION_USER_BACKGROUND );
boolean userSentForeground = intent.getAction().equals( Intent.ACTION_USER_FOREGROUND );
Log.d( TAG, "Switch received. User sent background = " + userSentBackground + "; User sent foreground = " + userSentForeground + ";" );
int user = intent.getExtras().getInt( "android.intent.extra.user_handle" );
Log.d( TAG, "user = " + user );
}
}
Try ACTION_USER_FOREGROUND and ACTION_USER_BACKGROUND. I have not used them, but they were added in API Level 17, and their description seems like it may help.

Android Broadcast Receiver battery life every 10 mins or change, can it go in its own thread?

I would like to write an application (for research) that makes a timestamp every time the battery level changes. If I can't do that, I want to make it so it takes a battery reading every 10 or so minutes.
I have this BroadcastReceiver code but I am not sure where to put it.
My application keeps crashing with the following exception:
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.BATTERY_CHANGED flg=0x60000000 (has extras) } in com.mdog.datareceive.Receive$1#43c9cd10
In the onCreate of my activity I spawn 3 AsyncTask threads that do stuff in the background. Where would be a good place to put the broadcast receiver?
I have tried in the onCreate and I have tried in a new method that gets called by one of the background tasks. I think the problem might be that the function the BroadcastRecevier code is in might be ending prematurely?
Is there anyway I could put it in its own thread so that it just waits for broadcasts?
Code:
batteryLevelTimeStamps = new LinkedList<String>();
BroadcastReceiver batteryLevelReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent){
int rawlevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
int level = -1;
if (rawlevel >= 0 && scale > 0) {
level = (rawlevel * 100) / scale;
}
batteryLevel = level + "%";
batteryLevelTimeStamps.add("At time: " + new Date().toString() + " the battery level is:" +batteryLevel);
out.print("At time: " + new Date().toString() + " the battery level is:" +batteryLevel + " in onCreate\n");
}
};
IntentFilter batteryLevelFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
registerReceiver(batteryLevelReceiver, batteryLevelFilter);
I think you want to look at using the AlarmManager. You can set an alarm when the phone boots up, and after you receive that alarm, you can check the battery and set another alarm 10 minutes in the future.
Using AlarmManager is better than your own thread because
It allows the phone to go to low power usage mode during the ten minute wait
At the end of 10 minutes, it will wake up the phone if it is currently in that low power state.

Categories

Resources