blob: 0c144dfef42723f286bd46199877638d710289c3 [file] [log] [blame]
/*
Copyright (C) 2011 The University of Michigan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Please send inquiries to powertutor@umich.edu
*/
package edu.umich.PowerTutor.service;
import edu.umich.PowerTutor.R;
import edu.umich.PowerTutor.ui.PowerTabs;
import edu.umich.PowerTutor.ui.UMLogger;
import edu.umich.PowerTutor.util.BatteryStats;
import edu.umich.PowerTutor.util.SystemInfo;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
public class UMLoggerService extends Service{
private static final String TAG = "UMLoggerService";
private static final int NOTIFICATION_ID = 1;
private static final int NOTIFICATION_ID_LETTER = 2;
private Thread estimatorThread;
private PowerEstimator powerEstimator;
private Notification notification;
private NotificationManager notificationManager;
private TelephonyManager phoneManager;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
powerEstimator = new PowerEstimator(this);
/* Register to receive phone state messages. */
phoneManager = (TelephonyManager)this.getSystemService(TELEPHONY_SERVICE);
phoneManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE |
PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
PhoneStateListener.LISTEN_SERVICE_STATE |
PhoneStateListener.LISTEN_SIGNAL_STRENGTH);
/* Register to receive airplane mode and battery low messages. */
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(Intent.ACTION_BATTERY_LOW);
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
registerReceiver(broadcastIntentReceiver, filter);
notificationManager = (NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
//android.os.Debug.startMethodTracing("pt.trace");
if(intent.getBooleanExtra("stop", false)) {
stopSelf();
return;
} else if(estimatorThread != null) {
return;
}
showNotification();
estimatorThread = new Thread(powerEstimator);
estimatorThread.start();
}
@Override
public void onDestroy() {
//android.os.Debug.stopMethodTracing();
if(estimatorThread != null) {
estimatorThread.interrupt();
while(estimatorThread.isAlive()) {
try {
estimatorThread.join();
} catch(InterruptedException e) {
}
}
}
unregisterReceiver(broadcastIntentReceiver);
/* See comments in showNotification() for why we are using reflection here.
*/
boolean foregroundSet = false;
try {
Method stopForeground = getClass().getMethod("stopForeground",
boolean.class);
stopForeground.invoke(this, true);
foregroundSet = true;
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
} catch(NoSuchMethodException e) {
}
if(!foregroundSet) {
setForeground(false);
notificationManager.cancel(NOTIFICATION_ID);
}
super.onDestroy();
};
/** This function is to construct the real-time updating notification*/
public void showNotification(){
int icon = R.drawable.level;
// icon from resources
CharSequence tickerText = "PowerTutor"; // ticker-text
long when = System.currentTimeMillis(); // notification time
Context context = getApplicationContext(); // application Context
CharSequence contentTitle = "PowerTutor"; // expanded message title
CharSequence contentText = ""; // expanded message text
Intent notificationIntent = new Intent(this, UMLogger.class);
notificationIntent.putExtra("isFromIcon", true);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
/* the next two lines initialize the Notification, using the
* configurations above.
*/
notification = new Notification(icon, tickerText, when);
notification.iconLevel = 2;
notification.setLatestEventInfo(context, contentTitle,
contentText, contentIntent);
/* We need to set the service to run in the foreground so that system
* won't try to destroy the power logging service except in the most
* critical situations (which should be fairly rare). Due to differences
* in apis across versions of android we have to use reflection. The newer
* api simultaneously sets an app to be in the foreground while adding a
* notification icon so services can't 'hide' in the foreground.
* In the new api the old call, setForeground, does nothing.
* See: http://developer.android.com/reference/android/app/Service.html#startForeground%28int,%20android.app.Notification%29
*/
boolean foregroundSet = false;
try {
Method startForeground = getClass().getMethod("startForeground",
int.class, Notification.class);
startForeground.invoke(this, NOTIFICATION_ID, notification);
foregroundSet = true;
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
} catch(NoSuchMethodException e) {
}
if(!foregroundSet) {
setForeground(true);
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
/* This function is to update the notification in real time. This function
* is apparently fairly expensive cpu wise. Updating once a second caused a
* 8% cpu utilization penalty.
*/
public void updateNotification(int level, double totalPower) {
notification.icon = R.drawable.level;
notification.iconLevel = level;
// If we know how much charge the battery has left we'll override the
// normal icon with one that indicates how much time the user can expect
// left.
BatteryStats bst = BatteryStats.getInstance();
if(bst.hasCharge() && bst.hasVoltage()) {
double charge = bst.getCharge();
double volt = bst.getVoltage();
if(charge > 0 && volt > 0) {
notification.icon = R.drawable.time;
double minutes = charge * volt / (totalPower / 1000) / 60;
if(minutes < 55) {
notification.iconLevel = 1 +
(int)Math.max(0, Math.round(minutes / 10.0) - 1);
} else {
notification.iconLevel = (int)Math.min(13,
6 + Math.max(0, Math.round(minutes / 60.0) - 1));
}
}
}
CharSequence contentTitle = "PowerTutor";
CharSequence contentText = "Total Power: " + (int)Math.round(totalPower) +
" mW";
/* When the user selects the notification the tab view for global power
* usage will appear.
*/
Intent notificationIntent = new Intent(this, UMLogger.class);
notificationIntent.putExtra("isFromIcon", true);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
notification.setLatestEventInfo(this, contentTitle, contentText,
contentIntent);
notificationManager.notify(NOTIFICATION_ID, notification);
}
private final ICounterService.Stub binder =
new ICounterService.Stub() {
public String[] getComponents() {
return powerEstimator.getComponents();
}
public int[] getComponentsMaxPower() {
return powerEstimator.getComponentsMaxPower();
}
public int getNoUidMask() {
return powerEstimator.getNoUidMask();
}
public int[] getComponentHistory(int count, int componentId, int uid) {
return powerEstimator.getComponentHistory(count, componentId, uid, -1);
}
public long[] getTotals(int uid, int windowType) {
return powerEstimator.getTotals(uid, windowType);
}
public long getRuntime(int uid, int windowType) {
return powerEstimator.getRuntime(uid, windowType);
}
public long[] getMeans(int uid, int windowType) {
return powerEstimator.getMeans(uid, windowType);
}
public byte[] getUidInfo(int windowType, int ignoreMask) {
UidInfo[] infos = powerEstimator.getUidInfo(windowType, ignoreMask);
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
new ObjectOutputStream(output).writeObject(infos);
} catch(IOException e) {
return null;
}
for(UidInfo info : infos) {
info.recycle();
}
return output.toByteArray();
}
public long getUidExtra(String name, int uid) {
return powerEstimator.getUidExtra(name, uid);
}
};
BroadcastReceiver broadcastIntentReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
Bundle extra = intent.getExtras();
try {
if ((Boolean)extra.get("state")) {
powerEstimator.writeToLog("airplane-mode on\n");
} else {
powerEstimator.writeToLog("airplane-mode off\n");
}
} catch(ClassCastException e) {
// Some people apparently are having this problem. I'm not really
// sure why this should happen.
Log.w(TAG, "Couldn't determine airplane mode state");
}
} else if(intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) {
powerEstimator.writeToLog("battery low\n");
} else if(intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {
powerEstimator.writeToLog("battery-change " +
intent.getIntExtra("plugged", -1) + " " +
intent.getIntExtra("level", -1) + "/" +
intent.getIntExtra("scale", -1) + " " +
intent.getIntExtra("voltage", -1) +
intent.getIntExtra("temperature", -1) + "\n");
powerEstimator.plug(
intent.getIntExtra("plugged", -1) != 0);
} else if(intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) ||
intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
// A package has either been removed or its metadata has changed and we
// need to clear the cache of metadata for that app.
SystemInfo.getInstance().voidUidCache(
intent.getIntExtra(Intent.EXTRA_UID, -1));
}
};
};
PhoneStateListener phoneListener = new PhoneStateListener() {
public void onServiceStateChanged(ServiceState serviceState) {
switch(serviceState.getState()) {
case ServiceState.STATE_EMERGENCY_ONLY:
powerEstimator.writeToLog("phone-service emergency-only\n");
break;
case ServiceState.STATE_IN_SERVICE:
powerEstimator.writeToLog("phone-service in-service\n");
switch(phoneManager.getNetworkType()) {
case(TelephonyManager.NETWORK_TYPE_EDGE):
powerEstimator.writeToLog("phone-network edge\n");
break;
case(TelephonyManager.NETWORK_TYPE_GPRS):
powerEstimator.writeToLog("phone-network GPRS\n");
break;
case 8:
powerEstimator.writeToLog("phone-network HSDPA\n");
break;
case(TelephonyManager.NETWORK_TYPE_UMTS):
powerEstimator.writeToLog("phone-network UMTS\n");
break;
default:
powerEstimator.writeToLog("phone-network " +
phoneManager.getNetworkType() + "\n");
}
break;
case ServiceState.STATE_OUT_OF_SERVICE:
powerEstimator.writeToLog("phone-service out-of-service\n");
break;
case ServiceState.STATE_POWER_OFF:
powerEstimator.writeToLog("phone-service power-off\n");
break;
}
}
public void onCallStateChanged(int state, String incomingNumber) {
switch(state) {
case TelephonyManager.CALL_STATE_IDLE:
powerEstimator.writeToLog("phone-call idle\n");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
powerEstimator.writeToLog("phone-call off-hook\n");
break;
case TelephonyManager.CALL_STATE_RINGING:
powerEstimator.writeToLog("phone-call ringing\n");
break;
}
}
public void onDataConnectionStateChanged(int state) {
switch(state) {
case TelephonyManager.DATA_DISCONNECTED:
powerEstimator.writeToLog("data disconnected\n");
break;
case TelephonyManager.DATA_CONNECTING:
powerEstimator.writeToLog("data connecting\n");
break;
case TelephonyManager.DATA_CONNECTED:
powerEstimator.writeToLog("data connected\n");
break;
case TelephonyManager.DATA_SUSPENDED:
powerEstimator.writeToLog("data suspended\n");
break;
}
}
public void onSignalStrengthChanged(int asu) {
powerEstimator.writeToLog("signal " + asu + "\n");
}
};
}